1use std::fmt::{Debug, Display};
2
3use num_traits::{One, Zero};
4
5use crate::{
6 parser::{PropertyValue, Type},
7 token::{number::ProcNum, Span, StrLit},
8 unroll::{figure::NoContentNode, AnyExpr},
9};
10
11use super::{prelude::*, Overload};
12
13trait Transform<T: GeoType> {
14 type Output: GeoType + Into<AnyExpr>;
16
17 #[must_use]
19 fn transform(&self, object: T, context: &mut CompileContext, props: Properties)
20 -> Self::Output;
21}
22
23trait TransformErased<T: GeoType> {
24 #[must_use]
26 fn transform_erased(
27 &self,
28 object: T,
29 context: &mut CompileContext,
30 props: Properties,
31 ) -> AnyExpr;
32
33 #[must_use]
35 fn get_transformed_type(&self) -> Type;
36}
37
38impl<T: Transform<U>, U: GeoType> TransformErased<U> for T {
39 fn transform_erased(
40 &self,
41 object: U,
42 context: &mut CompileContext,
43 props: Properties,
44 ) -> AnyExpr {
45 Transform::transform(self, object, context, props).into()
46 }
47
48 fn get_transformed_type(&self) -> Type {
49 T::Output::get_type()
50 }
51}
52
53trait DynTransform: Debug + Display {
54 fn transform_any(
55 &self,
56 expr: AnyExpr,
57 context: &mut CompileContext,
58 props: Properties,
59 ) -> AnyExpr;
60
61 fn transform_type(&self, ty: Type) -> Option<Type>;
62}
63
64macro_rules! impl_dyn {
65 ($ty:ty) => {
66 impl DynTransform for $ty {
67 fn transform_any(
68 &self,
69 expr: AnyExpr,
70 context: &mut CompileContext,
71 props: Properties,
72 ) -> AnyExpr {
73 match expr {
74 AnyExpr::Point(v) => self.transform_erased(v, context, props),
75 AnyExpr::Line(v) => self.transform_erased(v, context, props),
76 AnyExpr::Circle(v) => self.transform_erased(v, context, props),
77 _ => panic!(),
78 }
79 }
80
81 fn transform_type(&self, ty: Type) -> Option<Type> {
82 match ty {
83 Type::Point | Type::PointCollection(1) => {
84 Some(TransformErased::<Expr<Point>>::get_transformed_type(self))
85 }
86 Type::Line | Type::PointCollection(2) => {
87 Some(TransformErased::<Expr<Line>>::get_transformed_type(self))
88 }
89 Type::Circle => {
90 Some(TransformErased::<Expr<Circle>>::get_transformed_type(self))
91 }
92 _ => None,
93 }
94 }
95 }
96 };
97}
98
99#[derive(Debug)]
101pub struct TransformType(Box<dyn DynTransform>);
102
103impl DerivedType for TransformType {
104 fn as_any(&self) -> &dyn std::any::Any {
105 self
106 }
107}
108
109impl Display for TransformType {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 Display::fmt(self.0.as_ref(), f)
112 }
113}
114
115impl_derived! {TransformType}
116
117fn projection(point: &Distance, line: &Expr<Line>, context: &CompileContext) -> Expr<Point> {
118 context.intersection(
119 context.perpendicular_through(
120 line.clone_without_node(),
121 context.to_point(point.0.clone_without_node()),
122 ),
123 line.clone_without_node(),
124 )
125}
126
127#[derive(Debug)]
128struct Translation {
129 vector: Distance,
130}
131
132impl Display for Translation {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 write!(f, "Translation({})", self.vector)
135 }
136}
137
138impl Transform<Expr<Point>> for Translation {
139 type Output = Expr<Point>;
140
141 fn transform(
142 &self,
143 object: Expr<Point>,
144 context: &mut CompileContext,
145 props: Properties,
146 ) -> Self::Output {
147 let p = context.to_complex(object);
148 let q = context.add(p, self.vector.0.clone_without_node());
149 context.to_point_display(q, props)
150 }
151}
152
153impl Transform<Expr<Line>> for Translation {
154 type Output = Expr<Line>;
155
156 fn transform(
157 &self,
158 object: Expr<Line>,
159 context: &mut CompileContext,
160 props: Properties,
161 ) -> Self::Output {
162 let proj = projection(
163 &Distance::from(number!(DISTANCE ProcNum::zero())),
164 &object,
165 context,
166 );
167 let transformed_proj = self.transform(proj, context, Properties::default());
168 context.parallel_through_display(object, transformed_proj, props)
169 }
170}
171
172impl Transform<Expr<Circle>> for Translation {
173 type Output = Expr<Circle>;
174
175 fn transform(
176 &self,
177 object: Expr<Circle>,
178 context: &mut CompileContext,
179 props: Properties,
180 ) -> Self::Output {
181 let old_radius = context.circle_radius(object.clone_without_node());
182 let old_center = context.circle_center(object);
183 let new_center = self.transform(old_center, context, Properties::default());
184 context.circle_display(new_center, old_radius, props)
185 }
186}
187
188impl_dyn! {Translation}
189
190fn translation(
191 mut vector: Distance,
192 context: &CompileContext,
193 mut props: Properties,
194) -> TransformTypeExpr {
195 let node = NoContentNode {
196 display: props.get("display").maybe_unset(true),
197 };
198 props.ignore("default-label");
199 props.finish(context);
200
201 let mut node = HierarchyNode::new_dyn(node);
202 node.extend_children(vector.take_node());
203
204 TransformTypeExpr::new(TransformType(Box::new(Translation { vector })), node)
205}
206
207#[derive(Debug)]
208pub struct Spiral {
209 origin: Distance,
210 scale: Unitless,
211 vector: Unitless,
212}
213
214impl Display for Spiral {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 write!(
217 f,
218 "Spiral similarity with origin {}, scale {}, and transform vector {}",
219 self.origin, self.scale, self.vector
220 )
221 }
222}
223
224impl Transform<Expr<Point>> for Spiral {
225 type Output = Expr<Point>;
226
227 fn transform(
228 &self,
229 object: Expr<Point>,
230 context: &mut CompileContext,
231 props: Properties,
232 ) -> Self::Output {
233 let point = context.to_complex(object);
234 let diff = context.sub(point, self.origin.clone_without_node());
235 let new_diff = context.mult(diff, self.vector.clone_without_node());
236 let new_point = context.add(self.origin.clone_without_node(), new_diff);
237 context.to_point_display(new_point, props)
238 }
239}
240
241impl Transform<Expr<Line>> for Spiral {
242 type Output = Expr<Line>;
243
244 fn transform(
245 &self,
246 object: Expr<Line>,
247 context: &mut CompileContext,
248 props: Properties,
249 ) -> Self::Output {
250 let proj = projection(&self.origin, &object, context);
251 let transformed_proj = self.transform(proj, context, Properties::default());
252 let ray = context.line(
253 context.to_point(self.origin.0.clone_without_node()),
254 transformed_proj.clone_without_node(),
255 );
256
257 context.perpendicular_through_display(ray, transformed_proj, props)
258 }
259}
260
261impl Transform<Expr<Circle>> for Spiral {
262 type Output = Expr<Circle>;
263
264 fn transform(
265 &self,
266 object: Expr<Circle>,
267 context: &mut CompileContext,
268 props: Properties,
269 ) -> Self::Output {
270 let center = context.circle_center(object.clone_without_node());
271 let radius = context.circle_radius(object);
272
273 let new_center = self.transform(center, context, Properties::default());
274 let new_radius = context.mult(radius, self.scale.0.clone_without_node());
275 context.circle_display(new_center, new_radius, props)
276 }
277}
278
279impl_dyn! {Spiral}
280
281fn spiral(
282 mut origin: Expr<Point>,
283 mut angle: Angle,
284 mut scale: Unitless,
285 context: &CompileContext,
286 mut props: Properties,
287) -> TransformTypeExpr {
288 let node = NoContentNode {
289 display: props.get("display").maybe_unset(true),
290 };
291 props.ignore("default-label");
292 props.finish(context);
293
294 let mut node = HierarchyNode::new_dyn(node);
295 node.extend_children(origin.take_node());
296 node.extend_children(scale.0.take_node());
297 node.extend_children(angle.0.take_node());
298
299 let vector = context.add(
300 context.cos(angle.0.clone_without_node()),
301 context.mult(context.sin(angle.0), number!(SCALAR ProcNum::i())),
302 );
303 let vector = context.mult(vector, scale.0.clone_without_node());
304
305 TransformTypeExpr::new(
306 TransformType(Box::new(Spiral {
307 origin: Distance::from(context.to_complex(origin)),
308 scale,
309 vector: Unitless::from(vector),
310 })),
311 node,
312 )
313}
314
315#[derive(Debug)]
316pub struct Reflect {
317 line: Expr<Line>,
318}
319
320impl Display for Reflect {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 write!(f, "Reflection by line {}", self.line)
323 }
324}
325
326impl Transform<Expr<Point>> for Reflect {
327 type Output = Expr<Point>;
328
329 fn transform(
330 &self,
331 object: Expr<Point>,
332 context: &mut CompileContext,
333 props: Properties,
334 ) -> Self::Output {
335 let mut no_display = Properties::default();
336 no_display.add_if_not_present(
337 "display",
338 (
339 Span::empty(),
340 PropertyValue::String(StrLit {
341 span: Span::empty(),
342 content: String::from("false"),
343 }),
344 ),
345 );
346 let proj = context.intersection_display(
347 context
348 .perpendicular_through(self.line.clone_without_node(), object.clone_without_node()),
349 self.line.clone_without_node(),
350 no_display,
351 );
352 let proj = context.to_complex(proj);
353 let obj = context.to_complex(object);
354 let img = context.add(proj.clone_without_node(), context.sub(proj, obj));
355 context.to_point_display(img, props)
356 }
357}
358
359impl Transform<Expr<Line>> for Reflect {
360 type Output = Expr<Line>;
361
362 fn transform(
363 &self,
364 object: Expr<Line>,
365 context: &mut CompileContext,
366 props: Properties,
367 ) -> Self::Output {
368 let inter =
369 context.intersection(self.line.clone_without_node(), object.clone_without_node());
370 let obj_dir = context.direction(self.line.clone_without_node());
371 let self_dir = context.direction(self.line.clone_without_node());
372 let dir_rel = context.div(obj_dir, self_dir.clone_without_node());
373 let conj = context.add(
374 context.real(dir_rel.clone_without_node()),
375 context.neg(context.imaginary(dir_rel)),
376 );
377 let new_dir = context.mult(self_dir, conj);
378 context.point_vector_display(inter, new_dir, props)
379 }
380}
381
382impl Transform<Expr<Circle>> for Reflect {
383 type Output = Expr<Circle>;
384
385 fn transform(
386 &self,
387 object: Expr<Circle>,
388 context: &mut CompileContext,
389 props: Properties,
390 ) -> Self::Output {
391 let center = context.circle_center(object.clone_without_node());
392 let radius = context.circle_radius(object);
393
394 let new_center = self.transform(center, context, Properties::default());
395 context.circle_display(new_center, radius, props)
396 }
397}
398
399impl_dyn! {Reflect}
400
401fn reflection(
402 mut line: Expr<Line>,
403 context: &CompileContext,
404 mut props: Properties,
405) -> TransformTypeExpr {
406 let node = NoContentNode {
407 display: props.get("display").maybe_unset(true),
408 };
409 props.ignore("default-label");
410 props.finish(context);
411
412 let mut node = HierarchyNode::new_dyn(node);
413 node.extend_children(line.take_node());
414
415 TransformTypeExpr::new(TransformType(Box::new(Reflect { line })), node)
416}
417
418#[derive(Debug)]
419struct Compose {
420 t1: TransformTypeExpr,
421 t2: TransformTypeExpr,
422}
423
424impl Display for Compose {
425 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426 write!(f, "Composition of ({}) and ({})", self.t1, self.t2)
427 }
428}
429
430impl DynTransform for Compose {
431 fn transform_any(
432 &self,
433 expr: AnyExpr,
434 context: &mut CompileContext,
435 props: Properties,
436 ) -> AnyExpr {
437 let t2_res = self
438 .t2
439 .get()
440 .unwrap()
441 .0
442 .transform_any(expr, context, Properties::default());
443 self.t1
444 .get()
445 .unwrap()
446 .0
447 .transform_any(t2_res, context, props)
448 }
449
450 fn transform_type(&self, ty: Type) -> Option<Type> {
451 let t2_res = self.t2.get()?.0.transform_type(ty)?;
452 self.t1.get()?.0.transform_type(t2_res)
453 }
454}
455
456fn compose(
457 mut t1: TransformTypeExpr,
458 mut t2: TransformTypeExpr,
459 context: &CompileContext,
460 mut props: Properties,
461) -> TransformTypeExpr {
462 let node = NoContentNode {
463 display: props.get("display").maybe_unset(true),
464 };
465 props.ignore("default-label");
466 props.finish(context);
467
468 let mut node = HierarchyNode::new_dyn(node);
469 node.extend_children(t1.take_node());
470 node.extend_children(t2.take_node());
471
472 TransformTypeExpr::new(TransformType(Box::new(Compose { t1, t2 })), node)
473}
474
475struct TransformOverload;
476
477impl Overload for TransformOverload {
478 fn get_returned_type(&self, params: &[AnyExpr]) -> Option<Type> {
479 if params.len() == 2 && params[0].can_convert_to_derived("TransformType") {
480 TransformTypeExpr::from(params[0].as_derived()?.clone_without_node())
481 .get()
482 .and_then(|v| v.0.transform_type(params[1].get_type()))
483 } else {
484 None
485 }
486 }
487
488 fn unroll(
489 &self,
490 mut params: Vec<AnyExpr>,
491 context: &mut CompileContext,
492 props: Properties,
493 ) -> AnyExpr {
494 let ty = self.get_returned_type(¶ms).unwrap();
495 let o = params.swap_remove(1).convert_to(ty, context);
496 let t = TransformTypeExpr::from(params.swap_remove(0).try_into_derived().unwrap());
497
498 t.get().unwrap().0.transform_any(o, context, props)
499 }
500}
501
502pub fn register(library: &mut Library) {
503 library
504 .add(
505 Function::new("homothety")
506 .alias("scale")
507 .overload(
508 |scale: Unitless, origin: Expr<Point>, context: &CompileContext, props| {
509 spiral(
510 origin,
511 Angle::from(number!(SCALAR ProcNum::zero())),
512 scale,
513 context,
514 props,
515 )
516 },
517 )
518 .overload(
519 |origin: Expr<Point>, scale: Unitless, context: &CompileContext, props| {
520 spiral(
521 origin,
522 Angle::from(number!(ANGLE ProcNum::zero())),
523 scale,
524 context,
525 props,
526 )
527 },
528 ),
529 )
530 .add(
531 Function::new("spiral")
532 .alias("rotate")
533 .overload(spiral)
534 .overload(
535 |origin: Expr<Point>,
536 scale: Unitless,
537 angle: Angle,
538 context: &CompileContext,
539 props| { spiral(origin, angle, scale, context, props) },
540 )
541 .overload(
542 |angle: Angle,
543 origin: Expr<Point>,
544 scale: Unitless,
545 context: &CompileContext,
546 props| { spiral(origin, angle, scale, context, props) },
547 )
548 .overload(
549 |angle: Angle,
550 scale: Unitless,
551 origin: Expr<Point>,
552 context: &CompileContext,
553 props| { spiral(origin, angle, scale, context, props) },
554 )
555 .overload(
556 |scale: Unitless,
557 angle: Angle,
558 origin: Expr<Point>,
559 context: &CompileContext,
560 props| { spiral(origin, angle, scale, context, props) },
561 )
562 .overload(
563 |scale: Unitless,
564 origin: Expr<Point>,
565 angle: Angle,
566 context: &CompileContext,
567 props| { spiral(origin, angle, scale, context, props) },
568 )
569 .overload(
570 |origin: Expr<Point>, angle: Angle, context: &CompileContext, props| {
571 spiral(
572 origin,
573 angle,
574 Unitless::from(number!(SCALAR ProcNum::one())),
575 context,
576 props,
577 )
578 },
579 )
580 .overload(
581 |angle: Angle, origin: Expr<Point>, context: &CompileContext, props| {
582 spiral(
583 origin,
584 angle,
585 Unitless::from(number!(SCALAR ProcNum::one())),
586 context,
587 props,
588 )
589 },
590 ),
591 )
592 .add(
593 Function::new("translation")
594 .alias("translate")
595 .overload(translation),
596 )
597 .add(
598 Function::new("reflect")
599 .alias("reflection")
600 .overload(reflection),
601 )
602 .add(
603 Function::new("transform")
604 .alias_method(ty::derived("TransformType"), "transform")
605 .alias_method(ty::derived("TransformType"), "t")
606 .overload(TransformOverload),
607 )
608 .add(
609 Function::new("compose")
610 .alias_method(Type::Derived("TransformType"), "compose")
611 .overload(compose),
612 );
613}