@needle-tools/engine 2.63.3-pre → 2.63.3-pre.1

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 (46) hide show
  1. package/dist/needle-engine.js +4314 -4273
  2. package/dist/needle-engine.umd.cjs +108 -108
  3. package/lib/engine/engine_application.d.ts +7 -1
  4. package/lib/engine/engine_application.js +11 -0
  5. package/lib/engine/engine_application.js.map +1 -1
  6. package/lib/engine/engine_serialization_builtin_serializer.d.ts +1 -0
  7. package/lib/engine/engine_serialization_builtin_serializer.js +30 -25
  8. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  9. package/lib/engine/engine_serialization_core.js +8 -0
  10. package/lib/engine/engine_serialization_core.js.map +1 -1
  11. package/lib/engine-components/AudioListener.js +2 -0
  12. package/lib/engine-components/AudioListener.js.map +1 -1
  13. package/lib/engine-components/AudioSource.d.ts +2 -1
  14. package/lib/engine-components/AudioSource.js +18 -3
  15. package/lib/engine-components/AudioSource.js.map +1 -1
  16. package/lib/engine-components/GridHelper.d.ts +1 -0
  17. package/lib/engine-components/GridHelper.js +7 -0
  18. package/lib/engine-components/GridHelper.js.map +1 -1
  19. package/lib/engine-components/WebARSessionRoot.d.ts +1 -0
  20. package/lib/engine-components/WebARSessionRoot.js +10 -0
  21. package/lib/engine-components/WebARSessionRoot.js.map +1 -1
  22. package/lib/engine-components/js-extensions/Object3D.js +4 -1
  23. package/lib/engine-components/js-extensions/Object3D.js.map +1 -1
  24. package/lib/engine-components/timeline/TimelineTracks.js +9 -2
  25. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  26. package/lib/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +2 -1
  28. package/src/engine/engine_application.ts +14 -3
  29. package/src/engine/engine_serialization_builtin_serializer.ts +36 -27
  30. package/src/engine/engine_serialization_core.ts +8 -1
  31. package/src/engine-components/AudioListener.ts +1 -0
  32. package/src/engine-components/AudioSource.ts +19 -5
  33. package/src/engine-components/GridHelper.ts +10 -2
  34. package/src/engine-components/WebARSessionRoot.ts +14 -0
  35. package/src/engine-components/js-extensions/Object3D.ts +4 -2
  36. package/src/engine-components/timeline/TimelineTracks.ts +9 -3
  37. package/src/tsconfig.json +33 -0
  38. /package/{src/plugins → plugins}/vite/build.js +0 -0
  39. /package/{src/plugins → plugins}/vite/config.js +0 -0
  40. /package/{src/plugins → plugins}/vite/gzip.js +0 -0
  41. /package/{src/plugins → plugins}/vite/index.js +0 -0
  42. /package/{src/plugins → plugins}/vite/meta.js +0 -0
  43. /package/{src/plugins → plugins}/vite/poster-client.js +0 -0
  44. /package/{src/plugins → plugins}/vite/poster.js +0 -0
  45. /package/{src/plugins → plugins}/vite/reload-client.js +0 -0
  46. /package/{src/plugins → plugins}/vite/reload.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "2.63.3-pre",
3
+ "version": "2.63.3-pre.1",
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",
@@ -26,6 +26,7 @@
26
26
  "src",
27
27
  "dist",
28
28
  "lib",
29
+ "plugins",
29
30
  "license-2447137e.js"
30
31
  ],
