// 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.
///|
pub struct CCDSolver {
priv marker : Unit
}
///|
pub fn CCDSolver::CCDSolver() -> CCDSolver {
let value = { marker: () }
value.marker |> ignore
value
}
///|
pub fn CCDSolver::clamp_motions(
self : CCDSolver,
dt : @core.Real,
bodies : RigidBodySet,
impacts : PredictedImpacts,
) -> Unit {
self |> ignore
if impacts is Impacts(tois) {
for i in 0.. 0.0F {
let value = ccd.ccd_thickness * 0.15F / max_vel
if value < dt {
value
} else {
dt
}
} else {
dt
}
let effective_toi = if toi > min_toi { toi } else { min_toi }
let local_com = body.mass_props.local_mprops.center_of_mass
body.position.next_position = body.vels.integrate(
effective_toi,
body.position.position,
local_com,
)
}
}
}
}
///|
pub fn CCDSolver::clamp_motions3(
self : CCDSolver,
dt : @core.Real,
bodies : RigidBodySet3,
impacts : PredictedImpacts,
) -> Unit {
self.clamp_motions(dt, bodies.inner, impacts)
}
///|
pub fn CCDSolver::update_ccd_active_flags(
self : CCDSolver,
islands : IslandManager,
bodies : RigidBodySet,
dt : @core.Real,
include_forces : Bool,
) -> Bool {
self |> ignore
let mut any_active = false
for handle in islands.active_bodies() {
if bodies.get_mut_internal(handle) is Some(body) {
let ccd = body.ccd
let active = if ccd.ccd_enabled {
let forces = if include_forces { Some(body.forces) } else { None }
ccd.is_moving_fast(dt, body.vels, forces)
} else {
false
}
let updated = RigidBodyCcd::{
ccd_thickness: ccd.ccd_thickness,
ccd_max_dist: ccd.ccd_max_dist,
ccd_active: active,
ccd_enabled: ccd.ccd_enabled,
soft_ccd_prediction: ccd.soft_ccd_prediction,
}
body.ccd = updated
if active {
any_active = true
}
}
}
any_active
}
///|
pub fn CCDSolver::update_ccd_active_flags3(
self : CCDSolver,
islands : IslandManager3,
bodies : RigidBodySet3,
dt : @core.Real,
include_forces : Bool,
) -> Bool {
self.update_ccd_active_flags(islands.inner, bodies.inner, dt, include_forces)
}
///|
/// Predicts potential impacts if all bodies moved to their `next_position`.
///
/// This port currently does not perform CCD shape-casts against colliders here, so it returns
/// `NoImpacts`. It is still exposed for pub parity.
pub fn CCDSolver::predict_impacts_at_next_positions(
self : CCDSolver,
_bodies : RigidBodySet,
_dt : @core.Real,
) -> PredictedImpacts {
self |> ignore
NoImpacts
}
///|
/// Find the first time-of-impact among the predicted impacts.
///
/// This port currently does not compute CCD impacts, so this always returns `None`.
pub fn CCDSolver::find_first_impact(
self : CCDSolver,
impacts : PredictedImpacts,
) -> (RigidBodyHandle, @core.Real)? {
self |> ignore
match impacts {
Impacts(tois) =>
if tois.length() == 0 {
None
} else {
// Keep the earliest toi.
let mut best = tois[0]
for i in 1.. None
}
}