@needle-tools/engine 2.38.0-pre.2 → 2.40.0-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 (63) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/needle-engine.d.ts +129 -36
  3. package/dist/needle-engine.js +356 -356
  4. package/dist/needle-engine.js.map +3 -3
  5. package/dist/needle-engine.min.js +61 -61
  6. package/dist/needle-engine.min.js.map +3 -3
  7. package/lib/engine/api.d.ts +1 -0
  8. package/lib/engine/api.js +1 -0
  9. package/lib/engine/api.js.map +1 -1
  10. package/lib/engine/engine_gizmos.d.ts +24 -0
  11. package/lib/engine/engine_gizmos.js +97 -5
  12. package/lib/engine/engine_gizmos.js.map +1 -1
  13. package/lib/engine/engine_gltf_builtin_components.js +4 -2
  14. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  15. package/lib/engine/engine_input.d.ts +2 -0
  16. package/lib/engine/engine_input.js +9 -2
  17. package/lib/engine/engine_input.js.map +1 -1
  18. package/lib/engine/engine_networking.d.ts +1 -0
  19. package/lib/engine/engine_networking.js +9 -0
  20. package/lib/engine/engine_networking.js.map +1 -1
  21. package/lib/engine/engine_physics.d.ts +20 -2
  22. package/lib/engine/engine_physics.js +133 -16
  23. package/lib/engine/engine_physics.js.map +1 -1
  24. package/lib/engine/engine_serialization_core.d.ts +11 -1
  25. package/lib/engine/engine_serialization_core.js +41 -10
  26. package/lib/engine/engine_serialization_core.js.map +1 -1
  27. package/lib/engine/engine_three_utils.js +1 -22
  28. package/lib/engine/engine_three_utils.js.map +1 -1
  29. package/lib/engine/engine_types.d.ts +12 -6
  30. package/lib/engine/engine_types.js +22 -5
  31. package/lib/engine/engine_types.js.map +1 -1
  32. package/lib/engine/engine_utils.d.ts +8 -0
  33. package/lib/engine/engine_utils.js +22 -0
  34. package/lib/engine/engine_utils.js.map +1 -1
  35. package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
  36. package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
  37. package/lib/engine-components/Component.d.ts +18 -0
  38. package/lib/engine-components/Component.js +15 -2
  39. package/lib/engine-components/Component.js.map +1 -1
  40. package/lib/engine-components/Joints.d.ts +5 -1
  41. package/lib/engine-components/Joints.js +17 -7
  42. package/lib/engine-components/Joints.js.map +1 -1
  43. package/lib/engine-components/Light.js +1 -1
  44. package/lib/engine-components/codegen/components.d.ts +1 -0
  45. package/lib/engine-components/codegen/components.js +1 -0
  46. package/lib/engine-components/codegen/components.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/engine/api.ts +2 -1
  49. package/src/engine/codegen/register_types.js +4 -0
  50. package/src/engine/engine_gizmos.ts +117 -5
  51. package/src/engine/engine_gltf_builtin_components.ts +5 -2
  52. package/src/engine/engine_input.ts +12 -2
  53. package/src/engine/engine_networking.ts +10 -0
  54. package/src/engine/engine_physics.ts +159 -17
  55. package/src/engine/engine_serialization_core.ts +49 -12
  56. package/src/engine/engine_three_utils.ts +1 -29
  57. package/src/engine/engine_types.ts +33 -14
  58. package/src/engine/engine_utils.ts +27 -0
  59. package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
  60. package/src/engine-components/Component.ts +30 -4
  61. package/src/engine-components/Joints.ts +19 -7
  62. package/src/engine-components/Light.ts +1 -1
  63. package/src/engine-components/codegen/components.ts +1 -0
@@ -1,15 +1,127 @@
1
1
  import * as THREE from 'three';
2
+ import { BufferAttribute, Line, BoxGeometry, EdgesGeometry, Color, LineSegments, LineBasicMaterial, Object3D, Mesh, SphereGeometry, ColorRepresentation, Vector3 } from 'three';
3
+ import { Context } from './engine_setup';
4
+ import { setWorldPosition, setWorldPositionXYZ } from './engine_three_utils';
5
+ import { Vec3 } from './engine_types';
2
6
 
3
- const box: THREE.BoxGeometry = new THREE.BoxGeometry(1, 1, 1);
7
+ const _tmp = new Vector3();
4
8
 