31
32
  "keywords": [
@@ -3,13 +3,24 @@ import { Context } from "./engine_setup";
3
3
  export enum ApplicationEvents {
4
4
  Visible = "application-visible",
5
5
  Hidden = "application-hidden",
6
+ MuteChanged = "application-mutechanged",
6
7
  }
7
8
 
8
9
  export class Application extends EventTarget {
9
10
 
10
- private context : Context;
11
+ private _mute: boolean = false;
12
+ /** audio muted? */
13
+ get muted() { return this._mute; }
14
+ /** set global audio mute */
15
+ set muted(value) {
16
+ if (value === this._mute) return;
17
+ this._mute = value;
18
+ this.dispatchEvent(new Event(ApplicationEvents.MuteChanged));
19
+ }
20
+
21
+ private context: Context;
11
22
 
12
- public get hasFocus() : boolean {
23
+ public get hasFocus(): boolean {
13
24
  return document.hasFocus();
14
25
  }
15
26
 
@@ -19,7 +30,7 @@ export class Application extends EventTarget {
19
30
 
20
31
  private _isVisible: boolean = true;
21
32
 
22
- constructor(context : Context) {
33
+ constructor(context: Context) {
23
34
  super();
24
35
  this.context = context;
25
36
  // console.log("APP");
@@ -206,6 +206,8 @@ declare type EventListCall = {
206
206
  enabled?: boolean,
207
207
  }
208
208
 
209
+ const $eventListDebugInfo = Symbol("eventListDebugInfo");
210
+
209
211
  class EventListSerializer extends TypeSerializer {
210
212
  constructor() {
211
213
  super([EventList]);
@@ -238,13 +240,18 @@ class EventListSerializer extends TypeSerializer {
238
240
  const hasMethod = call.method?.length > 0;
239
241
  if (target && hasMethod) {
240
242
  const printWarningMethodNotFound = () => console.warn(`Could not find method ${call.method} on object ${target.name}`, target, typeof target[call.method]);
241
- if (typeof target[call.method] !== "function") {
243
+ const method = target[call.method];
244
+ if (typeof method !== "function") {
242
245
  let foundMethod = false;
246
+ let currentPrototype = target;
243
247
  // test if the target method is actually a property setter
244
- // in which case we want to remove the leading set prefix and see if the method or property exists
245
- if (call.method.startsWith("set_") && call.method.length > 4) {
246
- call.method = call.method.substring(4);
247
- if (target[call.method] !== undefined) foundMethod = true;
248
+ while (currentPrototype) {
249
+ const desc = Object.getOwnPropertyDescriptor(currentPrototype, call.method);
250
+ if (desc && (desc.writable === true || desc.set)) {
251
+ foundMethod = true;
252
+ break;
253
+ }
254
+ currentPrototype = Object.getPrototypeOf(currentPrototype);
248
255
  }
249
256
  if (!foundMethod && (isDevEnvironment() || debugExtension))
250
257
  printWarningMethodNotFound();
@@ -252,38 +259,23 @@ class EventListSerializer extends TypeSerializer {
252
259
  }
253
260
  let fn: CallInfo | undefined;
254
261
  let args = call.argument;
262
+
263
+ // This is the final method we pass to the call info (or undefined if the method couldnt be resolved)
264
+ const eventMethod = hasMethod ? this.createEventMethod(target, call.method, args) : undefined;
265
+
255
266
  if (call.argument !== undefined) {
256
267
  if (typeof args === "object") {
257
268
  args = objectSerializer.onDeserialize(call.argument, context);
258
269
  if (!args) args = componentSerializer.onDeserialize(call.argument, context);
259
270
  }
260
- fn = new CallInfo(hasMethod ? (...args) => invokeFunction(...args) : undefined, call.enabled);
271
+ fn = new CallInfo(eventMethod, call.enabled);
261
272
  }
262
273
  else
263
- fn = new CallInfo(hasMethod ? (...args) => invokeFunction(...args) : undefined, call.enabled);
274
+ fn = new CallInfo(eventMethod, call.enabled);
264
275
 
265
276
 
266
- // TODO: move this into the event list directly
267
- // this scope should not stay in the serializer.
268
- // the event list should be able to modify the args that were set in unity if necessary
269
- // and pass them on to the component
270
- const invokeFunction = (...forwardedArgs) => {
271
- const method = target[call.method];
272
-
273
- if (typeof method === "function") {
274
- if (args !== undefined)
275
- method?.call(target, args);
276
- else // support invoking EventList with any number of arguments (if none were declared in unity)
277
- method?.call(target, ...forwardedArgs);
278
- }
279
- else // the target "method" can be a property too
280
- {
281
- target[call.method] = args;
282
- }
283
- };
284
-
285
277
  if (!fn.method)
286
- fn["__debuginfo"] = context.object?.name;
278
+ fn[$eventListDebugInfo] = context.object?.name;
287
279
  if (!target || !fn.method) {
288
280
  const debugInfo = context.object ? "Current object: " + context.object.name + ", " + context.object["guid"] : null;
289
281
  if (!target)
@@ -307,6 +299,23 @@ class EventListSerializer extends TypeSerializer {
307
299
  }
308
300
  return undefined;
309
301
  }
302
+
303
+ private createEventMethod(target : object, methodName: string, args?: any) : Function | undefined {
304
+
305
+ return (...forwardedArgs) => {
306
+ const method = target[methodName];
307
+ if (typeof method === "function") {
308
+ if (args !== undefined)
309
+ method?.call(target, args);
310
+ else // support invoking EventList with any number of arguments (if none were declared in unity)
311
+ method?.call(target, ...forwardedArgs);
312
+ }
313
+ else // the target "method" can be a property too
314
+ {
315
+ target[methodName] = args;
316
+ }
317
+ };
318
+ }
310
319
  }
311
320
  export const eventListSerializer = new EventListSerializer();
312
321
 
@@ -1,6 +1,6 @@
1
1
  import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
2
2
  import { getParam } from "./engine_utils";
3
- import { Object3D } from "three";
3
+ import { AnimationClip, Material, Mesh, Object3D, Texture } from "three";
4
4
  import { Context } from "./engine_setup";
5
5
  import { isPersistentAsset } from "./extensions/NEEDLE_persistent_assets";
6
6
  import { SourceIdentifier } from "./engine_types";
@@ -545,6 +545,13 @@ function deserializeObjectWithType(data: any, typeOrConstructor: Constructor<any
545
545
  else {
546
546
  // happens when exporting e.g. Animation component with only clip assigned (clips array is marked as serialized but it might be undefined if no clips are assigned in e.g. blender)
547
547
  if (data === undefined) return undefined;
548
+ if (data === null) {
549
+ // Dont implictly create instances for three types that are not assigned
550
+ // see: https://github.com/needle-tools/needle-tiny/issues/637
551
+ if (type === Material || type === Texture || type === Mesh || type === AnimationClip) {
552
+ return null;
553
+ }
554
+ }
548
555
  // the fallback - this assumes that the type has a constructor that accepts the serialized arguments
549
556
  // made originally with THREE.Vector3 in mind but SHOULD actually not be used/called anymore
550
557
  instance = new type(...setBuffer(data));
@@ -16,6 +16,7 @@ export class AudioListener extends Behaviour {
16
16
 
17
17
  awake() {
18
18
  AudioSource.registerWaitForAllowAudio(() => {
19
+ if (this.destroyed) return;
19
20
  const listener = this.listener;
20
21
  if (listener == null) return;
21
22
  // if the listener is already parented to some object d0nt change it
@@ -133,7 +133,7 @@ export class AudioSource extends Behaviour {
133
133
  get volume(): number { return this._volume; }
134
134
  set volume(val: number) {
135
135
  this._volume = val;
136
- if (this.sound) {
136
+ if (this.sound && !this.context.application.muted) {
137
137
  if (debug) console.log(this.name, "audio set volume", val);
138
138
  this.sound.setVolume(val);
139
139
  }
@@ -167,7 +167,9 @@ export class AudioSource extends Behaviour {
167
167
 
168
168
  public get ShouldPlay(): boolean { return this.shouldPlay; }
169
169
 
170
- private _focusCallback?: any;
170
+
171
+ private _focusCallback: any;
172
+ private _muteChangedCallback: any;
171
173
 
172
174
  awake() {
173
175
  this.audioLoader = new THREE.AudioLoader();
@@ -189,13 +191,22 @@ export class AudioSource extends Behaviour {
189
191
  if (this.enabled && this.playOnAwake && !this.isPlaying && AudioSource._userInteractionRegistered) {
190
192
  this.play();
191
193
  }
192
- };
194
+ }
195
+
196
+ this._muteChangedCallback = () => {
197
+ if (this.context.application.muted)
198
+ this.sound?.setVolume(0);
199
+ else
200
+ this.sound?.setVolume(this.volume);
201
+ }
193
202
 
194
203
  this.context.application.addEventListener(ApplicationEvents.Visible, this._focusCallback);
204
+ this.context.application.addEventListener(ApplicationEvents.MuteChanged, this._muteChangedCallback);
195
205
  }
196
206
 
197
207
  onDestroy() {
198
208
  this.context.application.removeEventListener(ApplicationEvents.Visible, this._focusCallback);
209
+ this.context.application.removeEventListener(ApplicationEvents.MuteChanged, this._muteChangedCallback);
199
210
  }
200
211
 
201
212
 
@@ -233,7 +244,8 @@ export class AudioSource extends Behaviour {
233
244
 
234
245
  sound.setBuffer(buffer);
235
246
  sound.loop = this._loop;
236
- sound.setVolume(this.volume);
247
+ if(this.context.application.muted) sound.setVolume(0);
248
+ else sound.setVolume(this.volume);
237
249
  sound.autoplay = this.shouldPlay;
238
250
  // sound.setDistanceModel('linear');
239
251
  // sound.setRolloffFactor(1);
@@ -313,7 +325,9 @@ export class AudioSource extends Behaviour {
313
325
  if (debug)
314
326
  console.log("play", this.sound?.getVolume(), this.sound);
315
327
  if (this.sound && !this.sound.isPlaying) {
316
- this.sound.play();
328
+ const muted = this.context.application.muted;
329
+ if(muted) this.sound.setVolume(0);
330
+ this.sound.play(muted ? .1 : 0);
317
331
  }
318
332
  }
319
333
 
@@ -6,7 +6,7 @@ import { Color, GridHelper as _GridHelper } from "three";
6
6
  export class GridHelper extends Behaviour {
7
7
 
8
8
  @serializable()
9
- public isGizmo:boolean = false;
9
+ public isGizmo: boolean = false;
10
10
  @serializable(Color)
11
11
  private color0!: THREE.Color;
12
12
  @serializable(Color)
@@ -23,10 +23,18 @@ export class GridHelper extends Behaviour {
23
23
  const size = this.size;
24
24
  const divisions = this.divisions;
25
25
  if (!this.gridHelper) {
26
- this.gridHelper = new _GridHelper(size, divisions, this.color0 ?? new Color(.4, .4, .4), this.color1 ?? new Color(.6,.6,.6));
26
+ this.gridHelper = new _GridHelper(size, divisions, this.color0 ?? new Color(.4, .4, .4), this.color1 ?? new Color(.6, .6, .6));
27
27
  if (this.offset !== undefined)
28
28
  this.gridHelper.position.y += this.offset;
29
+ }
30
+ if (this.gridHelper)
29
31
  this.gameObject.add(this.gridHelper);
32
+ }
33
+
34
+ onDisable(): void {
35
+ if (this.gridHelper) {
36
+ this.gameObject.remove(this.gridHelper);
37
+ this.gridHelper = null;
30
38
  }
31
39
  }
32
40
  }
@@ -45,6 +45,7 @@ export class WebARSessionRoot extends Behaviour {
45
45
  private _placementPose: Matrix4 | null = null;
46
46
  private _isTouching: boolean = false;
47
47
  private _rigStartPose: Matrix4 | undefined | null = null;
48
+ private _gotFirstHitTestResult: boolean = false;
48
49
 
49
50
  onBegin(session: XRSession) {
50
51
  this._placementPose = null;
@@ -52,6 +53,7 @@ export class WebARSessionRoot extends Behaviour {
52
53
  this.gameObject.matrixAutoUpdate = false;
53
54
  this._startPose = this.gameObject.matrix.clone();
54
55
  this._rigStartPose = this.rig?.matrix.clone();
56
+ this._gotFirstHitTestResult = false;
55
57
  session.addEventListener('selectstart', this._selectStartFn);
56
58
  session.addEventListener('selectend', this._selectEndFn);
57
59
  // setTimeout(() => this.gameObject.visible = false, 1000); // TODO test on phone AR and Hololens if this was still needed
@@ -66,12 +68,24 @@ export class WebARSessionRoot extends Behaviour {
66
68
  this.rig.matrixAutoUpdate = true;
67
69
  this._initalMatrix.decompose(this.rig.position, this.rig.quaternion, this.rig.scale);
68
70
  }
71
+
72
+ // TODO this is duplicate to WebXR events AND engine events, would be better in one place
73
+ this.dispatchEvent(new CustomEvent('onBeginSession'));
69
74
  }
70
75
 
71
76
  onUpdate(rig: Object3D | null, _session: XRSession, pose: XRPose | null | undefined): boolean {
72
77
 
73
78
  if (pose && !this._placementPose) {
79
+
80
+ if (!this._gotFirstHitTestResult) {
81
+ this._gotFirstHitTestResult = true;
82
+ this.dispatchEvent(new CustomEvent('canPlaceSession', { detail: { pose: pose } }));
83
+ }
84
+
74
85
  if (this._isTouching) {
86
+ // callbacks
87
+ this.dispatchEvent(new CustomEvent('placedSession', { detail: { pose: pose } }));
88
+
75
89
  if (this.webAR) this.webAR.setReticleActive(false);
76
90
  this.placeAt(rig, new Matrix4().fromArray(pose.transform.matrix).invert());
77
91
  return true;
@@ -13,11 +13,13 @@ export function apply(object: Object3D) {
13
13
  }
14
14
 
15
15
 
16
-
17
- // do we still need this?
18
16
  Object3D.prototype["SetActive"] = function (active: boolean) {
19
17
  this.visible = active;
20
18
  }
19
+ // e.g. when called via a UnityEvent
20
+ Object3D.prototype["setActive"] = function (active: boolean) {
21
+ this.visible = active;
22
+ }
21
23
 
22
24
  Object3D.prototype["addNewComponent"] = function <T extends Component>(type: ConstructorConcrete<T>) {
23
25
  return addNewComponent(this, new type());
@@ -524,6 +524,11 @@ export class AudioTrackHandler extends TrackHandler {
524
524
  evaluate(time: number) {
525
525
  if (muteAudioTracks) return;
526
526
  if (this.track.muted) return;
527
+ const isMuted = this.director.context.application.muted;
528
+ // this is just so that we dont hear the very first beat when the audio starts but is muted
529
+ // if we dont add a delay we hear a little bit of the audio before it shuts down
530
+ // MAYBE instead of doing it like this we should connect a custom audio node (or disconnect the output node?)
531
+ const playTimeOffset = isMuted ? .1 : 0;
527
532
  for (let i = 0; i < this.models.length; i++) {
528
533
  const model = this.models[i];
529
534
  const audio = this.audio[i];
@@ -535,7 +540,7 @@ export class AudioTrackHandler extends TrackHandler {
535
540
  }
536
541
  else if (!audio.isPlaying) {
537
542
  audio.offset = model.clipIn + (time - model.start) * model.timeScale;
538
- audio.play();
543
+ audio.play(playTimeOffset);
539
544
  }
540
545
  else {
541
546
  const targetOffset = model.clipIn + (time - model.start) * model.timeScale;
@@ -547,10 +552,11 @@ export class AudioTrackHandler extends TrackHandler {
547
552
  if (diff > 0.3) {
548
553
  audio.offset = targetOffset;
549
554
  audio.stop();
550
- audio.play();
555
+ audio.play(playTimeOffset);
551
556
  }
552
557
  }
553
- let vol = model.asset.volume;
558
+ let vol = model.asset.volume as number;
559
+ if(isMuted) vol = 0;
554
560
  if (model.easeInDuration > 0) {
555
561
  const easeIn = Math.min((time - model.start) / model.easeInDuration, 1);
556
562
  vol *= easeIn;
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "outDir": "dist",
6
+ "useDefineForClassFields": true,
7
+ "lib": ["ESNext", "DOM"],
8
+ "moduleResolution": "Node",
9
+ "strict": true,
10
+ "sourceMap": true,
11
+ "resolveJsonModule": true,
12
+ "esModuleInterop": true,
13
+ "noEmit": true,
14
+ "noUnusedLocals": false,
15
+ "noUnusedParameters": true,
16
+ "noImplicitReturns": true,
17
+ "noImplicitAny": false,
18
+ "isolatedModules": true,
19
+ "jsx": "preserve",
20
+ "incremental": true,
21
+ "experimentalDecorators": true,
22
+ "skipLibCheck": true,
23
+ "allowJs": true,
24
+ "types": [
25
+ "three",
26
+ "vite/client"
27
+ ]
28
+ },
29
+ "include": [
30
+ "**/*.ts"
31
+ ],
32
+ "exclude": ["node_modules", "dist"]
33
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes