@needle-tools/engine 2.61.0-pre → 2.62.1-pre

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.61.0-pre",
3
+ "version": "2.62.1-pre",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development, and can be deployed anywhere. It is flexible, extensible, and collaboration and XR come naturally.",
5
5
  "main": "dist/needle-engine.umd.cjs",
6
6
  "module": "dist/needle-engine.js",
@@ -115,39 +115,37 @@ const logsContainerStyles = `
115
115
 
116
116
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
117
117
 
118
-
119
- div {
118
+ div[data-needle_engine_debug_overlay] {
120
119
  font-family: 'Roboto', sans-serif;
121
120
  font-weight: 400;
122
121
  }
123
122
 
124
- strong {
123
+ div[data-needle_engine_debug_overlay] strong {
125
124
  font-weight: 700;
126
125
  }
127
126
 
128
- a {
127
+ div[data-needle_engine_debug_overlay] a {
129
128
  color: white;
130
129
  text-decoration: none;
131
130
  border-bottom: 1px solid rgba(255, 255, 255, 0.3);
132
131
  }
133
132
 
134
- a:hover {
133
+ div[data-needle_engine_debug_overlay] a:hover {
135
134
  text-decoration: none;
136
135
  border: none;
137
136
  }
138
137
 
139
- .log strong {
138
+ div[data-needle_engine_debug_overlay] .log strong {
140
139
  color: rgba(200,200,200,.9);
141
140
  }
142
141
 
143
- .warn strong {
142
+ div[data-needle_engine_debug_overlay] .warn strong {
144
143
  color: rgba(255,255,230, 1);
145
144
  }
146
145
 
147
- .error strong {
146
+ div[data-needle_engine_debug_overlay] .error strong {
148
147
  color: rgba(255,100,120, 1);
149
148
  }
150
-
151
149
  `;
152
150
 
