@rapierphysicsplugin/client 1.0.0

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 (83) hide show
  1. package/dist/__tests__/clock-sync.test.d.ts +2 -0
  2. package/dist/__tests__/clock-sync.test.d.ts.map +1 -0
  3. package/dist/__tests__/clock-sync.test.js +63 -0
  4. package/dist/__tests__/clock-sync.test.js.map +1 -0
  5. package/dist/__tests__/interpolator.test.d.ts +2 -0
  6. package/dist/__tests__/interpolator.test.d.ts.map +1 -0
  7. package/dist/__tests__/interpolator.test.js +82 -0
  8. package/dist/__tests__/interpolator.test.js.map +1 -0
  9. package/dist/__tests__/state-reconciler.test.d.ts +2 -0
  10. package/dist/__tests__/state-reconciler.test.d.ts.map +1 -0
  11. package/dist/__tests__/state-reconciler.test.js +86 -0
  12. package/dist/__tests__/state-reconciler.test.js.map +1 -0
  13. package/dist/clock-sync.d.ts +17 -0
  14. package/dist/clock-sync.d.ts.map +1 -0
  15. package/dist/clock-sync.js +63 -0
  16. package/dist/clock-sync.js.map +1 -0
  17. package/dist/index.d.ts +10 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +8 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/input-manager.d.ts +18 -0
  22. package/dist/input-manager.d.ts.map +1 -0
  23. package/dist/input-manager.js +62 -0
  24. package/dist/input-manager.js.map +1 -0
  25. package/dist/interpolator.d.ts +35 -0
  26. package/dist/interpolator.d.ts.map +1 -0
  27. package/dist/interpolator.js +198 -0
  28. package/dist/interpolator.js.map +1 -0
  29. package/dist/networked-rapier-plugin.d.ts +82 -0
  30. package/dist/networked-rapier-plugin.d.ts.map +1 -0
  31. package/dist/networked-rapier-plugin.js +698 -0
  32. package/dist/networked-rapier-plugin.js.map +1 -0
  33. package/dist/rapier-body-ops.d.ts +27 -0
  34. package/dist/rapier-body-ops.d.ts.map +1 -0
  35. package/dist/rapier-body-ops.js +208 -0
  36. package/dist/rapier-body-ops.js.map +1 -0
  37. package/dist/rapier-collision-ops.d.ts +6 -0
  38. package/dist/rapier-collision-ops.d.ts.map +1 -0
  39. package/dist/rapier-collision-ops.js +200 -0
  40. package/dist/rapier-collision-ops.js.map +1 -0
  41. package/dist/rapier-constraint-ops.d.ts +29 -0
  42. package/dist/rapier-constraint-ops.d.ts.map +1 -0
  43. package/dist/rapier-constraint-ops.js +286 -0
  44. package/dist/rapier-constraint-ops.js.map +1 -0
  45. package/dist/rapier-plugin.d.ts +145 -0
  46. package/dist/rapier-plugin.d.ts.map +1 -0
  47. package/dist/rapier-plugin.js +263 -0
  48. package/dist/rapier-plugin.js.map +1 -0
  49. package/dist/rapier-shape-ops.d.ts +21 -0
  50. package/dist/rapier-shape-ops.d.ts.map +1 -0
  51. package/dist/rapier-shape-ops.js +314 -0
  52. package/dist/rapier-shape-ops.js.map +1 -0
  53. package/dist/rapier-types.d.ts +58 -0
  54. package/dist/rapier-types.d.ts.map +1 -0
  55. package/dist/rapier-types.js +4 -0
  56. package/dist/rapier-types.js.map +1 -0
  57. package/dist/state-reconciler.d.ts +28 -0
  58. package/dist/state-reconciler.d.ts.map +1 -0
  59. package/dist/state-reconciler.js +119 -0
  60. package/dist/state-reconciler.js.map +1 -0
  61. package/dist/sync-client.d.ts +110 -0
  62. package/dist/sync-client.d.ts.map +1 -0
  63. package/dist/sync-client.js +514 -0
  64. package/dist/sync-client.js.map +1 -0
  65. package/package.json +21 -0
  66. package/src/__tests__/clock-sync.test.ts +72 -0
  67. package/src/__tests__/interpolator.test.ts +98 -0
  68. package/src/__tests__/state-reconciler.test.ts +102 -0
  69. package/src/clock-sync.ts +77 -0
  70. package/src/index.ts +9 -0
  71. package/src/input-manager.ts +72 -0
  72. package/src/interpolator.ts +256 -0
  73. package/src/networked-rapier-plugin.ts +909 -0
  74. package/src/rapier-body-ops.ts +251 -0
  75. package/src/rapier-collision-ops.ts +229 -0
  76. package/src/rapier-constraint-ops.ts +327 -0
  77. package/src/rapier-plugin.ts +364 -0
  78. package/src/rapier-shape-ops.ts +369 -0
  79. package/src/rapier-types.ts +60 -0
  80. package/src/state-reconciler.ts +151 -0
  81. package/src/sync-client.ts +640 -0
  82. package/tsconfig.json +12 -0
  83. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,327 @@
