diff --git a/CHANGELOG.md b/CHANGELOG.md index 413325bf..415f0723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased + +### Added + +- Add zero-allocation scalar component getters and setters to `RigidBody`, `Collider`, + and `KinematicCharacterController`. Methods like `translationX()`, `translationY()`, + `rotationAngle()` (2D), `rotationX/Y/Z/W()` (3D), `linvelX/Y(/Z)()` return individual + `number` values without creating intermediate JS objects or WASM heap allocations. + Scalar setters `setLinvelXY/XYZ()`, `addForceXY/XYZ()`, `applyImpulseXY/XYZ()` avoid + the `VectorOps.intoRaw()` allocation on the input path. These are performance alternatives + to the existing struct-returning methods, which remain unchanged. + ## 0.19.3 (05 Nov. 2025) - Significantly improve performances of `combineVoxelStates`. diff --git a/rapier-compat/tests/ScalarGetters2d.test.ts b/rapier-compat/tests/ScalarGetters2d.test.ts new file mode 100644 index 00000000..7f4110db --- /dev/null +++ b/rapier-compat/tests/ScalarGetters2d.test.ts @@ -0,0 +1,110 @@ +import { + init, + Vector2, + World, + RigidBodyDesc, + ColliderDesc, +} from "../builds/2d-deterministic/pkg"; + +describe("2d/ScalarGetters", () => { + let world: World; + + beforeAll(init); + + afterAll(async () => { + await Promise.resolve(); + }); + + beforeEach(() => { + world = new World(new Vector2(0, -9.81)); + }); + + afterEach(() => { + world.free(); + }); + + test("rbTranslationX/Y match translation()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setTranslation(3.0, 7.0); + const body = world.createRigidBody(bodyDesc); + expect(body.translationX()).toBeCloseTo(3.0); + expect(body.translationY()).toBeCloseTo(7.0); + const full = body.translation(); + expect(body.translationX()).toBeCloseTo(full.x); + expect(body.translationY()).toBeCloseTo(full.y); + }); + + test("rbRotationAngle matches rotation()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setRotation(1.5); + const body = world.createRigidBody(bodyDesc); + expect(body.rotationAngle()).toBeCloseTo(1.5); + expect(body.rotationAngle()).toBeCloseTo(body.rotation()); + }); + + test("linvelX/Y match linvel()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setLinvel(2.0, -3.0); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + expect(body.linvelX()).toBeCloseTo(2.0); + expect(body.linvelY()).toBeCloseTo(-3.0); + }); + + test("setLinvelXY sets velocity correctly", () => { + const bodyDesc = RigidBodyDesc.dynamic(); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + body.setLinvelXY(5.0, -2.0, true); + expect(body.linvelX()).toBeCloseTo(5.0); + expect(body.linvelY()).toBeCloseTo(-2.0); + }); + + test("addForceXY adds force correctly", () => { + const bodyDesc = RigidBodyDesc.dynamic(); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + body.addForceXY(10.0, 20.0, true); + const force = body.userForce(); + expect(force.x).toBeCloseTo(10.0); + expect(force.y).toBeCloseTo(20.0); + }); + + test("applyImpulseXY changes velocity", () => { + const bodyDesc = RigidBodyDesc.dynamic(); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + body.applyImpulseXY(1.0, 0.0, true); + expect(body.linvelX()).not.toBeCloseTo(0.0); + }); + + test("scalar getters work after simulation step", () => { + const bodyDesc = RigidBodyDesc.dynamic().setTranslation(0, 10); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + world.step(); + const full = body.translation(); + expect(body.translationX()).toBeCloseTo(full.x); + expect(body.translationY()).toBeCloseTo(full.y); + }); + + test("collider scalar translation getters match translation()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setTranslation(5.0, 3.0); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + const collider = world.createCollider(colliderDesc, body); + const full = collider.translation(); + expect(collider.translationX()).toBeCloseTo(full.x); + expect(collider.translationY()).toBeCloseTo(full.y); + }); + + test("collider scalar rotation getter matches rotation()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setRotation(0.7); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + const collider = world.createCollider(colliderDesc, body); + expect(collider.rotationAngle()).toBeCloseTo(collider.rotation()); + }); +}); diff --git a/rapier-compat/tests/ScalarGetters3d.test.ts b/rapier-compat/tests/ScalarGetters3d.test.ts new file mode 100644 index 00000000..f8b99c3d --- /dev/null +++ b/rapier-compat/tests/ScalarGetters3d.test.ts @@ -0,0 +1,129 @@ +import { + init, + Vector3, + Quaternion, + World, + RigidBodyDesc, + ColliderDesc, +} from "../builds/3d-deterministic/pkg"; + +describe("3d/ScalarGetters", () => { + let world: World; + + beforeAll(init); + + afterAll(async () => { + await Promise.resolve(); + }); + + beforeEach(() => { + world = new World(new Vector3(0, -9.81, 0)); + }); + + afterEach(() => { + world.free(); + }); + + test("rbTranslationX/Y/Z match translation()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setTranslation(3.0, 7.0, -1.0); + const body = world.createRigidBody(bodyDesc); + expect(body.translationX()).toBeCloseTo(3.0); + expect(body.translationY()).toBeCloseTo(7.0); + expect(body.translationZ()).toBeCloseTo(-1.0); + const full = body.translation(); + expect(body.translationX()).toBeCloseTo(full.x); + expect(body.translationY()).toBeCloseTo(full.y); + expect(body.translationZ()).toBeCloseTo(full.z); + }); + + test("rbRotationX/Y/Z/W match rotation()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setRotation( + new Quaternion(0.0, 0.3826834, 0.0, 0.9238795), + ); + const body = world.createRigidBody(bodyDesc); + const full = body.rotation(); + expect(body.rotationX()).toBeCloseTo(full.x); + expect(body.rotationY()).toBeCloseTo(full.y); + expect(body.rotationZ()).toBeCloseTo(full.z); + expect(body.rotationW()).toBeCloseTo(full.w); + }); + + test("linvelX/Y/Z match linvel()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setLinvel(2.0, -3.0, 1.0); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + expect(body.linvelX()).toBeCloseTo(2.0); + expect(body.linvelY()).toBeCloseTo(-3.0); + expect(body.linvelZ()).toBeCloseTo(1.0); + }); + + test("setLinvelXYZ sets velocity correctly", () => { + const bodyDesc = RigidBodyDesc.dynamic(); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + body.setLinvelXYZ(5.0, -2.0, 3.0, true); + expect(body.linvelX()).toBeCloseTo(5.0); + expect(body.linvelY()).toBeCloseTo(-2.0); + expect(body.linvelZ()).toBeCloseTo(3.0); + }); + + test("addForceXYZ adds force correctly", () => { + const bodyDesc = RigidBodyDesc.dynamic(); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + body.addForceXYZ(10.0, 20.0, -5.0, true); + const force = body.userForce(); + expect(force.x).toBeCloseTo(10.0); + expect(force.y).toBeCloseTo(20.0); + expect(force.z).toBeCloseTo(-5.0); + }); + + test("applyImpulseXYZ changes velocity", () => { + const bodyDesc = RigidBodyDesc.dynamic(); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + body.applyImpulseXYZ(1.0, 0.0, 0.0, true); + expect(body.linvelX()).not.toBeCloseTo(0.0); + }); + + test("scalar getters work after simulation step", () => { + const bodyDesc = RigidBodyDesc.dynamic().setTranslation(0, 10, 0); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + world.createCollider(colliderDesc, body); + world.step(); + const full = body.translation(); + expect(body.translationX()).toBeCloseTo(full.x); + expect(body.translationY()).toBeCloseTo(full.y); + expect(body.translationZ()).toBeCloseTo(full.z); + }); + + test("collider scalar translation getters match translation()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setTranslation(5.0, 3.0, -2.0); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + const collider = world.createCollider(colliderDesc, body); + const full = collider.translation(); + expect(collider.translationX()).toBeCloseTo(full.x); + expect(collider.translationY()).toBeCloseTo(full.y); + expect(collider.translationZ()).toBeCloseTo(full.z); + }); + + test("collider scalar rotation getters match rotation()", () => { + const bodyDesc = RigidBodyDesc.dynamic().setRotation( + new Quaternion(0.0, 0.3826834, 0.0, 0.9238795), + ); + const body = world.createRigidBody(bodyDesc); + const colliderDesc = ColliderDesc.ball(0.5); + const collider = world.createCollider(colliderDesc, body); + const full = collider.rotation(); + expect(collider.rotationX()).toBeCloseTo(full.x); + expect(collider.rotationY()).toBeCloseTo(full.y); + expect(collider.rotationZ()).toBeCloseTo(full.z); + expect(collider.rotationW()).toBeCloseTo(full.w); + }); +}); diff --git a/src.ts/control/character_controller.ts b/src.ts/control/character_controller.ts index 8e536392..b10aa21c 100644 --- a/src.ts/control/character_controller.ts +++ b/src.ts/control/character_controller.ts @@ -337,6 +337,35 @@ export class KinematicCharacterController { return VectorOps.fromRaw(this.raw.computedMovement()); } + /** + * The `x` component of the movement computed by the last call to `this.computeColliderMovement`. + * + * This is a zero-allocation alternative to `computedMovement().x`. + */ + public computedMovementX(): number { + return this.raw.computedMovementX(); + } + + /** + * The `y` component of the movement computed by the last call to `this.computeColliderMovement`. + * + * This is a zero-allocation alternative to `computedMovement().y`. + */ + public computedMovementY(): number { + return this.raw.computedMovementY(); + } + + // #if DIM3 + /** + * The `z` component of the movement computed by the last call to `this.computeColliderMovement`. + * + * This is a zero-allocation alternative to `computedMovement().z`. + */ + public computedMovementZ(): number { + return this.raw.computedMovementZ(); + } + // #endif + /** * The result of ground detection computed by the last call to `this.computeColliderMovement`. */ diff --git a/src.ts/dynamics/rigid_body.ts b/src.ts/dynamics/rigid_body.ts index 8f52dbe5..a83ba345 100644 --- a/src.ts/dynamics/rigid_body.ts +++ b/src.ts/dynamics/rigid_body.ts @@ -307,6 +307,84 @@ export class RigidBody { return RotationOps.fromRaw(res); } + /** + * The `x` component of the world-space translation of this rigid-body. + * + * This is a zero-allocation alternative to `translation().x`. + */ + public translationX(): number { + return this.rawSet.rbTranslationX(this.handle); + } + + /** + * The `y` component of the world-space translation of this rigid-body. + * + * This is a zero-allocation alternative to `translation().y`. + */ + public translationY(): number { + return this.rawSet.rbTranslationY(this.handle); + } + + // #if DIM3 + /** + * The `z` component of the world-space translation of this rigid-body. + * + * This is a zero-allocation alternative to `translation().z`. + */ + public translationZ(): number { + return this.rawSet.rbTranslationZ(this.handle); + } + // #endif + + // #if DIM2 + /** + * The rotation angle of this rigid-body, in radians. + * + * This is a zero-allocation alternative to `rotation()`. + */ + public rotationAngle(): number { + return this.rawSet.rbRotationAngle(this.handle); + } + // #endif + + // #if DIM3 + /** + * The `x` component of the rotation quaternion of this rigid-body. + * + * This is a zero-allocation alternative to `rotation().x`. + */ + public rotationX(): number { + return this.rawSet.rbRotationX(this.handle); + } + + /** + * The `y` component of the rotation quaternion of this rigid-body. + * + * This is a zero-allocation alternative to `rotation().y`. + */ + public rotationY(): number { + return this.rawSet.rbRotationY(this.handle); + } + + /** + * The `z` component of the rotation quaternion of this rigid-body. + * + * This is a zero-allocation alternative to `rotation().z`. + */ + public rotationZ(): number { + return this.rawSet.rbRotationZ(this.handle); + } + + /** + * The `w` component of the rotation quaternion of this rigid-body. + * + * This is a zero-allocation alternative to `rotation().w`. + */ + public rotationW(): number { + return this.rawSet.rbRotationW(this.handle); + } + // #endif + /** * The world-space next translation of this rigid-body. * @@ -507,6 +585,35 @@ export class RigidBody { return VectorOps.fromRaw(this.rawSet.rbLinvel(this.handle)); } + /** + * The `x` component of the linear velocity of this rigid-body. + * + * This is a zero-allocation alternative to `linvel().x`. + */ + public linvelX(): number { + return this.rawSet.rbLinvelX(this.handle); + } + + /** + * The `y` component of the linear velocity of this rigid-body. + * + * This is a zero-allocation alternative to `linvel().y`. + */ + public linvelY(): number { + return this.rawSet.rbLinvelY(this.handle); + } + + // #if DIM3 + /** + * The `z` component of the linear velocity of this rigid-body. + * + * This is a zero-allocation alternative to `linvel().z`. + */ + public linvelZ(): number { + return this.rawSet.rbLinvelZ(this.handle); + } + // #endif + /** * The velocity of the given world-space point on this rigid-body. */ @@ -1042,6 +1149,104 @@ export class RigidBody { // #endif + // #if DIM2 + /** + * Sets the linear velocity using scalar components. + * + * This is a zero-allocation alternative to `setLinvel({x, y}, wakeUp)`. + * + * @param x - The linear velocity along the `x` axis. + * @param y - The linear velocity along the `y` axis. + * @param wakeUp - Forces the rigid-body to wake-up if it was asleep. + */ + public setLinvelXY(x: number, y: number, wakeUp: boolean) { + this.rawSet.rbSetLinvelXY(this.handle, x, y, wakeUp); + } + // #endif + + // #if DIM3 + /** + * Sets the linear velocity using scalar components. + * + * This is a zero-allocation alternative to `setLinvel({x, y, z}, wakeUp)`. + * + * @param x - The linear velocity along the `x` axis. + * @param y - The linear velocity along the `y` axis. + * @param z - The linear velocity along the `z` axis. + * @param wakeUp - Forces the rigid-body to wake-up if it was asleep. + */ + public setLinvelXYZ(x: number, y: number, z: number, wakeUp: boolean) { + this.rawSet.rbSetLinvelXYZ(this.handle, x, y, z, wakeUp); + } + // #endif + + // #if DIM2 + /** + * Adds a force at the center-of-mass using scalar components. + * + * This is a zero-allocation alternative to `addForce({x, y}, wakeUp)`. + * + * @param x - The force along the `x` axis. + * @param y - The force along the `y` axis. + * @param wakeUp - Should the rigid-body be automatically woken-up? + */ + public addForceXY(x: number, y: number, wakeUp: boolean) { + this.rawSet.rbAddForceXY(this.handle, x, y, wakeUp); + } + // #endif + + // #if DIM3 + /** + * Adds a force at the center-of-mass using scalar components. + * + * This is a zero-allocation alternative to `addForce({x, y, z}, wakeUp)`. + * + * @param x - The force along the `x` axis. + * @param y - The force along the `y` axis. + * @param z - The force along the `z` axis. + * @param wakeUp - Should the rigid-body be automatically woken-up? + */ + public addForceXYZ(x: number, y: number, z: number, wakeUp: boolean) { + this.rawSet.rbAddForceXYZ(this.handle, x, y, z, wakeUp); + } + // #endif + + // #if DIM2 + /** + * Applies an impulse at the center-of-mass using scalar components. + * + * This is a zero-allocation alternative to `applyImpulse({x, y}, wakeUp)`. + * + * @param x - The impulse along the `x` axis. + * @param y - The impulse along the `y` axis. + * @param wakeUp - Should the rigid-body be automatically woken-up? + */ + public applyImpulseXY(x: number, y: number, wakeUp: boolean) { + this.rawSet.rbApplyImpulseXY(this.handle, x, y, wakeUp); + } + // #endif + + // #if DIM3 + /** + * Applies an impulse at the center-of-mass using scalar components. + * + * This is a zero-allocation alternative to `applyImpulse({x, y, z}, wakeUp)`. + * + * @param x - The impulse along the `x` axis. + * @param y - The impulse along the `y` axis. + * @param z - The impulse along the `z` axis. + * @param wakeUp - Should the rigid-body be automatically woken-up? + */ + public applyImpulseXYZ( + x: number, + y: number, + z: number, + wakeUp: boolean, + ) { + this.rawSet.rbApplyImpulseXYZ(this.handle, x, y, z, wakeUp); + } + // #endif + /** * Adds a force at the given world-space point of this rigid-body. * diff --git a/src.ts/geometry/collider.ts b/src.ts/geometry/collider.ts index 25817362..8222bff1 100644 --- a/src.ts/geometry/collider.ts +++ b/src.ts/geometry/collider.ts @@ -176,6 +176,35 @@ export class Collider { ); } + /** + * The `x` component of the world-space translation of this collider. + * + * This is a zero-allocation alternative to `translation().x`. + */ + public translationX(): number { + return this.colliderSet.raw.coTranslationX(this.handle); + } + + /** + * The `y` component of the world-space translation of this collider. + * + * This is a zero-allocation alternative to `translation().y`. + */ + public translationY(): number { + return this.colliderSet.raw.coTranslationY(this.handle); + } + + // #if DIM3 + /** + * The `z` component of the world-space translation of this collider. + * + * This is a zero-allocation alternative to `translation().z`. + */ + public translationZ(): number { + return this.colliderSet.raw.coTranslationZ(this.handle); + } + // #endif + /** * The translation of this collider relative to its parent rigid-body. * @@ -196,6 +225,55 @@ export class Collider { ); } + // #if DIM2 + /** + * The rotation angle of this collider, in radians. + * + * This is a zero-allocation alternative to `rotation()`. + */ + public rotationAngle(): number { + return this.colliderSet.raw.coRotationAngle(this.handle); + } + // #endif + + // #if DIM3 + /** + * The `x` component of the rotation quaternion of this collider. + * + * This is a zero-allocation alternative to `rotation().x`. + */ + public rotationX(): number { + return this.colliderSet.raw.coRotationX(this.handle); + } + + /** + * The `y` component of the rotation quaternion of this collider. + * + * This is a zero-allocation alternative to `rotation().y`. + */ + public rotationY(): number { + return this.colliderSet.raw.coRotationY(this.handle); + } + + /** + * The `z` component of the rotation quaternion of this collider. + * + * This is a zero-allocation alternative to `rotation().z`. + */ + public rotationZ(): number { + return this.colliderSet.raw.coRotationZ(this.handle); + } + + /** + * The `w` component of the rotation quaternion of this collider. + * + * This is a zero-allocation alternative to `rotation().w`. + */ + public rotationW(): number { + return this.colliderSet.raw.coRotationW(this.handle); + } + // #endif + /** * The orientation of this collider relative to its parent rigid-body. * diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index 03cbdc5d..023a349d 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -216,6 +216,22 @@ impl RawKinematicCharacterController { self.result.translation.into() } + /// The x component of the movement computed by the last call to `computeColliderMovement`. + pub fn computedMovementX(&self) -> f32 { + self.result.translation.x + } + + /// The y component of the movement computed by the last call to `computeColliderMovement`. + pub fn computedMovementY(&self) -> f32 { + self.result.translation.y + } + + /// The z component of the movement computed by the last call to `computeColliderMovement`. + #[cfg(feature = "dim3")] + pub fn computedMovementZ(&self) -> f32 { + self.result.translation.z + } + pub fn computedGrounded(&self) -> bool { self.result.grounded } diff --git a/src/dynamics/rigid_body.rs b/src/dynamics/rigid_body.rs index d7174d77..dc950c02 100644 --- a/src/dynamics/rigid_body.rs +++ b/src/dynamics/rigid_body.rs @@ -750,4 +750,125 @@ impl RawRigidBodySet { pub fn rbUserTorque(&self, handle: FlatHandle) -> RawVector { self.map(handle, |rb| rb.user_torque().into()) } + + // --- Zero-allocation scalar component getters --- + + /// The x component of the world-space translation of this rigid-body. + pub fn rbTranslationX(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.position().translation.vector.x) + } + + /// The y component of the world-space translation of this rigid-body. + pub fn rbTranslationY(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.position().translation.vector.y) + } + + /// The z component of the world-space translation of this rigid-body. + #[cfg(feature = "dim3")] + pub fn rbTranslationZ(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.position().translation.vector.z) + } + + /// The rotation angle of this rigid-body, in radians. + #[cfg(feature = "dim2")] + pub fn rbRotationAngle(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.position().rotation.angle()) + } + + /// The x component of the rotation quaternion of this rigid-body. + #[cfg(feature = "dim3")] + pub fn rbRotationX(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.position().rotation.i) + } + + /// The y component of the rotation quaternion of this rigid-body. + #[cfg(feature = "dim3")] + pub fn rbRotationY(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.position().rotation.j) + } + + /// The z component of the rotation quaternion of this rigid-body. + #[cfg(feature = "dim3")] + pub fn rbRotationZ(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.position().rotation.k) + } + + /// The w component of the rotation quaternion of this rigid-body. + #[cfg(feature = "dim3")] + pub fn rbRotationW(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.position().rotation.w) + } + + /// The x component of the linear velocity of this rigid-body. + pub fn rbLinvelX(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.linvel().x) + } + + /// The y component of the linear velocity of this rigid-body. + pub fn rbLinvelY(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.linvel().y) + } + + /// The z component of the linear velocity of this rigid-body. + #[cfg(feature = "dim3")] + pub fn rbLinvelZ(&self, handle: FlatHandle) -> f32 { + self.map(handle, |rb| rb.linvel().z) + } + + // --- Zero-allocation scalar setters --- + + /// Sets the linear velocity of this rigid-body using scalar components. + #[cfg(feature = "dim2")] + pub fn rbSetLinvelXY(&mut self, handle: FlatHandle, x: f32, y: f32, wakeUp: bool) { + self.map_mut(handle, |rb| { + rb.set_linvel(na::Vector2::new(x, y), wakeUp); + }); + } + + /// Sets the linear velocity of this rigid-body using scalar components. + #[cfg(feature = "dim3")] + pub fn rbSetLinvelXYZ(&mut self, handle: FlatHandle, x: f32, y: f32, z: f32, wakeUp: bool) { + self.map_mut(handle, |rb| { + rb.set_linvel(na::Vector3::new(x, y, z), wakeUp); + }); + } + + /// Adds a force at the center-of-mass of this rigid-body using scalar components. + #[cfg(feature = "dim2")] + pub fn rbAddForceXY(&mut self, handle: FlatHandle, x: f32, y: f32, wakeUp: bool) { + self.map_mut(handle, |rb| { + rb.add_force(na::Vector2::new(x, y), wakeUp); + }); + } + + /// Adds a force at the center-of-mass of this rigid-body using scalar components. + #[cfg(feature = "dim3")] + pub fn rbAddForceXYZ(&mut self, handle: FlatHandle, x: f32, y: f32, z: f32, wakeUp: bool) { + self.map_mut(handle, |rb| { + rb.add_force(na::Vector3::new(x, y, z), wakeUp); + }); + } + + /// Applies an impulse at the center-of-mass of this rigid-body using scalar components. + #[cfg(feature = "dim2")] + pub fn rbApplyImpulseXY(&mut self, handle: FlatHandle, x: f32, y: f32, wakeUp: bool) { + self.map_mut(handle, |rb| { + rb.apply_impulse(na::Vector2::new(x, y), wakeUp); + }); + } + + /// Applies an impulse at the center-of-mass of this rigid-body using scalar components. + #[cfg(feature = "dim3")] + pub fn rbApplyImpulseXYZ( + &mut self, + handle: FlatHandle, + x: f32, + y: f32, + z: f32, + wakeUp: bool, + ) { + self.map_mut(handle, |rb| { + rb.apply_impulse(na::Vector3::new(x, y, z), wakeUp); + }); + } } diff --git a/src/geometry/collider.rs b/src/geometry/collider.rs index e8019044..24171bc5 100644 --- a/src/geometry/collider.rs +++ b/src/geometry/collider.rs @@ -990,4 +990,52 @@ impl RawColliderSet { co.set_mass_properties(props) }) } + + // --- Zero-allocation scalar component getters --- + + /// The x component of the world-space translation of this collider. + pub fn coTranslationX(&self, handle: FlatHandle) -> f32 { + self.map(handle, |co| co.position().translation.vector.x) + } + + /// The y component of the world-space translation of this collider. + pub fn coTranslationY(&self, handle: FlatHandle) -> f32 { + self.map(handle, |co| co.position().translation.vector.y) + } + + /// The z component of the world-space translation of this collider. + #[cfg(feature = "dim3")] + pub fn coTranslationZ(&self, handle: FlatHandle) -> f32 { + self.map(handle, |co| co.position().translation.vector.z) + } + + /// The rotation angle of this collider, in radians. + #[cfg(feature = "dim2")] + pub fn coRotationAngle(&self, handle: FlatHandle) -> f32 { + self.map(handle, |co| co.position().rotation.angle()) + } + + /// The x component of the rotation quaternion of this collider. + #[cfg(feature = "dim3")] + pub fn coRotationX(&self, handle: FlatHandle) -> f32 { + self.map(handle, |co| co.position().rotation.i) + } + + /// The y component of the rotation quaternion of this collider. + #[cfg(feature = "dim3")] + pub fn coRotationY(&self, handle: FlatHandle) -> f32 { + self.map(handle, |co| co.position().rotation.j) + } + + /// The z component of the rotation quaternion of this collider. + #[cfg(feature = "dim3")] + pub fn coRotationZ(&self, handle: FlatHandle) -> f32 { + self.map(handle, |co| co.position().rotation.k) + } + + /// The w component of the rotation quaternion of this collider. + #[cfg(feature = "dim3")] + pub fn coRotationW(&self, handle: FlatHandle) -> f32 { + self.map(handle, |co| co.position().rotation.w) + } }