Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 40 additions & 4 deletions crates/rmg-geom/src/temporal/timespan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,49 @@ impl Timespan {

/// Computes a conservative fat AABB for a collider with local-space `shape` AABB.
///
/// The fat box is defined as the union of the shape’s AABBs at the start and
/// end transforms. This is conservative for linear motion and suffices for
/// broad-phase pairing and CCD triggering.
/// Policy (deterministic): unions the AABBs at three sample poses — start (t=0),
/// midpoint (t=0.5), and end (t=1). This strictly contains pure translations
/// and captures protrusions that can occur at `t≈0.5` during rotations about
/// an off‑centre pivot, which a start/end‑only union can miss.
///
/// Sampling count is fixed (3) for determinism; future work may make the
/// sampling policy configurable while keeping results identical across peers.
#[must_use]
pub fn fat_aabb(&self, shape: &Aabb) -> Aabb {
let a0 = shape.transformed(&self.start.to_mat4());
let a1 = shape.transformed(&self.end.to_mat4());
a0.union(&a1)

// Midpoint transform via linear interp of translation/scale and
// normalized-linear blend of rotation (nlerp), then convert to Mat4.
let t0 = self.start.translation().to_array();
let t1 = self.end.translation().to_array();
let tm = rmg_core::math::Vec3::new(
0.5 * (t0[0] + t1[0]),
0.5 * (t0[1] + t1[1]),
0.5 * (t0[2] + t1[2]),
);

let s0 = self.start.scale().to_array();
let s1 = self.end.scale().to_array();
let sm = rmg_core::math::Vec3::new(
0.5 * (s0[0] + s1[0]),
0.5 * (s0[1] + s1[1]),
0.5 * (s0[2] + s1[2]),
);

let q0 = self.start.rotation().to_array();
let q1 = self.end.rotation().to_array();
let qm = rmg_core::math::Quat::new(
0.5 * (q0[0] + q1[0]),
0.5 * (q0[1] + q1[1]),
0.5 * (q0[2] + q1[2]),
0.5 * (q0[3] + q1[3]),
)
.normalize();

let mid_tf = crate::types::transform::Transform::new(tm, qm, sm);
let am = shape.transformed(&mid_tf.to_mat4());

a0.union(&a1).union(&am)
}
}
47 changes: 47 additions & 0 deletions crates/rmg-geom/tests/geom_broad_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,50 @@ fn broad_phase_pair_order_is_deterministic() {
// Expected canonical order: (0,1), (0,3), (1,3)
assert_eq!(pairs, vec![(0, 1), (0, 3), (1, 3)]);
}

#[test]
fn fat_aabb_covers_mid_rotation_with_offset() {
use core::f32::consts::FRAC_PI_2;
// Local shape: rod from x=0..2 (center at (1,0,0)) with small thickness
let local =
Aabb::from_center_half_extents(rmg_core::math::Vec3::new(1.0, 0.0, 0.0), 1.0, 0.1, 0.1);

let t0 = Transform::new(
rmg_core::math::Vec3::new(0.0, 0.0, 0.0),
rmg_core::math::Quat::identity(),
rmg_core::math::Vec3::new(1.0, 1.0, 1.0),
);
let t1 = Transform::new(
rmg_core::math::Vec3::new(0.0, 0.0, 0.0),
rmg_core::math::Quat::from_axis_angle(rmg_core::math::Vec3::new(0.0, 0.0, 1.0), FRAC_PI_2),
rmg_core::math::Vec3::new(1.0, 1.0, 1.0),
);
let span = Timespan::new(t0, t1);

// Compute mid pose explicitly (45°); this can protrude beyond both endpoints.
let mid_rot = rmg_core::math::Quat::from_axis_angle(
rmg_core::math::Vec3::new(0.0, 0.0, 1.0),
FRAC_PI_2 * 0.5,
);
let mid = Transform::new(
rmg_core::math::Vec3::new(0.0, 0.0, 0.0),
mid_rot,
rmg_core::math::Vec3::new(1.0, 1.0, 1.0),
);
let mid_aabb = local.transformed(&mid.to_mat4());

let fat = span.fat_aabb(&local);
let fmin = fat.min().to_array();
let fmax = fat.max().to_array();
let mmin = mid_aabb.min().to_array();
let mmax = mid_aabb.max().to_array();

assert!(
fmin[0] <= mmin[0] && fmin[1] <= mmin[1] && fmin[2] <= mmin[2],
"fat min must enclose mid min: fat={fmin:?} mid={mmin:?}"
);
assert!(
fmax[0] >= mmax[0] && fmax[1] >= mmax[1] && fmax[2] >= mmax[2],
"fat max must enclose mid max: fat={fmax:?} mid={mmax:?}"
);
}
19 changes: 13 additions & 6 deletions docs/decision-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
| 2025-10-27 | PR #7 prep | Extracted math + engine spike into `rmg-core` (split-core-math-engine); added inline rustdoc on canonical snapshot hashing (node/edge order, payload encoding). | Land the isolated, reviewable portion now; keep larger geometry/broad‑phase work split for follow-ups. | After docs update, run fmt/clippy/tests; merge is a fast‑forward over `origin/main`. |
| 2025-10-28 | PR #7 merged | Reachability-only snapshot hashing; ports demo registers rule; guarded ports footprint; scheduler `finalize_tx()` clears `pending`; `PortKey` u30 mask; hooks+CI hardened (toolchain pin, rustdoc fixes). | Determinism + memory hygiene; remove test footguns; pass CI with stable toolchain while keeping rmg-core MSRV=1.68. | Queued follow-ups: #13 (Mat4 canonical zero + MulAssign), #14 (geom train), #15 (devcontainer). |
| 2025-10-27 | MWMR reserve gate | Engine calls `scheduler.finalize_tx()` at commit; compact rule id used on execute path; per‑tx telemetry summary behind feature. | Enforce independence and clear active frontier deterministically; keep ordering stable with `(scope_hash, family_id)`. | Toolchain pinned to Rust 1.68; add design note for telemetry graph snapshot replay. |
## 2025-10-28 — Geometry merge train (PR #14)

