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}