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..13c6c130 100644 --- a/src.ts/pipeline/physics_hooks.ts +++ b/src.ts/pipeline/physics_hooks.ts @@ -1,11 +1,13 @@ -import {RigidBodyHandle} from "../dynamics"; -import {ColliderHandle} from "../geometry"; +import { RawContactManifold, RawContactModificationContext } from "../raw"; +import { RigidBodyHandle } from "../dynamics"; +import { ColliderHandle } from "../geometry"; +import { Vector, VectorOps } from "../math"; 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 +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 @@ -51,4 +341,16 @@ 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 raw context providing information and access to the contacts to modify. + * Can be used with ContactModificationContext for easier use. + */ + modifySolverContacts?(context: RawContactModificationContext): 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/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 2bbc8034..9c0b48da 100644 --- a/src/pipeline/physics_hooks.rs +++ b/src/pipeline/physics_hooks.rs @@ -1,13 +1,18 @@ -use crate::utils; +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}; +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: Option, } // HACK: the RawPhysicsHooks is no longer Send+Sync because the JS objects are @@ -73,47 +78,64 @@ impl PhysicsHooks for RawPhysicsHooks { .unwrap_or(false) } - fn modify_solver_contacts(&self, _ctxt: &mut ContactModificationContext) {} + 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), + 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 _ = 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 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, } #[wasm_bindgen] impl RawContactModificationContext { - pub fn collider1(&self) -> u32 { + // Simple getters and setters for the fields. + pub fn collider1(&self) -> FlatHandle { self.collider1 } - pub fn collider2(&self) -> u32 { + pub fn collider2(&self) -> FlatHandle { 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) } } #[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(); } } @@ -129,6 +151,7 @@ impl RawContactModificationContext { } } + // Solver contacts manipulation methods. pub fn num_solver_contacts(&self) -> usize { unsafe { (*self.solver_contacts).len() } } @@ -147,7 +170,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 +178,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 +186,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 +195,159 @@ 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() } } } + + 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) + } + + /// 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/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs index 287cfeea..80151ba7 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: Option, ) { 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( 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..ee2fc507 --- /dev/null +++ b/testbed2d/src/demos/oneWayPlatforms.ts @@ -0,0 +1,126 @@ +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: ( + rawContext: any, + ) => { + const context = new RAPIER.ContactModificationContext(rawContext); + 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.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) + ? -12.0 + : 12.0; + + for (let i = 0; i < context.numSolverContacts; ++i) { + context.setSolverContactTangentVelocity( + i, + { + 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],