@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.
- package/CHANGELOG.md +21 -0
- package/dist/needle-engine.d.ts +129 -36
- package/dist/needle-engine.js +356 -356
- package/dist/needle-engine.js.map +3 -3
- package/dist/needle-engine.min.js +61 -61
- package/dist/needle-engine.min.js.map +3 -3
- package/lib/engine/api.d.ts +1 -0
- package/lib/engine/api.js +1 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_gizmos.d.ts +24 -0
- package/lib/engine/engine_gizmos.js +97 -5
- package/lib/engine/engine_gizmos.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +4 -2
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_input.d.ts +2 -0
- package/lib/engine/engine_input.js +9 -2
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_networking.d.ts +1 -0
- package/lib/engine/engine_networking.js +9 -0
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_physics.d.ts +20 -2
- package/lib/engine/engine_physics.js +133 -16
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_serialization_core.d.ts +11 -1
- package/lib/engine/engine_serialization_core.js +41 -10
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_three_utils.js +1 -22
- package/lib/engine/engine_three_utils.js.map +1 -1
- package/lib/engine/engine_types.d.ts +12 -6
- package/lib/engine/engine_types.js +22 -5
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +8 -0
- package/lib/engine/engine_utils.js +22 -0
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
- package/lib/engine-components/Component.d.ts +18 -0
- package/lib/engine-components/Component.js +15 -2
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/Joints.d.ts +5 -1
- package/lib/engine-components/Joints.js +17 -7
- package/lib/engine-components/Joints.js.map +1 -1
- package/lib/engine-components/Light.js +1 -1
- package/lib/engine-components/codegen/components.d.ts +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/api.ts +2 -1
- package/src/engine/codegen/register_types.js +4 -0
- package/src/engine/engine_gizmos.ts +117 -5
- package/src/engine/engine_gltf_builtin_components.ts +5 -2
- package/src/engine/engine_input.ts +12 -2
- package/src/engine/engine_networking.ts +10 -0
- package/src/engine/engine_physics.ts +159 -17
- package/src/engine/engine_serialization_core.ts +49 -12
- package/src/engine/engine_three_utils.ts +1 -29
- package/src/engine/engine_types.ts +33 -14
- package/src/engine/engine_utils.ts +27 -0
- package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
- package/src/engine-components/Component.ts +30 -4
- package/src/engine-components/Joints.ts +19 -7
- package/src/engine-components/Light.ts +1 -1
- 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
|
|
7
|
+
const _tmp = new Vector3();
|
|
4
8
|
|
|
5
|
-
export
|
|
6
|
-
|
|
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
|
|
13
|
-
const line = new
|
|
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]
|
|
498
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
757
|
+
this._tempPosition, this._tempQuaternion,
|
|
675
758
|
);
|
|
676
759
|
const joint = this.world.createImpulseJoint(params, b1, b2, true);
|
|
677
|
-
|
|
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.
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
757
|
-
|
|
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.
|
|
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,
|
|
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(
|
|
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
|
-
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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 (
|
|
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;
|