@gg-web-engine/rapier3d 0.0.69 → 0.0.71

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.
@@ -50,7 +50,6 @@ export declare class Rapier3dCharacterControllerComponent implements ICharacterC
50
50
  * Rapier's character controller supports excluding specific colliders from a single query
51
51
  * natively, so no such trick is needed here. */
52
52
  readonly ignoredBodies: Set<Rapier3dRigidBodyComponent>;
53
- private static warnedMissingDtForPush;
54
53
  protected _nativeBody: RigidBody | null;
55
54
  protected _nativeCollider: Collider | null;
56
55
  protected _nativeController: KinematicCharacterController | null;
@@ -1,4 +1,4 @@
1
- import { BitMask, DebugBody3DSettings, Pnt3, Qtrn, } from '@gg-web-engine/core';
1
+ import { BitMask, DebugBody3DSettings, Pnt3, Qtrn, warnOnce, } from '@gg-web-engine/core';
2
2
  import { Quaternion, RigidBodyDesc, Vector3, } from '@dimforge/rapier3d-compat';
3
3
  /**
4
4
  * A capsule-shaped kinematic character controller backed by Rapier's own `KinematicCharacterController`
@@ -253,13 +253,10 @@ export class Rapier3dCharacterControllerComponent {
253
253
  // `dt` - silently wrong, not just imprecise - so skip the push for this tick instead when `dt`
254
254
  // isn't available, same as `AmmoCharacterControllerComponent.pushDynamicBody`.
255
255
  if (!dt || dt <= 1e-9) {
256
- if (!Rapier3dCharacterControllerComponent.warnedMissingDtForPush) {
257
- Rapier3dCharacterControllerComponent.warnedMissingDtForPush = true;
258
- console.warn('[Rapier3dCharacterControllerComponent] move() was called without `dt` while `pushMass` > ' +
259
- '0 - skipping this dynamic-body push rather than approximating character speed from raw ' +
260
- 'per-tick displacement (which would understate push force by roughly 1/dt). Pass the ' +
261
- 'real tick delta (seconds) as the third argument to move() to enable pushing dynamic bodies.');
262
- }
256
+ warnOnce('[Rapier3dCharacterControllerComponent] move() was called without `dt` while `pushMass` > ' +
257
+ '0 - skipping this dynamic-body push rather than approximating character speed from raw ' +
258
+ 'per-tick displacement (which would understate push force by roughly 1/dt). Pass the ' +
259
+ 'real tick delta (seconds) as the third argument to move() to enable pushing dynamic bodies.');
263
260
  return;
264
261
  }
265
262
  const direction = Pnt3.scalarMult(horizontal, 1 / horizLen);
@@ -423,7 +420,3 @@ export class Rapier3dCharacterControllerComponent {
423
420
  }
424
421
  }
425
422
  }
426
- // Module-wide (not per-instance) so a scene with several characters all being driven without
427
- // `dt` still only logs once, not once per character per tick - see `pushDynamicBodies`'s
428
- // missing-`dt` handling below.
429
- Rapier3dCharacterControllerComponent.warnedMissingDtForPush = false;
@@ -8,7 +8,7 @@ export declare class Rapier3dRigidBodyComponent implements IRigidBody3dComponent
8
8
  protected _colliderDescr: ColliderDesc[];
9
9
  readonly shape: Shape3DDescriptor;
10
10
  protected _bodyDescr: RigidBodyDesc;
11
- protected _colliderOptions: Omit<Omit<Body3DOptions, 'dynamic'>, 'mass'>;
11
+ protected _colliderOptions: Omit<Omit<Body3DOptions, 'bodyType'>, 'mass'>;
12
12
  entity: Entity3d | null;
13
13
  get position(): Point3;
14
14
  set position(value: Point3);
@@ -55,9 +55,9 @@ export declare class Rapier3dRigidBodyComponent implements IRigidBody3dComponent
55
55
  ColliderDesc[],
56
56
  Shape3DDescriptor,
57
57
  RigidBodyDesc,
58
- Omit<Omit<Body3DOptions, 'dynamic'>, 'mass'>
58
+ Omit<Omit<Body3DOptions, 'bodyType'>, 'mass'>
59
59
  ];
60
- constructor(world: Rapier3dWorldComponent, _colliderDescr: ColliderDesc[], shape: Shape3DDescriptor, _bodyDescr: RigidBodyDesc, _colliderOptions: Omit<Omit<Body3DOptions, 'dynamic'>, 'mass'>);
60
+ constructor(world: Rapier3dWorldComponent, _colliderDescr: ColliderDesc[], shape: Shape3DDescriptor, _bodyDescr: RigidBodyDesc, _colliderOptions: Omit<Omit<Body3DOptions, 'bodyType'>, 'mass'>);
61
61
  protected collisionGroups: InteractionGroups;
62
62
  get interactWithCollisionGroups(): ReadonlyArray<CollisionGroup>;
63
63
  set interactWithCollisionGroups(value: ReadonlyArray<CollisionGroup> | 'all');
@@ -1,5 +1,5 @@
1
1
  import { BitMask, DebugBody3DSettings, Pnt3, Qtrn, } from '@gg-web-engine/core';
2
- import { ColliderDesc, Quaternion, RigidBodyDesc, Vector3, } from '@dimforge/rapier3d-compat';
2
+ import { ColliderDesc, Quaternion, RigidBodyDesc, RigidBodyType, Vector3, } from '@dimforge/rapier3d-compat';
3
3
  import { Subject } from 'rxjs';
4
4
  export class Rapier3dRigidBodyComponent {
5
5
  get position() {
@@ -7,7 +7,18 @@ export class Rapier3dRigidBodyComponent {
7
7
  }
8
8
  set position(value) {
9
9
  if (this.nativeBody) {
10
- this.nativeBody.setTranslation(new Vector3(value.x, value.y, value.z), true);
10
+ // A `kinematicPositionBased` body must move through `setNextKinematicTranslation` rather
11
+ // than the immediate teleport `setTranslation` does - only that path lets Rapier derive the
12
+ // body's effective velocity for this step and correctly push/wake dynamic bodies in its way
13
+ // (see `BodyOptions.kinematic_pos`'s own doc, and the "plain teleport" pitfall it links to).
14
+ // `kinematicVelocityBased` and `dynamic` bodies keep the immediate teleport - the former is
15
+ // already driven by `linearVelocity` each step, not by `position` writes.
16
+ if (this.nativeBody.bodyType() === RigidBodyType.KinematicPositionBased) {
17
+ this.nativeBody.setNextKinematicTranslation(new Vector3(value.x, value.y, value.z));
18
+ }
19
+ else {
20
+ this.nativeBody.setTranslation(new Vector3(value.x, value.y, value.z), true);
21
+ }
11
22
  }
12
23
  else {
13
24
  this._bodyDescr.setTranslation(value.x, value.y, value.z);
@@ -18,7 +29,14 @@ export class Rapier3dRigidBodyComponent {
18
29
  }
19
30
  set rotation(value) {
20
31
  if (this.nativeBody) {
21
- this.nativeBody.setRotation(new Quaternion(value.x, value.y, value.z, value.w), true);
32
+ // see `position`'s setter above for why a kinematic-position-based body needs the "next
33
+ // kinematic" API instead of an immediate teleport.
34
+ if (this.nativeBody.bodyType() === RigidBodyType.KinematicPositionBased) {
35
+ this.nativeBody.setNextKinematicRotation(new Quaternion(value.x, value.y, value.z, value.w));
36
+ }
37
+ else {
38
+ this.nativeBody.setRotation(new Quaternion(value.x, value.y, value.z, value.w), true);
39
+ }
22
40
  }
23
41
  else {
24
42
  this._bodyDescr.setRotation(new Quaternion(value.x, value.y, value.z, value.w));
@@ -93,6 +111,10 @@ export class Rapier3dRigidBodyComponent {
93
111
  bd.mass = this._bodyDescr.mass;
94
112
  bd.setTranslation(this._bodyDescr.translation.x, this._bodyDescr.translation.y, this._bodyDescr.translation.z);
95
113
  bd.setRotation(Object.assign({}, this._bodyDescr.rotation));
114
+ // `ccdEnabled` is a plain field on `RigidBodyDesc` (not copied by the constructor above), not
115
+ // just a constructor-only `setCcdEnabled` call - carry it over explicitly so `clone()` doesn't
116
+ // silently drop CCD off the copy.
117
+ bd.setCcdEnabled(this._bodyDescr.ccdEnabled);
96
118
  // TODO more fields here?
97
119
  return [colliderDescr, this.shape, bd, this._colliderOptions];
98
120
  }
@@ -103,9 +125,11 @@ export class Rapier3dRigidBodyComponent {
103
125
  this._bodyDescr = _bodyDescr;
104
126
  this._colliderOptions = _colliderOptions;
105
127
  this.entity = null;
106
- this.debugBodySettings = new DebugBody3DSettings(this._bodyDescr.mass > 0
107
- ? { type: 'RIGID_DYNAMIC', sleeping: () => { var _a; return !!((_a = this._nativeBody) === null || _a === void 0 ? void 0 : _a.isSleeping()); } }
108
- : { type: 'RIGID_STATIC' }, this.shape);
128
+ this.debugBodySettings = new DebugBody3DSettings(this._bodyDescr.status == RigidBodyType.Fixed
129
+ ? { type: 'RIGID_STATIC' }
130
+ : this._bodyDescr.status == RigidBodyType.Dynamic
131
+ ? { type: 'RIGID_DYNAMIC', sleeping: () => { var _a; return !!((_a = this._nativeBody) === null || _a === void 0 ? void 0 : _a.isSleeping()); } }
132
+ : { type: 'RIGID_KINEMATIC' }, this.shape);
109
133
  this._nativeBody = null;
110
134
  this._nativeBodyColliders = null;
111
135
  this.name = '';
@@ -1,4 +1,4 @@
1
- import { Pnt3, Qtrn, } from '@gg-web-engine/core';
1
+ import { getCylinderRadii, Pnt3, Qtrn, } from '@gg-web-engine/core';
2
2
  import { ActiveEvents, ColliderDesc, Quaternion, RigidBodyDesc } from '@dimforge/rapier3d-compat';
3
3
  import { Rapier3dRigidBodyComponent } from './components/rapier-3d-rigid-body.component';
4
4
  import { Rapier3dTriggerComponent } from './components/rapier-3d-trigger.component';
@@ -29,7 +29,7 @@ export class Rapier3dFactory {
29
29
  // ordinary rigid-body-vs-rigid-body contact needs it here too, since neither side had it before).
30
30
  const colliderDescr = this.createColliderDescr(descriptor.shape);
31
31
  colliderDescr.forEach(c => c.setActiveEvents(ActiveEvents.COLLISION_EVENTS));
32
- return new Rapier3dRigidBodyComponent(this.world, colliderDescr, descriptor.shape, this.createRigidBodyDescr(descriptor.body, transform), Object.assign({ friction: 0.5, restitution: 0.1, ownCollisionGroups: [this.world.mainCollisionGroup], interactWithCollisionGroups: [this.world.mainCollisionGroup] }, descriptor.body));
32
+ return new Rapier3dRigidBodyComponent(this.world, colliderDescr, descriptor.shape, this.createRigidBodyDescr(descriptor.body, transform), Object.assign({ friction: 0.5, restitution: 0.1, ownCollisionGroups: [this.world.mainCollisionGroup], interactWithCollisionGroups: [this.world.mainCollisionGroup], ccd: false }, descriptor.body));
33
33
  }
34
34
  createTrigger(descriptor, transform) {
35
35
  const colliderDescr = this.createColliderDescr(descriptor);
@@ -37,7 +37,7 @@ export class Rapier3dFactory {
37
37
  c.isSensor = true;
38
38
  c.setActiveEvents(ActiveEvents.COLLISION_EVENTS);
39
39
  });
40
- return new Rapier3dTriggerComponent(this.world, colliderDescr, descriptor, this.createRigidBodyDescr({ dynamic: false }, transform));
40
+ return new Rapier3dTriggerComponent(this.world, colliderDescr, descriptor, this.createRigidBodyDescr({ bodyType: 'static' }, transform));
41
41
  }
42
42
  createRaycastVehicle(chassis) {
43
43
  throw new Error('Raycast vehicle bindings for rapier3D are not implemented');
@@ -66,10 +66,27 @@ export class Rapier3dFactory {
66
66
  const capsule = ColliderDesc.capsule(descriptor.centersDistance / 2, descriptor.radius);
67
67
  capsule.setRotation(new Quaternion(yToZUp.x, yToZUp.y, yToZUp.z, yToZUp.w));
68
68
  return [capsule];
69
- case 'CYLINDER':
70
- const cylinder = ColliderDesc.cylinder(descriptor.height / 2, descriptor.radius);
71
- cylinder.setRotation(new Quaternion(yToZUp.x, yToZUp.y, yToZUp.z, yToZUp.w));
72
- return [cylinder];
69
+ case 'CYLINDER': {
70
+ const { radiusX, radiusY } = getCylinderRadii(descriptor);
71
+ if (radiusX === radiusY) {
72
+ const cylinder = ColliderDesc.cylinder(descriptor.height / 2, radiusX);
73
+ cylinder.setRotation(new Quaternion(yToZUp.x, yToZUp.y, yToZUp.z, yToZUp.w));
74
+ return [cylinder];
75
+ }
76
+ // Rapier/parry has no native elliptical-radius primitive (unlike Bullet's
77
+ // btCylinderShapeZ, which takes independent X/Y half-extents) - approximate with a
78
+ // convex hull over the two elliptical end-cap rings instead.
79
+ const ellipseSegments = 32;
80
+ const halfHeight = descriptor.height / 2;
81
+ const vertices = [];
82
+ for (let i = 0; i < ellipseSegments; i++) {
83
+ const angle = (i / ellipseSegments) * Math.PI * 2;
84
+ const x = radiusX * Math.cos(angle);
85
+ const y = radiusY * Math.sin(angle);
86
+ vertices.push(x, y, -halfHeight, x, y, halfHeight);
87
+ }
88
+ return [ColliderDesc.convexHull(new Float32Array(vertices))];
89
+ }
73
90
  case 'CONE':
74
91
  const cone = ColliderDesc.cone(descriptor.height / 2, descriptor.radius);
75
92
  cone.setRotation(new Quaternion(yToZUp.x, yToZUp.y, yToZUp.z, yToZUp.w));
@@ -113,14 +130,24 @@ export class Rapier3dFactory {
113
130
  createRigidBodyDescr(options, transform) {
114
131
  const pos = (transform === null || transform === void 0 ? void 0 : transform.position) || Pnt3.O;
115
132
  const rot = (transform === null || transform === void 0 ? void 0 : transform.rotation) || Qtrn.O;
116
- const fixed = options.dynamic === false || !options.mass;
117
133
  let bodyDesc;
118
- if (fixed) {
134
+ let bodyType = options.bodyType || (options.mass ? 'dynamic' : 'static');
135
+ if (bodyType === 'static') {
119
136
  bodyDesc = RigidBodyDesc.fixed();
120
137
  }
138
+ else if (bodyType === 'kinematic_pos') {
139
+ bodyDesc = RigidBodyDesc.kinematicPositionBased();
140
+ }
141
+ else if (bodyType === 'kinematic_vel') {
142
+ bodyDesc = RigidBodyDesc.kinematicVelocityBased();
143
+ }
121
144
  else {
122
145
  bodyDesc = RigidBodyDesc.dynamic();
123
146
  bodyDesc.mass = options.mass || 1;
147
+ // Only a dynamic body can tunnel through geometry it crosses within a single step - a fixed
148
+ // or kinematic body is never the one doing the moving-too-fast-to-detect part of that, so CCD
149
+ // is meaningless for either (see `BodyOptions.ccd`'s own doc).
150
+ bodyDesc.setCcdEnabled(!!options.ccd);
124
151
  }
125
152
  return bodyDesc.setTranslation(pos.x, pos.y, pos.z).setRotation(new Quaternion(rot.x, rot.y, rot.z, rot.w));
126
153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gg-web-engine/rapier3d",
3
- "version": "0.0.69",
3
+ "version": "0.0.71",
4
4
  "description": "An attempt to create open source game engine for browser",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,7 +28,7 @@
28
28
  "homepage": "https://github.com/AndyGura/gg-web-engine#readme",
29
29
  "devDependencies": {
30
30
  "@dimforge/rapier3d-compat": "0.20.0",
31
- "@gg-web-engine/core": "0.0.69",
31
+ "@gg-web-engine/core": "0.0.71",
32
32
  "@types/jest": "^30.0.0",
33
33
  "@types/node": "^26.4.0",
34
34
  "jest": "^30.5.0",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@dimforge/rapier3d-compat": "0.20.0",
43
- "@gg-web-engine/core": "0.0.69",
43
+ "@gg-web-engine/core": "0.0.71",
44
44
  "rxjs": "7.8.2"
45
45
  },
46
46
  "jest": {
@@ -29,7 +29,7 @@ describe('Rapier3dCharacterControllerComponent', () => {
29
29
  const floor = factory.createRigidBody(
30
30
  {
31
31
  shape: { shape: 'BOX', dimensions: { x: sizeX, y: 20, z: thickness } },
32
- body: { dynamic: false, mass: 0 },
32
+ body: { bodyType: 'static', mass: 0 },
33
33
  },
34
34
  { position: { x: centerX, y: 0, z: topZ - thickness / 2 } },
35
35
  );
@@ -42,7 +42,7 @@ describe('Rapier3dCharacterControllerComponent', () => {
42
42
  const wall = factory.createRigidBody(
43
43
  {
44
44
  shape: { shape: 'BOX', dimensions: { x: thickness, y: 20, z: 5 } },
45
- body: { dynamic: false, mass: 0 },
45
+ body: { bodyType: 'static', mass: 0 },
46
46
  },
47
47
  { position: { x: x + thickness / 2, y: 0, z: 2.5 } },
48
48
  );
@@ -98,7 +98,7 @@ describe('Rapier3dCharacterControllerComponent', () => {
98
98
  // continued contact with the steep surface.
99
99
  addFloor(0, 20, 0);
100
100
  const sphere = factory.createRigidBody(
101
- { shape: { shape: 'SPHERE', radius: 1 }, body: { dynamic: false, mass: 0 } },
101
+ { shape: { shape: 'SPHERE', radius: 1 }, body: { bodyType: 'static', mass: 0 } },
102
102
  { position: { x: 0, y: 0, z: 1 } },
103
103
  );
104
104
  sphere.addToWorld({ physicsWorld: world } as any);
@@ -36,13 +36,13 @@ import { Rapier3dWorldComponent } from '../../src';
36
36
  describe('ObjectGrabController + Rapier3dWorldComponent - end-to-end pick-up range', () => {
37
37
  const setupPedestalAndRadio = (world: Rapier3dWorldComponent, radioPosition: Point3) => {
38
38
  const pedestal = world.factory.createRigidBody(
39
- { shape: { shape: 'CYLINDER', radius: 0.5, height: 0.9 }, body: { dynamic: false, mass: 0 } },
39
+ { shape: { shape: 'CYLINDER', radius: 0.5, height: 0.9 }, body: { bodyType: 'static', mass: 0 } },
40
40
  { position: { x: 0, y: 0, z: 0.45 } },
41
41
  );
42
42
  pedestal.addToWorld({ physicsWorld: world } as any);
43
43
 
44
44
  const propBody = world.factory.createRigidBody(
45
- { shape: { shape: 'BOX', dimensions: { x: 0.41, y: 0.23, z: 0.49 } }, body: { dynamic: true, mass: 3 } },
45
+ { shape: { shape: 'BOX', dimensions: { x: 0.41, y: 0.23, z: 0.49 } }, body: { bodyType: 'dynamic', mass: 3 } },
46
46
  { position: radioPosition },
47
47
  );
48
48
  propBody.addToWorld({ physicsWorld: world } as any);
@@ -21,7 +21,7 @@ const setup = async () => {
21
21
  await world.init();
22
22
  world.gravity = { x: 0, y: 0, z: 0 };
23
23
  const floor = world.factory.createRigidBody(
24
- { shape: { shape: 'BOX', dimensions: { x: 100, y: 100, z: 1 } }, body: { dynamic: false, mass: 0 } },
24
+ { shape: { shape: 'BOX', dimensions: { x: 100, y: 100, z: 1 } }, body: { bodyType: 'static', mass: 0 } },
25
25
  { position: { x: 0, y: 0, z: -0.5 } },
26
26
  );
27
27
  floor.addToWorld({ physicsWorld: world } as any);
@@ -149,12 +149,12 @@ describe('CharacterController3dEntity + Rapier3dCharacterControllerComponent - s
149
149
  await world.init();
150
150
  world.gravity = { x: 0, y: 0, z: -9.82 };
151
151
  const floor = world.factory.createRigidBody(
152
- { shape: { shape: 'BOX', dimensions: { x: 100, y: 100, z: 1 } }, body: { dynamic: false, mass: 0 } },
152
+ { shape: { shape: 'BOX', dimensions: { x: 100, y: 100, z: 1 } }, body: { bodyType: 'static', mass: 0 } },
153
153
  { position: { x: 0, y: 0, z: -0.5 } },
154
154
  );
155
155
  floor.addToWorld({ physicsWorld: world } as any);
156
156
  const sphere = world.factory.createRigidBody(
157
- { shape: { shape: 'SPHERE', radius: 1 }, body: { dynamic: false, mass: 0 } },
157
+ { shape: { shape: 'SPHERE', radius: 1 }, body: { bodyType: 'static', mass: 0 } },
158
158
  { position: { x: 0, y: 0, z: 1 } },
159
159
  );
160
160
  sphere.addToWorld({ physicsWorld: world } as any);
@@ -32,7 +32,7 @@ describe('Rapier3dRigidBodyComponent collision events', () => {
32
32
 
33
33
  const createFloor = () => {
34
34
  const floor = factory.createRigidBody(
35
- { shape: { shape: 'BOX', dimensions: { x: 20, y: 20, z: 1 } }, body: { dynamic: false } },
35
+ { shape: { shape: 'BOX', dimensions: { x: 20, y: 20, z: 1 } }, body: { bodyType: 'static' } },
36
36
  { position: { x: 0, y: 0, z: -0.5 } },
37
37
  );
38
38
  floor.addToWorld({ physicsWorld: world } as any);
@@ -43,7 +43,7 @@ describe('Rapier3dRigidBodyComponent collision events', () => {
43
43
  // restitution 0 so the ball settles without bouncing, keeping the "no further onCollisionStart
44
44
  // once resting" assertion below unambiguous.
45
45
  const ball = factory.createRigidBody(
46
- { shape: { shape: 'SPHERE', radius: 1 }, body: { dynamic: true, mass: 1, restitution: 0 } },
46
+ { shape: { shape: 'SPHERE', radius: 1 }, body: { bodyType: 'dynamic', mass: 1, restitution: 0 } },
47
47
  { position: { x: 0, y: 0, z } },
48
48
  );
49
49
  ball.addToWorld({ physicsWorld: world } as any);
@@ -41,7 +41,7 @@ describe(`Rapier3dTriggerComponent`, () => {
41
41
  const ball = factory.createRigidBody(
42
42
  {
43
43
  shape: { shape: 'SPHERE', radius: 1 },
44
- body: { dynamic: true, mass: 1 },
44
+ body: { bodyType: 'dynamic', mass: 1 },
45
45
  },
46
46
  { position: { x: 0, y: 0, z: 12 } },
47
47
  );
@@ -70,7 +70,7 @@ describe(`Rapier3dTriggerComponent`, () => {
70
70
  const ball = factory.createRigidBody(
71
71
  {
72
72
  shape: { shape: 'SPHERE', radius: 1 },
73
- body: { dynamic: true, mass: 1 },
73
+ body: { bodyType: 'dynamic', mass: 1 },
74
74
  },
75
75
  { position: { x: 0, y: 0, z: 12 } },
76
76
  );
@@ -92,7 +92,7 @@ describe(`Rapier3dTriggerComponent`, () => {
92
92
  const ball = factory.createRigidBody(
93
93
  {
94
94
  shape: { shape: 'SPHERE', radius: 1 },
95
- body: { dynamic: true, mass: 1 },
95
+ body: { bodyType: 'dynamic', mass: 1 },
96
96
  },
97
97
  { position: { x: 0, y: 0, z: 0 } },
98
98
  );
@@ -112,7 +112,7 @@ describe(`Rapier3dTriggerComponent`, () => {
112
112
  const ball = factory.createRigidBody(
113
113
  {
114
114
  shape: { shape: 'SPHERE', radius: 1 },
115
- body: { dynamic: true, mass: 1 },
115
+ body: { bodyType: 'dynamic', mass: 1 },
116
116
  },
117
117
  { position: { x: 0, y: 0, z: 0 } },
118
118
  );
@@ -133,7 +133,7 @@ describe(`Rapier3dTriggerComponent`, () => {
133
133
  const ball = factory.createRigidBody(
134
134
  {
135
135
  shape: { shape: 'SPHERE', radius: 1 },
136
- body: { dynamic: true, mass: 1 },
136
+ body: { bodyType: 'dynamic', mass: 1 },
137
137
  },
138
138
  { position: { x: 0, y: 0, z: 0 } },
139
139
  );
@@ -23,7 +23,7 @@ describe('Rapier3dWorldComponent', () => {
23
23
 
24
24
  const ball = world.factory.createRigidBody({
25
25
  shape: { shape: 'SPHERE', radius: 1 },
26
- body: { dynamic: true, mass: 5 },
26
+ body: { bodyType: 'dynamic', mass: 5 },
27
27
  }, { position: { x: -5, y: 0, z: 0 } });
28
28
  ball.addToWorld({ physicsWorld: world } as any);
29
29
  ball.linearVelocity = { x: 1, y: 0, z: 0 };
@@ -39,14 +39,14 @@ describe('Rapier3dWorldComponent', () => {
39
39
 
40
40
  const ball0 = world.factory.createRigidBody({
41
41
  shape: { shape: 'SPHERE', radius: 1 },
42
- body: { dynamic: true, mass: 5 },
42
+ body: { bodyType: 'dynamic', mass: 5 },
43
43
  }, { position: { x: -5, y: 0, z: 0 } });
44
44
  ball0.addToWorld({ physicsWorld: world } as any);
45
45
  ball0.linearVelocity = { x: 1, y: 0, z: 0 };
46
46
 
47
47
  const ball1 = world.factory.createRigidBody({
48
48
  shape: { shape: 'SPHERE', radius: 1 },
49
- body: { dynamic: true, mass: 5 },
49
+ body: { bodyType: 'dynamic', mass: 5 },
50
50
  }, { position: { x: 5, y: 0, z: 0 } });
51
51
  ball1.addToWorld({ physicsWorld: world } as any);
52
52
  ball1.linearVelocity = { x: -1, y: 0, z: 0 };
@@ -63,7 +63,7 @@ describe('Rapier3dWorldComponent', () => {
63
63
  const cg0 = world.registerCollisionGroup();
64
64
  const ball0 = world.factory.createRigidBody({
65
65
  shape: { shape: 'SPHERE', radius: 1 },
66
- body: { dynamic: true, mass: 5 },
66
+ body: { bodyType: 'dynamic', mass: 5 },
67
67
  }, { position: { x: -5, y: 0, z: 0 } });
68
68
  ball0.addToWorld({ physicsWorld: world } as any);
69
69
  ball0.linearVelocity = { x: 1, y: 0, z: 0 };
@@ -72,7 +72,7 @@ describe('Rapier3dWorldComponent', () => {
72
72
  const cg1 = world.registerCollisionGroup();
73
73
  const ball1 = world.factory.createRigidBody({
74
74
  shape: { shape: 'SPHERE', radius: 1 },
75
- body: { dynamic: true, mass: 5 },
75
+ body: { bodyType: 'dynamic', mass: 5 },
76
76
  }, { position: { x: 5, y: 0, z: 0 } });
77
77
  ball1.addToWorld({ physicsWorld: world } as any);
78
78
  ball1.linearVelocity = { x: -1, y: 0, z: 0 };
@@ -94,7 +94,7 @@ describe('Rapier3dWorldComponent', () => {
94
94
  'transform/velocity write passing wakeUp=false was silently never simulated)',
95
95
  () => {
96
96
  const ball = world.factory.createRigidBody(
97
- { shape: { shape: 'SPHERE', radius: 1 }, body: { dynamic: true, mass: 5 } },
97
+ { shape: { shape: 'SPHERE', radius: 1 }, body: { bodyType: 'dynamic', mass: 5 } },
98
98
  { position: { x: 0, y: 0, z: 0 } },
99
99
  );
100
100
  ball.addToWorld({ physicsWorld: world } as any);
@@ -121,7 +121,7 @@ describe('Rapier3dWorldComponent', () => {
121
121
  // Create a box far away from the ray
122
122
  const box = world.factory.createRigidBody({
123
123
  shape: { shape: 'BOX', dimensions: { x: 1, y: 1, z: 1 } },
124
- body: { dynamic: false, mass: 0 },
124
+ body: { bodyType: 'static', mass: 0 },
125
125
  }, { position: { x: 10, y: 10, z: 10 } });
126
126
  box.addToWorld({ physicsWorld: world } as any);
127
127
 
@@ -144,7 +144,7 @@ describe('Rapier3dWorldComponent', () => {
144
144
  // Create a box in the path of the ray
145
145
  const box = world.factory.createRigidBody({
146
146
  shape: { shape: 'BOX', dimensions: { x: 2, y: 2, z: 2 } },
147
- body: { dynamic: false, mass: 0 },
147
+ body: { bodyType: 'static', mass: 0 },
148
148
  }, { position: { x: 0, y: 0, z: -5 } });
149
149
  box.addToWorld({ physicsWorld: world } as any);
150
150
 
@@ -179,7 +179,7 @@ describe('Rapier3dWorldComponent', () => {
179
179
  const box1 = world.factory.createRigidBody({
180
180
  shape: { shape: 'BOX', dimensions: { x: 2, y: 2, z: 2 } },
181
181
  body: {
182
- dynamic: false,
182
+ bodyType: 'static',
183
183
  mass: 0,
184
184
  ownCollisionGroups: [group1],
185
185
  interactWithCollisionGroups: [group1, group2],
@@ -217,7 +217,7 @@ describe('Rapier3dWorldComponent', () => {
217
217
  // Create a box at a known distance
218
218
  const box = world.factory.createRigidBody({
219
219
  shape: { shape: 'BOX', dimensions: { x: 2, y: 2, z: 2 } },
220
- body: { dynamic: false, mass: 0 },
220
+ body: { bodyType: 'static', mass: 0 },
221
221
  }, { position: { x: 0, y: 0, z: -5 } });
222
222
  box.addToWorld({ physicsWorld: world } as any);
223
223
 
@@ -246,7 +246,7 @@ describe('Rapier3dWorldComponent', () => {
246
246
  const box = world.factory.createRigidBody({
247
247
  shape: { shape: 'BOX', dimensions: { x: 2, y: 2, z: 2 } },
248
248
  body: {
249
- dynamic: false,
249
+ bodyType: 'static',
250
250
  mass: 0,
251
251
  ownCollisionGroups: [group1],
252
252
  interactWithCollisionGroups: [group1, group2],
@@ -272,13 +272,13 @@ describe('Rapier3dWorldComponent', () => {
272
272
  // Create two boxes at different positions
273
273
  const box1 = world.factory.createRigidBody({
274
274
  shape: { shape: 'BOX', dimensions: { x: 1, y: 1, z: 1 } },
275
- body: { dynamic: false, mass: 0 },
275
+ body: { bodyType: 'static', mass: 0 },
276
276
  }, { position: { x: 0, y: 0, z: -3 } });
277
277
  box1.addToWorld({ physicsWorld: world } as any);
278
278
 
279
279
  const box2 = world.factory.createRigidBody({
280
280
  shape: { shape: 'BOX', dimensions: { x: 1, y: 1, z: 1 } },
281
- body: { dynamic: false, mass: 0 },
281
+ body: { bodyType: 'static', mass: 0 },
282
282
  }, { position: { x: 0, y: 0, z: -7 } });
283
283
  box2.addToWorld({ physicsWorld: world } as any);
284
284
 
@@ -300,7 +300,7 @@ describe('Rapier3dWorldComponent', () => {
300
300
  // Create a box
301
301
  const box = world.factory.createRigidBody({
302
302
  shape: { shape: 'BOX', dimensions: { x: 4, y: 4, z: 4 } },
303
- body: { dynamic: false, mass: 0 },
303
+ body: { bodyType: 'static', mass: 0 },
304
304
  }, { position: { x: 0, y: 0, z: 0 } });
305
305
  box.addToWorld({ physicsWorld: world } as any);
306
306
 
@@ -19,6 +19,26 @@ describe('Rapier3dFactory', () => {
19
19
  world.dispose();
20
20
  });
21
21
 
22
+ describe('createColliderDescr', () => {
23
+ it('creates a circular cylinder collider that can be added to the world', () => {
24
+ const body = factory.createRigidBody(
25
+ { shape: { shape: 'CYLINDER', radius: 0.5, height: 1 }, body: { bodyType: 'static', mass: 0 } },
26
+ { position: { x: 0, y: 0, z: 0 } },
27
+ );
28
+ body.addToWorld({ physicsWorld: world } as any);
29
+ world.simulate(16);
30
+ });
31
+
32
+ it('falls back to a convex-hull approximation for an elliptical cylinder', () => {
33
+ const body = factory.createRigidBody(
34
+ { shape: { shape: 'CYLINDER', radiusX: 0.5, radiusY: 1, height: 1 }, body: { bodyType: 'static', mass: 0 } },
35
+ { position: { x: 0, y: 0, z: 0 } },
36
+ );
37
+ body.addToWorld({ physicsWorld: world } as any);
38
+ world.simulate(16);
39
+ });
40
+ });
41
+
22
42
  describe('createCharacterController', () => {
23
43
  it('should produce a component usable without any extra setup', () => {
24
44
  const character = factory.createCharacterController(
@@ -38,7 +58,7 @@ describe('Rapier3dFactory', () => {
38
58
 
39
59
  // works: a downward move against a floor grounds it, fully synchronously
40
60
  const floor = factory.createRigidBody(
41
- { shape: { shape: 'BOX', dimensions: { x: 20, y: 20, z: 1 } }, body: { dynamic: false, mass: 0 } },
61
+ { shape: { shape: 'BOX', dimensions: { x: 20, y: 20, z: 1 } }, body: { bodyType: 'static', mass: 0 } },
42
62
  { position: { x: 0, y: 0, z: -0.5 } },
43
63
  );
44
64
  floor.addToWorld({ physicsWorld: world } as any);