153
151
  function getLogsContainer(domElement: HTMLElement): HTMLElement {
@@ -155,6 +153,7 @@ function getLogsContainer(domElement: HTMLElement): HTMLElement {
155
153
  return errorContainer.get(domElement)!;
156
154
  } else {
157
155
  const container = document.createElement("div");
156
+ container.setAttribute("data-needle_engine_debug_overlay", "");
158
157
  container.classList.add(arContainerClassName);
159
158
  container.classList.add("desktop");
160
159
  container.classList.add("debug-container");
@@ -206,6 +205,7 @@ function getMessageContainer(type: LogType, msg: string): HTMLElement {
206
205
  }
207
206
  }
208
207
  const element = document.createElement("div");
208
+ element.setAttribute("data-id", "__needle_engine_debug_overlay");
209
209
  element.style.marginRight = "5px";
210
210
  element.style.padding = ".5em";
211
211
  element.style.backgroundColor = "rgba(0,0,0,.9)";
@@ -60,7 +60,7 @@ export class AssetReference {
60
60
  return ref;
61
61
  }
62
62
 
63
- private static currentlyInstantiating: string[] = [];
63
+ private static currentlyInstantiating: Map<string, number> = new Map<string, number>();
64
64
 
65
65
  get asset(): any {
66
66
  return this._glbRoot ?? this._asset;
@@ -236,12 +236,16 @@ export class AssetReference {
236
236
  }
237
237
  }
238
238
 
239
- if (AssetReference.currentlyInstantiating.indexOf(this.uri) >= 0) {
240
- console.error("Recursive instantiation of", this.uri);
239
+ let count = AssetReference.currentlyInstantiating.get(this.uri);
240
+ // allow up to 100 instantiations of the same prefab in the same frame
241
+ if (count !== undefined && count >= 100) {
242
+ console.error("Recursive or too many instantiations of " + this.uri + " in the same frame (" + count + ")");
241
243
  return null;
242
244
  }
243
245
  try {
244
- AssetReference.currentlyInstantiating.push(this.uri);
246
+ if (count === undefined) count = 0;
247
+ count += 1;
248
+ AssetReference.currentlyInstantiating.set(this.uri, count);
245
249
  if (networked) {
246
250
  options.context = context;
247
251
  const prefab = this.asset;
@@ -259,7 +263,11 @@ export class AssetReference {
259
263
  }
260
264
  }
261
265
  finally {
262
- context.post_render_callbacks.push(() => AssetReference.currentlyInstantiating.pop());
266
+ context.post_render_callbacks.push(() => {
267
+ if (count === undefined || count < 0) count = 0;
268
+ else count -= 1;
269
+ AssetReference.currentlyInstantiating.set(this.uri, count)
270
+ });
263
271
  }
264
272
 
265
273
  }
@@ -3,6 +3,8 @@ import { Context } from "./engine_setup"
3
3
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
4
4
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
5
5
  import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
6
+ import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
7
+
6
8
  import { getParam } from "./engine_utils";
7
9
 
8
10
  const debug = getParam("debugdecoders");
@@ -11,6 +13,7 @@ const DEFAULT_DRACO_DECODER_LOCATION ='https://www.gstatic.com/draco/versioned/d
11
13
  const DEFAULT_KTX2_TRANSCODER_LOCATION ='https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/';
12
14
 
13
15
  let dracoLoader: DRACOLoader;
16
+ let meshoptDecoder: MeshoptDecoder;
14
17
  let ktx2Loader: KTX2Loader;
15
18
 
16
19
  export function setDracoDecoderPath(path: string | undefined) {
@@ -40,6 +43,11 @@ export function setKtx2TranscoderPath(path: string) {
40
43
  }
41
44
  }
42
45
 
46
+ export function setMeshoptDecoder(_meshoptDecoder: any) {
47
+ if (_meshoptDecoder !== undefined)
48
+ meshoptDecoder = _meshoptDecoder;
49
+ }
50
+
43
51
  export function addDracoAndKTX2Loaders(loader: GLTFLoader, context: Context) {
44
52
  if (!dracoLoader) {
45
53
  dracoLoader = new DRACOLoader();
@@ -54,7 +62,12 @@ export function addDracoAndKTX2Loaders(loader: GLTFLoader, context: Context) {
54
62
  if (context.renderer)
55
63
  ktx2Loader.detectSupport(context.renderer);
56
64
  }
65
+ if (!meshoptDecoder) {
66
+ meshoptDecoder = MeshoptDecoder;
67
+ if (debug) console.log("Using the default meshopt decoder");
68
+ }
57
69
 
58
70
  loader.setDRACOLoader(dracoLoader);
59
71
  loader.setKTX2Loader(ktx2Loader);
72
+ loader.setMeshoptDecoder(meshoptDecoder);
60
73
  }
@@ -145,12 +145,19 @@ export class Animation extends Behaviour {
145
145
  }
146
146
 
147
147
  play(clipOrNumber: AnimationClip | number | string | undefined, options?: PlayOptions): Promise<AnimationAction> | void {
148
+ if (debug) console.log("PLAY", clipOrNumber)
148
149
  this.init();
149
- if (!this.mixer) return;
150
+ if (!this.mixer) {
151
+ if (debug) console.warn("Missing mixer", this);
152
+ return;
153
+ }
150
154
  if (clipOrNumber === undefined) clipOrNumber = 0;
151
155
  let clip: AnimationClip | undefined = clipOrNumber as AnimationClip;
152
156
  if (typeof clipOrNumber === 'number') {
153
- if (clipOrNumber >= this.animations.length) return;
157
+ if (clipOrNumber >= this.animations.length) {
158
+ if (debug) console.log("No animation at index", clipOrNumber)
159
+ return;
160
+ }
154
161
  clip = this.animations[clipOrNumber];
155
162
  }
156
163
  else if (typeof clipOrNumber === "string") {
@@ -205,7 +212,8 @@ export class Animation extends Behaviour {
205
212
  action.loop = options.loop ? THREE.LoopRepeat : THREE.LoopOnce;
206
213
  else action.loop = THREE.LoopOnce;
207
214
  action.play();
208
- // console.log("PLAY", action.getClip().name, action)
215
+ if (debug)
216
+ console.log("PLAY", action.getClip().name, action)
209
217
 
210
218
  const handle = new AnimationHandle(action, this.mixer!, options, _ => {
211
219
  this._handles.splice(this._handles.indexOf(handle), 1);
@@ -149,7 +149,11 @@ export class PlayableDirector extends Behaviour {
149
149
  }
150
150
 
151
151
  pause() {
152
+ if (!this.isValid()) return;
153
+ this._isPlaying = false;
154
+ if (this._isPaused) return;
152
155
  this._isPaused = true;
156
+ this.evaluate();
153
157
  }
154
158
 
155
159
  stop() {
@@ -267,8 +271,10 @@ export class PlayableDirector extends Behaviour {
267
271
  for (const handler of this._animationTracks) {
268
272
  handler.evaluate(time);
269
273
  }
270
- for (const handler of this._audioTracks) {
271
- handler.evaluate(time);
274
+ if (AudioSource.userInteractionRegistered) {
275
+ for (const handler of this._audioTracks) {
276
+ handler.evaluate(time);
277
+ }
272
278
  }
273
279
  for (const sig of this._signalTracks) {
274
280
  sig.evaluate(time);
@@ -279,7 +285,6 @@ export class PlayableDirector extends Behaviour {
279
285
  for (const cust of this._customTracks) {
280
286
  cust.evaluate(time);
281
287
  }
282
-
283
288
  }
284
289
 
285
290
  private resolveBindings() {
@@ -358,6 +363,7 @@ export class PlayableDirector extends Behaviour {
358
363
 
359
364
  if (!this.playableAsset) return;
360
365
  const audioTracks: Array<Models.TrackModel> = [];
366
+ const audioAllowedCallbacks : any = [];
361
367
  for (const track of this.playableAsset!.tracks) {
362
368
  const type = track.type;
363
369
  const registered = PlayableDirector.createTrackFunctions[type];
@@ -425,7 +431,20 @@ export class PlayableDirector extends Behaviour {
425
431
  }
426
432
  else if (track.type === Models.TrackType.Audio) {
427
433
  if (track.clips.length <= 0) continue;
428
- audioTracks.push(track);
434
+ const audio = new Tracks.AudioTrackHandler();
435
+ audio.director = this;
436
+ audio.track = track;
437
+ this._audioTracks.push(audio);
438
+
439
+ audioAllowedCallbacks.push(() => {
440
+ const listener = GameObject.findObjectOfType(AudioListener, this.context) as AudioListener;
441
+ if (!listener) return;
442
+ audio.listener = listener.listener;
443
+ for (let i = 0; i < track.clips.length; i++) {
444
+ const clipModel = track.clips[i];
445
+ audio.addModel(clipModel);
446
+ }
447
+ });
429
448
  }
430
449
  else if (track.type === Models.TrackType.Marker) {
431
450
  const signalHandler: Tracks.SignalTrackHandler = new Tracks.SignalTrackHandler();
@@ -473,19 +492,8 @@ export class PlayableDirector extends Behaviour {
473
492
  }
474
493
 
475
494
  AudioSource.registerWaitForAllowAudio(() => {
476
- const listener = GameObject.findObjectOfType(AudioListener, this.context) as AudioListener;
477
- if (!listener) return;
478
- for (const track of audioTracks) {
479
- const audio = new Tracks.AudioTrackHandler();
480
- audio.director = this;
481
- audio.track = track;
482
- audio.listener = listener.listener;
483
- for (let i = 0; i < track.clips.length; i++) {
484
- const clipModel = track.clips[i];
485
- audio.addModel(clipModel);
486
- }
487
- this._audioTracks.push(audio);
488
- }
495
+ audioAllowedCallbacks.forEach((cb: any) => cb());
496
+ audioAllowedCallbacks.length = 0;
489
497
  });
490
498
  }
491
499
 
@@ -500,7 +500,9 @@ class MeshUIHelper {
500
500
  if (lu.context === context) {
501
501
  if (context.time.frameCount === lu.frame) return;
502
502
  lu.frame = context.time.frameCount;
503
- if (this.needsUpdate || context.time.frameCount < 30) {
503
+ if (this.needsUpdate || context.time.frameCount < 1) {
504
+ if (debug)
505
+ console.log("Update threemeshui");
504
506
  this.needsUpdate = false;
505
507
  threeMeshUI.update();
506
508
  }
@@ -187,6 +187,8 @@ export class Text extends Graphic {
187
187
  // @ts-ignore
188
188
  this.uiObject.onAfterUpdate = this.updateWidth.bind(this);
189
189
  }
190
+
191
+ setTimeout(()=> this.markDirty(), 10);
190
192
  }
191
193
 
192
194
  private createBlock(rt: RectTransform, hideOverflow: boolean, content: THREE.Object3D | Array<THREE.Object3D> | null, isTextIntermediate: boolean = false): ThreeMeshUI.Block | null {