fidget_rhai/
tree.rs

1//! Rhai bindings for the Fidget [`Tree`] type
2use crate::FromDynamic;
3use fidget_core::{
4    context::{Tree, TreeOp},
5    var::Var,
6};
7use rhai::{EvalAltResult, NativeCallContext};
8
9impl FromDynamic for Tree {
10    fn from_dynamic(
11        ctx: &rhai::NativeCallContext,
12        d: rhai::Dynamic,
13        _default: Option<&Tree>,
14    ) -> Result<Self, Box<EvalAltResult>> {
15        if let Some(t) = d.clone().try_cast::<Tree>() {
16            Ok(t)
17        } else if let Ok(v) = f64::from_dynamic(ctx, d.clone(), None) {
18            Ok(Tree::constant(v))
19        } else if let Ok(v) = <Vec<Tree>>::from_dynamic(ctx, d.clone(), None) {
20            Ok(fidget_shapes::Union { input: v }.into())
21        } else {
22            Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
23                "tree".to_string(),
24                d.type_name().to_string(),
25                ctx.position(),
26            )))
27        }
28    }
29}
30
31impl FromDynamic for Vec<Tree> {
32    fn from_dynamic(
33        ctx: &rhai::NativeCallContext,
34        d: rhai::Dynamic,
35        _default: Option<&Vec<Tree>>,
36    ) -> Result<Self, Box<EvalAltResult>> {
37        if let Ok(d) = d.clone().into_array() {
38            d.into_iter()
39                .map(|v| Tree::from_dynamic(ctx, v, None))
40                .collect::<Result<Vec<_>, _>>()
41        } else {
42            Err(Box::new(rhai::EvalAltResult::ErrorMismatchDataType(
43                "Vec<tree>".to_string(),
44                d.type_name().to_string(),
45                ctx.position(),
46            )))
47        }
48    }
49}
50
51fn register_tree(engine: &mut rhai::Engine) {
52    engine
53        .register_type_with_name::<Tree>("Tree")
54        .register_fn("to_string", |t: &mut Tree| {
55            match &**t {
56                TreeOp::Input(Var::X) => "x",
57                TreeOp::Input(Var::Y) => "y",
58                TreeOp::Input(Var::Z) => "z",
59                _ => "Tree(..)",
60            }
61            .to_owned()
62        })
63        .register_fn("remap", remap_xyz)
64        .register_fn("remap", remap_xy);
65}
66
67/// Installs the [`Tree`] type into a Rhai engine, with various overloads
68///
69/// Also installs `axes() -> {x, y, z}`
70pub fn register(engine: &mut rhai::Engine) {
71    register_tree(engine);
72    engine.register_fn("axes", || {
73        use fidget_core::context::Tree;
74        let mut out = rhai::Map::new();
75        out.insert("x".into(), rhai::Dynamic::from(Tree::x()));
76        out.insert("y".into(), rhai::Dynamic::from(Tree::y()));
77        out.insert("z".into(), rhai::Dynamic::from(Tree::z()));
78        out
79    });
80
81    macro_rules! register_binary_fns {
82        ($op:literal, $name:ident, $engine:ident) => {
83            $engine.register_fn($op, $name::tree_dyn);
84            $engine.register_fn($op, $name::dyn_tree);
85        };
86    }
87    macro_rules! register_unary_fns {
88        ($op:literal, $name:ident, $engine:ident) => {
89            $engine.register_fn($op, $name::tree);
90        };
91    }
92
93    register_binary_fns!("+", add, engine);
94    register_binary_fns!("-", sub, engine);
95    register_binary_fns!("*", mul, engine);
96    register_binary_fns!("/", div, engine);
97    register_binary_fns!("%", modulo, engine);
98    register_binary_fns!("min", min, engine);
99    register_binary_fns!("max", max, engine);
100    register_binary_fns!("compare", compare, engine);
101    register_binary_fns!("and", and, engine);
102    register_binary_fns!("or", or, engine);
103    register_binary_fns!("atan2", atan2, engine);
104    register_unary_fns!("abs", abs, engine);
105    register_unary_fns!("sqrt", sqrt, engine);
106    register_unary_fns!("square", square, engine);
107    register_unary_fns!("sin", sin, engine);
108    register_unary_fns!("cos", cos, engine);
109    register_unary_fns!("tan", tan, engine);
110    register_unary_fns!("asin", asin, engine);
111    register_unary_fns!("acos", acos, engine);
112    register_unary_fns!("atan", atan, engine);
113    register_unary_fns!("exp", exp, engine);
114    register_unary_fns!("ln", ln, engine);
115    register_unary_fns!("not", not, engine);
116    register_unary_fns!("ceil", ceil, engine);
117    register_unary_fns!("floor", floor, engine);
118    register_unary_fns!("round", round, engine);
119    register_unary_fns!("-", neg, engine);
120
121    // Ban comparison operators
122    for op in ["==", "!=", "<", ">", "<=", ">="] {
123        engine.register_fn(op, bad_cmp_tree_dyn);
124        engine.register_fn(op, bad_cmp_dyn_tree);
125    }
126}
127
128fn remap_xyz(
129    ctx: NativeCallContext,
130    shape: rhai::Dynamic,
131    x: Tree,
132    y: Tree,
133    z: Tree,
134) -> Result<Tree, Box<EvalAltResult>> {
135    let shape = Tree::from_dynamic(&ctx, shape, None)?;
136    Ok(shape.remap_xyz(x, y, z))
137}
138
139fn remap_xy(
140    ctx: NativeCallContext,
141    shape: rhai::Dynamic,
142    x: Tree,
143    y: Tree,
144) -> Result<Tree, Box<EvalAltResult>> {
145    let shape = Tree::from_dynamic(&ctx, shape, None)?;
146    Ok(shape.remap_xyz(x, y, Tree::z()))
147}
148
149macro_rules! define_binary_fns {
150    ($name:ident $(, $op:ident)?) => {
151        mod $name {
152            use super::*;
153            use NativeCallContext;
154            $(
155            use std::ops::$op;
156            )?
157            pub fn tree_dyn(
158                ctx: NativeCallContext,
159                a: Tree,
160                b: rhai::Dynamic,
161            ) -> Result<Tree, Box<rhai::EvalAltResult>> {
162                let b = Tree::from_dynamic(&ctx, b, None)?;
163                Ok(a.$name(b))
164            }
165            pub fn dyn_tree(
166                ctx: NativeCallContext,
167                a: rhai::Dynamic,
168                b: Tree,
169            ) -> Result<Tree, Box<rhai::EvalAltResult>> {
170                let a = Tree::from_dynamic(&ctx, a, None)?;
171                Ok(a.$name(b))
172            }
173        }
174    };
175}
176
177macro_rules! define_unary_fns {
178    ($name:ident) => {
179        mod $name {
180            use super::*;
181            pub fn tree(
182                ctx: NativeCallContext,
183                a: rhai::Dynamic,
184            ) -> Result<Tree, Box<EvalAltResult>> {
185                let a = Tree::from_dynamic(&ctx, a, None)?;
186                Ok(a.$name())
187            }
188        }
189    };
190}
191
192fn bad_cmp_tree_dyn(
193    _ctx: NativeCallContext,
194    _a: Tree,
195    _b: rhai::Dynamic,
196) -> Result<Tree, Box<rhai::EvalAltResult>> {
197    let e = "cannot compare Tree types during function tracing";
198    Err(e.into())
199}
200
201fn bad_cmp_dyn_tree(
202    _ctx: NativeCallContext,
203    _a: rhai::Dynamic,
204    _b: Tree,
205) -> Result<Tree, Box<rhai::EvalAltResult>> {
206    let e = "cannot compare Tree types during function tracing";
207    Err(e.into())
208}
209
210define_binary_fns!(add, Add);
211define_binary_fns!(sub, Sub);
212define_binary_fns!(mul, Mul);
213define_binary_fns!(div, Div);
214define_binary_fns!(min);
215define_binary_fns!(max);
216define_binary_fns!(compare);
217define_binary_fns!(modulo);
218define_binary_fns!(and);
219define_binary_fns!(or);
220define_binary_fns!(atan2);
221define_unary_fns!(sqrt);
222define_unary_fns!(square);
223define_unary_fns!(neg);
224define_unary_fns!(sin);
225define_unary_fns!(cos);
226define_unary_fns!(tan);
227define_unary_fns!(asin);
228define_unary_fns!(acos);
229define_unary_fns!(atan);
230define_unary_fns!(exp);
231define_unary_fns!(ln);
232define_unary_fns!(not);
233define_unary_fns!(abs);
234define_unary_fns!(floor);
235define_unary_fns!(ceil);
236define_unary_fns!(round);
237
238#[cfg(test)]
239mod test {
240    use super::*;
241
242    #[test]
243    fn tree_build_print() {
244        let mut e = rhai::Engine::new();
245        register(&mut e);
246        assert_eq!(e.eval::<String>("to_string(axes().x)").unwrap(), "x");
247        assert_eq!(
248            e.eval::<String>("to_string(axes().x + 1)").unwrap(),
249            "Tree(..)"
250        );
251    }
252}