Skip to main content

blockworx_geom/
lib.rs

1//! The app's world coordinate system, owned outright: positions, vectors, rects,
2//! ranges, alignment, the clamped-`f32` newtype, the grid metric, and the grid's
3//! bridge to the document's own integer geometry. No UI toolkit appears in this
4//! crate's dependency tree — normal, build or dev — so everything above it (the
5//! router, the render path, the tools) is spelled in coordinates of our own.
6//!
7//! The one crate below it is `blockworx-doc`: the bridge in [`grid`] converts
8//! world pixels to and from the document's `GridPoint`/`GridRect`/`ScreenRect`,
9//! which is the whole point of a shared coordinate system.
10//!
11//! The types are method-for-method the subset of
12//! [emath](https://crates.io/crates/emath) the app uses, and the
13//! implementations are copied from it (© Rerun / emilk, dual-licensed MIT OR
14//! Apache-2.0) wherever the semantics are load-bearing: `Rect`'s
15//! union/intersection against `NOTHING`/`EVERYTHING`, `Align2::anchor_size`,
16//! `lerp`/`remap`. The equivalence is held to by property tests, which live
17//! with the conversions in the backend (`blockworx_egui::convert`) because
18//! that is the only place allowed to name emath at all.
19
20mod align;
21mod angle;
22mod bounded;
23pub mod grid;
24mod pos2;
25mod range;
26mod rect;
27mod units;
28mod vec2;
29
30pub use align::{Align, Align2};
31pub use angle::Angle;
32pub use bounded::{Bounded, Bounds, OutOfBounds};
33pub use pos2::{Pos2, pos2};
34pub use range::Rangef;
35pub use rect::Rect;
36pub use units::WorldPx;
37pub use vec2::{Vec2, vec2};
38
39/// Linear interpolation from `range.0` at `t == 0` to `range.1` at `t == 1`.
40/// Extrapolates outside `0..=1`.
41#[inline]
42pub fn lerp(range: (f32, f32), t: f32) -> f32 {
43    (1.0 - t) * range.0 + t * range.1
44}
45
46/// Map `x` from the `from` range onto the `to` range, linearly and without
47/// clamping.
48#[inline]
49pub fn remap(x: f32, from: (f32, f32), to: (f32, f32)) -> f32 {
50    let t = (x - from.0) / (from.1 - from.0);
51    lerp(to, t)
52}
53
54/// [`remap`] with the result held inside `to`, and with `from` accepted in
55/// either order.
56pub fn remap_clamp(x: f32, from: (f32, f32), to: (f32, f32)) -> f32 {
57    if from.1 < from.0 {
58        return remap_clamp(x, (from.1, from.0), (to.1, to.0));
59    }
60    if x <= from.0 {
61        to.0
62    } else if from.1 <= x {
63        to.1
64    } else {
65        let t = (x - from.0) / (from.1 - from.0);
66        // Guards the far end against a `t` that rounded past 1.0.
67        if 1.0 <= t { to.1 } else { lerp(to, t) }
68    }
69}
70
71/// The midpoint of `a` and `b`.
72///
73/// Deliberately not `f32::midpoint`: that one avoids overflowing the sum, so it
74/// answers a finite midpoint where this answers ±∞. `Rect::NOTHING.center()`
75/// being NaN, and a rect near the top of the range centering at infinity, are
76/// the behaviors the render path was built against.
77#[inline]
78#[expect(clippy::manual_midpoint)]
79fn midpoint(a: f32, b: f32) -> f32 {
80    (a + b) / 2.0
81}