// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Rapier public-surface parity shims for the narrow-phase and contact-pair APIs.
///
/// Notes:
/// - This file focuses on making all Rapier `pub` items (default features) reachable
/// from this MoonBit port. Some advanced low-level fields (solver warmstart data,
/// SIMD contact layout, etc.) are currently exposed for API parity but are not
/// yet fully integrated with the solver in this port.
///|
/// Equivalent to Rapier's `geometry::Contact` (`TrackedContact`).
///
/// This port's 2D narrow-phase currently exposes `ContactManifoldPoint` as the
/// primary per-contact record, so we alias `Contact` to that type.
pub type Contact = ContactManifoldPoint
///|
/// Equivalent to Rapier's `geometry::contact_pair::ContactData`.
pub(all) struct ContactData {
impulse : @core.Real
tangent_impulse : @core.Vec2
warmstart_impulse : @core.Real
warmstart_tangent_impulse : @core.Vec2
// Rapier's dim3-only `warmstart_twist_impulse`.
warmstart_twist_impulse : @core.Real
}
///|
pub fn ContactData::default() -> ContactData {
{
impulse: 0.0F,
tangent_impulse: @core.Vec2::zero(),
warmstart_impulse: 0.0F,
warmstart_tangent_impulse: @core.Vec2::zero(),
warmstart_twist_impulse: 0.0F,
}
}
///|
pub fn ContactData::ContactData(
impulse : @core.Real,
tangent_impulse : @core.Vec2,
warmstart_impulse : @core.Real,
warmstart_tangent_impulse : @core.Vec2,
warmstart_twist_impulse : @core.Real,
) -> ContactData {
{
impulse,
tangent_impulse,
warmstart_impulse,
warmstart_tangent_impulse,
warmstart_twist_impulse,
}
}
///|
/// Equivalent to Rapier's `geometry::contact_pair::SolverContactGeneric`.
///
/// This is exposed primarily for pub-surface parity. The solver warmstart fields
/// are currently not written by the narrow-phase in this port.
pub struct SolverContactGeneric {
point : @core.Vec2
dist : @core.Real
friction : @core.Real
restitution : @core.Real
tangent_velocity : @core.Vec2
warmstart_impulse : @core.Real
warmstart_tangent_impulse : @core.Vec2
warmstart_twist_impulse : @core.Real
// Rapier uses a scalar for SIMD friendliness; we keep it as a scalar too.
is_new : @core.Real
// Rapier stores a fixed-size lane array; this port uses a dynamic array.
contact_id : Array[Int]
}
///|
pub fn SolverContactGeneric::default() -> SolverContactGeneric {
{
point: @core.Vec2::zero(),
dist: 0.0F,
friction: 0.0F,
restitution: 0.0F,
tangent_velocity: @core.Vec2::zero(),
warmstart_impulse: 0.0F,
warmstart_tangent_impulse: @core.Vec2::zero(),
warmstart_twist_impulse: 0.0F,
is_new: 1.0F,
contact_id: [],
}
}
///|
/// Equivalent to Rapier's SIMD solver-contact alias.
///
/// This port does not currently implement SIMD storage; we use the same record type.
pub type SimdSolverContact = SolverContactGeneric
///|
/// Equivalent to Rapier's `geometry::contact_pair::ContactManifoldData`.
pub struct ContactManifoldData {
solver_flags : SolverFlags
solver_contacts : Array[SolverContactGeneric]
relative_dominance : Int
}
///|
pub fn ContactManifoldData::default() -> ContactManifoldData {
{
solver_flags: SolverFlags::default(),
solver_contacts: [],
relative_dominance: 0,
}
}
///|
pub fn ContactManifoldData::num_active_contacts(
self : ContactManifoldData,
) -> Int {
self.solver_contacts.length()
}
///|
pub fn ContactPair::has_any_active_contact(self : ContactPair) -> Bool {
// This port currently doesn't build solver contacts, so we treat any manifold point
// as an "active" contact.
for i in 0.. 0 {
return true
}
}
false
}
///|
pub fn ContactPair::total_impulse(self : ContactPair) -> @core.Vec2 {
fn vec2_scale(v : @core.Vec2, s : @core.Real) -> @core.Vec2 {
Vec2(v.x * s, v.y * s)
}
let mut out = @core.Vec2::zero()
let manifolds = self.manifolds()
for mi in 0.. @core.Real {
let manifolds = self.manifolds()
let mut out = 0.0F
for mi in 0.. (@core.Real, @core.Vec2) {
let manifolds = self.manifolds()
let mut best = 0.0F
let mut best_dir = @core.Vec2::zero()
for mi in 0.. best {
best = total
best_dir = m.normal()
}
}
(best, best_dir)
}
///|
pub fn ContactPair::find_deepest_contact(
self : ContactPair,
) -> (ContactManifold, Contact)? {
let mut found = false
let mut best_m = ContactManifold::{ normal: @core.Vec2::zero(), points: [] }
let mut best_c = ContactManifoldPoint::{
local_p1: @core.Vec2::zero(),
local_p2: @core.Vec2::zero(),
dist: 0.0F,
fid1: Unknown,
fid2: Unknown,
data: ContactData::default(),
}
let mut best_dist = 0.0F
for mi in 0.. ContactForceEvent {
fn vec2_scale(v : @core.Vec2, s : @core.Real) -> @core.Vec2 {
Vec2(v.x * s, v.y * s)
}
if dt <= 0.0F {
return ContactForceEvent(
collider1,
collider2,
@core.Vec2::zero(),
total_force_magnitude,
@core.Vec2::zero(),
0.0F,
)
}
let mut total_impulse = @core.Vec2::zero()
let mut max_impulse = 0.0F
let mut max_dir = @core.Vec2::zero()
let manifolds = pair.manifolds()
for mi in 0.. max_impulse {
max_impulse = imp
max_dir = m.normal()
}
}
total_impulse = total_impulse.add(vec2_scale(m.normal(), manifold_impulse))
}
let inv_dt = 1.0F / dt
ContactForceEvent(
collider1,
collider2,
vec2_scale(total_impulse, inv_dt),
total_force_magnitude,
max_dir,
max_impulse * inv_dt,
)
}
///|
/// Equivalent broad-phase type alias for Rapier's `DefaultBroadPhase`.
pub type DefaultBroadPhase = BroadPhaseBvh
///|
/// Index types used by Rapier's interaction graphs.
pub type ColliderGraphIndex = Int
///|
pub type RigidBodyGraphIndex = Int
///|
pub type TemporaryInteractionIndex = Int
///|
/// Minimal interaction-graph surface for pub parity with Rapier.
pub struct InteractionGraph[T] {
interactions : Array[(ColliderHandle, ColliderHandle, T)]
}
///|
pub fn[T] InteractionGraph::InteractionGraph() -> InteractionGraph[T] {
{ interactions: [] }
}
///|
pub fn[T] InteractionGraph::raw_graph(
self : InteractionGraph[T],
) -> Array[(ColliderHandle, ColliderHandle, T)] {
let out : Array[(ColliderHandle, ColliderHandle, T)] = []
for i in 0.. Array[(ColliderHandle, ColliderHandle, T)] {
self.raw_graph()
}
///|
pub fn[T] InteractionGraph::index_interaction(
self : InteractionGraph[T],
a : ColliderHandle,
b : ColliderHandle,
) -> Int? {
for i in 0.. T? {
match self.index_interaction(a, b) {
None => None
Some(i) => Some(self.interactions[i].2)
}
}
///|
/// MoonBit does not currently expose `&mut` references; this returns a copy for parity.
pub fn[T] InteractionGraph::interaction_pair_mut(
self : InteractionGraph[T],
a : ColliderHandle,
b : ColliderHandle,
) -> T? {
self.interaction_pair(a, b)
}
///|
pub fn[T] InteractionGraph::interactions_with(
self : InteractionGraph[T],
collider : ColliderHandle,
) -> Array[(ColliderHandle, ColliderHandle, T)] {
let out : Array[(ColliderHandle, ColliderHandle, T)] = []
for i in 0.. Array[(ColliderHandle, ColliderHandle, T)] {
let out : Array[(ColliderHandle, ColliderHandle, T)] = []
for i in 0.. Array[(ColliderHandle, ColliderHandle, T)] {
// See `interaction_pair_mut` note.
self.interactions_with(collider)
}
///|
fn handle_id_equals(a : ColliderHandle, b : ColliderHandle) -> Bool {
a.id == b.id
}
///|
fn contains_handle_id(
handles : Array[ColliderHandle],
target : ColliderHandle,
) -> Bool {
for i in 0.. InteractionGraph[ContactPair] {
{ interactions: self.contacts }
}
///|
pub fn NarrowPhase::intersection_graph(
self : NarrowPhase,
) -> InteractionGraph[IntersectionPair] {
let out : Array[(ColliderHandle, ColliderHandle, IntersectionPair)] = []
for i in 0.. (ColliderHandle, ColliderHandle, ContactPair)? {
if index < 0 || index >= self.contacts.length() {
None
} else {
Some(self.contacts[index])
}
}
///|
pub fn NarrowPhase::contact_pair_unknown_gen(
self : NarrowPhase,
first : ColliderHandle,
second : ColliderHandle,
) -> ContactPair? {
for i in 0.. Array[(ColliderHandle, ColliderHandle, ContactPair)] {
let out : Array[(ColliderHandle, ColliderHandle, ContactPair)] = []
for i in 0.. IntersectionPair? {
for i in 0.. Array[IntersectionPair] {
let out : Array[IntersectionPair] = []
for i in 0.. Unit {
handle_user_changes_to_colliders(bodies, colliders, modified_colliders)
if modified_colliders.length() == 0 {
return
}
let kept_contacts : Array[(ColliderHandle, ColliderHandle, ContactPair)] = []
for i in 0.. QueryDispatcher {
self.query_dispatcher
}
///|
pub fn NarrowPhase::with_query_dispatcher(
self : NarrowPhase,
dispatcher : QueryDispatcher,
) -> NarrowPhase {
self.query_dispatcher = dispatcher
self
}