From 71070d3439e366b72b2090d12754e1dc2e54c95b Mon Sep 17 00:00:00 2001 From: Nicholas Date: Mon, 17 Nov 2025 01:10:01 -0600 Subject: [PATCH 1/6] Un-comment contact modification physics hook code. Clean it up. --- Cargo.lock | 34 +++++++++------- src.ts/pipeline/physics_hooks.ts | 19 +++++++-- src.ts/pipeline/physics_pipeline.ts | 15 +++---- src/pipeline/physics_hooks.rs | 63 ++++++++++++++++------------- src/pipeline/physics_pipeline.rs | 2 + 5 files changed, 82 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d2a5111..6502b3e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -338,12 +338,13 @@ dependencies = [ [[package]] name = "dimforge_rapier2d" -version = "0.19.2" +version = "0.19.3" dependencies = [ "bincode", "js-sys", "nalgebra", "palette", + "parry2d", "rapier2d", "ref-cast", "serde", @@ -352,12 +353,13 @@ dependencies = [ [[package]] name = "dimforge_rapier2d-deterministic" -version = "0.19.2" +version = "0.19.3" dependencies = [ "bincode", "js-sys", "nalgebra", "palette", + "parry2d", "rapier2d", "ref-cast", "serde", @@ -366,12 +368,13 @@ dependencies = [ [[package]] name = "dimforge_rapier2d-simd" -version = "0.19.2" +version = "0.19.3" dependencies = [ "bincode", "js-sys", "nalgebra", "palette", + "parry2d", "rapier2d", "ref-cast", "serde", @@ -380,12 +383,13 @@ dependencies = [ [[package]] name = "dimforge_rapier3d" -version = "0.19.2" +version = "0.19.3" dependencies = [ "bincode", "js-sys", "nalgebra", "palette", + "parry3d", "rapier3d", "ref-cast", "serde", @@ -394,12 +398,13 @@ dependencies = [ [[package]] name = "dimforge_rapier3d-deterministic" -version = "0.19.2" +version = "0.19.3" dependencies = [ "bincode", "js-sys", "nalgebra", "palette", + "parry3d", "rapier3d", "ref-cast", "serde", @@ -408,12 +413,13 @@ dependencies = [ [[package]] name = "dimforge_rapier3d-simd" -version = "0.19.2" +version = "0.19.3" dependencies = [ "bincode", "js-sys", "nalgebra", "palette", + "parry3d", "rapier3d", "ref-cast", "serde", @@ -578,9 +584,9 @@ checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" [[package]] name = "glam" -version = "0.30.8" +version = "0.30.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460" +checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" [[package]] name = "globset" @@ -796,7 +802,7 @@ dependencies = [ "glam 0.27.0", "glam 0.28.0", "glam 0.29.3", - "glam 0.30.8", + "glam 0.30.9", "matrixmultiply", "nalgebra-macros", "num-complex", @@ -926,9 +932,9 @@ dependencies = [ [[package]] name = "parry2d" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ea16e5cdf52dd91b2a98ef781a2121f48dfb0880a92ae1ec6bc9e78097fceb" +checksum = "ef681740349cec3ab9b5996b03b459b383b6998e1ffcb2804e8b57eb1e8491d9" dependencies = [ "approx", "arrayvec", @@ -955,9 +961,9 @@ dependencies = [ [[package]] name = "parry3d" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2b4291e3c8fcba5f514ed627228c92fa4c94c38bb202c35be7b3b021090193" +checksum = "e99471b7b6870f7fe406d5611dd4b4c9b07aa3e5436b1d27e1515f9832bb0c6b" dependencies = [ "approx", "arrayvec", @@ -966,7 +972,7 @@ dependencies = [ "either", "ena", "foldhash 0.2.0", - "glam 0.30.8", + "glam 0.30.9", "hashbrown 0.16.0", "indexmap", "log", diff --git a/src.ts/pipeline/physics_hooks.ts b/src.ts/pipeline/physics_hooks.ts index 20e12ad9..35c4d956 100644 --- a/src.ts/pipeline/physics_hooks.ts +++ b/src.ts/pipeline/physics_hooks.ts @@ -1,11 +1,11 @@ -import {RigidBodyHandle} from "../dynamics"; -import {ColliderHandle} from "../geometry"; +import { RigidBodyHandle } from "../dynamics"; +import { ColliderHandle } from "../geometry"; export enum ActiveHooks { NONE = 0, FILTER_CONTACT_PAIRS = 0b0001, FILTER_INTERSECTION_PAIRS = 0b0010, - // MODIFY_SOLVER_CONTACTS = 0b0100, /* Not supported yet in JS. */ + MODIFY_SOLVER_CONTACTS = 0b0100, } export enum SolverFlags { @@ -13,6 +13,8 @@ export enum SolverFlags { COMPUTE_IMPULSE = 0b001, } +export type ContactModificationContext = any; // Placeholder for the actual ContactModificationContext type. + export interface PhysicsHooks { /** * Function that determines if contacts computation should happen between two colliders, and how the @@ -51,4 +53,15 @@ export interface PhysicsHooks { body1: RigidBodyHandle, body2: RigidBodyHandle, ): boolean; + + /** + * Function that modifies the set of contacts seen by the constraints solver. + * + * Note that this method will only be called if at least one of the colliders + * involved in the contact contains the `ActiveHooks::MODIFY_SOLVER_CONTACTS` flags + * in its physics hooks flags. + * + * @param context - The context providing information and access to the contacts to modify. + */ + modifySolverContacts(context: ContactModificationContext): void; } diff --git a/src.ts/pipeline/physics_pipeline.ts b/src.ts/pipeline/physics_pipeline.ts index a3319205..51597efb 100644 --- a/src.ts/pipeline/physics_pipeline.ts +++ b/src.ts/pipeline/physics_pipeline.ts @@ -1,13 +1,13 @@ -import {RawPhysicsPipeline} from "../raw"; -import {Vector, VectorOps} from "../math"; +import { RawPhysicsPipeline } from "../raw"; +import { Vector, VectorOps } from "../math"; import { - IntegrationParameters, + CCDSolver, ImpulseJointSet, + IntegrationParameters, + IslandManager, MultibodyJointSet, RigidBodyHandle, RigidBodySet, - CCDSolver, - IslandManager, } from "../dynamics"; import { BroadPhase, @@ -15,8 +15,8 @@ import { ColliderSet, NarrowPhase, } from "../geometry"; -import {EventQueue} from "./event_queue"; -import {PhysicsHooks} from "./physics_hooks"; +import { EventQueue } from "./event_queue"; +import { PhysicsHooks } from "./physics_hooks"; export class PhysicsPipeline { raw: RawPhysicsPipeline; @@ -64,6 +64,7 @@ export class PhysicsPipeline { hooks, !!hooks ? hooks.filterContactPair : null, !!hooks ? hooks.filterIntersectionPair : null, + !!hooks ? hooks.modifySolverContacts : null, ); } else { this.raw.step( diff --git a/src/pipeline/physics_hooks.rs b/src/pipeline/physics_hooks.rs index 2bbc8034..d3ad9fc7 100644 --- a/src/pipeline/physics_hooks.rs +++ b/src/pipeline/physics_hooks.rs @@ -1,13 +1,16 @@ -use crate::utils; +use crate::math::RawVector; +use crate::utils::{self, FlatHandle}; use rapier::geometry::SolverFlags; +use rapier::math::{Real, Vector}; use rapier::pipeline::{ContactModificationContext, PairFilterContext, PhysicsHooks}; +use rapier::prelude::{ContactManifold, SolverContact}; use wasm_bindgen::prelude::*; pub struct RawPhysicsHooks { pub this: js_sys::Object, pub filter_contact_pair: js_sys::Function, pub filter_intersection_pair: js_sys::Function, - // pub modify_solver_contacts: &'a js_sys::Function, + pub modify_solver_contacts: js_sys::Function, } // HACK: the RawPhysicsHooks is no longer Send+Sync because the JS objects are @@ -73,22 +76,29 @@ impl PhysicsHooks for RawPhysicsHooks { .unwrap_or(false) } - fn modify_solver_contacts(&self, _ctxt: &mut ContactModificationContext) {} + fn modify_solver_contacts(&self, ctxt: &mut ContactModificationContext) { + let raw_context = RawContactModificationContext { + collider1: utils::flat_handle(ctxt.collider1.0), + collider2: utils::flat_handle(ctxt.collider2.0), + rigid_body1: ctxt.rigid_body1.map(|rb| utils::flat_handle(rb.0)), + rigid_body2: ctxt.rigid_body2.map(|rb| utils::flat_handle(rb.0)), + manifold: ctxt.manifold as *const ContactManifold, + solver_contacts: ctxt.solver_contacts as *mut Vec, + normal: ctxt.normal as *mut Vector, + user_data: ctxt.user_data as *mut u32, + }; + let _ = self + .modify_solver_contacts + .call1(&self.this, &JsValue::from(raw_context)); + } } -/* NOTE: the following is an attempt to make contact modification work. - * -#[wasm_bindgen] -#[derive(Copy, Clone, Debug)] -pub struct RawContactManifold(*const ContactManifold); -pub struct RawSolverContact(*const SolverContact); - #[wasm_bindgen] pub struct RawContactModificationContext { - pub collider1: u32, - pub collider2: u32, - pub rigid_body1: Option, - pub rigid_body2: Option, + pub collider1: FlatHandle, + pub collider2: FlatHandle, + pub rigid_body1: Option, + pub rigid_body2: Option, pub manifold: *const ContactManifold, pub solver_contacts: *mut Vec, normal: *mut Vector, @@ -97,11 +107,11 @@ pub struct RawContactModificationContext { #[wasm_bindgen] impl RawContactModificationContext { - pub fn collider1(&self) -> u32 { + pub fn collider1(&self) -> FlatHandle { self.collider1 } - pub fn collider2(&self) -> u32 { + pub fn collider2(&self) -> FlatHandle { self.collider2 } @@ -147,7 +157,7 @@ impl RawContactModificationContext { pub fn solver_contact_point(&self, i: usize) -> Option { unsafe { - (*self.solver_contacts) + (&(*self.solver_contacts)) .get(i) .map(|c| c.point.coords.into()) } @@ -155,7 +165,7 @@ impl RawContactModificationContext { pub fn set_solver_contact_point(&mut self, i: usize, pt: &RawVector) { unsafe { - if let Some(c) = (*self.solver_contacts).get_mut(i) { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { c.point = pt.0.into() } } @@ -163,7 +173,7 @@ impl RawContactModificationContext { pub fn solver_contact_dist(&self, i: usize) -> Real { unsafe { - (*self.solver_contacts) + (&(*self.solver_contacts)) .get(i) .map(|c| c.dist) .unwrap_or(0.0) @@ -172,46 +182,45 @@ impl RawContactModificationContext { pub fn set_solver_contact_dist(&mut self, i: usize, dist: Real) { unsafe { - if let Some(c) = (*self.solver_contacts).get_mut(i) { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { c.dist = dist } } } pub fn solver_contact_friction(&self, i: usize) -> Real { - unsafe { (*self.solver_contacts)[i].friction } + unsafe { (&(*self.solver_contacts))[i].friction } } pub fn set_solver_contact_friction(&mut self, i: usize, friction: Real) { unsafe { - if let Some(c) = (*self.solver_contacts).get_mut(i) { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { c.friction = friction } } } pub fn solver_contact_restitution(&self, i: usize) -> Real { - unsafe { (*self.solver_contacts)[i].restitution } + unsafe { (&(*self.solver_contacts))[i].restitution } } pub fn set_solver_contact_restitution(&mut self, i: usize, restitution: Real) { unsafe { - if let Some(c) = (*self.solver_contacts).get_mut(i) { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { c.restitution = restitution } } } pub fn solver_contact_tangent_velocity(&self, i: usize) -> RawVector { - unsafe { (*self.solver_contacts)[i].tangent_velocity.into() } + unsafe { (&(*self.solver_contacts))[i].tangent_velocity.into() } } pub fn set_solver_contact_tangent_velocity(&mut self, i: usize, vel: &RawVector) { unsafe { - if let Some(c) = (*self.solver_contacts).get_mut(i) { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { c.tangent_velocity = vel.0.into() } } } } -*/ diff --git a/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs index 287cfeea..d7e5ce6b 100644 --- a/src/pipeline/physics_pipeline.rs +++ b/src/pipeline/physics_pipeline.rs @@ -141,6 +141,7 @@ impl RawPhysicsPipeline { hookObject: js_sys::Object, hookFilterContactPair: js_sys::Function, hookFilterIntersectionPair: js_sys::Function, + hookModifySolverContacts: js_sys::Function, ) { if eventQueue.auto_drain { eventQueue.clear(); @@ -150,6 +151,7 @@ impl RawPhysicsPipeline { this: hookObject, filter_contact_pair: hookFilterContactPair, filter_intersection_pair: hookFilterIntersectionPair, + modify_solver_contacts: hookModifySolverContacts, }; self.0.step( From dba94df892b094fcc581df65ac6e30b4a6a6d7ac Mon Sep 17 00:00:00 2001 From: Nicholas Date: Mon, 17 Nov 2025 02:09:44 -0600 Subject: [PATCH 2/6] Add missing elements from RawContactModificationContext. --- src.ts/pipeline/physics_hooks.ts | 53 +++++++++++++++++++++++++- src/geometry/narrow_phase.rs | 2 +- src/pipeline/physics_hooks.rs | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src.ts/pipeline/physics_hooks.ts b/src.ts/pipeline/physics_hooks.ts index 35c4d956..f8d0d83f 100644 --- a/src.ts/pipeline/physics_hooks.ts +++ b/src.ts/pipeline/physics_hooks.ts @@ -1,5 +1,6 @@ import { RigidBodyHandle } from "../dynamics"; import { ColliderHandle } from "../geometry"; +import { Vector } from "../math"; export enum ActiveHooks { NONE = 0, @@ -13,7 +14,57 @@ export enum SolverFlags { COMPUTE_IMPULSE = 0b001, } -export type ContactModificationContext = any; // Placeholder for the actual ContactModificationContext type. +export declare class ContactModificationContext { + collider1: ColliderHandle; + collider2: ColliderHandle; + body1: RigidBodyHandle; + body2: RigidBodyHandle; + + normal: Vector; + user_data: number; + + num_solver_contacts(): number; + + clear_solver_contacts(): void; + + remove_solver_contact(index: number): void; + + solver_contact_point(index: number): Vector | null; + set_solver_contact_point(index: number, point: Vector): void; + + solver_contact_dist(index: number): number; + set_solver_contact_dist(index: number, dist: number): void; + + solver_contact_friction(index: number): number; + set_solver_contact_friction(index: number, friction: number): void; + + solver_contact_restitution(index: number): number; + set_solver_contact_restitution(index: number, restitution: number): void; + + solver_contact_tangent_velocity(index: number): Vector | null; + set_solver_contact_tangent_velocity( + index: number, + tangent_velocity: Vector, + ): void; + + solver_contact_warmstart_impulse(index: number): number; + set_solver_contact_warmstart_impulse(index: number, impulse: number): void; + + solver_contact_warmstart_tangent_impulse(index: number): number; + set_solver_contact_warmstart_tangent_impulse( + index: number, + impulse: number, + ): void; + + solver_contact_warmstart_twist_impulse(index: number): number; + set_solver_contact_warmstart_twist_impulse( + index: number, + impulse: number, + ): void; + + solver_contact_is_new(index: number): boolean; + set_solver_contact_is_new(index: number, is_new: boolean): void; +} export interface PhysicsHooks { /** diff --git a/src/geometry/narrow_phase.rs b/src/geometry/narrow_phase.rs index be3fee25..9e8666b6 100644 --- a/src/geometry/narrow_phase.rs +++ b/src/geometry/narrow_phase.rs @@ -62,7 +62,7 @@ impl RawNarrowPhase { #[wasm_bindgen] pub struct RawContactPair(*const ContactPair); #[wasm_bindgen] -pub struct RawContactManifold(*const ContactManifold); +pub struct RawContactManifold(pub(crate) *const ContactManifold); // SAFETY: the use of a raw pointer is very unsafe. // We need this because wasm-bindgen doesn't support diff --git a/src/pipeline/physics_hooks.rs b/src/pipeline/physics_hooks.rs index d3ad9fc7..6753ae27 100644 --- a/src/pipeline/physics_hooks.rs +++ b/src/pipeline/physics_hooks.rs @@ -1,3 +1,4 @@ +use crate::geometry::RawContactManifold; use crate::math::RawVector; use crate::utils::{self, FlatHandle}; use rapier::geometry::SolverFlags; @@ -107,6 +108,7 @@ pub struct RawContactModificationContext { #[wasm_bindgen] impl RawContactModificationContext { + // Simple getters and setters for the fields. pub fn collider1(&self) -> FlatHandle { self.collider1 } @@ -115,6 +117,14 @@ impl RawContactModificationContext { self.collider2 } + pub fn rigid_body1(&self) -> Option { + self.rigid_body1 + } + + pub fn rigid_body2(&self) -> Option { + self.rigid_body2 + } + #[wasm_bindgen(getter)] pub fn normal(&self) -> RawVector { unsafe { RawVector(*self.normal) } @@ -139,6 +149,7 @@ impl RawContactModificationContext { } } + // Solver contacts manipulation methods. pub fn num_solver_contacts(&self) -> usize { unsafe { (*self.solver_contacts).len() } } @@ -223,4 +234,57 @@ impl RawContactModificationContext { } } } + + pub fn solver_contact_warmstart_impulse(&self, i: usize) -> Real { + unsafe { (&(*self.solver_contacts))[i].warmstart_impulse } + } + + pub fn set_solver_contact_warmstart_impulse(&mut self, i: usize, impulse: Real) { + unsafe { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { + c.warmstart_impulse = impulse + } + } + } + + pub fn solver_contact_warmstart_tangent_impulse(&self, i: usize) -> Real { + unsafe { (&(*self.solver_contacts))[i].warmstart_tangent_impulse.x } + } + + pub fn set_solver_contact_warmstart_tangent_impulse(&mut self, i: usize, impulse: Real) { + unsafe { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { + c.warmstart_tangent_impulse.x = impulse; + } + } + } + + pub fn solver_contact_warmstart_twist_impulse(&self, i: usize) -> Real { + unsafe { (&(*self.solver_contacts))[i].warmstart_twist_impulse } + } + + pub fn set_solver_contact_warmstart_twist_impulse(&mut self, i: usize, impulse: Real) { + unsafe { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { + c.warmstart_twist_impulse = impulse + } + } + } + + pub fn solver_contact_is_new(&self, i: usize) -> bool { + unsafe { (&(*self.solver_contacts))[i].is_new == 1.0 } + } + + pub fn set_solver_contact_is_new(&mut self, i: usize, is_new: bool) { + unsafe { + if let Some(c) = (&mut (*self.solver_contacts)).get_mut(i) { + c.is_new = if is_new { 1.0 } else { 0.0 }; + } + } + } + + #[wasm_bindgen(getter)] + pub fn contact_manifold(&self) -> RawContactManifold { + RawContactManifold(self.manifold) + } } From 8cb8bfc99cae03f6d49057d958ee1b2c2d5c8230 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Mon, 17 Nov 2025 20:31:23 -0600 Subject: [PATCH 3/6] Fix TypeScript types and make set_normal more consistent with the rest of the setters. --- src.ts/pipeline/physics_hooks.ts | 11 ++++++----- src/pipeline/physics_hooks.rs | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src.ts/pipeline/physics_hooks.ts b/src.ts/pipeline/physics_hooks.ts index f8d0d83f..1077d6ab 100644 --- a/src.ts/pipeline/physics_hooks.ts +++ b/src.ts/pipeline/physics_hooks.ts @@ -1,3 +1,4 @@ +import { RawVector } from "../raw"; import { RigidBodyHandle } from "../dynamics"; import { ColliderHandle } from "../geometry"; import { Vector } from "../math"; @@ -20,7 +21,7 @@ export declare class ContactModificationContext { body1: RigidBodyHandle; body2: RigidBodyHandle; - normal: Vector; + normal: RawVector; user_data: number; num_solver_contacts(): number; @@ -29,8 +30,8 @@ export declare class ContactModificationContext { remove_solver_contact(index: number): void; - solver_contact_point(index: number): Vector | null; - set_solver_contact_point(index: number, point: Vector): void; + solver_contact_point(index: number): RawVector | null; + set_solver_contact_point(index: number, point: RawVector): void; solver_contact_dist(index: number): number; set_solver_contact_dist(index: number, dist: number): void; @@ -41,10 +42,10 @@ export declare class ContactModificationContext { solver_contact_restitution(index: number): number; set_solver_contact_restitution(index: number, restitution: number): void; - solver_contact_tangent_velocity(index: number): Vector | null; + solver_contact_tangent_velocity(index: number): RawVector | null; set_solver_contact_tangent_velocity( index: number, - tangent_velocity: Vector, + tangent_velocity: RawVector, ): void; solver_contact_warmstart_impulse(index: number): number; diff --git a/src/pipeline/physics_hooks.rs b/src/pipeline/physics_hooks.rs index 6753ae27..a536595a 100644 --- a/src/pipeline/physics_hooks.rs +++ b/src/pipeline/physics_hooks.rs @@ -131,9 +131,9 @@ impl RawContactModificationContext { } #[wasm_bindgen(setter)] - pub fn set_normal(&mut self, normal: RawVector) { + pub fn set_normal(&mut self, normal: &RawVector) { unsafe { - *self.normal = normal.0; + *self.normal = normal.0.into(); } } From 43945b235c2ad2dec02a89a48adc98ec649cac78 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Wed, 19 Nov 2025 17:24:48 -0600 Subject: [PATCH 4/6] Fix Typescript compile and add testbed demo for one way platforms. --- src.ts/pipeline/physics_hooks.ts | 56 +---------- src/pipeline/physics_hooks.rs | 74 +++++++++++++-- testbed2d/src/Testbed.ts | 7 +- testbed2d/src/demos/oneWayPlatforms.ts | 125 +++++++++++++++++++++++++ testbed2d/src/index.ts | 4 +- 5 files changed, 202 insertions(+), 64 deletions(-) create mode 100644 testbed2d/src/demos/oneWayPlatforms.ts diff --git a/src.ts/pipeline/physics_hooks.ts b/src.ts/pipeline/physics_hooks.ts index 1077d6ab..e6dd2cca 100644 --- a/src.ts/pipeline/physics_hooks.ts +++ b/src.ts/pipeline/physics_hooks.ts @@ -1,4 +1,4 @@ -import { RawVector } from "../raw"; +import { RawContactModificationContext } from "../raw"; import { RigidBodyHandle } from "../dynamics"; import { ColliderHandle } from "../geometry"; import { Vector } from "../math"; @@ -15,58 +15,6 @@ export enum SolverFlags { COMPUTE_IMPULSE = 0b001, } -export declare class ContactModificationContext { - collider1: ColliderHandle; - collider2: ColliderHandle; - body1: RigidBodyHandle; - body2: RigidBodyHandle; - - normal: RawVector; - user_data: number; - - num_solver_contacts(): number; - - clear_solver_contacts(): void; - - remove_solver_contact(index: number): void; - - solver_contact_point(index: number): RawVector | null; - set_solver_contact_point(index: number, point: RawVector): void; - - solver_contact_dist(index: number): number; - set_solver_contact_dist(index: number, dist: number): void; - - solver_contact_friction(index: number): number; - set_solver_contact_friction(index: number, friction: number): void; - - solver_contact_restitution(index: number): number; - set_solver_contact_restitution(index: number, restitution: number): void; - - solver_contact_tangent_velocity(index: number): RawVector | null; - set_solver_contact_tangent_velocity( - index: number, - tangent_velocity: RawVector, - ): void; - - solver_contact_warmstart_impulse(index: number): number; - set_solver_contact_warmstart_impulse(index: number, impulse: number): void; - - solver_contact_warmstart_tangent_impulse(index: number): number; - set_solver_contact_warmstart_tangent_impulse( - index: number, - impulse: number, - ): void; - - solver_contact_warmstart_twist_impulse(index: number): number; - set_solver_contact_warmstart_twist_impulse( - index: number, - impulse: number, - ): void; - - solver_contact_is_new(index: number): boolean; - set_solver_contact_is_new(index: number, is_new: boolean): void; -} - export interface PhysicsHooks { /** * Function that determines if contacts computation should happen between two colliders, and how the @@ -115,5 +63,5 @@ export interface PhysicsHooks { * * @param context - The context providing information and access to the contacts to modify. */ - modifySolverContacts(context: ContactModificationContext): void; + modifySolverContacts(context: RawContactModificationContext): void; } diff --git a/src/pipeline/physics_hooks.rs b/src/pipeline/physics_hooks.rs index a536595a..e0b1bc2a 100644 --- a/src/pipeline/physics_hooks.rs +++ b/src/pipeline/physics_hooks.rs @@ -1,6 +1,7 @@ use crate::geometry::RawContactManifold; use crate::math::RawVector; use crate::utils::{self, FlatHandle}; +use na::ComplexField; use rapier::geometry::SolverFlags; use rapier::math::{Real, Vector}; use rapier::pipeline::{ContactModificationContext, PairFilterContext, PhysicsHooks}; @@ -96,12 +97,12 @@ impl PhysicsHooks for RawPhysicsHooks { #[wasm_bindgen] pub struct RawContactModificationContext { - pub collider1: FlatHandle, - pub collider2: FlatHandle, - pub rigid_body1: Option, - pub rigid_body2: Option, - pub manifold: *const ContactManifold, - pub solver_contacts: *mut Vec, + collider1: FlatHandle, + collider2: FlatHandle, + rigid_body1: Option, + rigid_body2: Option, + manifold: *const ContactManifold, + solver_contacts: *mut Vec, normal: *mut Vector, user_data: *mut u32, } @@ -287,4 +288,65 @@ impl RawContactModificationContext { pub fn contact_manifold(&self) -> RawContactManifold { RawContactManifold(self.manifold) } + + /// Helper function to update `self` to emulate a oneway-platform. + /// + /// Duplicated from ContactModificationContext::update_as_oneway_platform + pub fn update_as_oneway_platform(&mut self, allowed_local_n1: &RawVector, allowed_angle: Real) { + const CONTACT_CONFIGURATION_UNKNOWN: u32 = 0; + const CONTACT_CURRENTLY_ALLOWED: u32 = 1; + const CONTACT_CURRENTLY_FORBIDDEN: u32 = 2; + + let cang = ComplexField::cos(allowed_angle); + + // Test the allowed normal with the local-space contact normal that + // points towards the exterior of context.collider1. + unsafe { + let contact_is_ok = (*self.manifold).local_n1.dot((&allowed_local_n1.0).into()) >= cang; + + match *self.user_data { + CONTACT_CONFIGURATION_UNKNOWN => { + if contact_is_ok { + // The contact is close enough to the allowed normal. + *self.user_data = CONTACT_CURRENTLY_ALLOWED; + } else { + // The contact normal isn't close enough to the allowed + // normal, so remove all the contacts and mark further contacts + // as forbidden. + (&mut (*self.solver_contacts)).clear(); + + // NOTE: in some very rare cases `local_n1` will be + // zero if the objects are exactly touching at one point. + // So in this case we can't really conclude. + // If the norm is non-zero, then we can tell we need to forbid + // further contacts. Otherwise we have to wait for the next frame. + if (*self.manifold).local_n1.norm_squared() > 0.1 { + *self.user_data = CONTACT_CURRENTLY_FORBIDDEN; + } + } + } + CONTACT_CURRENTLY_FORBIDDEN => { + // Contacts are forbidden so we need to continue forbidding contacts + // until all the contacts are non-penetrating again. In that case, if + // the contacts are OK with respect to the contact normal, then we can + // mark them as allowed. + if contact_is_ok && (&mut (*self.solver_contacts)).iter().all(|c| c.dist > 0.0) + { + *self.user_data = CONTACT_CURRENTLY_ALLOWED; + } else { + // Discard all the contacts. + (&mut (*self.solver_contacts)).clear(); + } + } + CONTACT_CURRENTLY_ALLOWED => { + // We allow all the contacts right now. The configuration becomes + // uncertain again when the contact manifold no longer contains any contact. + if (&mut (*self.solver_contacts)).is_empty() { + *self.user_data = CONTACT_CONFIGURATION_UNKNOWN; + } + } + _ => unreachable!(), + } + } + } } diff --git a/testbed2d/src/Testbed.ts b/testbed2d/src/Testbed.ts index 61279ab2..6e5cb711 100644 --- a/testbed2d/src/Testbed.ts +++ b/testbed2d/src/Testbed.ts @@ -49,8 +49,9 @@ export class Testbed { inhibitLookAt: boolean; parameters: SimulationParameters; demoToken: number; - mouse: {x: number; y: number}; + mouse: { x: number; y: number }; events: RAPIER.EventQueue; + hooks: RAPIER.PhysicsHooks; world: RAPIER.World; preTimestepAction?: (gfx: Graphics) => void; stepId: number; @@ -68,7 +69,7 @@ export class Testbed { this.inhibitLookAt = false; this.parameters = parameters; this.demoToken = 0; - this.mouse = {x: 0, y: 0}; + this.mouse = { x: 0, y: 0 }; this.events = new RAPIER.EventQueue(true); this.switchToDemo(builders.keys().next().value); @@ -146,7 +147,7 @@ export class Testbed { } let t0 = new Date().getTime(); - this.world.step(this.events); + this.world.step(this.events, this.hooks); this.gui.setTiming(new Date().getTime() - t0); this.stepId += 1; diff --git a/testbed2d/src/demos/oneWayPlatforms.ts b/testbed2d/src/demos/oneWayPlatforms.ts new file mode 100644 index 00000000..ba584509 --- /dev/null +++ b/testbed2d/src/demos/oneWayPlatforms.ts @@ -0,0 +1,125 @@ +import { Graphics } from "../Graphics"; +import type { Testbed } from "../Testbed"; + +type RAPIER_API = typeof import("@dimforge/rapier2d"); + +export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) { + const physics_hooks = { + filterContactPair() { + return RAPIER.SolverFlags.COMPUTE_IMPULSE; + }, + filterIntersectionPair() { + return true; + }, + modifySolverContacts: ( + context: any, + ) => { + let allowed_local_n1 = { x: 0.0, y: 0.0 }; + + if (context.collider1() == platform1Collider.handle) { + allowed_local_n1 = { x: 0.0, y: 1.0 }; + } else if (context.collider2() == platform1Collider.handle) { + // Flip the allowed direction. + allowed_local_n1 = { x: 0.0, y: -1.0 }; + } + + if (context.collider1() == platform2Collider.handle) { + allowed_local_n1 = { x: 0.0, y: -1.0 }; + } else if (context.collider2() == platform2Collider.handle) { + // Flip the allowed direction. + allowed_local_n1 = { x: 0.0, y: 1.0 }; + } + + // Call the helper function that simulates one-way platforms. + context.update_as_oneway_platform( + RAPIER.VectorOps.intoRaw(allowed_local_n1), + 0.1, + ); + + // Set the surface velocity of the accepted contacts. + let tangent_velocity = + (context.collider1() == platform1Collider.handle || + context.collider2() == platform2Collider.handle) + ? -12.0 + : 12.0; + + for (let i = 0; i < context.num_solver_contacts(); ++i) { + context.set_solver_contact_tangent_velocity( + i, + RAPIER.VectorOps.intoRaw({ + x: tangent_velocity, + y: 0.0, + }), + ); + } + }, + }; + + let gravity = new RAPIER.Vector2(0.0, -9.81); + let world = new RAPIER.World(gravity); + + let groundBody = RAPIER.RigidBodyDesc.fixed().setTranslation(0.0, 0.0); + let groundHandle = world.createRigidBody(groundBody); + + let platform1 = RAPIER.ColliderDesc.cuboid(25.0, 0.5) + .setTranslation(30.0, 2.0) + .setActiveHooks(RAPIER.ActiveHooks.MODIFY_SOLVER_CONTACTS); + let platform1Collider = world.createCollider(platform1, groundHandle); + + let platform2 = RAPIER.ColliderDesc.cuboid(25.0, 0.5) + .setTranslation(-30.0, -2.0) + .setActiveHooks(RAPIER.ActiveHooks.MODIFY_SOLVER_CONTACTS); + let platform2Collider = world.createCollider(platform2, groundHandle); + + // Note: The OneWayPlatformHook implementation is omitted for brevity. + // let physicsHooks = new OneWayPlatformHook(/* platform1, platform2 */); + + let boxCollDesc = RAPIER.ColliderDesc.cuboid(1.5, 2.0); + let boxBodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation( + 20.0, + 10.0, + ); + let box0 = world.createRigidBody(boxBodyDesc); + world.createCollider( + boxCollDesc, + box0, + ); + let box1 = world.createRigidBody( + boxBodyDesc.setTranslation(40.0, -10.0).setGravityScale(-1), + ); + world.createCollider( + boxCollDesc, + box1, + ); + let box2 = world.createRigidBody( + boxBodyDesc.setTranslation(-20.0, 10.0).setGravityScale(1), + ); + world.createCollider( + boxCollDesc, + box2, + ); + let box3 = world.createRigidBody( + boxBodyDesc.setTranslation(-40.0, -10.0).setGravityScale(-1), + ); + world.createCollider( + boxCollDesc, + box3, + ); + + testbed.hooks = physics_hooks; + testbed.setpreTimestepAction((graphics: Graphics) => { + testbed.world.forEachActiveRigidBody((body) => { + if (body.translation().y > 1.0) { + body.setGravityScale(1.0, false); + } else if (body.translation().y < -1.0) { + body.setGravityScale(-1.0, false); + } + }); + }); + + testbed.setWorld(world); + testbed.lookAt({ + target: { x: -10.0, y: -30.0 }, + zoom: 7.0, + }); +} diff --git a/testbed2d/src/index.ts b/testbed2d/src/index.ts index 24bd18f4..7aeffd0a 100644 --- a/testbed2d/src/index.ts +++ b/testbed2d/src/index.ts @@ -1,4 +1,4 @@ -import {Testbed} from "./Testbed"; +import { Testbed } from "./Testbed"; import * as CollisionGroups from "./demos/collisionGroups"; import * as Cubes from "./demos/cubes"; import * as Keva from "./demos/keva"; @@ -9,6 +9,7 @@ import * as LockedRotations from "./demos/lockedRotations"; import * as ConvexPolygons from "./demos/convexPolygons"; import * as CharacterController from "./demos/characterController"; import * as PidController from "./demos/pidController"; +import * as OneWayPlatforms from "./demos/oneWayPlatforms"; import * as Voxels from "./demos/voxels"; import("@dimforge/rapier2d").then((RAPIER) => { @@ -21,6 +22,7 @@ import("@dimforge/rapier2d").then((RAPIER) => { ["joints: revolute", RevoluteJoints.initWorld], ["keva tower", Keva.initWorld], ["locked rotations", LockedRotations.initWorld], + ["one way platforms", OneWayPlatforms.initWorld], ["pid controller", PidController.initWorld], ["polyline", Polyline.initWorld], ["voxels", Voxels.initWorld], From 5669588488426b7c03a8bdfc50f7ed9ba60d1b9b Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 21 Nov 2025 14:23:24 -0600 Subject: [PATCH 5/6] Add Typescript ContactModificationContext to wrap the Raw. --- src.ts/pipeline/physics_hooks.ts | 292 ++++++++++++++++++++++++- testbed2d/src/demos/oneWayPlatforms.ts | 27 +-- 2 files changed, 304 insertions(+), 15 deletions(-) diff --git a/src.ts/pipeline/physics_hooks.ts b/src.ts/pipeline/physics_hooks.ts index e6dd2cca..1a4a4abe 100644 --- a/src.ts/pipeline/physics_hooks.ts +++ b/src.ts/pipeline/physics_hooks.ts @@ -1,7 +1,7 @@ -import { RawContactModificationContext } from "../raw"; +import { RawContactManifold, RawContactModificationContext } from "../raw"; import { RigidBodyHandle } from "../dynamics"; import { ColliderHandle } from "../geometry"; -import { Vector } from "../math"; +import { Vector, VectorOps } from "../math"; export enum ActiveHooks { NONE = 0, @@ -15,6 +15,294 @@ export enum SolverFlags { COMPUTE_IMPULSE = 0b001, } +export class ContactModificationContext { + raw: RawContactModificationContext; + constructor(raw: RawContactModificationContext) { + this.raw = raw; + } + + /** + * Collider handle of the first collider involved in this contact modification context. + */ + get collider1(): ColliderHandle { + return this.raw.collider1(); + } + + /** + * Collider handle of the second collider involved in this contact modification context. + */ + get collider2(): ColliderHandle { + return this.raw.collider2(); + } + + /** + * Rigid body handle of the first rigid body involved in this contact modification context. + */ + get rigidBody1(): RigidBodyHandle | undefined { + return this.raw.rigid_body1(); + } + + /** + * Rigid body handle of the second rigid body involved in this contact modification context. + */ + get rigidBody2(): RigidBodyHandle | undefined { + return this.raw.rigid_body2(); + } + + /** + * Normal of the contact in this context. + */ + get normal(): Vector { + return VectorOps.fromRaw(this.raw.normal); + } + + set normal(v: Vector) { + this.raw.normal = VectorOps.intoRaw(v); + } + + /** + * User data associated with this contact. + */ + get userData(): number { + return this.raw.user_data(); + } + + set userData(data: number) { + this.raw.user_data = data; + } + + /** + * Number of solver contacts in this contact modification context. + */ + get numSolverContacts(): number { + return this.raw.num_solver_contacts(); + } + + /** + * Clears all the solver contacts in this contact modification context. + */ + clearSolverContacts(): void { + this.raw.clear_solver_contacts(); + } + + /** + * Removes the solver contact at the given index. The last solver contact + * will be moved to the given index. + * @param index - The index of the solver contact to remove. + */ + removeSolverContact(index: number): void { + this.raw.remove_solver_contact(index); + } + + /** + * Gets the location of the solver contact at the given index. + * @param index - The index of the solver contact. + * @returns The location of the solver contact, in world-space coordinates. + */ + getSolverContactPoint(index: number): Vector { + return VectorOps.fromRaw(this.raw.solver_contact_point(index)); + } + + /** + * Sets the location of the solver contact at the given index. + * @param index - The index of the solver contact. + * @param point - The new location of the solver contact, in world-space coordinates. + */ + setSolverContactPoint(index: number, point: Vector): void { + this.raw.set_solver_contact_point(index, VectorOps.intoRaw(point)); + } + + /** + * Gets the distance between the two original contacts points along the contact normal. + * @param index - The index of the solver contact. + * @returns The distance between the two original contacts points along the contact normal. + */ + getSolverContactDist(index: number): number { + return this.raw.solver_contact_dist(index); + } + + /** + * Modifies the distance between the two original contacts points along the contact normal. + * @param index - The index of the solver contact. + * @param dist - The new distance between the two original contacts points along the contact normal. + */ + setSolverContactDist(index: number, dist: number): void { + this.raw.set_solver_contact_dist(index, dist); + } + + /** + * Gets the effective friction of the solver contact at the given index. + * @param index - The index of the solver contact. + * @returns The friction of the solver contact. + */ + getSolverContactFriction(index: number): number { + return this.raw.solver_contact_friction(index); + } + + /** + * Sets the effective friction of the solver contact at the given index. + * @param index - The index of the solver contact. + * @param friction - The new friction of the solver contact. + */ + setSolverContactFriction(index: number, friction: number): void { + this.raw.set_solver_contact_friction(index, friction); + } + + /** + * Gets the effective restitution of the solver contact at the given index. + * @param index - The index of the solver contact. + * @returns The restitution of the solver contact. + */ + getSolverContactRestitution(index: number): number { + return this.raw.solver_contact_restitution(index); + } + + /** + * Sets the effective restitution of the solver contact at the given index. + * @param index - The index of the solver contact. + * @param restitution - The new restitution of the solver contact. + */ + setSolverContactRestitution(index: number, restitution: number): void { + this.raw.set_solver_contact_restitution(index, restitution); + } + + /** + * Gets the tangent velocity of the solver contact at the given index. This + * is set to zero by default. It can be used to simulate conveyor belts or + * similar effects. + * @param index - The index of the solver contact. + * @returns The tangent velocity of the solver contact. + */ + getSolverContactTangentVelocity(index: number): Vector { + return VectorOps.fromRaw( + this.raw.solver_contact_tangent_velocity(index), + ); + } + + /** + * Sets the tangent velocity of the solver contact at the given index. This + * is set to zero by default. It can be used to simulate conveyor belts or + * similar effects. + * @param index - The index of the solver contact. + * @param tangentVelocity - The new tangent velocity of the solver contact. + */ + setSolverContactTangentVelocity( + index: number, + tangentVelocity: Vector, + ): void { + this.raw.set_solver_contact_tangent_velocity( + index, + VectorOps.intoRaw(tangentVelocity), + ); + } + + /** + * Gets the impulse used to warmstart the solve for the normal constraint. + * @param index - The index of the solver contact. + * @returns Impulse used to warmstart the solve for the normal constraint. + */ + getSolverContactWarmstartImpulse(index: number): number { + return this.raw.solver_contact_warmstart_impulse(index); + } + + /** + * Sets the impulse used to warmstart the solve for the normal constraint. + * @param index - The index of the solver contact. + * @param impulse - New impulse to warmstart the solve for the normal constraint. + */ + setSolverContactWarmstartImpulse(index: number, impulse: number): void { + this.raw.set_solver_contact_warmstart_impulse(index, impulse); + } + + /** + * Gets the impulse used to warmstart the solve for the friction constraints. + * @param index - The index of the solver contact. + * @returns Impulse used to warmstart the solve for the friction constraints. + */ + getSolverContactWarmstartTangentImpulse(index: number): number { + return this.raw.solver_contact_warmstart_tangent_impulse(index); + } + + /** + * Sets the impulse used to warmstart the solve for the friction constraints. + * @param index - The index of the solver contact. + * @param impulse - New impulse to warmstart the solve for the friction constraints. + */ + setSolverContactWarmstartTangentImpulse( + index: number, + impulse: number, + ): void { + this.raw.set_solver_contact_warmstart_tangent_impulse(index, impulse); + } + + /** + * Gets the impulse used to warmstart the solve for the twist friction constraints. + * @param index - The index of the solver contact. + * @returns Impulse used to warmstart the solve for the twist friction constraints. + */ + getSolverContactWarmstartTwistImpulse(index: number): number { + return this.raw.solver_contact_warmstart_twist_impulse(index); + } + + /** + * Sets the impulse used to warmstart the solve for the twist friction constraints. + * @param index - The index of the solver contact. + * @param impulse New impulse to warmstart the solve for the twist friction constraints. + */ + setSolverContactWarmstartTwistImpulse( + index: number, + impulse: number, + ): void { + this.raw.set_solver_contact_warmstart_twist_impulse(index, impulse); + } + + /** + * @param index - The index of the solver contact. + * @returns Whether this contact existed during the last timestep. + */ + getSolverContactIsNew(index: number): boolean { + return this.raw.solver_contact_is_new(index); + } + + /** + * Sets whether this contact existed during the last timestep. + * @param index - The index of the solver contact. + * @param isNew - Whether this contact is new. + */ + setSolverContactIsNew(index: number, isNew: boolean): void { + this.raw.set_solver_contact_is_new(index, isNew); + } + + /** + * Returns the contact manifold associated with this contact modification context. + * + * Can be used with TempContactManifold methods for easier use. + */ + get contactManifold(): RawContactManifold { + return this.raw.contact_manifold(); + } + + /** + * Helper function to update `self` to emulate a oneway-platform. + * The "oneway" behavior will only allow contacts between two colliders + * if the local contact normal of the first collider involved in the contact + * is almost aligned with the provided `allowed_local_n1` direction. + * + * To make this method work properly it must be called as part of the + * `PhysicsHooks::modifySolverContacts` method at each timestep, for each + * contact manifold involving a one-way platform. The `self.userData` field + * must not be modified from the outside of this method. + * @param allowedLocalN1 - The allowed contact normal direction in the local space of the first collider. + * @param allowedAngle - The maximum angle (in radians) between the contact normal and the `allowed_local_n1` direction. + */ + updateAsOnewayPlatform(allowedLocalN1: Vector, allowedAngle: number): void { + this.raw.update_as_oneway_platform( + VectorOps.intoRaw(allowedLocalN1), + allowedAngle, + ); + } +} + export interface PhysicsHooks { /** * Function that determines if contacts computation should happen between two colliders, and how the diff --git a/testbed2d/src/demos/oneWayPlatforms.ts b/testbed2d/src/demos/oneWayPlatforms.ts index ba584509..ee2fc507 100644 --- a/testbed2d/src/demos/oneWayPlatforms.ts +++ b/testbed2d/src/demos/oneWayPlatforms.ts @@ -12,44 +12,45 @@ export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) { return true; }, modifySolverContacts: ( - context: any, + rawContext: any, ) => { + const context = new RAPIER.ContactModificationContext(rawContext); let allowed_local_n1 = { x: 0.0, y: 0.0 }; - if (context.collider1() == platform1Collider.handle) { + if (context.collider1 == platform1Collider.handle) { allowed_local_n1 = { x: 0.0, y: 1.0 }; - } else if (context.collider2() == platform1Collider.handle) { + } else if (context.collider2 == platform1Collider.handle) { // Flip the allowed direction. allowed_local_n1 = { x: 0.0, y: -1.0 }; } - if (context.collider1() == platform2Collider.handle) { + if (context.collider1 == platform2Collider.handle) { allowed_local_n1 = { x: 0.0, y: -1.0 }; - } else if (context.collider2() == platform2Collider.handle) { + } else if (context.collider2 == platform2Collider.handle) { // Flip the allowed direction. allowed_local_n1 = { x: 0.0, y: 1.0 }; } // Call the helper function that simulates one-way platforms. - context.update_as_oneway_platform( - RAPIER.VectorOps.intoRaw(allowed_local_n1), + context.updateAsOnewayPlatform( + allowed_local_n1, 0.1, ); // Set the surface velocity of the accepted contacts. let tangent_velocity = - (context.collider1() == platform1Collider.handle || - context.collider2() == platform2Collider.handle) + (context.collider1 == platform1Collider.handle || + context.collider2 == platform2Collider.handle) ? -12.0 : 12.0; - for (let i = 0; i < context.num_solver_contacts(); ++i) { - context.set_solver_contact_tangent_velocity( + for (let i = 0; i < context.numSolverContacts; ++i) { + context.setSolverContactTangentVelocity( i, - RAPIER.VectorOps.intoRaw({ + { x: tangent_velocity, y: 0.0, - }), + }, ); } }, From 322468cb43337b4a96f03f48f393f082ed067830 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 21 Nov 2025 14:34:11 -0600 Subject: [PATCH 6/6] Allow modifySolverContacts to be null, for backwards compatability. Fix some getters. --- src.ts/pipeline/physics_hooks.ts | 9 +++++---- src/pipeline/physics_hooks.rs | 9 +++++---- src/pipeline/physics_pipeline.rs | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src.ts/pipeline/physics_hooks.ts b/src.ts/pipeline/physics_hooks.ts index 1a4a4abe..13c6c130 100644 --- a/src.ts/pipeline/physics_hooks.ts +++ b/src.ts/pipeline/physics_hooks.ts @@ -64,7 +64,7 @@ export class ContactModificationContext { * User data associated with this contact. */ get userData(): number { - return this.raw.user_data(); + return this.raw.user_data; } set userData(data: number) { @@ -279,7 +279,7 @@ export class ContactModificationContext { * Can be used with TempContactManifold methods for easier use. */ get contactManifold(): RawContactManifold { - return this.raw.contact_manifold(); + return this.raw.contact_manifold; } /** @@ -349,7 +349,8 @@ export interface PhysicsHooks { * involved in the contact contains the `ActiveHooks::MODIFY_SOLVER_CONTACTS` flags * in its physics hooks flags. * - * @param context - The context providing information and access to the contacts to modify. + * @param context - The raw context providing information and access to the contacts to modify. + * Can be used with ContactModificationContext for easier use. */ - modifySolverContacts(context: RawContactModificationContext): void; + modifySolverContacts?(context: RawContactModificationContext): void; } diff --git a/src/pipeline/physics_hooks.rs b/src/pipeline/physics_hooks.rs index e0b1bc2a..9c0b48da 100644 --- a/src/pipeline/physics_hooks.rs +++ b/src/pipeline/physics_hooks.rs @@ -12,7 +12,7 @@ pub struct RawPhysicsHooks { pub this: js_sys::Object, pub filter_contact_pair: js_sys::Function, pub filter_intersection_pair: js_sys::Function, - pub modify_solver_contacts: js_sys::Function, + pub modify_solver_contacts: Option, } // HACK: the RawPhysicsHooks is no longer Send+Sync because the JS objects are @@ -79,6 +79,9 @@ impl PhysicsHooks for RawPhysicsHooks { } fn modify_solver_contacts(&self, ctxt: &mut ContactModificationContext) { + let Some(modify_solver_contacts) = &self.modify_solver_contacts else { + return; + }; let raw_context = RawContactModificationContext { collider1: utils::flat_handle(ctxt.collider1.0), collider2: utils::flat_handle(ctxt.collider2.0), @@ -89,9 +92,7 @@ impl PhysicsHooks for RawPhysicsHooks { normal: ctxt.normal as *mut Vector, user_data: ctxt.user_data as *mut u32, }; - let _ = self - .modify_solver_contacts - .call1(&self.this, &JsValue::from(raw_context)); + let _ = modify_solver_contacts.call1(&self.this, &JsValue::from(raw_context)); } } diff --git a/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs index d7e5ce6b..80151ba7 100644 --- a/src/pipeline/physics_pipeline.rs +++ b/src/pipeline/physics_pipeline.rs @@ -141,7 +141,7 @@ impl RawPhysicsPipeline { hookObject: js_sys::Object, hookFilterContactPair: js_sys::Function, hookFilterIntersectionPair: js_sys::Function, - hookModifySolverContacts: js_sys::Function, + hookModifySolverContacts: Option, ) { if eventQueue.auto_drain { eventQueue.clear();