@needle-tools/engine 2.60.4-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.
Files changed (43) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/needle-engine.js +24654 -24499
  3. package/dist/needle-engine.umd.cjs +231 -233
  4. package/lib/engine/debug/debug_overlay.js +9 -9
  5. package/lib/engine/debug/debug_overlay.js.map +1 -1
  6. package/lib/engine/engine_addressables.js +16 -5
  7. package/lib/engine/engine_addressables.js.map +1 -1
  8. package/lib/engine/engine_loaders.d.ts +1 -0
  9. package/lib/engine/engine_loaders.js +12 -0
  10. package/lib/engine/engine_loaders.js.map +1 -1
  11. package/lib/engine-components/Animation.js +12 -3
  12. package/lib/engine-components/Animation.js.map +1 -1
  13. package/lib/engine-components/AnimatorController.js +1 -2
  14. package/lib/engine-components/AnimatorController.js.map +1 -1
  15. package/lib/engine-components/Light.js +3 -1
  16. package/lib/engine-components/Light.js.map +1 -1
  17. package/lib/engine-components/ParticleSystem.d.ts +1 -0
  18. package/lib/engine-components/ParticleSystem.js +20 -3
  19. package/lib/engine-components/ParticleSystem.js.map +1 -1
  20. package/lib/engine-components/Renderer.js.map +1 -1
  21. package/lib/engine-components/timeline/PlayableDirector.js +27 -17
  22. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  23. package/lib/engine-components/ui/Canvas.d.ts +1 -0
  24. package/lib/engine-components/ui/Canvas.js +3 -0
  25. package/lib/engine-components/ui/Canvas.js.map +1 -1
  26. package/lib/engine-components/ui/EventSystem.js +3 -1
  27. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  28. package/lib/engine-components/ui/Text.js +1 -0
  29. package/lib/engine-components/ui/Text.js.map +1 -1
  30. package/lib/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +2 -2
  32. package/src/engine/debug/debug_overlay.ts +9 -9
  33. package/src/engine/engine_addressables.ts +13 -5
  34. package/src/engine/engine_loaders.ts +13 -0
  35. package/src/engine-components/Animation.ts +11 -3
  36. package/src/engine-components/AnimatorController.ts +1 -1
  37. package/src/engine-components/Light.ts +2 -1
  38. package/src/engine-components/ParticleSystem.ts +24 -6
  39. package/src/engine-components/Renderer.ts +1 -2
  40. package/src/engine-components/timeline/PlayableDirector.ts +25 -17
  41. package/src/engine-components/ui/Canvas.ts +3 -0
  42. package/src/engine-components/ui/EventSystem.ts +3 -1
  43. package/src/engine-components/ui/Text.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.60.4-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",
