1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//! Exact-aware planar similarity transforms for native curve geometry.
//!
//! Similarities preserve lines and circles. This module accepts finite `f64`
//! affine entries at the API boundary, certifies that the linear part is a
//! nonsingular similarity, promotes coefficients to [`Real`](hyperreal::Real),
//! and applies the transform to native line/circular-arc objects without
//! flattening. Keeping exact curve objects authoritative follows Yap, "Towards
//! Exact Geometric Computation," *Computational Geometry* 7(1-2), 1997
//! (<https://doi.org/10.1016/0925-7721(95)00040-2>). The line/circle
//! preservation property is the standard Euclidean similarity model described
//! in Schneider and Eberly, *Geometric Tools for Computer Graphics* (Morgan
//! Kaufmann, 2002).
use hyperreal::Real;
use crate::{
CircularArc2, Contour2, CurveError, CurveResult, CurveString2, LineSeg2, Point2, Region2,
Segment2,
};
/// A 2D affine transform whose linear part is a nonsingular similarity.
#[derive(Clone, Debug, PartialEq)]
pub struct Similarity2 {
a: Real,
b: Real,
d: Real,
e: Real,
xoff: Real,
yoff: Real,
reverses_orientation: bool,
}
impl Similarity2 {
/// Constructs a planar similarity from finite affine entries.
///
/// The transform is:
///
/// ```text
/// x' = a*x + b*y + xoff
/// y' = d*x + e*y + yoff
/// ```
///
/// The finite validation tolerance is only used to accept API-boundary
/// matrix entries as a similarity. Once accepted, all transformed geometry
/// is built with hyperreal coefficients.
pub fn try_from_f64_affine(
a: f64,
b: f64,
d: f64,
e: f64,
xoff: f64,
yoff: f64,
tolerance: f64,
) -> CurveResult<Self> {
if ![a, b, d, e, xoff, yoff, tolerance]
.into_iter()
.all(f64::is_finite)
|| tolerance <= 0.0
{
return Err(CurveError::InvalidSimilarityTransform);
}
let first_len_squared = a * a + d * d;
let second_len_squared = b * b + e * e;
let dot = a * b + d * e;
let determinant = a * e - b * d;
if determinant.abs() <= tolerance
|| (first_len_squared - second_len_squared).abs() > tolerance
|| dot.abs() > tolerance
{
return Err(CurveError::InvalidSimilarityTransform);
}
Ok(Self {
a: real_from_f64(a)?,
b: real_from_f64(b)?,
d: real_from_f64(d)?,
e: real_from_f64(e)?,
xoff: real_from_f64(xoff)?,
yoff: real_from_f64(yoff)?,
reverses_orientation: determinant < 0.0,
})
}
/// Returns true when the transform reverses orientation.
pub const fn reverses_orientation(&self) -> bool {
self.reverses_orientation
}
/// Transforms a point with hyperreal arithmetic.
pub fn transform_point(&self, point: &Point2) -> Point2 {
Point2::new(
(&self.a * point.x()) + (&self.b * point.y()) + self.xoff.clone(),
(&self.d * point.x()) + (&self.e * point.y()) + self.yoff.clone(),
)
}
}
impl Point2 {
/// Applies a certified planar similarity transform.
pub fn transform_similarity(&self, transform: &Similarity2) -> Self {
transform.transform_point(self)
}
}
impl Segment2 {
/// Applies a certified planar similarity transform while preserving segment type.
pub fn transform_similarity(&self, transform: &Similarity2) -> CurveResult<Self> {
match self {
Self::Line(line) => line.transform_similarity(transform).map(Self::Line),
Self::Arc(arc) => arc.transform_similarity(transform).map(Self::Arc),
}
}
}
impl LineSeg2 {
/// Applies a certified planar similarity transform.
pub fn transform_similarity(&self, transform: &Similarity2) -> CurveResult<Self> {
Self::try_new(
transform.transform_point(self.start()),
transform.transform_point(self.end()),
)
}
}
impl CircularArc2 {
/// Applies a certified planar similarity transform.
///
/// Similarities preserve circular arcs. Reflections reverse orientation, so
/// clockwise state is toggled exactly when the transform reverses
/// orientation.
pub fn transform_similarity(&self, transform: &Similarity2) -> CurveResult<Self> {
Self::try_from_center(
transform.transform_point(self.start()),
transform.transform_point(self.end()),
transform.transform_point(self.center()),
self.is_clockwise() ^ transform.reverses_orientation(),
)
}
}
impl CurveString2 {
/// Applies a certified planar similarity transform while preserving line/arc topology.
pub fn transform_similarity(&self, transform: &Similarity2) -> CurveResult<Self> {
let segments = self
.segments()
.iter()
.map(|segment| segment.transform_similarity(transform))
.collect::<CurveResult<Vec<_>>>()?;
Self::try_new(segments)
}
}
impl Contour2 {
/// Applies a certified planar similarity transform while preserving the fill rule.
pub fn transform_similarity(&self, transform: &Similarity2) -> CurveResult<Self> {
let curve = self.curve_string().transform_similarity(transform)?;
Self::try_new_with_fill_rule(curve.into_segments(), self.fill_rule())
}
}
impl Region2 {
/// Applies a certified planar similarity transform to every material and hole contour.
pub fn transform_similarity(&self, transform: &Similarity2) -> CurveResult<Self> {
let material = self
.material_contours()
.iter()
.map(|contour| contour.transform_similarity(transform))
.collect::<CurveResult<Vec<_>>>()?;
let holes = self
.hole_contours()
.iter()
.map(|contour| contour.transform_similarity(transform))
.collect::<CurveResult<Vec<_>>>()?;
Ok(Self::new(material, holes))
}
}
fn real_from_f64(value: f64) -> CurveResult<Real> {
if !value.is_finite() {
return Err(CurveError::InvalidSimilarityTransform);
}
Ok(Real::try_from(value)?)
}