Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b989b8b
2026.8.21.21.27
baka4n Aug 21, 2026
02f6bea
Update physics_world.rs
baka4n Aug 21, 2026
87f7521
2026.8.21.23.50
baka4n Aug 21, 2026
965fb06
2026.8.22.0.1 fork fixes: #778 cylinder axis, #488 KCC moving platfor…
baka4n Aug 22, 2026
bf65a5c
2026.8.22.0.2 issue #181 set_max_linear/angular_velocity: RigidBody/R…
baka4n Aug 22, 2026
6e98d06
2026.8.22.0.3 issue #457 joint query/force: GenericJoint::position()/…
baka4n Aug 22, 2026
c6d6cc8
2026.8.22.0.4 issue #548 CCD point of impact: SweepToiOutput witness …
baka4n Aug 22, 2026
894d34e
2026.8.22.18.20
baka4n Aug 22, 2026
372d125
2026.8.22.20.27 force containers: contact solver bridge (observation-…
baka4n Aug 22, 2026
079afe5
git link parry
baka4n Aug 22, 2026
085ad95
add submodule nalgebra
baka4n Aug 22, 2026
731d9e0
remove submodule
baka4n Aug 22, 2026
dc81809
rapier issues + force lifecycle (#488 #984 #993 #223 #778 #548 #457 #…
baka4n Aug 23, 2026
9383b01
2026.8.23 parallel: wake per-body reset (P1) + sleep-scan two-pass ra…
baka4n Aug 23, 2026
3552202
2026.8.23 linalg: DVector foundation (bit-identical dot/component_mul…
baka4n Aug 23, 2026
5c42a68
2026.8.23 linalg: DMatrix + bit-identical gemm (matrixmultiply large …
baka4n Aug 23, 2026
5947fcf
2026.8.23 linalg: P4 LU partial-pivot (bit-identical to nalgebra) + t…
baka4n Aug 23, 2026
3e43e36
2026.8.24 linalg: P5-support API (from_fn/From<Vec>/rows_owned/set_co…
baka4n Aug 24, 2026
a73e62b
2026.8.24 soft_body: Phase 0a 软体数据结构 + 质点/弹簧 Hooke 力 + 半隐式 Euler 积分器 …
baka4n Aug 24, 2026
cb0ab91
2026.8.24 soft_body: Phase 0b 接入 World — PhysicsWorld 持有 SoftBodySet …
baka4n Aug 24, 2026
e0cbd6a
2026.8.24 soft_body: Phase 2 弹簧力路由进 force_containers — SoftParticle.b…
baka4n Aug 24, 2026
e7fa3c1
2026.8.24 soft_body: Phase 3 可变形网格 XPBD (路线 C MVP) — SoftSolver 枚举 + …
baka4n Aug 24, 2026
4c7f472
2026.8.25 soft_body: Phase 5b 支持软体删除/生命周期(SoftBodySet.bodies->Vec<Opt…
baka4n Aug 25, 2026
fac25d1
2026.8.25 soft_body: Phase 5f 碰撞耦合最小改动 — SoftBody.collide 标志 + step 跳…
baka4n Aug 25, 2026
f89d93f
2026.8.24 soft_body: Phase 6 布料软体 (cloth) — SoftBody.triangles 字段 + a…
baka4n Aug 25, 2026
6a9d56a
2026.8.25 Phase 7 软体风场/空气阻力 + 休眠/诊断: SoftBody.wind 字段 + Wind 结构 + app…
baka4n Aug 25, 2026
fa97b0e
2026.8.25 Phase 8 锚定任意刚体: SoftParticle.bound_local + attach_particle/…
baka4n Aug 26, 2026
370155e
2026.8.25 Phase 9 撕裂: SoftBody.tear_strain + set_tear_strain + tear()…
baka4n Aug 26, 2026
971f082
2026.8.25 Phase 10 塑性变形: SoftBody.plasticity + PlasticityParams{yield…
baka4n Aug 26, 2026
3576fca
2026.8.25 Phase 11 充气/气压: SoftBody.pressure + set_pressure/clear_pres…
baka4n Aug 26, 2026
233f970
2026.8.26 Phase 12 自碰撞: SoftBody.self_collision + SelfCollisionParams…
baka4n Aug 26, 2026
d75ed3f
2026.8.26 Phase 13 逐约束刚度: XPBD step_xpbd 改为逐约束读取 compliance(alpha 按 c…
baka4n Aug 26, 2026
0e3f3eb
2026.8.26 Phase 14 软软碰撞(cross-body): SoftBody.cross_collision: Option…
baka4n Aug 26, 2026
ba02c24
2026.8.26 Phase 16 体积守恒: SoftBody.volume_conservation: Option<Real> +…
baka4n Aug 26, 2026
8b41e6b
2026.8.26 Phase 17 软体间黏连: SoftBody.cohesion: Option<CohesionParams>{r…
baka4n Aug 26, 2026
d344cff
2026.8.26 Phase 18 结构阻尼: SoftBody.damping: Real + set_damping; step_x…
baka4n Aug 26, 2026
9ddba73
2026.8.26 Phase 19 各向异性柔度: DistanceConstraint 加 compression 字段(压缩侧柔度)…
baka4n Aug 26, 2026
0590b96
2026.8.26 Phase 20 软软摩擦: SelfCollisionParams 加 friction 字段(0≤μ≤1); se…
baka4n Aug 26, 2026
d7dd50d
2026.8.26 Phase 21 自适应四面体细分: subdivide_tetrahedra (1→4 重心细分); 仅细分最长边>…
baka4n Aug 26, 2026
31fc638
Update soft_body.rs
baka4n Aug 26, 2026
203ec6e
2026.8.27 Phase 24 软体 substeps: SoftBody 加 substeps:u32(默认 1), step()…
baka4n Aug 27, 2026
b5d33fc
2026.8.27 Phase 27 软体断裂力学撕裂准则+体级正交各向异性
baka4n Aug 27, 2026
2694353
2026.8.27 Phase 27 软体黏弹性率相关本构 + 均匀温度场
baka4n Aug 27, 2026
fd9a856
2026.8.28 Phase 27 隐式 backward-Euler 参考求解器(B8 比较路径) - SoftBody::step_…
baka4n Aug 27, 2026
9705ff7
Phase 29 soft body corotated linear elasticity: per-tet shape matchin…
baka4n Aug 28, 2026
1a9b2d9
Phase 30 soft body Neo-Hookean logarithmic volume energy: neo_hookean…
baka4n Aug 29, 2026
22a072d
Phase 31 软体主动应变/肌肉收缩 (active strain): Spring/DistanceConstraint 加 act…
baka4n Aug 29, 2026
c2f01da
Phase 32 肌纤维走向 (muscle fibre direction): Spring/DistanceConstraint 加 …
baka4n Aug 30, 2026
f8de028
Merge pull request #1 from dimforge/master
baka4n Aug 30, 2026
3cb18e8
Phase 35 流体软体 SPH 状态层 (fluid body): 新增 src/dynamics/fluid.rs (FluidPa…
baka4n Aug 30, 2026
09ca6ca
granular add
baka4n Sep 2, 2026
8ba670c
Phase 38 granular: uniform spatial-hash broad-phase (cell=2·r_max, 27…
baka4n Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "parry"]
path = parry
url = https://github.com/Polari-Stars-MC/parry.git
12 changes: 7 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ default-members = [
]
# `typescript/` is the JS/Wasm bindings' own Cargo workspace; keep it out of
# this one so root-level `cargo` commands never try to absorb its crates.
exclude = ["typescript"]
exclude = ["typescript", "parry", "nalgebra"]
resolver = "2"

[workspace.package]
Expand Down Expand Up @@ -69,13 +69,15 @@ nalgebra = { version = "0.35", default-features = false, features = ["macros"] }
glamx = { version = "0.3", default-features = false }
simba = { version = "0.10.2", default-features = false }
num-traits = { version = "0.2", default-features = false }
# Bit-identical gemm backend (same crate nalgebra delegates to for dynamic matrices).
matrixmultiply = "0.3"
approx = { version = "0.5", default-features = false }

# Parry (each crate picks its own variant)
parry2d = { version = "0.30.2", default-features = false, features = ["required-features"] }
parry3d = { version = "0.30.2", default-features = false, features = ["required-features"] }
parry2d-f64 = { version = "0.30.2", default-features = false, features = ["required-features"] }
parry3d-f64 = { version = "0.30.2", default-features = false, features = ["required-features"] }
parry2d = { package = "parry2d", path = "parry/crates/parry2d", default-features = false, features = ["required-features"] }
parry3d = { package = "parry3d", path = "parry/crates/parry3d", default-features = false, features = ["required-features"] }
parry2d-f64 = { package = "parry2d-f64", path = "parry/crates/parry2d-f64", default-features = false, features = ["required-features"] }
parry3d-f64 = { package = "parry3d-f64", path = "parry/crates/parry3d-f64", default-features = false, features = ["required-features"] }

# Utilities
arrayvec = { version = "0.7", default-features = false }
Expand Down
2 changes: 2 additions & 0 deletions crates/rapier2d-f64/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ serde-serialize = [
"nalgebra/serde-serialize",
"parry2d-f64/serde-serialize",
"dep:serde",
"dep:bincode",
"std",
]
enhanced-determinism = ["simba/libm_force", "parry2d-f64/enhanced-determinism"]
Expand Down Expand Up @@ -104,6 +105,7 @@ web-time = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
bytemuck = { workspace = true, optional = true }
bincode = { workspace = true, optional = true }

# Nalgebra is only included on non-spirv targets. On spirv, rapier exposes only the
# subset of its API that doesn't require nalgebra.
Expand Down
2 changes: 2 additions & 0 deletions crates/rapier2d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ serde-serialize = [
"nalgebra/serde-serialize",
"parry2d/serde-serialize",
"dep:serde",
"dep:bincode",
"std",
]
enhanced-determinism = ["simba/libm_force", "parry2d/enhanced-determinism"]
Expand Down Expand Up @@ -111,6 +112,7 @@ web-time = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
bytemuck = { workspace = true, optional = true }
bincode = { workspace = true, optional = true }

# Nalgebra is only included on non-spirv targets. On spirv, rapier exposes only the
# subset of its API that doesn't require nalgebra.
Expand Down
3 changes: 3 additions & 0 deletions crates/rapier3d-f64/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ serde-serialize = [
"nalgebra/serde-serialize",
"parry3d-f64/serde-serialize",
"dep:serde",
"dep:bincode",
"std",
]
enhanced-determinism = ["simba/libm_force", "parry3d-f64/enhanced-determinism"]
Expand Down Expand Up @@ -105,12 +106,14 @@ web-time = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
bytemuck = { workspace = true, optional = true }
bincode = { workspace = true, optional = true }

# Nalgebra is only included on non-spirv targets. On spirv, rapier exposes only the
# subset of its API that doesn't require nalgebra.
[target.'cfg(not(target_arch = "spirv"))'.dependencies]
nalgebra = { workspace = true }
glamx = { workspace = true, features = ["nalgebra"] }
matrixmultiply = { workspace = true }

[dev-dependencies]
bincode.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions crates/rapier3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ serde-serialize = [
"nalgebra/serde-serialize",
"parry3d/serde-serialize",
"dep:serde",
"dep:bincode",
"std",
]
enhanced-determinism = ["simba/libm_force", "parry3d/enhanced-determinism"]
Expand Down Expand Up @@ -111,6 +112,7 @@ web-time = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
bytemuck = { workspace = true, optional = true }
bincode = { workspace = true, optional = true }

# Nalgebra is only included on non-spirv targets. On spirv, rapier exposes only the
# subset of its API that doesn't require nalgebra.
Expand Down
1 change: 1 addition & 0 deletions parry
Submodule parry added at 1be4b1
169 changes: 167 additions & 2 deletions src/control/character_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,33 @@ impl KinematicCharacterController {
.contact(&pos12, character_shape, collider.shape(), 0.0)
{
if contact.dist < -1.0e-5 {
// A negative `dist` means the character overlaps the collider. This can
// happen because (a) the character was pushed into a static obstacle, or
// (b) a *moving* (kinematic) platform advanced into the character this
// step. For (b) — the heart of issue #488 — the overlap is the platform's
// motion, and we must resolve the *full* penetration here so the character
// is carried with the platform instead of lagging a frame (a visible dip)
// or, once the platform moves faster than `max_correction` per step,
// tunneling straight through. So contacts against a kinematic parent are
// depenetrated without the height-based correction cap.
let is_kinematic = collider
.parent
.and_then(|p| queries.bodies.get(p.handle))
.map(|rb| rb.is_kinematic())
.unwrap_or(false);
let correction_budget = if is_kinematic {
offset - contact.dist // uncapped: resolve the full overlap
} else {
max_correction - applied
};

// Push out until the usual `offset` gap is restored.
let push = (offset - contact.dist).min(max_correction - applied);
let push = (offset - contact.dist).min(correction_budget);
if push <= 0.0 {
return; // The per-call correction budget is exhausted.
if !is_kinematic {
return; // The per-call correction budget is exhausted.
}
continue;
}

// `normal1` (expressed in the character’s local frame) points towards
Expand Down Expand Up @@ -1375,3 +1398,145 @@ mod test {
assert!(movement.grounded);
}
}

#[cfg(feature = "dim3")]
#[cfg(test)]
mod moving_platform_tests {
use crate::control::KinematicCharacterController;
use crate::prelude::*;

#[test]
fn character_controller_moving_platform() {
// Regression test for issue #488: a kinematic character controller that stands
// still on top of a *moving* kinematic platform must be carried along with the
// platform (no dip, no fall-through). When the platform moves into the character
// it creates penetration that `check_and_fix_penetrations` must resolve in full;
// if that resolution is capped (as it is for static obstacles) a fast platform
// leaves the character behind until it tunnels straight through.
fn run(platform_speed: Real) {
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut pipeline = PhysicsPipeline::new();
let mut bf = BroadPhaseBvh::new();
let mut nf = NarrowPhase::new();
let mut islands = IslandManager::new();
let mut bodies = RigidBodySet::new();

let gravity = Vector::ZERO; // disable gravity; we drive the platform by hand
let integration_parameters = IntegrationParameters::default();
let dt = integration_parameters.dt;

// Vertical kinematic platform: a thin slab centered at y = 0.
let platform_half_height = 0.5;
let platform = RigidBodyBuilder::kinematic_position_based()
.translation(Vector::new(0.0, 0.0, 0.0))
.build();
let platform_handle = bodies.insert(platform);
colliders.insert_with_parent(
ColliderBuilder::cuboid(5.0, platform_half_height, 5.0),
platform_handle,
&mut bodies,
);

// Character: a ball of radius 0.5 resting on top of the platform.
let character = RigidBodyBuilder::kinematic_position_based()
.translation(Vector::new(0.0, platform_half_height + 0.5, 0.0))
.build();
let character_handle = bodies.insert(character);
let character_collider = ColliderBuilder::ball(0.5).build();
let character_shape = character_collider.shape();
colliders.insert_with_parent(character_collider.clone(), character_handle, &mut bodies);

let controller = KinematicCharacterController {
slide: true,
..Default::default()
};

// Track the character's world position explicitly: after `set_next_kinematic_translation`
// the body's `position()` only updates on the next `step`, so read it from our own copy.
// The controller keeps a small `offset` gap above the platform, so we assert on the
// *per-step displacement* (it must match the platform's motion) and that the character
// never sinks below the platform's top face (the #488 failure: fall-through).
let mut char_pos = *bodies.get(character_handle).unwrap().position();
let mut prev_char_y = char_pos.translation.y;
for i in 0..120 {
let expected_platform_y = platform_speed * dt * (i as Real + 1.0);
bodies
.get_mut(platform_handle)
.unwrap()
.set_next_kinematic_translation(Vector::new(0.0, expected_platform_y, 0.0));

pipeline.step(
gravity,
&integration_parameters,
&mut islands,
&mut bf,
&mut nf,
&mut bodies,
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
&mut CCDSolver::new(),
&(),
&(),
);

let filter = QueryFilter::new().exclude_rigid_body(character_handle);
let query_pipeline =
bf.as_query_pipeline(nf.query_dispatcher(), &bodies, &colliders, filter);

// Stationary character: zero desired translation.
let movement = controller.move_shape(
dt,
&query_pipeline,
character_shape,
&char_pos,
Vector::ZERO,
|_| {},
);

char_pos.translation += movement.translation;
bodies
.get_mut(character_handle)
.unwrap()
.set_next_kinematic_translation(char_pos.translation);

let step_delta = char_pos.translation.y - prev_char_y;
let expected_delta = platform_speed * dt;
// The character must follow the platform's per-step motion (issue #488: a lagging
// character dips and, for a fast platform, falls through entirely).
assert!(
(step_delta - expected_delta).abs() < 2.0e-2,
"character should track platform motion (speed {}, iter {}): delta={}, expected={}",
platform_speed,
i,
step_delta,
expected_delta
);
// And it must never sink below the platform's top face.
let platform_top = expected_platform_y + platform_half_height;
assert!(
char_pos.translation.y >= platform_top - 1.0e-3,
"character fell through platform (speed {}, iter {}): char_y={}, top={}",
platform_speed,
i,
char_pos.translation.y,
platform_top
);
assert!(
movement.grounded,
"character must stay grounded (speed {}, iter {})",
platform_speed, i
);
prev_char_y = char_pos.translation.y;
}
}

// Normal platform (already worked before the fix).
run(2.0);
// Fast platform: 10 m/s * dt (~0.016s) ≈ 0.16 m/step, which exceeds the old
// `max_correction = height * 0.25` cap for this character (would tunnel).
run(10.0);
}
}
Loading