5
- export function CreateWireCube(col: THREE.ColorRepresentation | null = null) : THREE.LineSegments {
6
- const color = new THREE.Color(col ?? 0xdddddd);
9
+ export class Gizmos {
10
+
11
+ static DrawRay(origin: Vec3, dir: Vec3, color: ColorRepresentation = 0x888888, duration: number = 0, depthTest: boolean = true) {
12
+ const obj = Internal.getLine(duration);
13
+ const positions = obj.geometry.getAttribute("position");
14
+ positions.setXYZ(0, origin.x, origin.y, origin.z);
15
+ _tmp.set(dir.x, dir.y, dir.z).multiplyScalar(999999999);
16
+ positions.setXYZ(1, origin.x + _tmp.x, origin.y + _tmp.y, origin.z + _tmp.z);
17
+ positions.needsUpdate = true;
18
+ obj.material["color"].set(color);
19
+ obj.material["depthTest"] = depthTest;
20
+ }
21
+
22
+ static DrawLine(pt0: { x: number, y: number, z: number }, pt1: { x: number, y: number, z: number }, color: ColorRepresentation = 0x888888, duration: number = 0, depthTest: boolean = true) {
23
+ const obj = Internal.getLine(duration);
24
+
25
+ const positions = obj.geometry.getAttribute("position");
26
+ positions.setXYZ(0, pt0.x, pt0.y, pt0.z);
27
+ positions.setXYZ(1, pt1.x, pt1.y, pt1.z);
28
+ positions.needsUpdate = true;
29
+ obj.material["color"].set(color);
30
+ obj.material["depthTest"] = depthTest;
31
+ }
32
+
33
+ static DrawWireSphere(center: { x: number, y: number, z: number }, radius: number, color: ColorRepresentation = 0x888888, duration: number = 0, depthTest: boolean = true) {
34
+ const obj = Internal.getSphere(radius, duration, true);
35
+ setWorldPositionXYZ(obj, center.x, center.y, center.z);
36
+ obj.material["color"].set(color);
37
+ obj.material["depthTest"] = depthTest;
38
+ }
39
+
40
+ static DrawSphere(center: { x: number, y: number, z: number }, radius: number, color: ColorRepresentation = 0x888888, duration: number = 0, depthTest: boolean = true) {
41
+ const obj = Internal.getSphere(radius, duration, false);
42
+ setWorldPositionXYZ(obj, center.x, center.y, center.z);
43
+ obj.material["color"].set(color);
44
+ obj.material["depthTest"] = depthTest;
45
+ }
46
+ }
47
+
48
+ const box: BoxGeometry = new BoxGeometry(1, 1, 1);
49
+ export function CreateWireCube(col: THREE.ColorRepresentation | null = null): THREE.LineSegments {
50
+ const color = new Color(col ?? 0xdddddd);
7
51
  // const material = new THREE.MeshBasicMaterial();
8
52
  // material.color = new THREE.Color(col ?? 0xdddddd);
9
53
  // material.wireframe = true;
10
54
  // const box = new THREE.Mesh(box, material);
11
55
  // box.name = "BOX_GIZMO";
12
- const edges = new THREE.EdgesGeometry(box);
13
- const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: color }));
56
+ const edges = new EdgesGeometry(box);
57
+ const line = new LineSegments(edges, new LineBasicMaterial({ color: color }));
14
58
  return line;
59
+ }
60
+
61
+
62
+
63
+ const $cacheSymbol = Symbol("GizmoCache");
64
+ class Internal {
65
+ // private static createdLines: number = 0;
66
+
67
+ static getLine(duration: number): Line {
68
+ let line = this.linesCache.pop();
69
+ if (!line) {
70
+ line = new Line();
71
+ let positions = line.geometry.getAttribute("position");
72
+ if (!positions) {
73
+ positions = new BufferAttribute(new Float32Array(2 * 3), 3);
74
+ line.geometry.setAttribute("position", positions);
75
+ }
76
+ }
77
+ this.registerTimedObject(Context.Current, line, duration, this.linesCache);
78
+ return line;
79
+ }
80
+
81
+ static getSphere(radius: number, duration: number, wireframe: boolean): Mesh {
82
+
83
+ let sphere = this.spheresCache.pop();
84
+ if (!sphere) {
85
+ sphere = new Mesh(new SphereGeometry(.5, 8, 8));
86
+ }
87
+ sphere.scale.set(radius, radius, radius);
88
+ sphere.material["wireframe"] = wireframe;
89
+ this.registerTimedObject(Context.Current, sphere, duration, this.spheresCache);
90
+ return sphere;
91
+ }
92
+
93
+ private static linesCache: Array<Line> = [];
94
+ private static spheresCache: Mesh[] = [];
95
+
96
+ private static registerTimedObject(context: Context, object: Object3D, duration: number, cache: Array<Object3D>) {
97
+ if (!this.contextPostRenderCallbacks.get(context)) {
98
+ const cb = () => { this.onPostRender(context, this.timedObjectsBuffer, this.timesBuffer) };
99
+ this.contextPostRenderCallbacks.set(context, cb);
100
+ context.post_render_callbacks.push(cb);
101
+ }
102
+ object[$cacheSymbol] = cache;
103
+ this.timedObjectsBuffer.push(object);
104
+ this.timesBuffer.push(Context.Current.time.time + duration);
105
+ context.scene.add(object);
106
+ }
107
+
108
+
109
+ private static timedObjectsBuffer = new Array<Object3D>();
110
+ private static timesBuffer = new Array<number>();
111
+ private static contextPostRenderCallbacks = new Map<Context, () => void>();
112
+
113
+ private static onPostRender(ctx: Context, objects: Array<Object3D>, times: Array<number>) {
114
+ const time = ctx.time.time;
115
+ for (let i = 0; i < objects.length; i++) {
116
+ if (time > times[i]) {
117
+ const obj = objects[i];
118
+ const cache = obj[$cacheSymbol];
119
+ cache.push(obj as Line);
120
+ ctx.scene.remove(obj);
121
+ objects.splice(i, 1);
122
+ times.splice(i, 1);
123
+ }
124
+ }
125
+ }
126
+
15
127
  }