1
+ import type RAPIER from '@dimforge/rapier3d-compat';
2
+ import {
3
+ PhysicsConstraintType,
4
+ PhysicsConstraintAxis,
5
+ PhysicsConstraintMotorType,
6
+ } from '@babylonjs/core';
7
+ import type {
8
+ PhysicsBody,
9
+ PhysicsConstraint,
10
+ PhysicsConstraintAxisLimitMode,
11
+ ConstrainedBodyPair,
12
+ Nullable,
13
+ } from '@babylonjs/core';
14
+ import type { ConstraintDescriptor } from '@rapierphysicsplugin/shared';
15
+ import { createJointData } from '@rapierphysicsplugin/shared';
16
+ import type { RapierPluginState, AxisConfig } from './rapier-types.js';
17
+ import { v3toVec } from './rapier-types.js';
18
+
19
+ export function buildConstraintDescriptor(constraint: PhysicsConstraint): ConstraintDescriptor {
20
+ const opts = (constraint as any)._options ?? {};
21
+ const cType = (constraint as any)._type as PhysicsConstraintType;
22
+
23
+ let type: ConstraintDescriptor['type'];
24
+ switch (cType) {
25
+ case PhysicsConstraintType.BALL_AND_SOCKET: type = 'ball_and_socket'; break;
26
+ case PhysicsConstraintType.DISTANCE: type = 'distance'; break;
27
+ case PhysicsConstraintType.HINGE: type = 'hinge'; break;
28
+ case PhysicsConstraintType.SLIDER: type = 'slider'; break;
29
+ case PhysicsConstraintType.LOCK: type = 'lock'; break;
30
+ case PhysicsConstraintType.PRISMATIC: type = 'prismatic'; break;
31
+ case PhysicsConstraintType.SIX_DOF: type = 'six_dof'; break;
32
+ default: type = 'ball_and_socket';
33
+ }
34
+
35
+ const sixDofLimits = (constraint as any).limits as Array<{ axis: number; minLimit?: number; maxLimit?: number; stiffness?: number; damping?: number }> | undefined;
36
+ let isSpring = false;
37
+ if (type === 'six_dof' && sixDofLimits) {
38
+ isSpring = sixDofLimits.some(l => l.stiffness !== undefined && l.stiffness > 0);
39
+ }
40
+
41
+ const desc: ConstraintDescriptor = {
42
+ id: '',
43
+ bodyIdA: '',
44
+ bodyIdB: '',
45
+ type: isSpring ? 'spring' : type,
46
+ pivotA: opts.pivotA ? v3toVec(opts.pivotA) : undefined,
47
+ pivotB: opts.pivotB ? v3toVec(opts.pivotB) : undefined,
48
+ axisA: opts.axisA ? v3toVec(opts.axisA) : undefined,
49
+ axisB: opts.axisB ? v3toVec(opts.axisB) : undefined,
50
+ perpAxisA: opts.perpAxisA ? v3toVec(opts.perpAxisA) : undefined,
51
+ perpAxisB: opts.perpAxisB ? v3toVec(opts.perpAxisB) : undefined,
52
+ maxDistance: opts.maxDistance,
53
+ collision: opts.collision,
54
+ };
55
+
56
+ if (isSpring && sixDofLimits) {
57
+ const springLimit = sixDofLimits.find(l => l.stiffness !== undefined);
58
+ if (springLimit) {
59
+ desc.stiffness = springLimit.stiffness;
60
+ desc.damping = springLimit.damping;
61
+ }
62
+ }
63
+
64
+ if (type === 'six_dof' && !isSpring && sixDofLimits) {
65
+ desc.limits = sixDofLimits.map(l => ({
66
+ axis: l.axis,
67
+ minLimit: l.minLimit,
68
+ maxLimit: l.maxLimit,
69
+ }));
70
+ }
71
+
72
+ return desc;
73
+ }
74
+
75
+ export function createJointFromConstraint(
76
+ state: RapierPluginState,
77
+ constraint: PhysicsConstraint,
78
+ rbA: RAPIER.RigidBody,
79
+ rbB: RAPIER.RigidBody,
80
+ ): RAPIER.ImpulseJoint {
81
+ const desc = buildConstraintDescriptor(constraint);
82
+ const jointData = createJointData(state.rapier, desc);
83
+ return state.world.createImpulseJoint(jointData, rbA, rbB, true);
84
+ }
85
+
86
+ export function initConstraint(
87
+ state: RapierPluginState,
88
+ constraint: PhysicsConstraint,
89
+ body: PhysicsBody,
90
+ childBody: PhysicsBody,
91
+ ): void {
92
+ if (state.constraintToJoint.has(constraint)) return;
93
+
94
+ const rbA = state.bodyToRigidBody.get(body);
95
+ const rbB = state.bodyToRigidBody.get(childBody);
96
+ if (!rbA || !rbB) return;
97
+
98
+ const joint = createJointFromConstraint(state, constraint, rbA, rbB);
99
+ state.constraintToJoint.set(constraint, joint);
100
+ state.constraintBodies.set(constraint, { body, childBody });
101
+ state.constraintEnabled.set(constraint, true);
102
+
103
+ const opts = (constraint as any)._options;
104
+ if (opts?.collision === false) {
105
+ joint.setContactsEnabled(false);
106
+ }
107
+
108
+ applyInitialLimits(constraint, joint);
109
+ }
110
+
111
+ function applyInitialLimits(constraint: PhysicsConstraint, joint: RAPIER.ImpulseJoint): void {
112
+ const sixDofLimits = (constraint as any).limits as Array<{ axis: number; minLimit?: number; maxLimit?: number }> | undefined;
113
+ if (!sixDofLimits) return;
114
+
115
+ const cType = (constraint as any)._type as PhysicsConstraintType;
116
+ if (cType === PhysicsConstraintType.HINGE) {
117
+ const angLim = sixDofLimits.find(l => l.axis === PhysicsConstraintAxis.ANGULAR_X);
118
+ if (angLim && angLim.minLimit !== undefined && angLim.maxLimit !== undefined) {
119
+ (joint as any).setLimits?.(angLim.minLimit, angLim.maxLimit);
120
+ }
121
+ } else if (cType === PhysicsConstraintType.SLIDER || cType === PhysicsConstraintType.PRISMATIC) {
122
+ const linLim = sixDofLimits.find(l => l.axis === PhysicsConstraintAxis.LINEAR_X);
123
+ if (linLim && linLim.minLimit !== undefined && linLim.maxLimit !== undefined) {
124
+ (joint as any).setLimits?.(linLim.minLimit, linLim.maxLimit);
125
+ }
126
+ }
127
+ }
128
+
129
+ export function disposeConstraint(state: RapierPluginState, constraint: PhysicsConstraint): void {
130
+ const joint = state.constraintToJoint.get(constraint);
131
+ if (joint) {
132
+ state.world.removeImpulseJoint(joint, true);
133
+ state.constraintToJoint.delete(constraint);
134
+ }
135
+ state.constraintBodies.delete(constraint);
136
+ state.constraintAxisState.delete(constraint);
137
+ state.constraintEnabled.delete(constraint);
138
+ state.constraintDescriptors.delete(constraint);
139
+ }
140
+
141
+ export function setEnabled(state: RapierPluginState, constraint: PhysicsConstraint, isEnabled: boolean): void {
142
+ const currentlyEnabled = state.constraintEnabled.get(constraint) ?? true;
143
+ if (isEnabled === currentlyEnabled) return;
144
+
145
+ if (!isEnabled) {
146
+ const joint = state.constraintToJoint.get(constraint);
147
+ if (joint) {
148
+ state.world.removeImpulseJoint(joint, true);
149
+ state.constraintToJoint.delete(constraint);
150
+ }
151
+ state.constraintEnabled.set(constraint, false);
152
+ } else {
153
+ const pair = state.constraintBodies.get(constraint);
154
+ if (pair) {
155
+ const rbA = state.bodyToRigidBody.get(pair.body);
156
+ const rbB = state.bodyToRigidBody.get(pair.childBody);
157
+ if (rbA && rbB) {
158
+ const joint = createJointFromConstraint(state, constraint, rbA, rbB);
159
+ state.constraintToJoint.set(constraint, joint);
160
+
161
+ const opts = (constraint as any)._options;
162
+ if (opts?.collision === false) {
163
+ joint.setContactsEnabled(false);
164
+ }
165
+ }
166
+ }
167
+ state.constraintEnabled.set(constraint, true);
168
+ }
169
+ }
170
+
171
+ export function getEnabled(state: RapierPluginState, constraint: PhysicsConstraint): boolean {
172
+ return state.constraintEnabled.get(constraint) ?? true;
173
+ }
174
+
175
+ export function setCollisionsEnabled(state: RapierPluginState, constraint: PhysicsConstraint, isEnabled: boolean): void {
176
+ const joint = state.constraintToJoint.get(constraint);
177
+ if (joint) {
178
+ joint.setContactsEnabled(isEnabled);
179
+ }
180
+ }
181
+
182
+ export function getCollisionsEnabled(state: RapierPluginState, constraint: PhysicsConstraint): boolean {
183
+ const joint = state.constraintToJoint.get(constraint);
184
+ if (joint) {
185
+ return joint.contactsEnabled();
186
+ }
187
+ return true;
188
+ }
189
+
190
+ function ensureAxisConfig(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): AxisConfig {
191
+ let axisMap = state.constraintAxisState.get(constraint);
192
+ if (!axisMap) {
193
+ axisMap = new Map();
194
+ state.constraintAxisState.set(constraint, axisMap);
195
+ }
196
+ let config = axisMap.get(axis);
197
+ if (!config) {
198
+ config = {};
199
+ axisMap.set(axis, config);
200
+ }
201
+ return config;
202
+ }
203
+
204
+ function getAxisConfig(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): AxisConfig | undefined {
205
+ return state.constraintAxisState.get(constraint)?.get(axis);
206
+ }
207
+
208
+ export function setAxisFriction(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, friction: number): void {
209
+ ensureAxisConfig(state, constraint, axis).friction = friction;
210
+ }
211
+
212
+ export function getAxisFriction(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> {
213
+ return getAxisConfig(state, constraint, axis)?.friction ?? null;
214
+ }
215
+
216
+ export function setAxisMode(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, limitMode: PhysicsConstraintAxisLimitMode): void {
217
+ ensureAxisConfig(state, constraint, axis).mode = limitMode;
218
+ applyAxisLimitsToJoint(state, constraint);
219
+ }
220
+
221
+ export function getAxisMode(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<PhysicsConstraintAxisLimitMode> {
222
+ return getAxisConfig(state, constraint, axis)?.mode ?? null;
223
+ }
224
+
225
+ export function setAxisMinLimit(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, minLimit: number): void {
226
+ ensureAxisConfig(state, constraint, axis).minLimit = minLimit;
227
+ applyAxisLimitsToJoint(state, constraint);
228
+ }
229
+
230
+ export function getAxisMinLimit(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> {
231
+ return getAxisConfig(state, constraint, axis)?.minLimit ?? null;
232
+ }
233
+
234
+ export function setAxisMaxLimit(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, limit: number): void {
235
+ ensureAxisConfig(state, constraint, axis).maxLimit = limit;
236
+ applyAxisLimitsToJoint(state, constraint);
237
+ }
238
+
239
+ export function getAxisMaxLimit(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> {
240
+ return getAxisConfig(state, constraint, axis)?.maxLimit ?? null;
241
+ }
242
+
243
+ function applyAxisLimitsToJoint(state: RapierPluginState, constraint: PhysicsConstraint): void {
244
+ const joint = state.constraintToJoint.get(constraint);
245
+ if (!joint) return;
246
+
247
+ const cType = (constraint as any)._type as PhysicsConstraintType;
248
+
249
+ if (cType === PhysicsConstraintType.HINGE) {
250
+ const angConfig = getAxisConfig(state, constraint, PhysicsConstraintAxis.ANGULAR_X);
251
+ if (angConfig?.minLimit !== undefined && angConfig?.maxLimit !== undefined) {
252
+ (joint as any).setLimits?.(angConfig.minLimit, angConfig.maxLimit);
253
+ }
254
+ } else if (cType === PhysicsConstraintType.SLIDER || cType === PhysicsConstraintType.PRISMATIC) {
255
+ const linConfig = getAxisConfig(state, constraint, PhysicsConstraintAxis.LINEAR_X);
256
+ if (linConfig?.minLimit !== undefined && linConfig?.maxLimit !== undefined) {
257
+ (joint as any).setLimits?.(linConfig.minLimit, linConfig.maxLimit);
258
+ }
259
+ }
260
+ }
261
+
262
+ export function setAxisMotorType(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, motorType: PhysicsConstraintMotorType): void {
263
+ ensureAxisConfig(state, constraint, axis).motorType = motorType;
264
+ applyMotorToJoint(state, constraint);
265
+ }
266
+
267
+ export function getAxisMotorType(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<PhysicsConstraintMotorType> {
268
+ return getAxisConfig(state, constraint, axis)?.motorType ?? null;
269
+ }
270
+
271
+ export function setAxisMotorTarget(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, target: number): void {
272
+ ensureAxisConfig(state, constraint, axis).motorTarget = target;
273
+ applyMotorToJoint(state, constraint);
274
+ }
275
+
276
+ export function getAxisMotorTarget(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> {
277
+ return getAxisConfig(state, constraint, axis)?.motorTarget ?? null;
278
+ }
279
+
280
+ export function setAxisMotorMaxForce(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, maxForce: number): void {
281
+ ensureAxisConfig(state, constraint, axis).motorMaxForce = maxForce;
282
+ applyMotorToJoint(state, constraint);
283
+ }
284
+
285
+ export function getAxisMotorMaxForce(state: RapierPluginState, constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> {
286
+ return getAxisConfig(state, constraint, axis)?.motorMaxForce ?? null;
287
+ }
288
+
289
+ function applyMotorToJoint(state: RapierPluginState, constraint: PhysicsConstraint): void {
290
+ const joint = state.constraintToJoint.get(constraint);
291
+ if (!joint) return;
292
+
293
+ const cType = (constraint as any)._type as PhysicsConstraintType;
294
+
295
+ if (cType === PhysicsConstraintType.HINGE) {
296
+ const config = getAxisConfig(state, constraint, PhysicsConstraintAxis.ANGULAR_X);
297
+ if (config?.motorTarget !== undefined) {
298
+ const maxForce = config.motorMaxForce ?? 1000;
299
+ if (config.motorType === PhysicsConstraintMotorType.VELOCITY) {
300
+ (joint as any).configureMotorVelocity?.(config.motorTarget, maxForce);
301
+ } else {
302
+ (joint as any).configureMotorPosition?.(config.motorTarget, maxForce, 0);
303
+ }
304
+ }
305
+ } else if (cType === PhysicsConstraintType.SLIDER || cType === PhysicsConstraintType.PRISMATIC) {
306
+ const config = getAxisConfig(state, constraint, PhysicsConstraintAxis.LINEAR_X);
307
+ if (config?.motorTarget !== undefined) {
308
+ const maxForce = config.motorMaxForce ?? 1000;
309
+ if (config.motorType === PhysicsConstraintMotorType.VELOCITY) {
310
+ (joint as any).configureMotorVelocity?.(config.motorTarget, maxForce);
311
+ } else {
312
+ (joint as any).configureMotorPosition?.(config.motorTarget, maxForce, 0);
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ export function getBodiesUsingConstraint(state: RapierPluginState, constraint: PhysicsConstraint): ConstrainedBodyPair[] {
319
+ const pair = state.constraintBodies.get(constraint);
320
+ if (!pair) return [];
321
+ return [{
322
+ parentBody: pair.body,
323
+ parentBodyIndex: 0,
324
+ childBody: pair.childBody,
325
+ childBodyIndex: 0,
326
+ }];
327
+ }
@@ -0,0 +1,364 @@
1
+ import type RAPIER from '@dimforge/rapier3d-compat';
2
+ import {
3
+ Vector3,
4
+ Quaternion,
5
+ Observable,
6
+ } from '@babylonjs/core';
7
+ import type {
8
+ IPhysicsEnginePluginV2,
9
+ PhysicsBody,
10
+ PhysicsShape,
11
+ PhysicsConstraint,
12
+ PhysicsMassProperties,
13
+ PhysicsMaterial,
14
+ PhysicsShapeParameters,
15
+ PhysicsRaycastResult,
16
+ IRaycastQuery,
17
+ IPhysicsCollisionEvent,
18
+ IBasePhysicsCollisionEvent,
19
+ ConstrainedBodyPair,
20
+ BoundingBox,
21
+ } from '@babylonjs/core';
22
+ import type {
23
+ PhysicsMotionType,
24
+ PhysicsShapeType,
25
+ PhysicsConstraintAxisLimitMode,
26
+ PhysicsConstraintMotorType,
27
+ PhysicsConstraintAxis,
28
+ } from '@babylonjs/core';
29
+ import type { Mesh, TransformNode, Nullable } from '@babylonjs/core';
30
+ import type { CollisionEventData } from '@rapierphysicsplugin/shared';
31
+ import type { AxisConfig } from './rapier-types.js';
32
+
33
+ import { processCollisionEvents, injectCollisionEvents } from './rapier-collision-ops.js';
34
+ import * as bodyOps from './rapier-body-ops.js';
35
+ import * as shapeOps from './rapier-shape-ops.js';
36
+ import * as constraintOps from './rapier-constraint-ops.js';
37
+
38
+ export class RapierPlugin implements IPhysicsEnginePluginV2 {
39
+ public world: RAPIER.World;
40
+ public name = 'RapierPlugin';
41
+ public onCollisionObservable = new Observable<IPhysicsCollisionEvent>();
42
+ public onCollisionEndedObservable = new Observable<IBasePhysicsCollisionEvent>();
43
+ public onTriggerCollisionObservable = new Observable<IBasePhysicsCollisionEvent>();
44
+
45
+ public rapier: typeof RAPIER;
46
+ public bodyToRigidBody = new Map<PhysicsBody, RAPIER.RigidBody>();
47
+ public bodyToColliders = new Map<PhysicsBody, RAPIER.Collider[]>();
48
+ public shapeToColliderDesc = new Map<PhysicsShape, RAPIER.ColliderDesc>();
49
+ public shapeTypeMap = new Map<PhysicsShape, PhysicsShapeType>();
50
+ public shapeMaterialMap = new Map<PhysicsShape, PhysicsMaterial>();
51
+ public shapeDensityMap = new Map<PhysicsShape, number>();
52
+ public shapeFilterMembership = new Map<PhysicsShape, number>();
53
+ public shapeFilterCollide = new Map<PhysicsShape, number>();
54
+ public bodyCollisionObservables = new Map<PhysicsBody, Observable<IPhysicsCollisionEvent>>();
55
+ public bodyCollisionEndedObservables = new Map<PhysicsBody, Observable<IBasePhysicsCollisionEvent>>();
56
+ public constraintToJoint = new Map<PhysicsConstraint, RAPIER.ImpulseJoint>();
57
+ public constraintBodies = new Map<PhysicsConstraint, { body: PhysicsBody; childBody: PhysicsBody }>();
58
+ public constraintAxisState = new Map<PhysicsConstraint, Map<number, AxisConfig>>();
59
+ public constraintEnabled = new Map<PhysicsConstraint, boolean>();
60
+ public constraintDescriptors = new Map<PhysicsConstraint, { body: PhysicsBody; childBody: PhysicsBody }>();
61
+ public collisionCallbackEnabled = new Set<PhysicsBody>();
62
+ public collisionEndedCallbackEnabled = new Set<PhysicsBody>();
63
+ public triggerShapes = new Set<PhysicsShape>();
64
+ public bodyIdToPhysicsBody = new Map<string, PhysicsBody>();
65
+ private maxLinearVelocity = 200;
66
+ private maxAngularVelocity = 200;
67
+
68
+ public bodyToShape = new Map<PhysicsBody, PhysicsShape>();
69
+ public shapeToBody = new Map<PhysicsShape, PhysicsBody>();
70
+ public compoundChildren = new Map<PhysicsShape, Array<{ child: PhysicsShape; translation?: Vector3; rotation?: Quaternion; scale?: Vector3 }>>();
71
+ public bodyEventMask = new Map<PhysicsBody, number>();
72
+ public eventQueue!: RAPIER.EventQueue;
73
+ public colliderHandleToBody = new Map<number, PhysicsBody>();
74
+
75
+ constructor(rapier: typeof RAPIER, gravity?: Vector3) {
76
+ this.rapier = rapier;
77
+ const g = gravity ?? new Vector3(0, -9.81, 0);
78
+ this.world = new rapier.World(new rapier.Vector3(g.x, g.y, g.z));
79
+ this.eventQueue = new rapier.EventQueue(false);
80
+ }
81
+
82
+ // --- Core ---
83
+
84
+ getPluginVersion(): number { return 2; }
85
+
86
+ setGravity(gravity: Vector3): void {
87
+ this.world.gravity = new this.rapier.Vector3(gravity.x, gravity.y, gravity.z);
88
+ }
89
+
90
+ setTimeStep(timeStep: number): void { this.world.timestep = timeStep; }
91
+ getTimeStep(): number { return this.world.timestep; }
92
+
93
+ setVelocityLimits(maxLinearVelocity: number, maxAngularVelocity: number): void {
94
+ this.maxLinearVelocity = maxLinearVelocity;
95
+ this.maxAngularVelocity = maxAngularVelocity;
96
+ }
97
+
98
+ getMaxLinearVelocity(): number { return this.maxLinearVelocity; }
99
+ getMaxAngularVelocity(): number { return this.maxAngularVelocity; }
100
+
101
+ executeStep(_delta: number, bodies: Array<PhysicsBody>): void {
102
+ this.world.step(this.eventQueue);
103
+ processCollisionEvents(this, this.eventQueue);
104
+ for (const body of bodies) {
105
+ this.sync(body);
106
+ }
107
+ }
108
+
109
+ // --- Body lifecycle ---
110
+
111
+ initBody(body: PhysicsBody, motionType: PhysicsMotionType, position: Vector3, orientation: Quaternion): void {
112
+ bodyOps.initBody(this, body, motionType, position, orientation);
113
+ }
114
+
115
+ initBodyInstances(_body: PhysicsBody, _motionType: PhysicsMotionType, _mesh: Mesh): void {}
116
+ updateBodyInstances(_body: PhysicsBody, _mesh: Mesh): void {}
117
+
118
+ removeBody(body: PhysicsBody): void { this.disposeBody(body); }
119
+ disposeBody(body: PhysicsBody): void { bodyOps.disposeBody(this, body); }
120
+
121
+ sync(body: PhysicsBody): void { bodyOps.syncBody(this, body); }
122
+ syncTransform(body: PhysicsBody, transformNode: TransformNode): void { bodyOps.syncTransform(this, body, transformNode); }
123
+
124
+ // --- Shape management ---
125
+
126
+ initShape(shape: PhysicsShape, type: PhysicsShapeType, options: PhysicsShapeParameters): void {
127
+ shapeOps.initShape(this, shape, type, options);
128
+ }
129
+
130
+ setShape(body: PhysicsBody, shape: Nullable<PhysicsShape>): void { shapeOps.setShape(this, body, shape); }
131
+ getShape(body: PhysicsBody): Nullable<PhysicsShape> { return this.bodyToShape.get(body) ?? null; }
132
+ getShapeType(shape: PhysicsShape): PhysicsShapeType { return this.shapeTypeMap.get(shape) as PhysicsShapeType; }
133
+ disposeShape(shape: PhysicsShape): void { shapeOps.disposeShape(this, shape); }
134
+
135
+ // --- Shape filtering ---
136
+
137
+ setShapeFilterMembershipMask(shape: PhysicsShape, membershipMask: number): void { shapeOps.setShapeFilterMembershipMask(this, shape, membershipMask); }
138
+ getShapeFilterMembershipMask(shape: PhysicsShape): number { return shapeOps.getShapeFilterMembershipMask(this, shape); }
139
+ setShapeFilterCollideMask(shape: PhysicsShape, collideMask: number): void { shapeOps.setShapeFilterCollideMask(this, shape, collideMask); }
140
+ getShapeFilterCollideMask(shape: PhysicsShape): number { return shapeOps.getShapeFilterCollideMask(this, shape); }
141
+
142
+ // --- Shape material ---
143
+
144
+ setMaterial(shape: PhysicsShape, material: PhysicsMaterial): void { shapeOps.setMaterial(this, shape, material); }
145
+ getMaterial(shape: PhysicsShape): PhysicsMaterial { return shapeOps.getMaterial(this, shape); }
146
+ setDensity(shape: PhysicsShape, density: number): void { shapeOps.setDensity(this, shape, density); }
147
+ getDensity(shape: PhysicsShape): number { return shapeOps.getDensity(this, shape); }
148
+
149
+ // --- Compound shapes ---
150
+
151
+ addChild(shape: PhysicsShape, newChild: PhysicsShape, translation?: Vector3, rotation?: Quaternion, scale?: Vector3): void {
152
+ shapeOps.addChild(this, shape, newChild, translation, rotation, scale);
153
+ }
154
+ removeChild(shape: PhysicsShape, childIndex: number): void { shapeOps.removeChild(this, shape, childIndex); }
155
+ getNumChildren(shape: PhysicsShape): number { return shapeOps.getNumChildren(this, shape); }
156
+
157
+ // --- Bounding box ---
158
+
159
+ getBoundingBox(shape: PhysicsShape): BoundingBox { return shapeOps.getBoundingBox(this, shape); }
160
+ getBodyBoundingBox(body: PhysicsBody): BoundingBox { return shapeOps.getBodyBoundingBox(this, body); }
161
+
162
+ // --- Triggers & collision callbacks ---
163
+
164
+ setTrigger(shape: PhysicsShape, isTrigger: boolean): void { shapeOps.setTrigger(this, shape, isTrigger); }
165
+
166
+ setCollisionCallbackEnabled(body: PhysicsBody, enabled: boolean, _instanceIndex?: number): void {
167
+ if (enabled) { this.collisionCallbackEnabled.add(body); } else { this.collisionCallbackEnabled.delete(body); }
168
+ }
169
+
170
+ setCollisionEndedCallbackEnabled(body: PhysicsBody, enabled: boolean, _instanceIndex?: number): void {
171
+ if (enabled) { this.collisionEndedCallbackEnabled.add(body); } else { this.collisionEndedCallbackEnabled.delete(body); }
172
+ }
173
+
174
+ // --- Event mask ---
175
+
176
+ setEventMask(body: PhysicsBody, eventMask: number, _instanceIndex?: number): void { this.bodyEventMask.set(body, eventMask); }
177
+ getEventMask(body: PhysicsBody, _instanceIndex?: number): number { return this.bodyEventMask.get(body) ?? 0; }
178
+
179
+ // --- Motion type ---
180
+
181
+ setMotionType(body: PhysicsBody, motionType: PhysicsMotionType, _instanceIndex?: number): void { bodyOps.setMotionType(this, body, motionType); }
182
+ getMotionType(body: PhysicsBody, _instanceIndex?: number): PhysicsMotionType { return bodyOps.getMotionType(this, body); }
183
+
184
+ // --- Mass properties ---
185
+
186
+ computeMassProperties(body: PhysicsBody, _instanceIndex?: number): PhysicsMassProperties { return bodyOps.computeMassProperties(this, body); }
187
+ setMassProperties(body: PhysicsBody, massProps: PhysicsMassProperties, _instanceIndex?: number): void { bodyOps.setMassProperties(this, body, massProps); }
188
+ getMassProperties(body: PhysicsBody, _instanceIndex?: number): PhysicsMassProperties { return bodyOps.getMassProperties(this, body); }
189
+
190
+ // --- Damping ---
191
+
192
+ setLinearDamping(body: PhysicsBody, damping: number, _instanceIndex?: number): void { bodyOps.setLinearDamping(this, body, damping); }
193
+ getLinearDamping(body: PhysicsBody, _instanceIndex?: number): number { return bodyOps.getLinearDamping(this, body); }
194
+ setAngularDamping(body: PhysicsBody, damping: number, _instanceIndex?: number): void { bodyOps.setAngularDamping(this, body, damping); }
195
+ getAngularDamping(body: PhysicsBody, _instanceIndex?: number): number { return bodyOps.getAngularDamping(this, body); }
196
+
197
+ // --- Velocity ---
198
+
199
+ setLinearVelocity(body: PhysicsBody, linVel: Vector3, _instanceIndex?: number): void { bodyOps.setLinearVelocity(this, body, linVel); }
200
+ getLinearVelocityToRef(body: PhysicsBody, linVel: Vector3, _instanceIndex?: number): void { bodyOps.getLinearVelocityToRef(this, body, linVel); }
201
+ setAngularVelocity(body: PhysicsBody, angVel: Vector3, _instanceIndex?: number): void { bodyOps.setAngularVelocity(this, body, angVel); }
202
+ getAngularVelocityToRef(body: PhysicsBody, angVel: Vector3, _instanceIndex?: number): void { bodyOps.getAngularVelocityToRef(this, body, angVel); }
203
+
204
+ // --- Forces & impulses ---
205
+
206
+ applyImpulse(body: PhysicsBody, impulse: Vector3, location: Vector3, _instanceIndex?: number): void { bodyOps.applyImpulse(this, body, impulse, location); }
207
+ applyAngularImpulse(body: PhysicsBody, angularImpulse: Vector3, _instanceIndex?: number): void { bodyOps.applyAngularImpulse(this, body, angularImpulse); }
208
+ applyForce(body: PhysicsBody, force: Vector3, location: Vector3, _instanceIndex?: number): void { bodyOps.applyForce(this, body, force, location); }
209
+
210
+ // --- Gravity factor ---
211
+
212
+ setGravityFactor(body: PhysicsBody, factor: number, _instanceIndex?: number): void { bodyOps.setGravityFactor(this, body, factor); }
213
+ getGravityFactor(body: PhysicsBody, _instanceIndex?: number): number { return bodyOps.getGravityFactor(this, body); }
214
+
215
+ // --- Target transform (kinematic) ---
216
+
217
+ setTargetTransform(body: PhysicsBody, position: Vector3, rotation: Quaternion, _instanceIndex?: number): void {
218
+ bodyOps.setTargetTransform(this, body, position, rotation);
219
+ }
220
+
221
+ // --- Body geometry ---
222
+
223
+ getBodyGeometry(_body: PhysicsBody): {} { return {}; }
224
+
225
+ // --- Constraints ---
226
+
227
+ initConstraint(constraint: PhysicsConstraint, body: PhysicsBody, childBody: PhysicsBody): void {
228
+ constraintOps.initConstraint(this, constraint, body, childBody);
229
+ }
230
+
231
+ addConstraint(body: PhysicsBody, childBody: PhysicsBody, constraint: PhysicsConstraint, _instanceIndex?: number, _childInstanceIndex?: number): void {
232
+ if (!this.constraintToJoint.has(constraint)) {
233
+ this.initConstraint(constraint, body, childBody);
234
+ }
235
+ }
236
+
237
+ disposeConstraint(constraint: PhysicsConstraint): void { constraintOps.disposeConstraint(this, constraint); }
238
+ setEnabled(constraint: PhysicsConstraint, isEnabled: boolean): void { constraintOps.setEnabled(this, constraint, isEnabled); }
239
+ getEnabled(constraint: PhysicsConstraint): boolean { return constraintOps.getEnabled(this, constraint); }
240
+ setCollisionsEnabled(constraint: PhysicsConstraint, isEnabled: boolean): void { constraintOps.setCollisionsEnabled(this, constraint, isEnabled); }
241
+ getCollisionsEnabled(constraint: PhysicsConstraint): boolean { return constraintOps.getCollisionsEnabled(this, constraint); }
242
+
243
+ setAxisFriction(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, friction: number): void { constraintOps.setAxisFriction(this, constraint, axis, friction); }
244
+ getAxisFriction(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> { return constraintOps.getAxisFriction(this, constraint, axis); }
245
+ setAxisMode(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, limitMode: PhysicsConstraintAxisLimitMode): void { constraintOps.setAxisMode(this, constraint, axis, limitMode); }
246
+ getAxisMode(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<PhysicsConstraintAxisLimitMode> { return constraintOps.getAxisMode(this, constraint, axis); }
247
+ setAxisMinLimit(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, minLimit: number): void { constraintOps.setAxisMinLimit(this, constraint, axis, minLimit); }
248
+ getAxisMinLimit(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> { return constraintOps.getAxisMinLimit(this, constraint, axis); }
249
+ setAxisMaxLimit(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, limit: number): void { constraintOps.setAxisMaxLimit(this, constraint, axis, limit); }
250
+ getAxisMaxLimit(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> { return constraintOps.getAxisMaxLimit(this, constraint, axis); }
251
+ setAxisMotorType(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, motorType: PhysicsConstraintMotorType): void { constraintOps.setAxisMotorType(this, constraint, axis, motorType); }
252
+ getAxisMotorType(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<PhysicsConstraintMotorType> { return constraintOps.getAxisMotorType(this, constraint, axis); }
253
+ setAxisMotorTarget(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, target: number): void { constraintOps.setAxisMotorTarget(this, constraint, axis, target); }
254
+ getAxisMotorTarget(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> { return constraintOps.getAxisMotorTarget(this, constraint, axis); }
255
+ setAxisMotorMaxForce(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis, maxForce: number): void { constraintOps.setAxisMotorMaxForce(this, constraint, axis, maxForce); }
256
+ getAxisMotorMaxForce(constraint: PhysicsConstraint, axis: PhysicsConstraintAxis): Nullable<number> { return constraintOps.getAxisMotorMaxForce(this, constraint, axis); }
257
+
258
+ getBodiesUsingConstraint(constraint: PhysicsConstraint): ConstrainedBodyPair[] { return constraintOps.getBodiesUsingConstraint(this, constraint); }
259
+
260
+ // --- Collision observables ---
261
+
262
+ getCollisionObservable(body: PhysicsBody, _instanceIndex?: number): Observable<IPhysicsCollisionEvent> {
263
+ let obs = this.bodyCollisionObservables.get(body);
264
+ if (!obs) {
265
+ obs = new Observable<IPhysicsCollisionEvent>();
266
+ this.bodyCollisionObservables.set(body, obs);
267
+ }
268
+ return obs;
269
+ }
270
+
271
+ getCollisionEndedObservable(body: PhysicsBody, _instanceIndex?: number): Observable<IBasePhysicsCollisionEvent> {
272
+ let obs = this.bodyCollisionEndedObservables.get(body);
273
+ if (!obs) {
274
+ obs = new Observable<IBasePhysicsCollisionEvent>();
275
+ this.bodyCollisionEndedObservables.set(body, obs);
276
+ }
277
+ return obs;
278
+ }
279
+
280
+ // --- Raycast ---
281
+
282
+ raycast(from: Vector3, to: Vector3, result: PhysicsRaycastResult, _query?: IRaycastQuery): void {
283
+ const dir = to.subtract(from);
284
+ const maxToi = dir.length();
285
+ const normalizedDir = dir.normalize();
286
+
287
+ const ray = new this.rapier.Ray(
288
+ new this.rapier.Vector3(from.x, from.y, from.z),
289
+ new this.rapier.Vector3(normalizedDir.x, normalizedDir.y, normalizedDir.z)
290
+ );
291
+
292
+ const hit = this.world.castRayAndGetNormal(ray, maxToi, true);
293
+ if (hit) {
294
+ const hitPoint = ray.pointAt(hit.timeOfImpact);
295
+ const hitNormal = hit.normal;
296
+ result.setHitData(
297
+ new Vector3(hitNormal.x, hitNormal.y, hitNormal.z),
298
+ new Vector3(hitPoint.x, hitPoint.y, hitPoint.z)
299
+ );
300
+ result.calculateHitDistance();
301
+ }
302
+ }
303
+
304
+ // --- Dispose ---
305
+
306
+ dispose(): void {
307
+ this.eventQueue.free();
308
+ this.world.free();
309
+ this.bodyToRigidBody.clear();
310
+ this.bodyToColliders.clear();
311
+ this.shapeToColliderDesc.clear();
312
+ this.shapeTypeMap.clear();
313
+ this.constraintToJoint.clear();
314
+ this.constraintBodies.clear();
315
+ this.constraintAxisState.clear();
316
+ this.constraintEnabled.clear();
317
+ this.constraintDescriptors.clear();
318
+ this.collisionCallbackEnabled.clear();
319
+ this.collisionEndedCallbackEnabled.clear();
320
+ this.triggerShapes.clear();
321
+ this.bodyIdToPhysicsBody.clear();
322
+ this.bodyToShape.clear();
323
+ this.shapeToBody.clear();
324
+ this.compoundChildren.clear();
325
+ this.bodyEventMask.clear();
326
+ this.colliderHandleToBody.clear();
327
+ this.shapeFilterMembership.clear();
328
+ this.shapeFilterCollide.clear();
329
+ }
330
+
331
+ // --- Rapier-specific helpers for sync module ---
332
+
333
+ getRigidBody(body: PhysicsBody): RAPIER.RigidBody | undefined {
334
+ return this.bodyToRigidBody.get(body);
335
+ }
336
+
337
+ setBodyTranslation(body: PhysicsBody, position: Vector3): void {
338
+ const rb = this.bodyToRigidBody.get(body);
339
+ if (rb) {
340
+ rb.setTranslation(new this.rapier.Vector3(position.x, position.y, position.z), true);
341
+ }
342
+ }
343
+
344
+ setBodyRotation(body: PhysicsBody, rotation: Quaternion): void {
345
+ const rb = this.bodyToRigidBody.get(body);
346
+ if (rb) {
347
+ rb.setRotation(new this.rapier.Quaternion(rotation.x, rotation.y, rotation.z, rotation.w), true);
348
+ }
349
+ }
350
+
351
+ // --- Server collision event injection ---
352
+
353
+ registerBodyId(bodyId: string, body: PhysicsBody): void {
354
+ this.bodyIdToPhysicsBody.set(bodyId, body);
355
+ }
356
+
357
+ unregisterBodyId(bodyId: string): void {
358
+ this.bodyIdToPhysicsBody.delete(bodyId);
359
+ }
360
+
361
+ injectCollisionEvents(events: CollisionEventData[]): void {
362
+ injectCollisionEvents(this, events);
363
+ }
364
+ }