geo_aid_script/unroll/library/
transform.rs

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    /// Result of the transform.
15    type Output: GeoType + Into<AnyExpr>;
16
17    /// Perform the transformation.
18    #[must_use]
19    fn transform(&self, object: T, context: &mut CompileContext, props: Properties)
20        -> Self::Output;
21}
22
23trait TransformErased<T: GeoType> {
24    /// Perform the transformation.
25    #[must_use]
26    fn transform_erased(
27        &self,
28        object: T,
29        context: &mut CompileContext,
30        props: Properties,
31    ) -> AnyExpr;
32
33    /// What type is the output
34    #[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/// A transformation of space.
100#[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(&params).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}