@@ -5,7 +5,7 @@ import { Component, GameObject } from "../engine-components/Component";
5
5
  import { InstantiateIdProvider } from "./engine_networking_instantiate"
6
6
  import { Context } from "./engine_setup";
7
7
  import { deserializeObject, serializeObject } from "./engine_serialization";
8
- import { assign, ISerializable, SerializationContext } from "./engine_serialization_core";
8
+ import { assign, ImplementationInformation, ISerializable, SerializationContext } from "./engine_serialization_core";
9
9
  import { NEEDLE_components } from "./extensions/NEEDLE_components";
10
10
  import { debugExtension } from "./engine_default_parameters";
11
11
  import { builtinComponentKeyName } from "./engine_constants";
@@ -38,6 +38,8 @@ export function writeBuiltinComponentData(comp: Component, context: Serializatio
38
38
  return null;
39
39
  }
40
40
 
41
+ const typeImplementationInformation = new ImplementationInformation();
42
+
41
43
  export async function createBuiltinComponents(context: Context, gltfId: SourceIdentifier, gltf, seed: number | null | UIDProvider = null, extension?: NEEDLE_components) {
42
44
  if (!gltf) return;
43
45
  const lateResolve: Array<(gltf: THREE.Object3D) => {}> = [];
@@ -52,6 +54,7 @@ export async function createBuiltinComponents(context: Context, gltfId: SourceId
52
54
  serializationContext.context = context;
53
55
  serializationContext.gltf = gltf;
54
56
  serializationContext.nodeToObject = extension?.nodeToObjectMap;
57
+ serializationContext.implementationInformation = typeImplementationInformation;
55
58
 
56
59
  const deserialize: DeserializeData[] = [];
57
60
 
@@ -149,7 +152,7 @@ async function onCreateBuiltinComponents(context: SerializationContext, obj: THR
149
152
  instance.sourceId = context.gltfId;
150
153
 
151
154
  // assign basic fields
152
- assign(instance, compData);
155
+ assign(instance, compData, context.implementationInformation);
153
156
  // Object.assign(instance, compData);
154
157
  // dont call awake here because some references might not be resolved yet and components that access those fields in awake will throw
155
158
  // for example Duplicatable reference to object might still be { node: id }
@@ -1,6 +1,8 @@
1
1
  import * as THREE from 'three';
2
+ import { Vector2 } from 'three';
2
3
  import { assign } from './engine_serialization_core';
3
4
  import { Context } from './engine_setup';
5
+ import { Vec2 } from './engine_types';
4
6
 
5
7
  export declare type PointerEventArgs = {
6
8
  button: number;
@@ -225,6 +227,11 @@ export class Input extends EventTarget {
225
227
  this.onUp(args);
226
228
  }
227
229
 
230
+ convertScreenspaceToRaycastSpace(vec2 : Vec2){
231
+ vec2.x = (vec2.x - this.context.domX) / this.context.domWidth * 2 - 1;
232
+ vec2.y = -((vec2.y - this.context.domY) / this.context.domHeight) * 2 + 1;
233
+ }
234
+
228
235
  constructor(context: Context) {
229
236
  super();
230
237
  this.context = context;
@@ -494,8 +501,11 @@ export class Input extends EventTarget {
494
501
  const px = evt.clientX + window.scrollX;
495
502
  const py = evt.clientY + window.scrollY;
496
503
  while (evt.button >= this._pointerPositionsRC.length) this._pointerPositionsRC.push(new THREE.Vector2());
497
- this._pointerPositionsRC[evt.button].x = (px - this.context.domX) / this.context.domWidth * 2 - 1;
498
- this._pointerPositionsRC[evt.button].y = -((py - this.context.domY) / this.context.domHeight) * 2 + 1;
504
+ const rc = this._pointerPositionsRC[evt.button];
505
+ rc.set(px, py);
506
+ this.convertScreenspaceToRaycastSpace(rc);
507
+ // this._pointerPositionsRC[evt.button].x = (px - this.context.domX) / this.context.domWidth * 2 - 1;
508
+ // this._pointerPositionsRC[evt.button].y = -((py - this.context.domY) / this.context.domHeight) * 2 + 1;
499
509
  // console.log(evt.button)
500
510
  }
501
511
 
@@ -327,6 +327,11 @@ export class NetworkConnection implements INetworkConnection {
327
327
  delete this._state[guid];
328
328
  }
329
329
 
330
+ public sendDeleteRemoteStateAll(){
331
+ this.send("delete-all-state");
332
+ this._state = {};
333
+ }
334
+
330
335
  public sendBinary(bin: Uint8Array) {
331
336
  if (debugNet) console.log("<< bin", bin.length);
332
337
  this._ws?.send(bin);
@@ -563,6 +568,11 @@ export class NetworkConnection implements INetworkConnection {
563
568
  }
564
569
  break;
565
570
 
571
+ case "all-room-state-deleted":
572
+ if(debugNet) console.log("RECEIVED all-room-state-deleted");
573
+ this._state = {};
574
+ break;
575
+
566
576
  case "ping":
567
577
  case "pong":
568
578
  const time = (message as any).data?.time;
@@ -1,14 +1,16 @@
1
1
  import { BasicDepthPacking, Box3, BufferAttribute, BufferGeometry, Camera, Intersection, Layers, LineBasicMaterial, LineSegments, Matrix4, Mesh, NormalAnimationBlendMode, NumberKeyframeTrack, Object3D, Quaternion, Ray, Raycaster, Sphere, Vector2, Vector3 } from 'three'
2
2
  import { Context } from './engine_setup';
3
- import { getParam } from "./engine_utils"
4
- import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPositionXYZ, setWorldQuaternionXYZW } from "./engine_three_utils"
3
+ import { CircularBuffer, getParam } from "./engine_utils"
4
+ import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPositionXYZ, setWorldQuaternion, setWorldQuaternionXYZW } from "./engine_three_utils"
5
5
  import {
6
6
  IComponent,
7
7
  ICollider,
8
8
  IRigidbody,
9
9
  Collision,
10
10
  ContactPoint,
11
- Vec3
11
+ Vec3,
12
+ IGameObject,
13
+ Vec2,
12
14
  } from './engine_types';
13
15
  import { InstancingUtil } from './engine_instancing';
14
16
  import { foreachComponent } from './engine_gameobject';
@@ -21,6 +23,7 @@ export type Rapier = typeof RAPIER;
21
23
  const debugPhysics = getParam("debugphysics");
22
24
  const debugColliderPlacement = getParam("debugphysicscolliders");
23
25
  const debugCollisions = getParam("debugcollisions");
26
+ const showColliders = getParam("showcolliders");
24
27
 
25
28
 
26
29
  declare type PhysicsBody = {
@@ -28,7 +31,9 @@ declare type PhysicsBody = {
28
31
  rotation(): { x: number, y: number, z: number, w: number }
29
32
  }
30
33
 
34
+ /** on physics body and references the needle component */
31
35
  const $componentKey = Symbol("needle component");
36
+ /** on needle component and references physics body */
32
37
  const $bodyKey = Symbol("physics body");
33
38
  const $colliderRigidbody = Symbol("rigidbody");
34
39
  // const $removed = Symbol("removed");
@@ -75,6 +80,12 @@ export class SphereIntersection implements Intersection {
75
80
  }
76
81
  }
77
82
 
83
+ declare type PhysicsRaycastResult = {
84
+ point: Vector3,
85
+ normal?: Vector3,
86
+ collider?: ICollider
87
+ }
88
+
78
89
  export class Physics {
79
90
 
80
91
  // raycasting
@@ -140,6 +151,10 @@ export class Physics {
140
151
  return this.raycast(opts);
141
152
  }
142
153
 
154
+ /** raycast against rendered three objects. This might be very slow depending on your scene complexity.
155
+ * We recommend setting objects to IgnoreRaycast layer (2) when you don't need them to be raycasted.
156
+ * Raycasting SkinnedMeshes is specially expensive.
157
+ */
143
158
  public raycast(options: RaycastOptions | null = null): Array<Intersection> {
144
159
  if (!options) options = this.defaultRaycastOptions;
145
160
  const mp = options.screenPoint ?? this.context.input.mousePositionRC;
@@ -194,6 +209,7 @@ export class Physics {
194
209
  results.length = 0;
195
210
  rc.intersectObjects(targets, options.recursive, results);
196
211
 
212
+ // TODO: instead of doing this we should temporerly set these objects to layer 2 during raycasting
197
213
  const ignorelist = options.ignore;
198
214
  if (ignorelist !== undefined && ignorelist.length > 0) {
199
215
  results = results.filter(r => !ignorelist.includes(r.object));
@@ -201,8 +217,70 @@ export class Physics {
201
217
  return results;
202
218
  }
203
219
 
220
+ private rapierRay = new RAPIER.Ray({ x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 1 });
221
+ private raycastVectorsBuffer = new CircularBuffer(() => new Vector3(), 10);
204
222
 
223
+ /** raycast against colliders */
224
+ public raycastPhysicsFast(origin: Vec2 | Vec3, direction: Vec3 | undefined = undefined, maxDistance: number = Infinity, solid: boolean = true)
225
+ : null | { point: Vector3, collider:ICollider } {
205
226
 
227
+ const ray = this.getPhysicsRay(this.rapierRay, origin, direction);
228
+ if (!ray) return null;
229
+
230
+ const hit = this.world?.castRay(ray, maxDistance, solid);
231
+ if (hit) {
232
+ const point = ray.pointAt(hit.toi);
233
+ const vec = this.raycastVectorsBuffer.get();
234
+ vec.set(point.x, point.y, point.z);
235
+ return { point: vec, collider: hit.collider[$componentKey] };
236
+ }
237
+
238
+ return null;
239
+ }
240
+
241
+ private getPhysicsRay(ray: RAPIER.Ray, origin: Vec2 | Vec3, direction: Vec3 | undefined = undefined): RAPIER.Ray | null {
242
+ const cam = this.context.mainCamera;
243
+ // if we get origin in 2d space we need to project it to 3d space
244
+ if (origin["z"] === undefined) {
245
+ if (!cam) {
246
+ console.error("Can not perform raycast from 2d point - no main camera found");
247
+ return null;
248
+ }
249
+ const vec3 = this.raycastVectorsBuffer.get();
250
+ // if the origin is in screen space we need to convert it to raycaster space
251
+ if (origin.x > 1 || origin.y > 1 || origin.y < -1 || origin.x < -1) {
252
+ this.context.input.convertScreenspaceToRaycastSpace(origin);
253
+ }
254
+ vec3.set(origin.x, origin.y, -1);
255
+ vec3.unproject(cam);
256
+ origin = vec3;
257
+ }
258
+
259
+ const o = origin as Vec3;
260
+
261
+ ray.origin.x = o.x;
262
+ ray.origin.y = o.y;
263
+ ray.origin.z = o.z;
264
+ const vec = this.raycastVectorsBuffer.get();
265
+ if (direction)
266
+ vec.set(direction.x, direction.y, direction.z);
267
+ else {
268
+ if (!cam) {
269
+ console.error("Can not perform raycast - no camera found");
270
+ return null;
271
+ }
272
+ vec.set(ray.origin.x, ray.origin.y, ray.origin.z);
273
+ const camPosition = getWorldPosition(cam);
274
+ vec.sub(camPosition);
275
+ }
276
+ // we need to normalize the ray because our input is a max travel length and the direction may be not normalized
277
+ vec.normalize();
278
+ ray.dir.x = vec.x;
279
+ ray.dir.y = vec.y;
280
+ ray.dir.z = vec.z;
281
+ // Gizmos.DrawRay(ray.origin, ray.dir, 0xff0000, Infinity);
282
+ return ray;
283
+ }
206
284
 
207
285
  // physics simulation
208
286
 
@@ -514,7 +592,7 @@ export class Physics {
514
592
  }
515
593
 
516
594
  private updateDebugRendering(world: World) {
517
- if (debugPhysics || debugColliderPlacement) {
595
+ if (debugPhysics || debugColliderPlacement || showColliders) {
518
596
  if (!this.lines) {
519
597
  let material = new LineBasicMaterial({
520
598
  color: 0xffffff,
@@ -661,22 +739,62 @@ export class Physics {
661
739
  private static centerConnectionPos = { x: 0, y: 0, z: 0 };
662
740
  private static centerConnectionRot = { x: 0, y: 0, z: 0, w: 1 };
663
741
 
664
- addFixedJoint(body1: IRigidbody, body2: IRigidbody, rel: { x: number, y: number, z: number }) {
742
+
743
+
744
+ addFixedJoint(body1: IRigidbody, body2: IRigidbody) {
665
745
  if (!this.world) {
666
746
  console.error("Physics world not initialized");
667
747
  return;
668
748
  }
669
749
  const b1 = body1[$bodyKey] as RigidBody;
670
750
  const b2 = body2[$bodyKey] as RigidBody;
671
- const rot = body1.worldQuaternion.multiply(body2.worldQuaternion.invert());
751
+
752
+ this.calculateJointRelativeMatrices(body1.gameObject, body2.gameObject, this._tempMatrix);
753
+ this._tempMatrix.decompose(this._tempPosition, this._tempQuaternion, this._tempScale);
754
+
672
755
  const params = JointData.fixed(
673
756
  Physics.centerConnectionPos, Physics.centerConnectionRot,
674
- rel, rot
757
+ this._tempPosition, this._tempQuaternion,
675
758
  );
676
759
  const joint = this.world.createImpulseJoint(params, b1, b2, true);
677
- console.log("ADD JOINT", joint)
760
+ if (debugPhysics)
761
+ console.log("ADD FIXED JOINT", joint)
678
762
  }
679
763
 
764
+
765
+
766
+ addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: { x: number, y: number, z: number }, axis: { x: number, y: number, z: number }) {
767
+ if (!this.world) {
768
+ console.error("Physics world not initialized");
769
+ return;
770
+ }
771
+ const b1 = body1[$bodyKey] as RigidBody;
772
+ const b2 = body2[$bodyKey] as RigidBody;
773
+
774
+ this.calculateJointRelativeMatrices(body1.gameObject, body2.gameObject, this._tempMatrix);
775
+ this._tempMatrix.decompose(this._tempPosition, this._tempQuaternion, this._tempScale);
776
+
777
+ let params = RAPIER.JointData.revolute(anchor, this._tempPosition, axis);
778
+ let joint = this.world.createImpulseJoint(params, b1, b2, true);
779
+ if (debugPhysics)
780
+ console.log("ADD HINGE JOINT", joint)
781
+ }
782
+
783
+
784
+ private calculateJointRelativeMatrices(body1: IGameObject, body2: IGameObject, mat: Matrix4) {
785
+ body1.updateWorldMatrix(true, false);
786
+ body2.updateWorldMatrix(true, false);
787
+ const world1 = body1.matrixWorld;
788
+ const world2 = body2.matrixWorld;
789
+ // set scale to 1
790
+ world1.elements[0] = 1;
791
+ world1.elements[5] = 1;
792
+ world1.elements[10] = 1;
793
+ world2.elements[0] = 1;
794
+ world2.elements[5] = 1;
795
+ world2.elements[10] = 1;
796
+ mat.copy(world2).premultiply(world1.invert()).invert();
797
+ }
680
798
  }
681
799
 
682
800
 
@@ -693,6 +811,7 @@ class PhysicsCollisionHandler {
693
811
  }
694
812
 
695
813
  private activeCollisions: Array<{ collider: ICollider, component: IComponent, collision: Collision }> = [];
814
+ private activeCollisionsStay: Array<{ collider: ICollider, component: IComponent, collision: Collision }> = [];
696
815
  private activeTriggers: Array<{ collider: ICollider, component: IComponent, otherCollider: ICollider }> = [];
697
816
 
698
817
  handleCollisionEvents() {
@@ -739,22 +858,32 @@ class PhysicsCollisionHandler {
739
858
  // TODO: we dont respect the flip value here!
740
859
  this.world.contactPair(selfBody, otherBody, (manifold, _flipped) => {
741
860
  foreachComponent(object, (c: IComponent) => {
742
- if (c.onCollisionEnter) {
861
+ if (c.onCollisionEnter || c.onCollisionStay || c.onCollisionExit) {
743
862
  if (!collision) {
744
863
  const contacts: Array<ContactPoint> = [];
745
864
  const normal = manifold.normal();
746
- for (let i = 0; i < manifold.numContacts(); i++) {
747
- const pt1 = manifold.localContactPoint1(i);
748
- const dist = manifold.contactDist(i);
749
- if (pt1) {
750
- const contact = new ContactPoint(pt1, dist, normal);
865
+ for (let i = 0; i < manifold.numSolverContacts(); i++) {
866
+ // solver points are in world space
867
+ // https://rapier.rs/docs/user_guides/javascript/advanced_collision_detection_js#the-contact-graph
868
+ const pt = manifold.solverContactPoint(i);
869
+ const impulse = manifold.contactImpulse(i);
870
+ if (pt) {
871
+ const dist = manifold.contactDist(i);
872
+ const friction = manifold.solverContactFriction(i);
873
+ const contact = new ContactPoint(pt, dist, normal, impulse, friction);
751
874
  contacts.push(contact);
752
875
  }
753
876
  }
754
877
  collision = new Collision(object, other, contacts);
755
878
  }
756
- c.onCollisionEnter.call(c, collision);
757
- this.activeCollisions.push({ collider: self, component: c, collision });
879
+
880
+ const info = { collider: self, component: c, collision };
881
+ this.activeCollisions.push(info);
882
+ if (c.onCollisionStay) {
883
+ this.activeCollisionsStay.push(info);
884
+ }
885
+
886
+ c.onCollisionEnter?.call(c, collision);
758
887
  }
759
888
  });
760
889
  });
@@ -762,7 +891,7 @@ class PhysicsCollisionHandler {
762
891
  }
763
892
 
764
893
  private onHandleCollisionStay() {
765
- for (const active of this.activeCollisions) {
894
+ for (const active of this.activeCollisionsStay) {
766
895
  const c = active.component;
767
896
  if (c.activeAndEnabled && c.onCollisionStay) {
768
897
  const arg = active.collision;
@@ -792,6 +921,19 @@ class PhysicsCollisionHandler {
792
921
  }
793
922
  }
794
923
  }
924
+ for (let i = 0; i < this.activeCollisionsStay.length; i++) {
925
+ const active = this.activeCollisionsStay[i];
926
+ const collider = active.collider;
927
+ if (collider === self && active.collision.collider === other) {
928
+ const c = active.component;
929
+ this.activeCollisionsStay.splice(i, 1);
930
+ i--;
931
+ if (c.activeAndEnabled && c.onCollisionExit) {
932
+ const collision = active.collision;
933
+ c.onCollisionExit(collision);
934
+ }
935
+ }
936
+ }
795
937
  for (let i = 0; i < this.activeTriggers.length; i++) {
796
938
  const active = this.activeTriggers[i];
797
939
  const collider = active.collider;
@@ -144,6 +144,29 @@ export interface ITypeInformation {
144
144
  type?: Constructor<any>;
145
145
  }
146
146
 
147
+ /** holds information if a field was undefined before serialization. This gives us info if we might want to warn the user about missing attributes */
148
+ export class ImplementationInformation {
149
+
150
+ private isDevMode = isLocalNetwork();
151
+ private cache: { [key: string]: string[] } = {};
152
+
153
+
154
+ /** only call when assigning values for the very first time */
155
+ registerDefinedKeys(typeName: string, type: object) {
156
+ if (!this.isDevMode) return;
157
+ if (this.cache[typeName] === undefined) {
158
+ this.cache[typeName] = Object.keys(type);
159
+ }
160
+ }
161
+
162
+
163
+ getDefinedKey(typeName: string, key: string) {
164
+ if (this.cache[typeName] === undefined) return false;
165
+ const keys = this.cache[typeName];
166
+ const res = keys.includes(key);
167
+ return res;
168
+ }
169
+ }
147
170
 
148
171
  // passed to serializers
149
172
  export class SerializationContext {
@@ -158,6 +181,8 @@ export class SerializationContext {
158
181
  objectToNode?: ObjectToNodeMap;
159
182
  context?: Context;
160
183
  path?: string;
184
+ /** holds information if a field was undefined before serialization. This gives us info if we might want to warn the user about missing attributes */
185
+ implementationInformation?: ImplementationInformation;
161
186
 
162
187
  constructor(root: THREE.Object3D) {
163
188
  this.root = root;
@@ -231,6 +256,7 @@ function collectSerializedTypesInBaseTypes(obj: ISerializable, typeInfoObject?:
231
256
  return collectSerializedTypesInBaseTypes(parentTarget, typeInfoObject);
232
257
  }
233
258
 
259
+
234
260
  export function deserializeObject(obj: ISerializable, serializedData: object, context: SerializationContext): boolean {
235
261
  if (!obj) return false;
236
262
 
@@ -316,7 +342,7 @@ export function deserializeObject(obj: ISerializable, serializedData: object, co
316
342
  implictlyAssignPrimitiveTypes(obj, serializedData);
317
343
  }
318
344
 
319
- checkObjectAssignments(obj, serializedData);
345
+ checkObjectAssignments(obj, serializedData, context.implementationInformation);
320
346
 
321
347
  if (obj.onAfterDeserialize !== undefined) {
322
348
  obj.onAfterDeserialize(serializedData, context);
@@ -327,25 +353,29 @@ export function deserializeObject(obj: ISerializable, serializedData: object, co
327
353
  }
328
354
 
329
355
  const blockChecks = getParam("noerrors");
330
- function checkObjectAssignments(obj: any, _serializedData?: any) {
331
- if(blockChecks) return;
356
+ function checkObjectAssignments(obj: any, serializedData: any, implementationInformation?: ImplementationInformation) {
357
+ if (blockChecks) return;
332
358
  if (isLocalNetwork() === false) return;
333
359
  if (!obj) return;
334
360
 
335
361
  // ignore builtin components that we dont want to check
336
- if(obj.constructor && obj.constructor[$BuiltInTypeFlag] === true) return;
362
+ if (obj.constructor && obj.constructor[$BuiltInTypeFlag] === true) return;
337
363
 
338
364
  const typeName = obj.constructor?.name as string;
339
365
  // test if any object reference is missing serializable
340
- const ownKeys = Object.getOwnPropertyNames(obj);
366
+ const ownKeys = Object.getOwnPropertyNames(serializedData);
341
367
  for (const key of ownKeys) {
342
368
  if (key === "sourceId") continue;
343
369
  const value = obj[key];
344
- if (value === undefined || value === null) continue;
345
- if (typeof value === "object") {
346
- if (!value.isObject3D) {
347
- if (typeof value["node"] === "number" || typeof value["guid"] === "string") {
348
- const hasOtherKeys = Object.keys(value).length > 1;
370
+ const serialized = serializedData[key];
371
+ if (implementationInformation?.getDefinedKey(typeName, key) === false) {
372
+ continue;
373
+ }
374
+ if (serialized === undefined || serialized === null) continue;
375
+ if (typeof serialized === "object") {
376
+ if (value === undefined || !value.isObject3D) {
377
+ if (typeof serialized["node"] === "number" || typeof serialized["guid"] === "string") {
378
+ const hasOtherKeys = value !== undefined && Object.keys(value).length > 1;
349
379
  if (!hasOtherKeys) {
350
380
  showBalloonMessage(`<strong>Missing serialization for object reference!</strong>\n\nPlease change to: \n@serializable(Object3D)\n${key}? : Object3D;\n\nin script ${typeName}.ts\n<a href="https://docs.needle.tools/serializeable" target="_blank">documentation</a>`, LogType.Warn);
351
381
  console.warn(typeName, key, obj[key], obj);
@@ -354,7 +384,7 @@ function checkObjectAssignments(obj: any, _serializedData?: any) {
354
384
  }
355
385
  }
356
386
  else if (typeof value === "string") {
357
- if (value.endsWith(".gltf") || value.endsWith(".glb")) {
387
+ if (serialized.endsWith(".gltf") || serialized.endsWith(".glb")) {
358
388
  showBalloonMessage(`<strong>Missing serialization for object reference!</strong>\n\nPlease change to: \n@serializable(AssetReference)\n${key}? : AssetReference;\n\nin script ${typeName}.ts\n<a href="https://docs.needle.tools/serializeable" target="_blank">documentation</a>`, LogType.Warn);
359
389
  console.warn(typeName, key, obj[key], obj);
360
390
  }
@@ -524,7 +554,7 @@ export const $isAssigningProperties = Symbol("assigned component properties");
524
554
  // const developmentMode = getParam("dev")
525
555
 
526
556
  /** Object.assign behaviour but check if property is writeable (e.g. getter only properties are skipped) */
527
- export function assign(target: any, source: any) {
557
+ export function assign(target: any, source: any, info?: ImplementationInformation) {
528
558
  if (source === undefined || source === null) return;
529
559
  if (target === undefined || target === null) return;
530
560
 
@@ -546,6 +576,13 @@ export function assign(target: any, source: any) {
546
576
  // onlyDeclared = false;
547
577
 
548
578
  target[$isAssigningProperties] = true;
579
+ const typeName = target.constructor?.name ?? "unknown";
580
+
581
+ // register the keys that the actual type has defined
582
+ // this will be used later when checking if deserialization has assigned all properties
583
+ // or if anything could not be deserialized to warn the user
584
+ info?.registerDefinedKeys(typeName, target);
585
+
549
586
  for (const key of Object.keys(source)) {
550
587
  const desc = getPropertyDescriptor(target, key);
551
588
  if (onlyDeclared && desc === undefined) continue;