1use crate::{Expr, PointExpr, Relation, Row, VarId};
16
17fn eq(expr: Expr, scale: f64) -> Row {
18 Row { expr, relation: Relation::Eq, scale }
19}
20
21fn le(expr: Expr, scale: f64) -> Row {
22 Row { expr, relation: Relation::Le, scale }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub enum Cmp {
29 Eq,
31 Le,
33 Ge,
35}
36
37#[must_use]
39pub fn fix(v: Expr, value: f64, scale: f64) -> Vec<Row> {
40 vec![eq(Expr::sub(v, Expr::c(value)), scale)]
41}
42
43#[must_use]
45pub fn equal(a: Expr, b: Expr, scale: f64) -> Vec<Row> {
46 vec![eq(Expr::sub(a, b), scale)]
47}
48
49#[must_use]
51pub fn linear(terms: &[(f64, Expr)], cmp: Cmp, rhs: f64, scale: f64) -> Vec<Row> {
52 let sum = terms.iter().fold(Expr::c(0.0), |acc, (c, e)| Expr::add(acc, Expr::mul(Expr::c(*c), e.clone())));
53 let lhs = Expr::sub(sum, Expr::c(rhs));
54 match cmp {
55 Cmp::Eq => vec![eq(lhs, scale)],
56 Cmp::Le => vec![le(lhs, scale)],
57 Cmp::Ge => vec![le(Expr::neg(lhs), scale)],
58 }
59}
60
61#[must_use]
63pub fn ratio(a: Expr, b: Expr, k: f64, scale: f64) -> Vec<Row> {
64 vec![eq(Expr::sub(a, Expr::mul(Expr::c(k), b)), scale)]
65}
66
67#[must_use]
69pub fn at_least(a: Expr, min: f64, scale: f64) -> Vec<Row> {
70 vec![le(Expr::sub(Expr::c(min), a), scale)]
71}
72
73#[must_use]
75pub fn at_most(a: Expr, max: f64, scale: f64) -> Vec<Row> {
76 vec![le(Expr::sub(a, Expr::c(max)), scale)]
77}
78
79#[must_use]
81pub fn equal_spacing(values: &[Expr], scale: f64) -> Vec<Row> {
82 let mut rows = Vec::new();
83 let mut iter = values.windows(2);
84 let Some(first) = iter.next() else { return rows };
85 let d0 = Expr::sub(first[1].clone(), first[0].clone());
86 for w in iter {
87 rows.push(eq(Expr::sub(Expr::sub(w[1].clone(), w[0].clone()), d0.clone()), scale));
88 }
89 rows
90}
91
92#[must_use]
94pub fn all_equal(values: &[Expr], scale: f64) -> Vec<Row> {
95 values.windows(2).map(|w| eq(Expr::sub(w[1].clone(), w[0].clone()), scale)).collect()
96}
97
98#[must_use]
100pub fn coincident(p: &PointExpr, q: &PointExpr, scale: f64) -> Vec<Row> {
101 vec![eq(Expr::sub(p.x.clone(), q.x.clone()), scale), eq(Expr::sub(p.y.clone(), q.y.clone()), scale)]
102}
103
104#[must_use]
106pub fn horizontal(p: &PointExpr, q: &PointExpr, scale: f64) -> Vec<Row> {
107 vec![eq(Expr::sub(p.y.clone(), q.y.clone()), scale)]
108}
109
110#[must_use]
112pub fn vertical(p: &PointExpr, q: &PointExpr, scale: f64) -> Vec<Row> {
113 vec![eq(Expr::sub(p.x.clone(), q.x.clone()), scale)]
114}
115
116#[must_use]
118pub fn fix_point(p: &PointExpr, at: (f64, f64), scale: f64) -> Vec<Row> {
119 vec![eq(Expr::sub(p.x.clone(), Expr::c(at.0)), scale), eq(Expr::sub(p.y.clone(), Expr::c(at.1)), scale)]
120}
121
122fn dx(p: &PointExpr, q: &PointExpr) -> Expr {
123 Expr::sub(q.x.clone(), p.x.clone())
124}
125
126fn dy(p: &PointExpr, q: &PointExpr) -> Expr {
127 Expr::sub(q.y.clone(), p.y.clone())
128}
129
130fn dist(p: &PointExpr, q: &PointExpr) -> Expr {
131 Expr::hypot(dx(p, q), dy(p, q))
132}
133
134#[must_use]
136pub fn distance(p: &PointExpr, q: &PointExpr, d: f64, scale: f64) -> Vec<Row> {
137 vec![eq(Expr::sub(dist(p, q), Expr::c(d)), scale)]
138}
139
140#[must_use]
142pub fn distance_cmp(p: &PointExpr, q: &PointExpr, cmp: Cmp, d: f64, scale: f64) -> Vec<Row> {
143 let r = Expr::sub(dist(p, q), Expr::c(d));
144 match cmp {
145 Cmp::Eq => vec![eq(r, scale)],
146 Cmp::Le => vec![le(r, scale)],
147 Cmp::Ge => vec![le(Expr::neg(r), scale)],
148 }
149}
150
151#[must_use]
153pub fn signed_line_distance(p: &PointExpr, a: &PointExpr, b: &PointExpr) -> Expr {
154 let cross = Expr::sub(Expr::mul(dx(a, b), dy(a, p)), Expr::mul(dy(a, b), dx(a, p)));
156 Expr::div(cross, dist(a, b))
157}
158
159#[must_use]
161pub fn point_line_distance(p: &PointExpr, a: &PointExpr, b: &PointExpr, signed_d: f64, scale: f64) -> Vec<Row> {
162 vec![eq(Expr::sub(signed_line_distance(p, a, b), Expr::c(signed_d)), scale)]
163}
164
165#[must_use]
167pub fn point_on_line(p: &PointExpr, a: &PointExpr, b: &PointExpr, scale: f64) -> Vec<Row> {
168 vec![eq(signed_line_distance(p, a, b), scale)]
169}
170
171#[must_use]
173pub fn point_on_circle(p: &PointExpr, c: &PointExpr, r: Expr, scale: f64) -> Vec<Row> {
174 vec![eq(Expr::sub(dist(c, p), r), scale)]
175}
176
177#[must_use]
179pub fn length(a: &PointExpr, b: &PointExpr, len: f64, scale: f64) -> Vec<Row> {
180 distance(a, b, len, scale)
181}
182
183#[must_use]
185pub fn equal_length(a1: &PointExpr, b1: &PointExpr, a2: &PointExpr, b2: &PointExpr, scale: f64) -> Vec<Row> {
186 vec![eq(Expr::sub(dist(a1, b1), dist(a2, b2)), scale)]
187}
188
189fn cross_dot(a1: &PointExpr, b1: &PointExpr, a2: &PointExpr, b2: &PointExpr) -> (Expr, Expr, Expr) {
190 let (ux, uy) = (dx(a1, b1), dy(a1, b1));
191 let (vx, vy) = (dx(a2, b2), dy(a2, b2));
192 let cross = Expr::sub(Expr::mul(ux.clone(), vy.clone()), Expr::mul(uy.clone(), vx.clone()));
193 let dot = Expr::add(Expr::mul(ux.clone(), vx.clone()), Expr::mul(uy.clone(), vy.clone()));
194 let norm = Expr::mul(Expr::hypot(ux, uy), Expr::hypot(vx, vy));
195 (cross, dot, norm)
196}
197
198#[must_use]
200pub fn parallel(a1: &PointExpr, b1: &PointExpr, a2: &PointExpr, b2: &PointExpr) -> Vec<Row> {
201 let (cross, _, norm) = cross_dot(a1, b1, a2, b2);
202 vec![eq(Expr::div(cross, norm), 1.0)]
203}
204
205#[must_use]
207pub fn perpendicular(a1: &PointExpr, b1: &PointExpr, a2: &PointExpr, b2: &PointExpr) -> Vec<Row> {
208 let (_, dot, norm) = cross_dot(a1, b1, a2, b2);
209 vec![eq(Expr::div(dot, norm), 1.0)]
210}
211
212#[must_use]
215pub fn angle(a1: &PointExpr, b1: &PointExpr, a2: &PointExpr, b2: &PointExpr, theta: f64) -> Vec<Row> {
216 let (cross, dot, _) = cross_dot(a1, b1, a2, b2);
217 let (s, c) = libm::sincos(theta);
218 let y = Expr::sub(Expr::mul(cross.clone(), Expr::c(c)), Expr::mul(dot.clone(), Expr::c(s)));
220 let x = Expr::add(Expr::mul(dot, Expr::c(c)), Expr::mul(cross, Expr::c(s)));
221 vec![eq(Expr::atan2(y, x), 1.0)]
222}
223
224#[must_use]
226pub fn concentric(c1: &PointExpr, c2: &PointExpr, scale: f64) -> Vec<Row> {
227 coincident(c1, c2, scale)
228}
229
230#[must_use]
232pub fn radius(r: Expr, value: f64, scale: f64) -> Vec<Row> {
233 fix(r, value, scale)
234}
235
236#[must_use]
238pub fn equal_radius(r1: Expr, r2: Expr, scale: f64) -> Vec<Row> {
239 equal(r1, r2, scale)
240}
241
242#[must_use]
245pub fn tangent_line_circle(a: &PointExpr, b: &PointExpr, c: &PointExpr, r: Expr, side: f64, scale: f64) -> Vec<Row> {
246 let d = signed_line_distance(c, a, b);
247 vec![eq(Expr::sub(Expr::mul(Expr::c(side.signum()), d), r), scale)]
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub enum CircleTangency {
254 External,
256 Internal {
259 sign: f64,
261 },
262}
263
264#[must_use]
266pub fn tangent_circles(
267 c1: &PointExpr,
268 r1: Expr,
269 c2: &PointExpr,
270 r2: Expr,
271 kind: CircleTangency,
272 scale: f64,
273) -> Vec<Row> {
274 let d = dist(c1, c2);
275 let target = match kind {
276 CircleTangency::External => Expr::add(r1, r2),
277 CircleTangency::Internal { sign } => Expr::mul(Expr::c(sign.signum()), Expr::sub(r1, r2)),
278 };
279 vec![eq(Expr::sub(d, target), scale)]
280}
281
282#[must_use]
284pub fn polar_point(cx: Expr, cy: Expr, r: Expr, angle: Expr) -> PointExpr {
285 PointExpr {
286 x: Expr::add(cx, Expr::mul(r.clone(), Expr::cos(angle.clone()))),
287 y: Expr::add(cy, Expr::mul(r, Expr::sin(angle))),
288 }
289}
290
291#[must_use]
293pub const fn v(id: VarId) -> Expr {
294 Expr::Var(id)
295}