@needle-tools/engine 2.35.5-pre → 2.37.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 +31 -0
- package/dist/needle-engine.d.ts +312 -232
- package/dist/needle-engine.js +456 -437
- package/dist/needle-engine.js.map +4 -4
- package/dist/needle-engine.min.js +81 -76
- package/dist/needle-engine.min.js.map +4 -4
- 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/debug/debug.d.ts +1 -0
- package/lib/engine/debug/debug.js +3 -0
- package/lib/engine/debug/debug.js.map +1 -1
- package/lib/engine/debug/debug_overlay.js +12 -1
- package/lib/engine/debug/debug_overlay.js.map +1 -1
- package/lib/engine/engine_element_loading.js +1 -1
- package/lib/engine/engine_element_loading.js.map +1 -1
- package/lib/engine/engine_gameobject.d.ts +1 -0
- package/lib/engine/engine_gameobject.js +15 -2
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +4 -0
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_mainloop_utils.d.ts +1 -1
- package/lib/engine/engine_mainloop_utils.js +7 -3
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_physics.d.ts +49 -57
- package/lib/engine/engine_physics.js +464 -417
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_physics.types.d.ts +16 -0
- package/lib/engine/engine_physics.types.js +19 -0
- package/lib/engine/engine_physics.types.js.map +1 -0
- package/lib/engine/engine_serialization_core.d.ts +3 -0
- package/lib/engine/engine_serialization_core.js +19 -6
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_setup.js +4 -2
- package/lib/engine/engine_setup.js.map +1 -1
- package/lib/engine/engine_time.d.ts +1 -0
- package/lib/engine/engine_time.js +1 -0
- package/lib/engine/engine_time.js.map +1 -1
- package/lib/engine/engine_types.d.ts +46 -26
- package/lib/engine/engine_types.js +24 -37
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_typestore.d.ts +1 -0
- package/lib/engine/engine_typestore.js +1 -0
- package/lib/engine/engine_typestore.js.map +1 -1
- package/lib/engine/engine_util_decorator.d.ts +6 -0
- package/lib/engine/engine_util_decorator.js +54 -0
- package/lib/engine/engine_util_decorator.js.map +1 -0
- package/lib/engine/engine_utils.d.ts +1 -1
- package/lib/engine/engine_utils.js +2 -2
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_animator_controller_model.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_gameobject_data.js +2 -0
- package/lib/engine/extensions/NEEDLE_gameobject_data.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_techniques_webgl.js +5 -0
- package/lib/engine/extensions/NEEDLE_techniques_webgl.js.map +1 -1
- package/lib/engine-components/Animation.d.ts +5 -0
- package/lib/engine-components/Animation.js +14 -0
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/AnimatorController.d.ts +1 -0
- package/lib/engine-components/AnimatorController.js +14 -7
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/BoxHelperComponent.d.ts +2 -2
- package/lib/engine-components/BoxHelperComponent.js +28 -9
- package/lib/engine-components/BoxHelperComponent.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +7 -2
- package/lib/engine-components/Collider.js +27 -15
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +8 -16
- package/lib/engine-components/Component.js +18 -117
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.js +9 -6
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/GroundProjection.d.ts +2 -0
- package/lib/engine-components/GroundProjection.js +18 -6
- package/lib/engine-components/GroundProjection.js.map +1 -1
- package/lib/engine-components/NavMesh.d.ts +0 -5
- package/lib/engine-components/NavMesh.js +100 -10
- package/lib/engine-components/NavMesh.js.map +1 -1
- package/lib/engine-components/NestedGltf.js +2 -0
- package/lib/engine-components/NestedGltf.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.d.ts +22 -0
- package/lib/engine-components/ReflectionProbe.js +134 -0
- package/lib/engine-components/ReflectionProbe.js.map +1 -0
- package/lib/engine-components/Renderer.d.ts +13 -2
- package/lib/engine-components/Renderer.js +96 -45
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/RigidBody.d.ts +40 -25
- package/lib/engine-components/RigidBody.js +253 -142
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/SpatialTrigger.js +1 -1
- package/lib/engine-components/SpatialTrigger.js.map +1 -1
- package/lib/engine-components/SpectatorCamera.d.ts +1 -0
- package/lib/engine-components/SpectatorCamera.js +9 -2
- package/lib/engine-components/SpectatorCamera.js.map +1 -1
- package/lib/engine-components/SpringJoint.d.ts +0 -13
- package/lib/engine-components/SpringJoint.js +42 -41
- package/lib/engine-components/SpringJoint.js.map +1 -1
- package/lib/engine-components/VideoPlayer.js.map +1 -1
- package/lib/engine-components/WebARSessionRoot.d.ts +7 -7
- package/lib/engine-components/WebARSessionRoot.js +7 -7
- package/lib/engine-components/WebARSessionRoot.js.map +1 -1
- package/lib/engine-components/WebXR.d.ts +10 -8
- package/lib/engine-components/WebXR.js +50 -26
- package/lib/engine-components/WebXR.js.map +1 -1
- package/lib/engine-components/WebXRAvatar.d.ts +4 -5
- package/lib/engine-components/WebXRAvatar.js +9 -8
- package/lib/engine-components/WebXRAvatar.js.map +1 -1
- package/lib/engine-components/WebXRController.d.ts +21 -21
- package/lib/engine-components/WebXRController.js +90 -68
- package/lib/engine-components/WebXRController.js.map +1 -1
- package/lib/engine-components/WebXRGrabRendering.d.ts +3 -3
- package/lib/engine-components/WebXRGrabRendering.js +2 -2
- package/lib/engine-components/WebXRGrabRendering.js.map +1 -1
- package/lib/engine-components/WebXRSync.d.ts +8 -8
- package/lib/engine-components/WebXRSync.js +15 -15
- package/lib/engine-components/WebXRSync.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +2 -3
- package/lib/engine-components/codegen/components.js +2 -3
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/ui/EventSystem.d.ts +1 -0
- package/lib/engine-components/ui/EventSystem.js +21 -1
- package/lib/engine-components/ui/EventSystem.js.map +1 -1
- package/package.json +3 -4
- package/src/engine/api.ts +2 -1
- package/src/engine/codegen/register_types.js +291 -6
- package/src/engine/debug/debug.ts +4 -0
- package/src/engine/debug/debug_overlay.ts +9 -2
- package/src/engine/engine_element_loading.ts +1 -1
- package/src/engine/engine_gameobject.ts +19 -6
- package/src/engine/engine_gltf_builtin_components.ts +5 -1
- package/src/engine/engine_mainloop_utils.ts +7 -3
- package/src/engine/engine_physics.ts +508 -469
- package/src/engine/engine_physics.types.ts +19 -0
- package/src/engine/engine_serialization_core.ts +22 -8
- package/src/engine/engine_setup.ts +6 -2
- package/src/engine/engine_time.ts +2 -0
- package/src/engine/engine_types.ts +82 -55
- package/src/engine/engine_typestore.ts +2 -0
- package/src/engine/engine_util_decorator.ts +69 -0
- package/src/engine/engine_utils.ts +6 -5
- package/src/engine/extensions/EXT_texture_exr.js +1 -1
- package/src/engine/extensions/NEEDLE_animator_controller_model.ts +2 -1
- package/src/engine/extensions/NEEDLE_gameobject_data.ts +2 -0
- package/src/engine/extensions/NEEDLE_techniques_webgl.ts +7 -0
- package/src/engine-components/Animation.ts +14 -1
- package/src/engine-components/AnimatorController.ts +19 -9
- package/src/engine-components/BoxHelperComponent.ts +30 -9
- package/src/engine-components/Collider.ts +29 -29
- package/src/engine-components/Component.ts +26 -135
- package/src/engine-components/DragControls.ts +9 -5
- package/src/engine-components/GroundProjection.ts +22 -7
- package/src/engine-components/NavMesh.ts +114 -115
- package/src/engine-components/NestedGltf.ts +2 -0
- package/src/engine-components/ReflectionProbe.ts +141 -0
- package/src/engine-components/Renderer.ts +796 -737
- package/src/engine-components/RigidBody.ts +258 -149
- package/src/engine-components/SpatialTrigger.ts +1 -1
- package/src/engine-components/SpectatorCamera.ts +10 -2
- package/src/engine-components/SpringJoint.ts +41 -41
- package/src/engine-components/VideoPlayer.ts +1 -2
- package/src/engine-components/WebARSessionRoot.ts +16 -16
- package/src/engine-components/WebXR.ts +65 -50
- package/src/engine-components/WebXRAvatar.ts +16 -16
- package/src/engine-components/WebXRController.ts +143 -112
- package/src/engine-components/WebXRGrabRendering.ts +6 -6
- package/src/engine-components/WebXRSync.ts +20 -20
- package/src/engine-components/codegen/components.ts +2 -3
- package/src/engine-components/ui/EventSystem.ts +26 -3
|
@@ -1,81 +1,62 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as THREE from 'three'
|
|
1
|
+
import { BasicDepthPacking, Box3, BufferAttribute, BufferGeometry, Camera, Intersection, Layers, LineBasicMaterial, LineSegments, Matrix4, Mesh, NormalAnimationBlendMode, Object3D, Quaternion, Ray, Raycaster, Sphere, Vector2, Vector3 } from 'three'
|
|
3
2
|
import { Context } from './engine_setup';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import * as threeutils from "./engine_three_utils"
|
|
3
|
+
import { getParam } from "./engine_utils"
|
|
4
|
+
import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldPositionXYZ, setWorldQuaternionXYZW } from "./engine_three_utils"
|
|
7
5
|
import {
|
|
8
|
-
IComponent
|
|
9
|
-
IGameObject
|
|
6
|
+
IComponent,
|
|
7
|
+
IGameObject,
|
|
10
8
|
ICollider,
|
|
11
|
-
IRigidbody
|
|
12
|
-
Collision,
|
|
9
|
+
IRigidbody,
|
|
10
|
+
Collision,
|
|
13
11
|
ICollisionContext,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import { Shape } from 'cannon-es';
|
|
12
|
+
ContactPoint,
|
|
13
|
+
Vec3
|
|
14
|
+
} from './engine_types';
|
|
18
15
|
import { InstancingUtil } from './engine_instancing';
|
|
19
16
|
import { foreachComponent } from './engine_gameobject';
|
|
20
|
-
import { getComponentInChildren } from './engine_components';
|
|
21
|
-
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
import RAPIER, { ActiveEvents, Collider, ColliderDesc, EventQueue, RigidBody, TempContactManifold, World } from '@dimforge/rapier3d-compat';
|
|
19
|
+
import { CollisionDetectionMode, RigidbodyConstraints } from '../engine/engine_physics.types';
|
|
20
|
+
import { showBalloonWarning } from './debug/debug';
|
|
21
|
+
export type Rapier = typeof RAPIER;
|
|
25
22
|
|
|
26
|
-
export class BodyOptions {
|
|
27
|
-
mass: number = 1;
|
|
28
|
-
kinematic: boolean = false;
|
|
29
|
-
physicsEvents: boolean = false;
|
|
30
|
-
drag: number = 0;
|
|
31
|
-
angularDrag: number = 0.05;
|
|
32
|
-
sleepThreshold: number = .01;
|
|
33
|
-
}
|
|
34
23
|
|
|
24
|
+
const debugPhysics = getParam("debugphysics");
|
|
25
|
+
const debugColliderPlacement = getParam("debugphysicscolliders");
|
|
26
|
+
const debugCollisions = getParam("debugcollisions");
|
|
35
27
|
|
|
36
|
-
// TODO: refactor to return some kind of handle for adding/removing
|
|
37
|
-
class PhysicsObject {
|
|
38
|
-
obj: THREE.Object3D;
|
|
39
|
-
parent: THREE.Object3D | null;
|
|
40
|
-
body: CANNON.Body | null;
|
|
41
|
-
shapes: Array<Shape> = [];
|
|
42
|
-
collisonCallback: Function | null = null;
|
|
43
28
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
constructor(obj: THREE.Object3D, body: CANNON.Body | null) {
|
|
48
|
-
this.obj = obj;
|
|
49
|
-
this.parent = obj.parent;
|
|
50
|
-
this.body = body;
|
|
51
|
-
if (this.body)
|
|
52
|
-
this.body[$physicsKey] = obj;
|
|
53
|
-
}
|
|
29
|
+
declare type PhysicsBody = {
|
|
30
|
+
translation(): { x: number, y: number, z: number }
|
|
31
|
+
rotation(): { x: number, y: number, z: number, w: number }
|
|
54
32
|
}
|
|
55
33
|
|
|
34
|
+
const $componentKey = Symbol("needle component");
|
|
35
|
+
const $bodyKey = Symbol("physics body");
|
|
36
|
+
|
|
56
37
|
export class RaycastOptions {
|
|
57
|
-
ray:
|
|
58
|
-
cam:
|
|
59
|
-
screenPoint:
|
|
60
|
-
raycaster:
|
|
61
|
-
results: Array<
|
|
62
|
-
targets: Array<
|
|
38
|
+
ray: Ray | undefined = undefined;
|
|
39
|
+
cam: Camera | undefined | null = undefined;
|
|
40
|
+
screenPoint: Vector2 | undefined = undefined;
|
|
41
|
+
raycaster: Raycaster | undefined = undefined;
|
|
42
|
+
results: Array<Intersection> | undefined = undefined;
|
|
43
|
+
targets: Array<Object3D> | undefined = undefined;
|
|
63
44
|
recursive: boolean | undefined = true;
|
|
64
45
|
minDistance: number | undefined = undefined;
|
|
65
46
|
maxDistance: number | undefined = undefined;
|
|
66
47
|
lineThreshold: number | undefined = undefined;
|
|
67
|
-
layerMask:
|
|
68
|
-
ignore:
|
|
48
|
+
layerMask: Layers | number | undefined = undefined;
|
|
49
|
+
ignore: Object3D[] | undefined = undefined;
|
|
69
50
|
|
|
70
51
|
screenPointFromOffset(ox: number, oy: number) {
|
|
71
|
-
if (this.screenPoint === undefined) this.screenPoint = new
|
|
52
|
+
if (this.screenPoint === undefined) this.screenPoint = new Vector2();
|
|
72
53
|
this.screenPoint.x = ox / window.innerWidth * 2 - 1;
|
|
73
54
|
this.screenPoint.y = -(oy / window.innerHeight) * 2 + 1;
|
|
74
55
|
}
|
|
75
56
|
|
|
76
57
|
setMask(mask: number) {
|
|
77
|
-
if (!this.layerMask) this.layerMask = new
|
|
78
|
-
const lm = this.layerMask as
|
|
58
|
+
if (!this.layerMask) this.layerMask = new Layers();
|
|
59
|
+
const lm = this.layerMask as Layers;
|
|
79
60
|
if (lm)
|
|
80
61
|
lm.mask = mask;
|
|
81
62
|
else this.layerMask = mask;
|
|
@@ -84,11 +65,11 @@ export class RaycastOptions {
|
|
|
84
65
|
public static AllLayers = 0xFFFFFFFF;
|
|
85
66
|
}
|
|
86
67
|
|
|
87
|
-
export class SphereIntersection implements
|
|
68
|
+
export class SphereIntersection implements Intersection {
|
|
88
69
|
distance: number;
|
|
89
|
-
point:
|
|
90
|
-
object:
|
|
91
|
-
constructor(object:
|
|
70
|
+
point: Vector3;
|
|
71
|
+
object: Object3D;
|
|
72
|
+
constructor(object: Object3D, distance: number, point: Vector3) {
|
|
92
73
|
this.object = object;
|
|
93
74
|
this.distance = distance;
|
|
94
75
|
this.point = point;
|
|
@@ -99,9 +80,9 @@ export class Physics {
|
|
|
99
80
|
|
|
100
81
|
// raycasting
|
|
101
82
|
|
|
102
|
-
private readonly raycaster:
|
|
83
|
+
private readonly raycaster: Raycaster = new Raycaster();
|
|
103
84
|
private readonly defaultRaycastOptions: RaycastOptions = new RaycastOptions();
|
|
104
|
-
private readonly targetBuffer: Array<
|
|
85
|
+
private readonly targetBuffer: Array<Object3D> = new Array<Object3D>(1);
|
|
105
86
|
private readonly defaultThresholds = {
|
|
106
87
|
Mesh: {},
|
|
107
88
|
Line: { threshold: 0 },
|
|
@@ -111,60 +92,56 @@ export class Physics {
|
|
|
111
92
|
}
|
|
112
93
|
|
|
113
94
|
|
|
114
|
-
private sphereResults: Array<
|
|
115
|
-
private sphereMask:
|
|
116
|
-
public sphereOverlap(spherePos:
|
|
95
|
+
private sphereResults: Array<Intersection> = new Array<Intersection>();
|
|
96
|
+
private sphereMask: Layers = new Layers();
|
|
97
|
+
public sphereOverlap(spherePos: Vector3, radius: number, traverseChildsAfterHit: boolean = true): Array<Intersection> {
|
|
117
98
|
this.sphereResults.length = 0;
|
|
118
99
|
if (!this.context.scene) return this.sphereResults;
|
|
119
|
-
const sphere = new
|
|
100
|
+
const sphere = new Sphere(spherePos, radius);
|
|
120
101
|
const mask = this.sphereMask;
|
|
121
102
|
mask.enableAll();
|
|
122
103
|
mask.disable(2);
|
|
123
|
-
// mask testing
|
|
124
|
-
// const dummy = new THREE.Layers();
|
|
125
|
-
// dummy.set(2);
|
|
126
|
-
// console.log(dummy.mask, mask.test(dummy))
|
|
127
104
|
for (const ch of this.context.scene.children) {
|
|
128
|
-
|
|
129
|
-
if (i) this.sphereResults.push(i);
|
|
105
|
+
this.onSphereOverlap(ch, sphere, mask, this.sphereResults, traverseChildsAfterHit);
|
|
130
106
|
}
|
|
131
107
|
return this.sphereResults.sort((a, b) => a.distance - b.distance);
|
|
132
108
|
}
|
|
133
|
-
private tempBoundingBox:
|
|
134
|
-
private onSphereOverlap(obj:
|
|
135
|
-
if (obj.type === "Mesh") {
|
|
136
|
-
|
|
137
|
-
const mesh = obj as THREE.Mesh;
|
|
109
|
+
private tempBoundingBox: Box3 = new Box3();
|
|
110
|
+
private onSphereOverlap(obj: Object3D, sp: Sphere, mask: Layers, results: Array<Intersection>, traverseChildsAfterHit: boolean): void {
|
|
111
|
+
if (obj.type === "Mesh" && obj.layers.test(mask)) {
|
|
112
|
+
const mesh = obj as Mesh;
|
|
138
113
|
const geo = mesh.geometry;
|
|
139
114
|
if (!geo.boundingBox)
|
|
140
115
|
geo.computeBoundingBox();
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
116
|
+
if (geo.boundingBox) {
|
|
117
|
+
if (mesh.matrixWorldNeedsUpdate) mesh.updateMatrixWorld();
|
|
118
|
+
const test = this.tempBoundingBox.copy(geo.boundingBox).applyMatrix4(mesh.matrixWorld);
|
|
119
|
+
if (sp.intersectsBox(test)) {
|
|
120
|
+
// console.log(obj, obj.layers.test(mask), obj.layers.mask, mask.mask);
|
|
121
|
+
const wp = getWorldPosition(obj);
|
|
122
|
+
const dist = wp.distanceTo(sp.center);
|
|
123
|
+
const int = new SphereIntersection(obj, dist, sp.center.clone());
|
|
124
|
+
results.push(int);
|
|
125
|
+
if (!traverseChildsAfterHit) return;
|
|
126
|
+
}
|
|
150
127
|
}
|
|
151
128
|
}
|
|
152
|
-
|
|
129
|
+
if (obj.children) {
|
|
153
130
|
for (const ch of obj.children) {
|
|
154
|
-
const
|
|
155
|
-
|
|
131
|
+
const len = results.length;
|
|
132
|
+
this.onSphereOverlap(ch, sp, mask, results, traverseChildsAfterHit);
|
|
133
|
+
if (len != results.length && !traverseChildsAfterHit) return;
|
|
156
134
|
}
|
|
157
135
|
}
|
|
158
|
-
return null;
|
|
159
136
|
}
|
|
160
137
|
|
|
161
|
-
public raycastFromRay(ray:
|
|
138
|
+
public raycastFromRay(ray: Ray, options: RaycastOptions | null = null): Array<Intersection> {
|
|
162
139
|
const opts = options ?? this.defaultRaycastOptions;
|
|
163
140
|
opts.ray = ray;
|
|
164
141
|
return this.raycast(opts);
|
|
165
142
|
}
|
|
166
143
|
|
|
167
|
-
public raycast(options: RaycastOptions | null = null): Array<
|
|
144
|
+
public raycast(options: RaycastOptions | null = null): Array<Intersection> {
|
|
168
145
|
if (!options) options = this.defaultRaycastOptions;
|
|
169
146
|
const mp = options.screenPoint ?? this.context.input.mousePositionRC;
|
|
170
147
|
const rc = options.raycaster ?? this.raycaster;
|
|
@@ -194,14 +171,14 @@ export class Physics {
|
|
|
194
171
|
let results = options.results;
|
|
195
172
|
if (!results) {
|
|
196
173
|
if (!this.defaultRaycastOptions.results)
|
|
197
|
-
this.defaultRaycastOptions.results = new Array<
|
|
174
|
+
this.defaultRaycastOptions.results = new Array<Intersection>();
|
|
198
175
|
results = this.defaultRaycastOptions.results;
|
|
199
176
|
}
|
|
200
177
|
|
|
201
178
|
// layermask
|
|
202
|
-
// https://github.com/mrdoob/
|
|
179
|
+
// https://github.com/mrdoob/js/blob/master/src/core/Layers.js
|
|
203
180
|
if (options.layerMask !== undefined) {
|
|
204
|
-
if (options.layerMask instanceof
|
|
181
|
+
if (options.layerMask instanceof Layers)
|
|
205
182
|
rc.layers.mask = options.layerMask.mask;
|
|
206
183
|
else
|
|
207
184
|
rc.layers.mask = options.layerMask;
|
|
@@ -230,479 +207,541 @@ export class Physics {
|
|
|
230
207
|
|
|
231
208
|
// physics simulation
|
|
232
209
|
|
|
233
|
-
|
|
210
|
+
private _tempPosition: Vector3 = new Vector3();
|
|
211
|
+
private _tempQuaternion: Quaternion = new Quaternion();
|
|
212
|
+
private _tempMatrix: Matrix4 = new Matrix4();
|
|
213
|
+
|
|
214
|
+
private static _didLoadPhysicsEngine: boolean = false;
|
|
234
215
|
|
|
235
216
|
private _isUpdatingPhysicsWorld: boolean = false;
|
|
217
|
+
get isUpdating(): boolean { return this._isUpdatingPhysicsWorld; }
|
|
218
|
+
|
|
236
219
|
|
|
237
220
|
private context: Context;
|
|
221
|
+
private world?: World;
|
|
222
|
+
private _hasCreatedWorld: boolean = false;
|
|
223
|
+
private eventQueue?: EventQueue;
|
|
224
|
+
private collisionHandler?: PhysicsCollisionHandler;
|
|
225
|
+
|
|
238
226
|
|
|
239
|
-
private
|
|
240
|
-
private objects:
|
|
227
|
+
// private rigidbodies: Array<IRigidbody | null> = [];
|
|
228
|
+
private objects: IComponent[] = [];
|
|
229
|
+
private bodies: PhysicsBody[] = [];
|
|
230
|
+
// private rigidbodiesLookup: Map<IRigidbody, RigidBody> = new Map<IRigidbody, RigidBody>();
|
|
231
|
+
// private kinematicColliders: Array<IComponent> = [];
|
|
232
|
+
// private rigidbodyLookup: Map<IRigidbody, IComponent[]> = new Map<IRigidbody, IComponent[]>();
|
|
233
|
+
// private objectLookup: Map<Object3D, IRigidbody> = new Map<Object3D, IRigidbody>();
|
|
241
234
|
|
|
242
|
-
private tempPosition: THREE.Vector3 = new THREE.Vector3();
|
|
243
|
-
private tempQuaternion: THREE.Quaternion = new THREE.Quaternion();
|
|
244
235
|
|
|
245
236
|
constructor(context: Context) {
|
|
246
237
|
this.context = context;
|
|
247
|
-
this.world.gravity.set(0, -9.82, 0);
|
|
248
|
-
if (debugPhysics) {
|
|
249
|
-
// https://www.npmjs.com/package/cannon-es-debugger
|
|
250
|
-
const opts = {};
|
|
251
|
-
opts["onInit"] = (_body: CANNON.Body, mesh: THREE.Mesh, _shape: CANNON.Shape) => {
|
|
252
|
-
// ignore in raycast
|
|
253
|
-
mesh.layers.set(-1);
|
|
254
|
-
};
|
|
255
|
-
cannonDebugger(context.scene, this.world.bodies, opts);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
this.world.addEventListener("beginContact", this.onBeginContact.bind(this));
|
|
259
|
-
this.world.addEventListener("endContact", this.onEndContact.bind(this))
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
public addPreStepListener(listener: (evt) => void) {
|
|
263
|
-
this.world.addEventListener("preStep", listener);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
public addPostStepListener(listener: (evt) => void) {
|
|
267
|
-
this.world.addEventListener("postStep", listener);
|
|
268
238
|
}
|
|
269
239
|
|
|
270
|
-
|
|
271
|
-
this.
|
|
240
|
+
async createWorld() {
|
|
241
|
+
if (this._hasCreatedWorld) {
|
|
242
|
+
console.error("Invalid call to create physics world: world is already created");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
this._hasCreatedWorld = true;
|
|
246
|
+
if (!Physics._didLoadPhysicsEngine) {
|
|
247
|
+
await RAPIER.init().then(() => RAPIER)
|
|
248
|
+
Physics._didLoadPhysicsEngine = true;
|
|
249
|
+
}
|
|
250
|
+
const gravity = { x: 0.0, y: -9.81, z: 0.0 };
|
|
251
|
+
this.world = new World(gravity);
|
|
272
252
|
}
|
|
273
253
|
|
|
274
|
-
|
|
275
|
-
|
|
254
|
+
addBoxCollider(collider: ICollider, center: Vector3, size: Vector3) {
|
|
255
|
+
const obj = collider.gameObject;
|
|
256
|
+
const scale = getWorldScale(obj, this._tempPosition).multiply(size);
|
|
257
|
+
scale.multiplyScalar(0.5);
|
|
258
|
+
const desc = ColliderDesc.cuboid(scale.x, scale.y, scale.z);
|
|
259
|
+
this.createCollider(collider, desc, center);
|
|
276
260
|
}
|
|
277
261
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
this.
|
|
281
|
-
|
|
262
|
+
addSphereCollider(collider: ICollider, center: Vector3, radius: number) {
|
|
263
|
+
const obj = collider.gameObject;
|
|
264
|
+
const scale = getWorldScale(obj, this._tempPosition).multiplyScalar(radius);
|
|
265
|
+
const desc = ColliderDesc.ball(scale.x);
|
|
266
|
+
this.createCollider(collider, desc, center);
|
|
282
267
|
}
|
|
283
268
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
// dont add the body before it has shapes
|
|
293
|
-
// otherwise things like forces appplied in the frame before the shapes exist will be zeroed out
|
|
294
|
-
if (body.shapes.length > 0)
|
|
295
|
-
this.world.addBody(body);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
public removeBody(go: GameObject, body: CANNON.Body, removeCompletely: boolean = true) {
|
|
299
|
-
this.world.removeBody(body);
|
|
300
|
-
for (let i = 0; i < this.objects.length; i++) {
|
|
301
|
-
const reg = this.objects[i];
|
|
302
|
-
if (reg.obj === go) {
|
|
303
|
-
reg._hasRigidbody = false;
|
|
304
|
-
if (removeCompletely)
|
|
305
|
-
this.objects.splice(i, 1);
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
269
|
+
addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean) {
|
|
270
|
+
const geo = mesh.geometry;
|
|
271
|
+
if (!geo) {
|
|
272
|
+
if (debugPhysics) console.warn("Missing mesh geometry", mesh.name);
|
|
273
|
+
return;
|
|
308
274
|
}
|
|
309
|
-
}
|
|
310
275
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
276
|
+
let positions = geo.getAttribute("position").array as Float32Array;
|
|
277
|
+
const indices = geo.index?.array as Uint32Array;
|
|
278
|
+
|
|
279
|
+
// console.log(geo.center())
|
|
280
|
+
|
|
281
|
+
// scaling seems not supported yet https://github.com/dimforge/rapier/issues/243
|
|
282
|
+
const scale = getWorldScale(mesh, this._tempPosition)
|
|
283
|
+
if (Math.abs(scale.x - 1) > 0.0001 || Math.abs(scale.y - 1) > 0.0001 || Math.abs(scale.z - 1) > 0.0001) {
|
|
284
|
+
console.warn("Your model is using scaled mesh colliders which is not optimal for performance", mesh.name, Object.assign({}, scale), mesh);
|
|
285
|
+
// showBalloonWarning("Your model is using scaled mesh colliders which is not optimal for performance: " + mesh.name + ", consider using unscaled objects");
|
|
286
|
+
const scaledPositions = new Float32Array(positions.length);
|
|
287
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
288
|
+
scaledPositions[i] = positions[i] * scale.x;
|
|
289
|
+
scaledPositions[i + 1] = positions[i + 1] * scale.y;
|
|
290
|
+
scaledPositions[i + 2] = positions[i + 2] * scale.z;
|
|
319
291
|
}
|
|
292
|
+
positions = scaledPositions;
|
|
320
293
|
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// TODO: make it work with rigibody in parent
|
|
324
|
-
public createBody(obj: THREE.Object3D, settings: BodyOptions): CANNON.Body {
|
|
325
|
-
const body = this.internalCreateBody(obj, null);
|
|
326
|
-
if (settings.mass)
|
|
327
|
-
body.mass = settings.mass;
|
|
328
|
-
if (settings.kinematic)
|
|
329
|
-
body.type = CANNON.Body.KINEMATIC;
|
|
330
|
-
else body.type = CANNON.Body.DYNAMIC;
|
|
331
|
-
if (settings.drag)
|
|
332
|
-
body.linearDamping = settings.drag;
|
|
333
|
-
if (settings.angularDrag)
|
|
334
|
-
body.angularDamping = settings.angularDrag;
|
|
335
|
-
if (settings.sleepThreshold)
|
|
336
|
-
body.sleepSpeedLimit = settings.sleepThreshold;
|
|
337
294
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
this.objects.push(po);
|
|
295
|
+
const desc = convex ? ColliderDesc.convexMesh(positions) : ColliderDesc.trimesh(positions, indices);
|
|
296
|
+
if (desc) {
|
|
297
|
+
this.createCollider(collider, desc);
|
|
298
|
+
// col.setTranslationWrtParent(new Vector3(0,2,0));
|
|
343
299
|
|
|
344
|
-
if (debugPhysics) {
|
|
345
|
-
console.log("created new body", obj.name, body, body.sleepState, this.world.gravity);
|
|
346
300
|
}
|
|
347
|
-
|
|
348
|
-
if (settings.physicsEvents)
|
|
349
|
-
this.registerCollisionEvents(po);
|
|
350
|
-
|
|
351
|
-
return body;
|
|
352
301
|
}
|
|
353
302
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
303
|
+
private createCollider(collider: ICollider, desc: ColliderDesc, center?: Vector3) {
|
|
304
|
+
if (!this.world) throw new Error("Physics world not initialized");
|
|
305
|
+
const matrix = this._tempMatrix;
|
|
306
|
+
const {
|
|
307
|
+
rigidBody,
|
|
308
|
+
useExplicitMassProperties
|
|
309
|
+
} = this.getRigidbody(collider, this._tempMatrix);
|
|
358
310
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
.
|
|
362
|
-
|
|
363
|
-
);
|
|
364
|
-
const shape = new CANNON.Box(pos);
|
|
365
|
-
shape.collisionResponse = !trigger;
|
|
311
|
+
matrix.decompose(this._tempPosition, this._tempQuaternion, new Vector3());
|
|
312
|
+
if (center)
|
|
313
|
+
this._tempPosition.add(center);
|
|
314
|
+
desc.setTranslation(this._tempPosition.x, this._tempPosition.y, this._tempPosition.z);
|
|
315
|
+
desc.setRotation(this._tempQuaternion);
|
|
366
316
|
|
|
367
|
-
|
|
368
|
-
center.multiply(scale);
|
|
317
|
+
desc.setSensor(collider.isTrigger);
|
|
369
318
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
this.objects.push(po);
|
|
319
|
+
// if we want to use explicit mass properties, we need to set the collider density to 0
|
|
320
|
+
// otherwise rapier will compute the mass properties based on the collider shape and density
|
|
321
|
+
// https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
|
|
322
|
+
if (useExplicitMassProperties) {
|
|
323
|
+
// desc.setDensity(0);
|
|
376
324
|
}
|
|
377
|
-
return shape;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
public addSphereCollider(obj: THREE.Object3D, center: THREE.Vector3, radius: number, rb: Rigidbody | null): CANNON.Shape {
|
|
381
|
-
const scale = this.tempPosition;
|
|
382
|
-
obj.getWorldScale(scale);
|
|
383
325
|
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
326
|
+
const col = this.world.createCollider(desc, rigidBody);
|
|
327
|
+
col[$componentKey] = collider;
|
|
328
|
+
collider[$bodyKey] = col;
|
|
329
|
+
col.setActiveEvents(ActiveEvents.COLLISION_EVENTS);
|
|
330
|
+
this.objects.push(collider);
|
|
331
|
+
this.bodies.push(col);
|
|
332
|
+
return col;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private getRigidbody(collider: ICollider, _matrix: Matrix4): { rigidBody: RigidBody, useExplicitMassProperties: boolean } {
|
|
336
|
+
|
|
337
|
+
if (!this.world) throw new Error("Physics world not initialized");
|
|
338
|
+
let rigidBody: RigidBody | null = null;
|
|
339
|
+
let useExplicitMassProperties = false;
|
|
340
|
+
|
|
341
|
+
if (collider.attachedRigidbody) {
|
|
342
|
+
|
|
343
|
+
const rb = collider.attachedRigidbody;
|
|
344
|
+
rigidBody = rb[$bodyKey];
|
|
345
|
+
useExplicitMassProperties = true;
|
|
346
|
+
if (!rigidBody) {
|
|
347
|
+
const kinematic = rb.isKinematic && !debugColliderPlacement;
|
|
348
|
+
if (debugPhysics)
|
|
349
|
+
console.log("Create rigidbody", kinematic);
|
|
350
|
+
const rigidBodyDesc = kinematic ? RAPIER.RigidBodyDesc.kinematicPositionBased() : RAPIER.RigidBodyDesc.dynamic();
|
|
351
|
+
const pos = getWorldPosition(collider.attachedRigidbody.gameObject);
|
|
352
|
+
rigidBodyDesc.setTranslation(pos.x, pos.y, pos.z);
|
|
353
|
+
rigidBodyDesc.setRotation(getWorldQuaternion(collider.attachedRigidbody.gameObject));
|
|
354
|
+
rigidBody = this.world.createRigidBody(rigidBodyDesc);
|
|
355
|
+
this.bodies.push(rigidBody);
|
|
356
|
+
this.objects.push(rb);
|
|
357
|
+
}
|
|
358
|
+
rigidBody[$componentKey] = rb;
|
|
359
|
+
rb[$bodyKey] = rigidBody;
|
|
360
|
+
this.internalUpdateProperties(rb, rigidBody);
|
|
361
|
+
this.getRigidbodyRelativeMatrix(collider.gameObject, rb.gameObject, _matrix);
|
|
390
362
|
|
|
391
|
-
const body = this.addShape(obj, shape, center, rb);
|
|
392
|
-
if (body !== null) {
|
|
393
|
-
this.world.addBody(body);
|
|
394
|
-
if (this.isAlreadyRegistered(body)) return shape;
|
|
395
|
-
const po = new PhysicsObject(obj, body);
|
|
396
|
-
this.objects.push(po);
|
|
397
363
|
}
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
public addMeshCollider(_obj: THREE.Object3D) {
|
|
402
|
-
// see https://github.com/schteppe/cannon.js/blob/master/demos/bunny.html
|
|
403
|
-
if (debugPhysics)
|
|
404
|
-
console.warn("TODO mesh collider not yet supported")
|
|
405
|
-
// const geometry: THREE.BufferGeometry = obj["geometry"];
|
|
406
|
-
// console.log(geometry);
|
|
407
|
-
// const size = geometry.boundingBox.max.clone();
|
|
408
|
-
// size.sub(geometry.boundingBox.min);
|
|
409
|
-
// console.log(size);
|
|
410
|
-
// this.addBoxCollider(obj, size);
|
|
411
|
-
|
|
412
|
-
// const verts = geometry.getAttribute("position").array;
|
|
413
|
-
// const faces = new Array<Array<number>>();
|
|
364
|
+
else {
|
|
414
365
|
|
|
415
|
-
|
|
366
|
+
const rigidBodyDesc = RAPIER.RigidBodyDesc.kinematicPositionBased();
|
|
367
|
+
const pos = getWorldPosition(collider.gameObject);
|
|
368
|
+
rigidBodyDesc.setTranslation(pos.x, pos.y, pos.z);
|
|
369
|
+
rigidBodyDesc.setRotation(getWorldQuaternion(collider.gameObject));
|
|
370
|
+
rigidBody = this.world.createRigidBody(rigidBodyDesc);
|
|
371
|
+
_matrix.identity();
|
|
372
|
+
rigidBody[$componentKey] = null;
|
|
416
373
|
|
|
417
|
-
|
|
418
|
-
// const i0 = geometry.index.array[i];
|
|
419
|
-
// const i1 = geometry.index.array[i + 1];
|
|
420
|
-
// const i2 = geometry.index.array[i + 2];
|
|
421
|
-
// const v0 = new THREE.Vector3(verts[i0 * 3], verts[i0 * 3 + 1], verts[i0 * 3 + 2]);
|
|
422
|
-
// const v1 = new THREE.Vector3(verts[i1 * 3], verts[i1 * 3 + 1], verts[i1 * 3 + 2]);
|
|
423
|
-
// const v2 = new THREE.Vector3(verts[i2 * 3], verts[i2 * 3 + 1], verts[i2 * 3 + 2]);
|
|
424
|
-
// const face = [v0, v1, v2];
|
|
425
|
-
// faces.push(face);
|
|
426
|
-
// }
|
|
427
|
-
// const convex = new THREE.ConvexBufferGeometry(faces);
|
|
374
|
+
}
|
|
428
375
|
|
|
429
|
-
|
|
430
|
-
// this.addShape(obj, shape);
|
|
376
|
+
return { rigidBody: rigidBody, useExplicitMassProperties: useExplicitMassProperties };
|
|
431
377
|
}
|
|
432
378
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
379
|
+
removeBody(obj: IComponent) {
|
|
380
|
+
const body = obj[$bodyKey];
|
|
381
|
+
obj[$bodyKey] = null;
|
|
382
|
+
if (body) {
|
|
383
|
+
const index = this.objects.findIndex(o => o === obj);
|
|
384
|
+
if (index >= 0) {
|
|
385
|
+
const body = this.bodies[index];
|
|
386
|
+
this.bodies.splice(index, 1);
|
|
387
|
+
this.objects.splice(index, 1);
|
|
388
|
+
|
|
389
|
+
if (body instanceof Collider) {
|
|
390
|
+
this.world?.removeCollider(body as Collider, true);
|
|
391
|
+
}
|
|
392
|
+
else if (body instanceof RigidBody) {
|
|
393
|
+
this.world?.removeRigidBody(body as RigidBody);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// check if we need to remove the rigidbody too
|
|
397
|
+
const col = obj as ICollider;
|
|
398
|
+
if (col.isCollider && col.attachedRigidbody) {
|
|
399
|
+
const rb = col.attachedRigidbody[$bodyKey];
|
|
400
|
+
if (rb && rb.numColliders() <= 0 && rb.world() === this.world) {
|
|
401
|
+
this.world?.removeRigidBody(rb);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
436
405
|
}
|
|
437
|
-
return false;
|
|
438
406
|
}
|
|
439
407
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
private addShape(obj: THREE.Object3D, shape: CANNON.Shape, center: THREE.Vector3, rb: Rigidbody | null): CANNON.Body | null {
|
|
444
|
-
|
|
445
|
-
let body: CANNON.Body | null = null;
|
|
408
|
+
updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean) {
|
|
409
|
+
if (comp.destroyed || !comp.gameObject) return;
|
|
410
|
+
if (!translation && !rotation) return;
|
|
446
411
|
|
|
447
|
-
if (
|
|
448
|
-
//
|
|
449
|
-
|
|
450
|
-
rb.initialize();
|
|
451
|
-
console.assert(rb.body ? true : false, "rigidbody didn't initialize / produce a physics body", rb);
|
|
452
|
-
body = rb.body;
|
|
412
|
+
if ((comp as ICollider).isCollider === true) {
|
|
413
|
+
// const collider = comp as ICollider;
|
|
414
|
+
console.warn("TODO: implement updating collider position");
|
|
453
415
|
}
|
|
454
416
|
else {
|
|
455
|
-
|
|
456
|
-
body =
|
|
457
|
-
body
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (body) {
|
|
461
|
-
// console.log(obj.name, obj.position, obj.rotation)
|
|
462
|
-
|
|
463
|
-
// the center is serialized from Unity so we need to move it into threejs space
|
|
464
|
-
// this should probably happen on export for colliders
|
|
465
|
-
center.x *= -1;
|
|
466
|
-
|
|
467
|
-
let wp = obj.position;
|
|
468
|
-
let wr = obj.quaternion;
|
|
469
|
-
|
|
470
|
-
// console.log(obj.name, wp)
|
|
471
|
-
|
|
472
|
-
if (rb && rb.gameObject !== obj) {
|
|
473
|
-
this.tempMat1.copy(obj.matrixWorld);
|
|
474
|
-
this.tempMat2.copy(rb.gameObject.matrixWorld).invert();
|
|
475
|
-
this.tempMat1.premultiply(this.tempMat2);
|
|
476
|
-
this.tempMat1.decompose(wp, wr, this.tempPosition);
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
wp = threeutils.getWorldPosition(obj);
|
|
480
|
-
const bp = body.position;
|
|
481
|
-
wp.x -= bp.x;
|
|
482
|
-
wp.y -= bp.y;
|
|
483
|
-
wp.z -= bp.z;
|
|
484
|
-
|
|
485
|
-
wr = threeutils.getWorldQuaternion(obj);
|
|
486
|
-
const r = new THREE.Quaternion(body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w);
|
|
487
|
-
wr.multiply(r.invert());
|
|
417
|
+
const rigidbody = comp as IRigidbody;
|
|
418
|
+
const body = rigidbody[$bodyKey];
|
|
419
|
+
if (body) {
|
|
420
|
+
this.syncPhysicsBody(rigidbody.gameObject, body, translation, rotation);
|
|
488
421
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
wp.add(center);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
492
424
|
|
|
425
|
+
updateProperties(rigidbody: IRigidbody) {
|
|
426
|
+
const physicsBody = rigidbody[$bodyKey]
|
|
427
|
+
if (physicsBody) {
|
|
428
|
+
this.internalUpdateProperties(rigidbody, physicsBody);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
493
431
|
|
|
432
|
+
internal_getRigidbody(rb: IRigidbody): RigidBody | null {
|
|
433
|
+
return rb[$bodyKey] as RigidBody;
|
|
434
|
+
}
|
|
494
435
|
|
|
436
|
+
private internalUpdateProperties(rb: IRigidbody, rigidbody: RigidBody) {
|
|
437
|
+
// continuous collision detection
|
|
438
|
+
// https://rapier.rs/docs/user_guides/javascript/rigid_bodies#continuous-collision-detection
|
|
439
|
+
rigidbody.enableCcd(rb.collisionDetectionMode !== CollisionDetectionMode.Discrete);
|
|
440
|
+
rigidbody.setLinearDamping(rb.drag);
|
|
441
|
+
rigidbody.setAngularDamping(rb.angularDrag);
|
|
442
|
+
rigidbody.setGravityScale(rb.useGravity ? 1 : 0, true);
|
|
495
443
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
444
|
+
// https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
|
|
445
|
+
// rigidbody.setAdditionalMass(rb.mass, true);
|
|
446
|
+
// for (let i = 0; i < rigidbody.numColliders(); i++) {
|
|
447
|
+
// const collider = rigidbody.collider(i);
|
|
448
|
+
// if (collider) {
|
|
449
|
+
// collider.setMass(rb.mass);
|
|
450
|
+
// // const density = rb.mass / collider.shape.computeMassProperties().mass;
|
|
451
|
+
// }
|
|
452
|
+
// }
|
|
503
453
|
|
|
504
|
-
|
|
454
|
+
// lock rotations
|
|
455
|
+
rigidbody.setEnabledRotations(!rb.lockRotationX, !rb.lockRotationY, !rb.lockRotationZ, true);
|
|
456
|
+
rigidbody.setEnabledTranslations(!rb.lockPositionX, !rb.lockPositionY, !rb.lockPositionZ, true);
|
|
505
457
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
458
|
+
if (rb.isKinematic) {
|
|
459
|
+
rigidbody.setBodyType(RAPIER.RigidBodyType.KinematicPositionBased);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
rigidbody.setBodyType(RAPIER.RigidBodyType.Dynamic);
|
|
511
463
|
}
|
|
512
|
-
return body;
|
|
513
464
|
}
|
|
514
465
|
|
|
515
466
|
// private _lastStepTime: number | undefined = 0;
|
|
467
|
+
private lines?: LineSegments;
|
|
516
468
|
|
|
517
|
-
public step(
|
|
469
|
+
public step(_deltaTime?: number) {
|
|
470
|
+
if (!this.world) return;
|
|
518
471
|
this._isUpdatingPhysicsWorld = true;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
this._isUpdatingPhysicsWorld = false;
|
|
522
|
-
if (debugPhysics && this.context.time.frameCount % 60 === 0) {
|
|
523
|
-
// console.log("physics world has " + this.world.bodies.length + " bodies", this.world);
|
|
472
|
+
if (!this.eventQueue) {
|
|
473
|
+
this.eventQueue = new EventQueue(false);
|
|
524
474
|
}
|
|
475
|
+
this.world.step(this.eventQueue);
|
|
476
|
+
this._isUpdatingPhysicsWorld = false;
|
|
477
|
+
this.updateDebugRendering(this.world);
|
|
525
478
|
}
|
|
526
479
|
|
|
527
|
-
private
|
|
528
|
-
|
|
480
|
+
private updateDebugRendering(world: World) {
|
|
481
|
+
if (debugPhysics || debugColliderPlacement) {
|
|
482
|
+
if (!this.lines) {
|
|
483
|
+
let material = new LineBasicMaterial({
|
|
484
|
+
color: 0xffffff,
|
|
485
|
+
// vertexColors: THREE.VertexColors
|
|
486
|
+
});
|
|
487
|
+
let geometry = new BufferGeometry();
|
|
488
|
+
this.lines = new LineSegments(geometry, material);
|
|
489
|
+
this.context.scene.add(this.lines);
|
|
490
|
+
}
|
|
491
|
+
const buffers = world.debugRender();
|
|
492
|
+
this.lines.geometry.setAttribute('position', new BufferAttribute(buffers.vertices, 3));
|
|
493
|
+
this.lines.geometry.setAttribute('color', new BufferAttribute(buffers.colors, 4));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
529
496
|
|
|
530
497
|
public postStep() {
|
|
498
|
+
if (!this.world) return;
|
|
531
499
|
this._isUpdatingPhysicsWorld = true;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const body = entry.body;
|
|
535
|
-
if (!body || !body.world) continue;
|
|
500
|
+
this.syncObjects();
|
|
501
|
+
this._isUpdatingPhysicsWorld = false;
|
|
536
502
|
|
|
537
|
-
|
|
503
|
+
if (this.eventQueue && !this.collisionHandler) {
|
|
504
|
+
this.collisionHandler = new PhysicsCollisionHandler(this.world, this.eventQueue);
|
|
505
|
+
}
|
|
506
|
+
if (this.collisionHandler) {
|
|
507
|
+
this.collisionHandler.handleCollisionEvents();
|
|
508
|
+
this.collisionHandler.update();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
538
511
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if (body.type === CANNON.Body.KINEMATIC) {
|
|
555
|
-
const wp = threeutils.getWorldPosition(obj, this.temp);
|
|
556
|
-
body.position.set(wp.x, wp.y, wp.z);
|
|
557
|
-
const rot = threeutils.getWorldQuaternion(obj, this.tempQuat);
|
|
558
|
-
body.quaternion.set(rot.x, rot.y, rot.z, rot.w);
|
|
512
|
+
/** sync rendered objects with physics world (except for colliders without rigidbody) */
|
|
513
|
+
private syncObjects() {
|
|
514
|
+
if (debugColliderPlacement) return;
|
|
515
|
+
for (let i = 0; i < this.bodies.length; i++) {
|
|
516
|
+
const obj = this.objects[i];
|
|
517
|
+
const body = this.bodies[i] as Collider;
|
|
518
|
+
|
|
519
|
+
// if the collider is not attached to a rigidbody
|
|
520
|
+
// it means that its kinematic so we need to update its position
|
|
521
|
+
const col = (obj as ICollider);
|
|
522
|
+
if (col?.isCollider === true && !col.attachedRigidbody) {
|
|
523
|
+
const rigidbody = body.parent();
|
|
524
|
+
if (rigidbody)
|
|
525
|
+
this.syncPhysicsBody(obj.gameObject, rigidbody, true, true);
|
|
559
526
|
continue;
|
|
560
527
|
}
|
|
561
528
|
|
|
529
|
+
// sync
|
|
530
|
+
const pos = body.translation();
|
|
531
|
+
setWorldPositionXYZ(obj.gameObject, pos.x, pos.y, pos.z);
|
|
532
|
+
const rot = body.rotation();
|
|
533
|
+
setWorldQuaternionXYZW(obj.gameObject, rot.x, rot.y, rot.z, rot.w);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
562
536
|
|
|
537
|
+
private syncPhysicsBody(obj: Object3D, body: RigidBody, translation: boolean, rotation: boolean) {
|
|
538
|
+
|
|
539
|
+
// const bodyType = body.bodyType();
|
|
540
|
+
// const previous = physicsBody.translation();
|
|
541
|
+
// const vel = physicsBody.linvel();
|
|
542
|
+
|
|
543
|
+
const worldPosition = getWorldPosition(obj, this._tempPosition);
|
|
544
|
+
const worldQuaternion = getWorldQuaternion(obj, this._tempQuaternion);
|
|
545
|
+
// physicsBody.setBodyType(RAPIER.RigidBodyType.Fixed);
|
|
546
|
+
if (translation)
|
|
547
|
+
body.setTranslation(worldPosition, false);
|
|
548
|
+
if (rotation)
|
|
549
|
+
body.setRotation(worldQuaternion, false);
|
|
550
|
+
// physicsBody.setLinvel(vel, false);
|
|
551
|
+
body.wakeUp();
|
|
552
|
+
|
|
553
|
+
// update velocity
|
|
554
|
+
// const pos = physicsBody.translation();
|
|
555
|
+
// pos.x -= previous.x;
|
|
556
|
+
// pos.y -= previous.y;
|
|
557
|
+
// pos.z -= previous.z;
|
|
558
|
+
// // threhold
|
|
559
|
+
// const t = 1;
|
|
560
|
+
// const canUpdateVelocity = Math.abs(pos.x) < t && Math.abs(pos.y) < t && Math.abs(pos.z) < t;
|
|
561
|
+
// if (canUpdateVelocity) {
|
|
562
|
+
// const damping = 1 + this.context.time.deltaTime;
|
|
563
|
+
// vel.x *= damping;
|
|
564
|
+
// vel.y *= damping;
|
|
565
|
+
// vel.z *= damping;
|
|
566
|
+
// vel.x += pos.x;
|
|
567
|
+
// vel.y += pos.y;
|
|
568
|
+
// vel.z += pos.z;
|
|
569
|
+
// console.log(vel);
|
|
570
|
+
// physicsBody.setLinvel(vel, true);
|
|
571
|
+
// }
|
|
572
|
+
// else if(debugPhysics) console.warn("Movement exceeded threshold, not updating velocity", pos);
|
|
563
573
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
threeutils.setWorldQuaternionXYZW(obj,
|
|
568
|
-
body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
const p = body.position;
|
|
572
|
-
threeutils.setWorldPositionXYZ(obj, p.x, p.y, p.z);
|
|
574
|
+
// body.setBodyType(bodyType);
|
|
575
|
+
}
|
|
573
576
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
// this.rotatedCenter.copy(entry.center);
|
|
587
|
-
// const rot = this.tempQuaternion;
|
|
588
|
-
// rot.copy(obj.quaternion);
|
|
589
|
-
// // obj.getWorldQuaternion(this.tempQuaternion)
|
|
590
|
-
// this.rotatedCenter.applyQuaternion(rot);
|
|
591
|
-
// obj.getWorldScale(this.tempVector);
|
|
592
|
-
// this.rotatedCenter.divide(this.tempVector);
|
|
593
|
-
// obj.position.sub(this.rotatedCenter);
|
|
594
|
-
// }
|
|
577
|
+
private static _matricesBuffer: Matrix4[] = [];
|
|
578
|
+
private getRigidbodyRelativeMatrix(comp: Object3D, rigidbody: Object3D, mat: Matrix4, matrices?: Matrix4[]): Matrix4 {
|
|
579
|
+
// collect all matrices to the rigidbody and then build the rigidbody relative matrix
|
|
580
|
+
if (matrices === undefined) {
|
|
581
|
+
matrices = Physics._matricesBuffer;
|
|
582
|
+
matrices.length = 0;
|
|
583
|
+
}
|
|
584
|
+
if (comp === rigidbody) {
|
|
585
|
+
const scale = getWorldScale(comp, this._tempPosition);
|
|
586
|
+
mat.makeScale(scale.x, scale.y, scale.z);
|
|
587
|
+
for (let i = matrices.length - 1; i >= 0; i--) {
|
|
588
|
+
mat.multiply(matrices[i]);
|
|
595
589
|
}
|
|
590
|
+
return mat;
|
|
596
591
|
}
|
|
597
|
-
|
|
592
|
+
matrices.push(comp.matrix);
|
|
593
|
+
if (comp.parent) {
|
|
594
|
+
this.getRigidbodyRelativeMatrix(comp.parent, rigidbody, mat, matrices);
|
|
595
|
+
}
|
|
596
|
+
return mat;
|
|
598
597
|
}
|
|
599
598
|
|
|
600
|
-
private internalCreateBody(obj: THREE.Object3D, shape: CANNON.Shape | undefined | null): CANNON.Body {
|
|
601
599
|
|
|
602
|
-
|
|
603
|
-
body["_owner"] = obj;
|
|
604
|
-
body["_name"] = obj.name;
|
|
605
|
-
obj.getWorldPosition(this.tempPosition);
|
|
606
|
-
const pos = this.tempPosition;
|
|
607
|
-
body.position = new CANNON.Vec3(pos.x, pos.y, pos.z);
|
|
600
|
+
}
|
|
608
601
|
|
|
609
|
-
const quat = this.tempQuaternion;
|
|
610
|
-
obj.getWorldQuaternion(quat);
|
|
611
|
-
body.quaternion = new CANNON.Quaternion(quat.x, quat.y, quat.z, quat.w);
|
|
612
602
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
body.updateMassProperties();
|
|
617
|
-
}
|
|
618
|
-
return body;
|
|
619
|
-
}
|
|
603
|
+
export interface IColliderProvider {
|
|
604
|
+
getCollider(obj: Object3D): ICollider;
|
|
605
|
+
}
|
|
620
606
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
// return entry;
|
|
626
|
-
// }
|
|
627
|
-
// return null;
|
|
628
|
-
// }
|
|
607
|
+
let colliderProvider: IColliderProvider | null = null;
|
|
608
|
+
export function registerColliderProvider(prov: IColliderProvider) {
|
|
609
|
+
colliderProvider = prov;
|
|
610
|
+
}
|
|
629
611
|
|
|
630
|
-
|
|
631
|
-
if (obj.collisonCallback) this.unregisterCollisionEvents(obj);
|
|
632
|
-
if (!obj.body) return;
|
|
633
|
-
const evt = evt => this.raiseCollisionEvents(obj.obj, evt);
|
|
634
|
-
obj.collisonCallback = evt.bind(this);
|
|
635
|
-
obj.body.addEventListener("collide", obj.collisonCallback);
|
|
636
|
-
}
|
|
612
|
+
class CollisionContext implements ICollisionContext {
|
|
637
613
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (!obj.body) return;
|
|
641
|
-
obj.body.removeEventListener("collide", obj.collisonCallback);
|
|
614
|
+
getCollider(obj: Object3D<Event>): ICollider {
|
|
615
|
+
return colliderProvider!.getCollider(obj);
|
|
642
616
|
}
|
|
643
617
|
|
|
644
|
-
|
|
645
|
-
// this is called after the object collide event so we dont really need it
|
|
646
|
-
// console.log("START");
|
|
647
|
-
}
|
|
618
|
+
}
|
|
648
619
|
|
|
649
|
-
private readonly collisionContext: ICollisionContext = new CollisionContext();
|
|
650
620
|
|
|
651
|
-
private raiseCollisionEvents(obj: THREE.Object3D, event: CannonCollision) {
|
|
652
|
-
const collision = new Collision(obj, event, this.collisionContext);
|
|
653
|
-
if (debugCollisions)
|
|
654
|
-
console.log("collision between", event.contact.bi, event.contact.bj, obj, event);
|
|
655
|
-
foreachComponent(obj, (c: Component) => {
|
|
656
|
-
c.__internalHandleCollision(collision, false);
|
|
657
|
-
});
|
|
658
621
|
|
|
659
|
-
// handle triggers
|
|
660
|
-
if (collision.collider && !collision.collider.attachedRigidbody && collision.collider.isTrigger) {
|
|
661
|
-
const collision2 = new Collision(collision.gameObject, event, this.collisionContext, true);
|
|
662
|
-
foreachComponent(collision.gameObject, (c: Component) => {
|
|
663
|
-
c.__internalHandleCollision(collision2, true);
|
|
664
|
-
});
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
622
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
// console.log("END", CANNON.BODY_SLEEP_STATES, args.bodyB.sleepState);
|
|
671
|
-
const obj1 = args.bodyA[$physicsKey];
|
|
672
|
-
const obj2 = args.bodyB[$physicsKey];
|
|
673
|
-
// console.log(obj2);
|
|
623
|
+
/** responsible of processing collision events for the component system */
|
|
624
|
+
class PhysicsCollisionHandler {
|
|
674
625
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
});
|
|
626
|
+
readonly world: World;
|
|
627
|
+
readonly eventQueue: EventQueue;
|
|
678
628
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
629
|
+
constructor(world: World, eventQueue: EventQueue) {
|
|
630
|
+
this.world = world;
|
|
631
|
+
this.eventQueue = eventQueue;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
private activeCollisions: Array<{ collider: ICollider, component: IComponent, collision: Collision }> = [];
|
|
635
|
+
private activeTriggers: Array<{ collider: ICollider, component: IComponent, otherCollider: ICollider }> = [];
|
|
636
|
+
|
|
637
|
+
handleCollisionEvents() {
|
|
638
|
+
if (!this.eventQueue) return;
|
|
639
|
+
if (!this.world) return;
|
|
640
|
+
this.eventQueue.drainCollisionEvents((handle1, handle2, started) => {
|
|
641
|
+
const col1 = this.world!.getCollider(handle1);
|
|
642
|
+
const col2 = this.world!.getCollider(handle2);
|
|
643
|
+
const colliderComponent1 = col1[$componentKey];
|
|
644
|
+
const colliderComponent2 = col2[$componentKey];
|
|
645
|
+
// console.log("EVT", colliderComponent1.name, colliderComponent2.name, started);
|
|
646
|
+
if (colliderComponent1 && colliderComponent2) {
|
|
647
|
+
if (started) {
|
|
648
|
+
this.onCollisionStarted(colliderComponent1, col1, colliderComponent2, col2);
|
|
649
|
+
this.onCollisionStarted(colliderComponent2, col2, colliderComponent1, col1);
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
this.onCollisionEnded(colliderComponent1, colliderComponent2);
|
|
653
|
+
this.onCollisionEnded(colliderComponent2, colliderComponent1);
|
|
654
|
+
}
|
|
686
655
|
}
|
|
687
656
|
});
|
|
688
657
|
}
|
|
689
658
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
getCollider(obj: THREE.Object3D): ICollider;
|
|
694
|
-
}
|
|
659
|
+
update() {
|
|
660
|
+
this.onHandleCollisionStay();
|
|
661
|
+
}
|
|
695
662
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
colliderProvider = prov;
|
|
699
|
-
}
|
|
663
|
+
private onCollisionStarted(self: ICollider, selfBody: Collider, other: ICollider, otherBody: Collider) {
|
|
664
|
+
let collision: Collision | null = null;
|
|
700
665
|
|
|
701
|
-
|
|
666
|
+
// if one is a trigger we dont get collisions but want to raise the trigger events
|
|
667
|
+
if (self.isTrigger || other.isTrigger) {
|
|
668
|
+
foreachComponent(self.gameObject, (c: IComponent) => {
|
|
669
|
+
if (c.onTriggerEnter) {
|
|
670
|
+
c.onTriggerEnter(other);
|
|
671
|
+
}
|
|
672
|
+
this.activeTriggers.push({ collider: self, component: c, otherCollider: other });
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
const object = self.gameObject;
|
|
677
|
+
// TODO: we dont respect the flip value here!
|
|
678
|
+
this.world.contactPair(selfBody, otherBody, (manifold, _flipped) => {
|
|
679
|
+
foreachComponent(object, (c: IComponent) => {
|
|
680
|
+
if (c.onCollisionEnter) {
|
|
681
|
+
if (!collision) {
|
|
682
|
+
const contacts: Array<ContactPoint> = [];
|
|
683
|
+
const normal = manifold.normal();
|
|
684
|
+
for (let i = 0; i < manifold.numContacts(); i++) {
|
|
685
|
+
const pt1 = manifold.localContactPoint1(i);
|
|
686
|
+
const dist = manifold.contactDist(i);
|
|
687
|
+
if (pt1) {
|
|
688
|
+
const contact = new ContactPoint(pt1, dist, normal);
|
|
689
|
+
contacts.push(contact);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
collision = new Collision(object, other, contacts);
|
|
693
|
+
}
|
|
694
|
+
c.onCollisionEnter.call(c, collision);
|
|
695
|
+
this.activeCollisions.push({ collider: self, component: c, collision });
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
702
701
|
|
|
703
|
-
|
|
704
|
-
|
|
702
|
+
private onHandleCollisionStay() {
|
|
703
|
+
for (const active of this.activeCollisions) {
|
|
704
|
+
const c = active.component;
|
|
705
|
+
if (c.activeAndEnabled && c.onCollisionStay) {
|
|
706
|
+
const arg = active.collision;
|
|
707
|
+
c.onCollisionStay(arg);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
for (const active of this.activeTriggers) {
|
|
711
|
+
const c = active.component;
|
|
712
|
+
if (c.activeAndEnabled && c.onTriggerStay) {
|
|
713
|
+
const arg = active.collider;
|
|
714
|
+
c.onTriggerStay(arg);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
705
717
|
}
|
|
706
718
|
|
|
719
|
+
private onCollisionEnded(self: ICollider, other: ICollider) {
|
|
720
|
+
for (let i = 0; i < this.activeCollisions.length; i++) {
|
|
721
|
+
const active = this.activeCollisions[i];
|
|
722
|
+
const collider = active.collider;
|
|
723
|
+
if (collider === self && active.collision.collider === other) {
|
|
724
|
+
const c = active.component;
|
|
725
|
+
this.activeCollisions.splice(i, 1);
|
|
726
|
+
i--;
|
|
727
|
+
if (c.activeAndEnabled && c.onCollisionExit) {
|
|
728
|
+
const collision = active.collision;
|
|
729
|
+
c.onCollisionExit(collision);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
for (let i = 0; i < this.activeTriggers.length; i++) {
|
|
734
|
+
const active = this.activeTriggers[i];
|
|
735
|
+
const collider = active.collider;
|
|
736
|
+
if (collider === self && active.otherCollider === other) {
|
|
737
|
+
const c = active.component;
|
|
738
|
+
this.activeTriggers.splice(i, 1);
|
|
739
|
+
i--;
|
|
740
|
+
if (c.activeAndEnabled && c.onTriggerExit) {
|
|
741
|
+
const collision = active.otherCollider;
|
|
742
|
+
c.onTriggerExit(collision);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
707
747
|
}
|
|
708
|
-
|