Skip to main content

geometry_algorithm/
assign.rs

1//! Write coordinates into a mutable geometry.
2//!
3//! Mirrors `boost::geometry::assign_values` from
4//! `boost/geometry/algorithms/assign.hpp`. Boost exposes variadic
5//! overloads (`assign_values(p, x, y)`, `assign_values(p, x, y, z)`,
6//! …); the Rust port takes a slice so one signature covers every
7//! arity. The arity check that Boost gets for free from SFINAE (a
8//! two-argument call on a 3-D point fails to compile) becomes a
9//! runtime `assert!` on the slice length here.
10
11use geometry_trait::PointMut;
12
13/// Write `values[D]` to `p.set::<D>(values[D])` for every dimension
14/// `D` in `0..P::DIM`.
15///
16/// Mirrors `boost::geometry::assign_values(p, v...)` from
17/// `boost/geometry/algorithms/assign.hpp`.
18///
19/// # Panics
20///
21/// Panics if `values.len() < P::DIM`. The bound is checked at
22/// runtime — Boost relies on the variadic arity matching `DIM` at
23/// compile time via SFINAE, a guarantee the slice signature trades
24/// away for arity-agnostic ergonomics.
25pub fn assign_values<P: PointMut>(p: &mut P, values: &[P::Scalar]) {
26    assert!(
27        values.len() >= P::DIM,
28        "assign_values: need at least {} values, got {}",
29        P::DIM,
30        values.len(),
31    );
32    // `PointMut::set` needs `&mut self`, so the dimension recursion
33    // cannot ride on `fold_dims` (which hands the closure a shared
34    // `&P`). Dispatch on the monomorphised `P::DIM` instead, falling
35    // through so that a 3-D point also writes dims 0 and 1. The arms
36    // track `geometry_trait::point::MAX_DIM`.
37    let dim = P::DIM;
38    assert!(dim <= 4, "assign_values: DIM exceeds MAX_DIM (4)");
39    if dim > 0 {
40        p.set::<0>(values[0]);
41    }
42    if dim > 1 {
43        p.set::<1>(values[1]);
44    }
45    if dim > 2 {
46        p.set::<2>(values[2]);
47    }
48    if dim > 3 {
49        p.set::<3>(values[3]);
50    }
51}
52
53#[cfg(test)]
54#[allow(
55    clippy::float_cmp,
56    reason = "Assigned coordinates are read back as exact literals."
57)]
58mod tests {
59    //! Reference behaviour from
60    //! `boost/geometry/test/algorithms/assign.cpp` — a slice written
61    //! into a point comes back coordinate-for-coordinate.
62
63    use super::assign_values;
64    use geometry_cs::Cartesian;
65    use geometry_model::{Point2D, Point3D};
66    use geometry_trait::Point as _;
67
68    #[test]
69    fn point2_round_trip() {
70        let mut p: Point2D<f64, Cartesian> = Point2D::default();
71        assign_values(&mut p, &[3.0, 4.0]);
72        assert_eq!(p.get::<0>(), 3.0);
73        assert_eq!(p.get::<1>(), 4.0);
74    }
75
76    #[test]
77    fn point3_round_trip() {
78        let mut p: Point3D<f64, Cartesian> = Point3D::default();
79        assign_values(&mut p, &[1.0, 2.0, 3.0]);
80        assert_eq!(p.get::<0>(), 1.0);
81        assert_eq!(p.get::<1>(), 2.0);
82        assert_eq!(p.get::<2>(), 3.0);
83    }
84
85    /// A 4-D point writes all four ordinates (the `dim > 3` arm).
86    #[test]
87    fn point4_round_trip() {
88        let mut p: geometry_model::Point<f64, 4, Cartesian> = geometry_model::Point::default();
89        assign_values(&mut p, &[1.0, 2.0, 3.0, 4.0]);
90        assert_eq!(p.get::<0>(), 1.0);
91        assert_eq!(p.get::<1>(), 2.0);
92        assert_eq!(p.get::<2>(), 3.0);
93        assert_eq!(p.get::<3>(), 4.0);
94    }
95
96    #[test]
97    #[should_panic(expected = "assign_values: need at least 2")]
98    fn too_few_values_panics() {
99        let mut p: Point2D<f64, Cartesian> = Point2D::default();
100        assign_values(&mut p, &[1.0]);
101    }
102}