@@ -56,7 +56,7 @@
56
56
  "devDependencies": {
57
57
  "@babel/runtime": "^7.16.0",
58
58
  "@luncheon/esbuild-plugin-gzip": "^0.1.0",
59
- "@needle-tools/gltf-transform-extensions": "^0.11.1-pre",
59
+ "@needle-tools/gltf-transform-extensions": "^0.11.2-pre",
60
60
  "@needle-tools/needle-component-compiler": "1.9.3",
61
61
  "@needle-tools/helper": "^0.2.1-pre",
62
62
  "@types/three": "0.146.0",
@@ -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);
@@ -652,7 +652,7 @@ class RootMotionAction {
652
652
  // lastRotation.invert().premultiply(firstKeyframe).invert();
653
653
  RootMotionAction.spaceRotation[this.clip.uuid].copy(lastRotation);
654
654
 
655
- // if (debugRootMotion)
655
+ if (debugRootMotion)
656
656
  {
657
657
  const euler = new THREE.Euler().setFromQuaternion(lastRotation);
658
658
  console.log("START", this.clip.name, Mathf.toDegrees(euler.y));
@@ -131,7 +131,7 @@ export class Light extends Behaviour implements ILight {
131
131
  }
132
132
  }
133
133
  get shadowNormalBias(): number { return this._shadowNormalBias; }
134
- private _shadowNormalBias: number = 1;
134
+ private _shadowNormalBias: number = 0;
135
135
 
136
136
  /** when enabled this will remove the multiplication when setting the shadow bias settings initially */
137
137
  private _overrideShadowBiasSettings: boolean = false;
@@ -242,6 +242,7 @@ export class Light extends Behaviour implements ILight {
242
242
 
243
243
  awake() {
244
244
  this.color = new THREE.Color(this.color ?? 0xffffff);
245
+ if(debug) console.log(this.name, this);
245
246
  }
246
247
 
247
248
  onEnable(): void {
@@ -10,15 +10,17 @@ import { serializable } from "../engine/engine_serialization";
10
10
  import { RGBAColor } from "./js-extensions/RGBAColor";
11
11
  import { AxesHelper, BufferGeometry, Color, Material, Matrix4, Mesh, MeshStandardMaterial, Object3D, OneMinusDstAlphaFactor, PlaneGeometry, Quaternion, Sprite, SpriteMaterial, Vector3, Vector4 } from "three";
12
12
  import { getWorldPosition, getWorldQuaternion, getWorldScale } from "../engine/engine_three_utils";
13
- import { assign, deserializeObject } from "../engine/engine_serialization_core";
13
+ import { assign } from "../engine/engine_serialization_core";
14
14
  import { BatchedParticleRenderer, Behavior, BillBoardSettings, BurstParameters, ColorGenerator, ConstantColor, ConstantValue, EmissionState, EmitSubParticleSystem, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, IntervalValue, MeshSettings, Particle, ParticleEmitter, ParticleSystem as _ParticleSystem, ParticleSystemParameters, PointEmitter, RecordState, RenderMode, RotationGenerator, SizeOverLife, TrailBatch, TrailParticle, TrailSettings, ValueGenerator } from "three.quarks";
15
15
  import { createFlatTexture } from "../engine/engine_shaders";
16
16
  import { Mathf } from "../engine/engine_math";
17
17
  import { Context } from "../engine/engine_setup";
18
18
  import { ParticleSubEmitter } from "./ParticleSystemSubEmitter";
19
- import { copyFile } from "fs";
19
+ import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive";
20
20
 
21
21
  const debug = getParam("debugparticles");
22
+ const suppressProgressiveLoading = getParam("noprogressive");
23
+ const debugProgressiveLoading = getParam("debugprogressive");
22
24
 
23
25
  export enum SubEmitterType {
24
26
  Birth = 0,
@@ -46,8 +48,6 @@ export class SubEmitterSystem {
46
48
  }
47
49
  }
48
50
 
49
-
50
-
51
51
  export class ParticleSystemRenderer extends Behaviour {
52
52
 
53
53
  @serializable()
@@ -69,8 +69,19 @@ export class ParticleSystemRenderer extends Behaviour {
69
69
  }
70
70
 
71
71
  getMaterial(trailEnabled: boolean = false) {
72
- if (trailEnabled === true && this.trailMaterial) return this.trailMaterial;
73
- return this.particleMaterial;
72
+ const material = (trailEnabled === true && this.trailMaterial) ? this.trailMaterial : this.particleMaterial;
73
+
74
+ // progressive load on start
75
+ // TODO: figure out how to do this before particle system rendering so we only load textures for visible materials
76
+ if (material && !suppressProgressiveLoading && material["_didRequestTextureLOD"] === undefined) {
77
+ material["_didRequestTextureLOD"] = 0;
78
+ if (debugProgressiveLoading) {
79
+ console.log("Load material LOD", material.name);
80
+ }
81
+ NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, material);
82
+ }
83
+
84
+ return material;
74
85
  }
75
86
 
76
87
  getMesh(renderMode?: ParticleSystemRenderMode) {
@@ -830,7 +841,14 @@ export class ParticleSystem extends Behaviour implements IParticleSystem {
830
841
  if (this._time > this.duration) this._time = 0;
831
842
  }
832
843
 
844
+ private lastMaterialVersion: number = -1;
833
845
  private onUpdate() {
846
+ const mat = this.renderer.getMaterial(this.trails.enabled);
847
+ if (mat && mat.version != this.lastMaterialVersion && this._particleSystem) {
848
+ this.lastMaterialVersion = mat.version;
849
+ this._particleSystem.texture = this._interface.texture;
850
+ }
851
+
834
852
  if (this._bursts) {
835
853
  this.emission.bursts = this._bursts;
836
854
  delete this._bursts;
@@ -285,7 +285,7 @@ export class Renderer extends Behaviour implements IRenderer {
285
285
  return lm !== null && lm !== undefined;
286
286
  }
287
287
 
288
- allowProgressiveLoading: boolean = true;
288
+ public allowProgressiveLoading: boolean = true;
289
289
 
290
290
  registering() {
291
291
  if (!this.enabled) {
@@ -546,7 +546,6 @@ export class Renderer extends Behaviour implements IRenderer {
546
546
  console.log("Load material LOD", material.name);
547
547
  }
548
548
  NEEDLE_progressive.assignTextureLOD(this.context, this.sourceId, material);
549
-
550
549
  }
551
550
 
552
551
  if (material.envMapIntensity !== undefined) {
@@ -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
 
@@ -117,6 +117,9 @@ export class Canvas extends UIRootComponent {
117
117
  }
118
118
  }
119
119
 
120
+ applyRenderSettings(){
121
+ this.onRenderSettingsChanged();
122
+ }
120
123
 
121
124
  private _updateRenderSettingsRoutine?: Generator;
122
125
  private onRenderSettingsChanged() {
@@ -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 {