Needle Engine

Changes between version 3.28.5-beta and 3.28.6-beta
Files changed (11) hide show
  1. plugins/vite/imports-logger.js +2 -1
  2. src/engine-components/Animator.ts +27 -9
  3. src/engine/engine_gameobject.ts +1 -0
  4. src/engine/engine_gizmos.ts +1 -0
  5. src/engine/engine_hot_reload.ts +2 -2
  6. src/engine/engine_physics_rapier.ts +5 -3
  7. src/engine/engine_serialization_builtin_serializer.ts +27 -9
  8. src/engine/engine_three_utils.ts +11 -0
  9. src/engine/engine_types.ts +31 -31
  10. src/engine/codegen/register_types.ts +2 -2
  11. src/engine-components/RigidBody.ts +1 -0
plugins/vite/imports-logger.js CHANGED
@@ -29,7 +29,8 @@
29
29
 
30
30
  // to make this work properly we need to resolve the paths all to absolute paths
31
31
  // assuming that id is relative to importer
32
- id = path.resolve(path.dirname(importer), id);
32
+ if (importer)
33
+ id = path.resolve(path.dirname(importer), id);
33
34
 
34
35
  if (!graph.allNodes.has(id)) {
35
36
  graph.allNodes.set(id, {
src/engine-components/Animator.ts CHANGED
@@ -87,7 +87,8 @@
87
87
  /**@deprecated use setBool */
88
88
  SetBool(name: string | number, val: boolean) { this.setBool(name, val); }
89
89
  setBool(name: string | number, value: boolean) {
90
- if(this.runtimeAnimatorController?.getBool(name) !== value)
90
+ if (debug) console.log("setBool", name, value);
91
+ if (this.runtimeAnimatorController?.getBool(name) !== value)
91
92
  this._parametersAreDirty = true;
92
93
  this.runtimeAnimatorController?.setBool(name, value);
93
94
  }
@@ -95,55 +96,71 @@
95
96
  /**@deprecated use getBool */
96
97
  GetBool(name: string | number) { return this.getBool(name); }
97
98
  getBool(name: string | number): boolean {
98
- return this.runtimeAnimatorController?.getBool(name) ?? false;
99
+ const res = this.runtimeAnimatorController?.getBool(name) ?? false;
100
+ if (debug) console.log("getBool", name, res);
101
+ return res;
99
102
  }
100
103
 
104
+ toggleBool(name: string | number) {
105
+ this.setBool(name, !this.getBool(name));
106
+ }
107
+
101
108
  /**@deprecated use setFloat */
102
109
  SetFloat(name: string | number, val: number) { this.setFloat(name, val); }
103
110
  setFloat(name: string | number, val: number) {
104
- if(this.runtimeAnimatorController?.getFloat(name) !== val)
111
+ if (this.runtimeAnimatorController?.getFloat(name) !== val)
105
112
  this._parametersAreDirty = true;
113
+ if (debug) console.log("setFloat", name, val);
106
114
  this.runtimeAnimatorController?.setFloat(name, val);
107
115
  }
108
116
 
109
117
  /**@deprecated use getFloat */
110
118
  GetFloat(name: string | number) { return this.getFloat(name); }
111
119
  getFloat(name: string | number): number {
112
- return this.runtimeAnimatorController?.getFloat(name) ?? -1;
120
+ const res = this.runtimeAnimatorController?.getFloat(name) ?? -1;
121
+ if (debug) console.log("getFloat", name, res);
122
+ return res;
113
123
  }
114
124
 
115
125
  /**@deprecated use setInteger */
116
126
  SetInteger(name: string | number, val: number) { this.setInteger(name, val); }
117
127
  setInteger(name: string | number, val: number) {
118
- if(this.runtimeAnimatorController?.getInteger(name) !== val)
128
+ if (this.runtimeAnimatorController?.getInteger(name) !== val)
119
129
  this._parametersAreDirty = true;
130
+ if (debug) console.log("setInteger", name, val);
120
131
  this.runtimeAnimatorController?.setInteger(name, val);
121
132
  }
122
133
 
123
134
  /**@deprecated use getInteger */
124
135
  GetInteger(name: string | number) { return this.getInteger(name); }
125
136
  getInteger(name: string | number): number {
126
- return this.runtimeAnimatorController?.getInteger(name) ?? -1;
137
+ const res = this.runtimeAnimatorController?.getInteger(name) ?? -1;
138
+ if (debug) console.log("getInteger", name, res);
139
+ return res;
127
140
  }
128
141
 
129
142
  /**@deprecated use setTrigger */
130
143
  SetTrigger(name: string | number) { this.setTrigger(name); }
131
144
  setTrigger(name: string | number) {
145
+ this._parametersAreDirty = true;
146
+ if (debug) console.log("setTrigger", name);
132
147
  this.runtimeAnimatorController?.setTrigger(name);
133
- this._parametersAreDirty = true;
134
148
  }
135
149
 
136
150
  /**@deprecated use resetTrigger */
137
151
  ResetTrigger(name: string | number) { this.resetTrigger(name); }
138
152
  resetTrigger(name: string | number) {
153
+ this._parametersAreDirty = true;
154
+ if (debug) console.log("resetTrigger", name);
139
155
  this.runtimeAnimatorController?.resetTrigger(name);
140
- this._parametersAreDirty = true;
141
156
  }
142
157
 
143
158
  /**@deprecated use getTrigger */
144
159
  GetTrigger(name: string | number) { this.getTrigger(name); }
145
160
  getTrigger(name: string | number) {
146
- this.runtimeAnimatorController?.getTrigger(name);
161
+ const res = this.runtimeAnimatorController?.getTrigger(name);
162
+ if (debug) console.log("getTrigger", name, res);
163
+ return res;
147
164
  }
148
165
 
149
166
  /**@deprecated use isInTransition */
@@ -156,6 +173,7 @@
156
173
  SetSpeed(speed: number) { return this.setSpeed(speed); }
157
174
  setSpeed(speed: number) {
158
175
  if (speed === this.speed) return;
176
+ if (debug) console.log("setSpeed", speed);
159
177
  this.speed = speed;
160
178
  this._animatorController?.setSpeed(speed);
161
179
  }
src/engine/engine_gameobject.ts CHANGED
@@ -113,6 +113,7 @@
113
113
  internalDestroy(instance, recursive, dispose, true, allComponents);
114
114
  for (const comp of allComponents) {
115
115
  comp.gameObject = null!;
116
+ //@ts-ignore
116
117
  comp.context = null;
117
118
  }
118
119
  }
src/engine/engine_gizmos.ts CHANGED
@@ -273,6 +273,7 @@
273
273
  this.contextBeforeRenderCallbacks.set(context, cb);
274
274
  context.pre_render_callbacks.push(cb);
275
275
  }
276
+ object.renderOrder = 999999;
276
277
  object.layers.disableAll();
277
278
  object.layers.enable(2);
278
279
  object[$cacheSymbol] = cache;
src/engine/engine_hot_reload.ts CHANGED
@@ -133,7 +133,7 @@
133
133
  const active = isComponent ? componentInstance.activeAndEnabled : true;
134
134
  const context = isComponent ? componentInstance.context : undefined;
135
135
  try {
136
- if (isComponent) {
136
+ if (isComponent && context) {
137
137
  removeScriptFromContext(componentInstance, context);
138
138
  }
139
139
  if (isComponent && active) {
@@ -170,7 +170,7 @@
170
170
  if (inst["onAfterHotReloadFields"]) inst["onAfterHotReloadFields"]();
171
171
  }
172
172
  finally {
173
- if (isComponent) {
173
+ if (isComponent && context) {
174
174
  addScriptToArrays(componentInstance, context);
175
175
  }
176
176
  if (isComponent && active) {
src/engine/engine_physics_rapier.ts CHANGED
@@ -1309,6 +1309,7 @@
1309
1309
  const c = active.component;
1310
1310
  if (c.destroyed) continue;
1311
1311
  if (c.activeAndEnabled && c.onCollisionStay) {
1312
+ if(active.collision.collider.destroyed) continue;
1312
1313
  const arg = active.collision;
1313
1314
  c.onCollisionStay(arg);
1314
1315
  }
@@ -1318,6 +1319,7 @@
1318
1319
  if (c.destroyed) continue;
1319
1320
  if (c.activeAndEnabled && c.onTriggerStay) {
1320
1321
  const arg = active.otherCollider;
1322
+ if(arg.destroyed) continue;
1321
1323
  c.onTriggerStay(arg);
1322
1324
  }
1323
1325
  }
@@ -1328,7 +1330,7 @@
1328
1330
  for (let i = 0; i < this.activeCollisions.length; i++) {
1329
1331
  const active = this.activeCollisions[i];
1330
1332
  const collider = active.collider;
1331
- if (collider.destroyed) {
1333
+ if (collider.destroyed || active.collision.collider.destroyed) {
1332
1334
  this.activeCollisions.splice(i, 1);
1333
1335
  i--;
1334
1336
  continue;
@@ -1346,7 +1348,7 @@
1346
1348
  for (let i = 0; i < this.activeCollisionsStay.length; i++) {
1347
1349
  const active = this.activeCollisionsStay[i];
1348
1350
  const collider = active.collider;
1349
- if (collider.destroyed) {
1351
+ if (collider.destroyed || active.collision.collider.destroyed) {
1350
1352
  this.activeCollisionsStay.splice(i, 1);
1351
1353
  i--;
1352
1354
  continue;
@@ -1364,7 +1366,7 @@
1364
1366
  for (let i = 0; i < this.activeTriggers.length; i++) {
1365
1367
  const active = this.activeTriggers[i];
1366
1368
  const collider = active.collider;
1367
- if (collider.destroyed) {
1369
+ if (collider.destroyed || active.otherCollider.destroyed) {
1368
1370
  this.activeTriggers.splice(i, 1);
1369
1371
  i--;
1370
1372
  continue;
src/engine/engine_serialization_builtin_serializer.ts CHANGED
@@ -215,6 +215,7 @@
215
215
  method: string,
216
216
  target: string,
217
217
  argument?: any,
218
+ arguments?: Array<any>,
218
219
  enabled?: boolean,
219
220
  }
220
221
 
@@ -280,16 +281,24 @@
280
281
  }
281
282
  }
282
283
 
283
- let args = call.argument;
284
- if (args !== undefined) {
285
- if (typeof args === "object") {
284
+ function deserializeArgument(arg: any) {
285
+ if (typeof arg === "object") {
286
286
  // Try to deserialize the call argument to either a object or a component reference
287
- let argRes = objectSerializer.onDeserialize(call.argument, context);
288
- if (!argRes) argRes = componentSerializer.onDeserialize(call.argument, context);
289
- if (argRes) args = argRes;
287
+ let argRes = objectSerializer.onDeserialize(arg, context);
288
+ if (!argRes) argRes = componentSerializer.onDeserialize(arg, context);
289
+ if (argRes) return argRes;
290
290
  }
291
+ return arg;
291
292
  }
292
293
 
294
+ let args = call.argument;
295
+ if (args !== undefined) {
296
+ args = deserializeArgument(args);
297
+ }
298
+ else if (call.arguments !== undefined) {
299
+ args = call.arguments.map(deserializeArgument);
300
+ }
301
+
293
302
  // This is the final method we pass to the call info (or undefined if the method couldnt be resolved)
294
303
  const eventMethod = hasMethod ? this.createEventMethod(target, call.method, args) : undefined;
295
304
  const fn = new CallInfo(eventMethod, call.enabled);
@@ -326,8 +335,17 @@
326
335
  return (...forwardedArgs) => {
327
336
  const method = target[methodName];
328
337
  if (typeof method === "function") {
329
- if (args !== undefined)
330
- method?.call(target, args);
338
+ if (args !== undefined) {
339
+ // we now have support for creating event methods with multiple arguments
340
+ // an argument can not be an array right now - so if we receive an array we assume it's the array of arguments that we want to call the method with
341
+ // this means ["test", true] will invoke the method like this: myFunction("test", true)
342
+ if (Array.isArray(args))
343
+ method?.call(target, ...args);
344
+ // in any other case (when we just have one argument) we just call the method with the argument
345
+ // we an not use ...args by default becaue that would break string arguments (it would then just use the first character)
346
+ else
347
+ method?.call(target, args);
348
+ }
331
349
  else // support invoking EventList with any number of arguments (if none were declared in unity)
332
350
  method?.call(target, ...forwardedArgs);
333
351
  }
@@ -357,7 +375,7 @@
357
375
  colorSpace: THREE.LinearSRGBColorSpace,
358
376
  });
359
377
  rt.texture = tex;
360
-
378
+
361
379
  tex.isRenderTargetTexture = true;
362
380
  tex.flipY = true;
363
381
  tex.offset.y = 1;
src/engine/engine_three_utils.ts CHANGED
@@ -236,7 +236,18 @@
236
236
  };
237
237
  }
238
238
 
239
+ export function getParentHierarchyPath(obj: Object3D): string {
240
+ let path = obj?.name || "";
241
+ if(!obj) return path;
242
+ let parent = obj.parent;
243
+ while (parent) {
244
+ path = parent.name + "/" + path;
245
+ parent = parent.parent;
246
+ }
247
+ return path;
248
+ }
239
249
 
250
+
240
251
  export function isAnimationAction(obj: object) {
241
252
  if (obj) {
242
253
  // this doesnt work :(
src/engine/engine_types.ts CHANGED
@@ -101,10 +101,14 @@
101
101
  }
102
102
 
103
103
  export declare interface IGameObject extends Object3D {
104
+
105
+ /** the object's unique identifier */
104
106
  guid: string | undefined;
105
107
 
108
+ /** if the object is enabled in the hierarchy (usually equivalent to `visible`) */
106
109
  activeSelf: boolean;
107
110
 
111
+ /** call to destroy this object including all components that are attached to it. Will destroy all children recursively */
108
112
  destroy(): void;
109
113
 
110
114
  /** NOTE: this is just a wrapper for devs coming from Unity. Please use this.gameObject instead. In Needle Engine this.gameObject is the same as this.gameObject.transform. See the tutorial link below for more information
@@ -113,14 +117,26 @@
113
117
  * */
114
118
  get transform(): IGameObject;
115
119
 
120
+ /** Add a new component to this object. Expects a component type (e.g. `addNewComponent(Animator)`) */
116
121
  addNewComponent<T>(type: Constructor<T>): T | null;
122
+ /** Remove a component from this object. Expected a component instance
123
+ * @returns the removed component (equal to the passed in component)
124
+ */
117
125
  removeComponent(comp: IComponent): IComponent;
126
+ /** Searches for a component type on this object. If no component of the searched type exists yet a new instance will be created and returned */
118
127
  getOrAddComponent<T>(typeName: Constructor<T> | null): T;
128
+ /** Tries to find a component of a type on this object.
129
+ * @returns the first instance of a component on this object that matches the passed in type or null if no component of this type (or a subtype) exists */
119
130
  getComponent<T>(type: Constructor<T>): T | null;
131
+ /** @returns all components of a certain type on this object */
120
132
  getComponents<T>(type: Constructor<T>, arr?: T[]): Array<T>;
133
+ /** Finds a component of a certain type on this object OR a child object if any exists */
121
134
  getComponentInChildren<T>(type: Constructor<T>): T | null;
135
+ /** Finds all components of a certain type on this object AND all children (recursively) */
122
136
  getComponentsInChildren<T>(type: Constructor<T>, arr?: T[]): Array<T>;
137
+ /** Finds a component of a certain type on this object OR a parent object if any exists */
123
138
  getComponentInParent<T>(type: Constructor<T>): T | null;
139
+ /** Finds all components of a certain type on this object AND all parents (recursively) */
124
140
  getComponentsInParent<T>(type: Constructor<T>, arr?: T[]): Array<T>;
125
141
 
126
142
  get worldPosition(): Vector3;
@@ -144,6 +160,7 @@
144
160
  export interface IComponent extends IHasGuid {
145
161
  get isComponent(): boolean;
146
162
 
163
+ /** the object this component is attached to */
147
164
  gameObject: IGameObject;
148
165
  // guid: string;
149
166
  enabled: boolean;
@@ -327,7 +344,9 @@
327
344
  private readonly _normal: Vec3;
328
345
  private readonly _tangentVelocity: Vec3;
329
346
 
347
+ /** the distance of the collision point */
330
348
  readonly distance: number;
349
+ /** the impulse velocity */
331
350
  readonly impulse: number;
332
351
  readonly friction: number;
333
352
 
@@ -343,7 +362,7 @@
343
362
  return target.set(this._normal.x, this._normal.y, this._normal.z);
344
363
  }
345
364
 
346
- /** */
365
+ /** worldspace tangent */
347
366
  get tangentVelocity() {
348
367
  const target = contactsVectorBuffer.get();
349
368
  return target.set(this._tangentVelocity.x, this._tangentVelocity.y, this._tangentVelocity.z);
@@ -361,55 +380,36 @@
361
380
 
362
381
  /// all info in here must be readonly because the object is only created once per started collision
363
382
  export class Collision {
383
+
384
+ /** The contact points of this collision. Contains information about positions, normals, distance, friction, impulse... */
364
385
  readonly contacts: ContactPoint[];
365
386
 
366
- constructor(obj: Object3D, otherCollider: ICollider, contacts: ContactPoint[]) {
387
+ constructor(obj: IGameObject, otherCollider: ICollider, contacts: ContactPoint[]) {
367
388
  this.me = obj;
368
389
  this._collider = otherCollider;
369
390
  this._gameObject = otherCollider.gameObject;
370
391
  this.contacts = contacts;
371
392
  }
372
393
 
373
- readonly me: Object3D;
394
+ /** the gameobject this collision event belongs to (e.g. if onCollisionEnter is called then `me` is the same as `this.gameObject`) */
395
+ readonly me: IGameObject;
396
+
374
397
  private _collider: ICollider;
375
-
376
- /** the collider the collision happened with */
398
+ /** the other collider the collision happened with */
377
399
  get collider(): ICollider {
378
400
  return this._collider;
379
401
  }
380
402
 
381
- /** the object the collision happened with */
382
- private _gameObject: Object3D;
383
- get gameObject(): Object3D {
403
+ private _gameObject: IGameObject;
404
+ /** the other object the collision happened with */
405
+ get gameObject(): IGameObject {
384
406
  return this._gameObject;
385
407
  }
386
408
 
387
- /** the rigidbody we hit, null if none attached */
409
+ /** the other rigidbody we hit, null if none attached */
388
410
  get rigidBody(): IRigidbody | null {
389
411
  return this.collider?.attachedRigidbody;
390
412
  }
391
-
392
-
393
-
394
- // private _normal?: Vector3;
395
- // get normal(): Vector3 {
396
- // if (!this._normal) {
397
- // const vec = this.collision.contact.ni;
398
- // this._normal = new Vector3(vec.x, vec.y, vec.z);
399
- // }
400
- // return this._normal;
401
- // }
402
-
403
-
404
- // private _point?: Vector3;
405
- // get point(): Vector3 {
406
- // if (!this._point) {
407
- // const c = this.collision.contact;
408
- // const point = c.bi.position.clone().vadd(c.ri);
409
- // this._point = new Vector3(point.x, point.y, point.z);
410
- // }
411
- // return this._point;
412
- // }
413
413
  }
414
414
 
415
415
  export type RaycastResult = null | { point: Vector3, collider: ICollider, normal?: Vector3 };
src/engine/codegen/register_types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore.js"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components.js";
5
5
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
@@ -219,7 +219,7 @@
219
219
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering.js";
220
220
  import { XRRig } from "../../engine-components/webxr/WebXRRig.js";
221
221
  import { XRState } from "../../engine-components/XRFlag.js";
222
-
222
+
223
223
  // Register types
224
224
  TypeStore.add("__Ignore", __Ignore);
225
225
  TypeStore.add("ActionBuilder", ActionBuilder);
src/engine-components/RigidBody.ts CHANGED
@@ -115,6 +115,7 @@
115
115
  const original = this.obj.matrixWorld.multiplyMatrices.bind(this.obj.matrixWorld);
116
116
  const lastParentMatrix = new Matrix4();
117
117
  this.obj.matrixWorld["multiplyMatrices"] = (parent: Matrix4, matrix: Matrix4) => {
118
+ if (this.context.physics.engine?.isUpdating || this.mute) return original(parent, matrix);
118
119
  if (!lastParentMatrix.equals(parent)) {
119
120
  this.positionChanged = true;
120
121
  this.rotationChanged = true;