- Decision: Use an integration branch to validate #8 (geom foundation) + #9 (broad-phase AABB) together.
- Rationale: Surface cross-PR interactions early and avoid rebase/force push; adhere to merge-only policy.
- Impact: New crate `rmg-geom` (AABB, Transform, TemporalTransform) and baseline broad-phase with tests. No public API breaks in core.
- Next: If green, merge train PR; close individual PRs as merged-via-train.

## 2025-10-28 — rmg-geom foundation (PR #8) compile + clippy fixes

Expand Down Expand Up @@ -55,12 +61,6 @@

- Decision: Raise the workspace floor (MSRV) to Rust 1.71.1. All crates and CI jobs target 1.71.1.
- Implementation: Updated `rust-toolchain.toml` to 1.71.1; bumped `rust-version` in crate manifests; CI jobs pin 1.71.1; devcontainer installs only 1.71.1.
## 2025-10-29 — Docs E2E carousel init (PR #10)

- Context: Playwright tour test clicks Next to enter carousel from "all" mode.
- Decision: Do not disable Prev/Next in "all" mode; allow navigation buttons to toggle into carousel.
- Change: docs/assets/collision/animate.js leaves Prev/Next enabled in 'all'; boundary disabling still applies in single-slide mode.
- Consequence: Users can initiate the carousel via navigation controls; E2E tour test passes deterministically.

## 2025-10-29 — Docs E2E carousel init (PR #10)

Expand All @@ -84,3 +84,10 @@
- Decision: Pre-commit runs `cargo fmt --all -- --check` whenever staged Rust files are detected. Retain the PRNG coupling guard but remove the unconditional early exit so formatting still runs when the PRNG file isn’t staged.
- EditorConfig: normalize line endings (LF), ensure final newline, trim trailing whitespace, set 2-space indent for JS/TS/JSON and 4-space for Rust.
- Consequence: Developers get immediate feedback on formatting; cleaner diffs and fewer CI round-trips.

## 2025-10-29 — Geom fat AABB bounds mid-rotation

- Context: Broad-phase must not miss overlaps when a shape rotates about an off‑centre pivot; union of endpoint AABBs can under‑approximate mid‑tick extents.
- Decision: `Timespan::fat_aabb` now unions AABBs at start, mid (t=0.5 via nlerp for rotation, lerp for translation/scale), and end. Sampling count is fixed (3) for determinism.
- Change: Implement midpoint sampling in `crates/rmg-geom/src/temporal/timespan.rs`; add test `fat_aabb_covers_mid_rotation_with_offset` to ensure mid‑pose is enclosed.
- Consequence: Deterministic and more conservative broad‑phase bounds for typical rotation cases without introducing policy/config surface yet; future work may expose a configurable sampling policy.
5 changes: 5 additions & 0 deletions docs/execution-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s

## Today’s Intent

> 2025-10-29 — Geom fat AABB midpoint sampling (merge-train)

- Update `rmg-geom::temporal::Timespan::fat_aabb` to union AABBs at start, mid (t=0.5), and end to conservatively bound rotations about off‑centre pivots.
- Add test `fat_aabb_covers_mid_rotation_with_offset` to verify the fat box encloses the mid‑pose AABB.

> 2025-10-29 — Hooks formatting gate (PR #12)

- Pre-commit: add rustfmt check for staged Rust files (`cargo fmt --all -- --check`).
Expand Down