1use crate::FromDynamic;
3use fidget_core::{
4 context::{Tree, TreeOp},
5 var::Var,
6};
7use fidget_shapes::types::{Axis, Plane, Vec2, Vec3, Vec4};
8use rhai::EvalAltResult;
9
10macro_rules! register_all {
11 ($engine:ident, $ty:ident) => {
12 register_binary!($engine, $ty, "+", add, Add);
13 register_binary!($engine, $ty, "*", mul, Mul);
14 register_binary!($engine, $ty, "-", sub, Sub);
15 register_binary!($engine, $ty, "/", div, Div);
16
17 register_binary!($engine, $ty, min);
18 register_binary!($engine, $ty, max);
19
20 register_unary!($engine, $ty, sqrt);
21 register_unary!($engine, $ty, abs);
22 $engine.register_fn("-", |v: $ty| -v);
23 };
24}
25
26macro_rules! register_binary {
27 ($engine:ident, $ty:ident, $rop:expr, $base_fn:ident $(, $op:ident)?) => {
28 $engine.register_fn($rop, |a: $ty, b: $ty| -> $ty {
29 $( use std::ops::$op; )?
30 a.$base_fn(b)
31 });
32 $engine.register_fn($rop, |a: $ty, b: f64| -> $ty {
33 $( use std::ops::$op; )?
34 a.$base_fn(b)
35 });
36 $engine.register_fn($rop, |a: $ty, b: i64| -> $ty {
37 $( use std::ops::$op; )?
38 a.$base_fn(b as f64)
39 });
40 $engine.register_fn($rop, |a: f64, b: $ty| -> $ty {
41 $( use std::ops::$op; )?
42 $ty::from(a).$base_fn(b)
43 });
44 $engine.register_fn($rop, |a: i64, b: $ty| -> $ty {
45 $( use std::ops::$op; )?
46 $ty::from(a as f64).$base_fn(b)
47 });
48 };
49 ($engine:ident, $ty:ident, $base_fn:ident) => {
50 register_binary!($engine, $ty, stringify!($base_fn), $base_fn)
51 };
52}
53
54macro_rules! register_unary {
55 ($engine:ident, $ty:ident, $base_fn:ident) => {
56 $engine.register_fn(stringify!($base_fn), |a: $ty| -> $ty {
57 a.$base_fn()
58 });
59 };
60}
61
62pub fn register(engine: &mut rhai::Engine) {
64 register_vec2(engine);
65 register_vec3(engine);
66 register_all!(engine, Vec2);
67 register_all!(engine, Vec3);
68
69 register_axis(engine);
70 register_plane(engine);
71}
72
73fn register_vec2(engine: &mut rhai::Engine) {
74 engine
75 .register_type_with_name::<Vec2>("Vec2")
76 .register_fn("to_string", |t: &mut Vec2| format!("[{}, {}]", t.x, t.y))
77 .register_fn("vec2", |v: Vec2| v) .register_fn(
79 "vec2",
80 |ctx: rhai::NativeCallContext,
81 x: rhai::Dynamic,
82 y: rhai::Dynamic|
83 -> Result<Vec2, Box<EvalAltResult>> {
84 let x = f64::from_dynamic(&ctx, x, None)?;
85 let y = f64::from_dynamic(&ctx, y, None)?;
86 Ok(Vec2 { x, y })
87 },
88 )
89 .register_fn(
90 "vec2",
91 |ctx: rhai::NativeCallContext,
92 array: rhai::Array|
93 -> Result<Vec2, Box<EvalAltResult>> {
94 vec2_from_rhai_array(&ctx, array)
95 },
96 )
97 .register_get_set("x", |v: &mut Vec2| v.x, |v: &mut Vec2, x| v.x = x)
98 .register_get_set("y", |v: &mut Vec2| v.y, |v: &mut Vec2, y| v.y = y);
99}
100
101fn register_vec3(engine: &mut rhai::Engine) {
102 engine
103 .register_type_with_name::<Vec3>("Vec3")
104 .register_fn("to_string", |t: &mut Vec3| {
105 format!("[{}, {}, {}]", t.x, t.y, t.z)
106 })
107 .register_fn("vec3", |v: Vec3| v) .register_fn(
109 "vec3",
110 |ctx: rhai::NativeCallContext,
111 x: rhai::Dynamic,
112 y: rhai::Dynamic,
113 z: rhai::Dynamic|
114 -> Result<Vec3, Box<EvalAltResult>> {
115 let x = f64::from_dynamic(&ctx, x, None)?;
116 let y = f64::from_dynamic(&ctx, y, None)?;
117 let z = f64::from_dynamic(&ctx, z, None)?;
118 Ok(Vec3 { x, y, z })
119 },
120 )
121 .register_fn(
122 "vec3",
123 |ctx: rhai::NativeCallContext,
124 array: rhai::Array|
125 -> Result<Vec3, Box<EvalAltResult>> {
126 vec3_from_rhai_array(&ctx, array, None)
127 },
128 )
129 .register_get_set("x", |v: &mut Vec3| v.x, |v: &mut Vec3, x| v.x = x)
130 .register_get_set("y", |v: &mut Vec3| v.y, |v: &mut Vec3, y| v.y = y)
131 .register_get_set("z", |v: &mut Vec3| v.z, |v: &mut Vec3, z| v.z = z);
132}
133
134impl FromDynamic for Vec2 {
135 fn from_dynamic(
136 ctx: &rhai::NativeCallContext,
137 d: rhai::Dynamic,
138 _default: Option<&Vec2>,
139 ) -> Result<Self, Box<EvalAltResult>> {
140 if let Some(v) = d.clone().try_cast() {
141 Ok(v)
142 } else {
143 let array = d.into_array().map_err(|ty| {
144 EvalAltResult::ErrorMismatchDataType(
145 "array".to_string(),
146 ty.to_string(),
147 ctx.position(),
148 )
149 })?;
150 vec2_from_rhai_array(ctx, array)
151 }
152 }
153}
154
155fn vec2_from_rhai_array(
156 ctx: &rhai::NativeCallContext,
157 array: rhai::Array,
158) -> Result<Vec2, Box<EvalAltResult>> {
159 match array.len() {
160 2 => {
161 let x = f64::from_dynamic(ctx, array[0].clone(), None)?;
162 let y = f64::from_dynamic(ctx, array[1].clone(), None)?;
163 Ok(Vec2 { x, y })
164 }
165 n => Err(EvalAltResult::ErrorMismatchDataType(
166 "[float; 2]".to_string(),
167 format!("[dynamic; {n}]"),
168 ctx.position(),
169 )
170 .into()),
171 }
172}
173
174impl FromDynamic for Vec3 {
175 fn from_dynamic(
176 ctx: &rhai::NativeCallContext,
177 d: rhai::Dynamic,
178 default: Option<&Vec3>,
179 ) -> Result<Self, Box<EvalAltResult>> {
180 if let Ok(v) = Vec2::from_dynamic(ctx, d.clone(), None) {
181 Ok(Vec3 {
182 x: v.x,
183 y: v.y,
184 z: default.map(|d| d.z).unwrap_or(0.0),
185 })
186 } else if let Some(v) = d.clone().try_cast() {
187 Ok(v)
188 } else {
189 let array = d.into_array().map_err(|ty| {
190 EvalAltResult::ErrorMismatchDataType(
191 "array".to_string(),
192 ty.to_string(),
193 ctx.position(),
194 )
195 })?;
196 vec3_from_rhai_array(ctx, array, default)
197 }
198 }
199}
200
201fn vec3_from_rhai_array(
202 ctx: &rhai::NativeCallContext,
203 array: rhai::Array,
204 default: Option<&Vec3>,
205) -> Result<Vec3, Box<EvalAltResult>> {
206 match array.len() {
207 2 => {
208 let x = f64::from_dynamic(ctx, array[0].clone(), None)?;
209 let y = f64::from_dynamic(ctx, array[1].clone(), None)?;
210 let z = default.map(|d| d.z).unwrap_or(0.0);
211 Ok(Vec3 { x, y, z })
212 }
213 3 => {
214 let x = f64::from_dynamic(ctx, array[0].clone(), None)?;
215 let y = f64::from_dynamic(ctx, array[1].clone(), None)?;
216 let z = f64::from_dynamic(ctx, array[2].clone(), None)?;
217 Ok(Vec3 { x, y, z })
218 }
219 n => Err(EvalAltResult::ErrorMismatchDataType(
220 "[float; 3]".to_string(),
221 format!("[dynamic; {n}]"),
222 ctx.position(),
223 )
224 .into()),
225 }
226}
227
228impl FromDynamic for Vec4 {
229 fn from_dynamic(
230 ctx: &rhai::NativeCallContext,
231 d: rhai::Dynamic,
232 _default: Option<&Vec4>,
233 ) -> Result<Self, Box<EvalAltResult>> {
234 if let Some(v) = d.clone().try_cast() {
235 Ok(v)
236 } else {
237 let array = d.into_array().map_err(|ty| {
238 EvalAltResult::ErrorMismatchDataType(
239 "array".to_string(),
240 ty.to_string(),
241 ctx.position(),
242 )
243 })?;
244 match array.len() {
245 4 => {
246 let x = f64::from_dynamic(ctx, array[0].clone(), None)?;
247 let y = f64::from_dynamic(ctx, array[1].clone(), None)?;
248 let z = f64::from_dynamic(ctx, array[2].clone(), None)?;
249 let w = f64::from_dynamic(ctx, array[3].clone(), None)?;
250 Ok(Vec4 { x, y, z, w })
251 }
252 n => Err(EvalAltResult::ErrorMismatchDataType(
253 "[float; 4]".to_string(),
254 format!("[dynamic; {n}]"),
255 ctx.position(),
256 )
257 .into()),
258 }
259 }
260 }
261}
262
263impl FromDynamic for Axis {
264 fn from_dynamic(
265 ctx: &rhai::NativeCallContext,
266 d: rhai::Dynamic,
267 _default: Option<&Self>,
268 ) -> Result<Self, Box<EvalAltResult>> {
269 let out = if let Some(v) = d.clone().try_cast() {
270 Some(v)
271 } else if let Ok(v) = Vec3::from_dynamic(ctx, d.clone(), None) {
272 let v = v.try_into().map_err(|e| {
273 Box::new(EvalAltResult::ErrorMismatchDataType(
274 format!("conversion failed: {e}"),
275 "vec3 with reasonable length".to_string(),
276 ctx.position(),
277 ))
278 })?;
279 Some(v)
280 } else if let Ok(s) = d.clone().into_immutable_string() {
281 match s.as_str() {
282 "x" | "X" => Some(Axis::X),
283 "y" | "Y" => Some(Axis::Y),
284 "z" | "Z" => Some(Axis::Z),
285 _ => None,
286 }
287 } else if let Ok(c) = d.clone().as_char() {
288 match c {
289 'x' | 'X' => Some(Axis::X),
290 'y' | 'Y' => Some(Axis::Y),
291 'z' | 'Z' => Some(Axis::Z),
292 _ => None,
293 }
294 } else if let Some(t) = d.clone().try_cast::<Tree>() {
295 match &*t {
296 TreeOp::Input(Var::X) => Some(Axis::X),
297 TreeOp::Input(Var::Y) => Some(Axis::Y),
298 TreeOp::Input(Var::Z) => Some(Axis::Z),
299 _ => None,
300 }
301 } else {
302 None
303 };
304
305 out.ok_or_else(|| {
306 EvalAltResult::ErrorMismatchDataType(
307 "vec3 or [float; 3]".to_string(),
308 d.type_name().to_owned(),
309 ctx.position(),
310 )
311 .into()
312 })
313 }
314}
315
316fn print_axis(t: Axis) -> String {
317 if t == Axis::X {
318 "axis(\"x\")".to_owned()
319 } else if t == Axis::Y {
320 "axis(\"y\")".to_owned()
321 } else if t == Axis::Z {
322 "axis(\"z\")".to_owned()
323 } else {
324 let v = t.vec();
325 format!("axis([{}, {}, {}])", v.x, v.y, v.z)
326 }
327}
328
329fn register_axis(engine: &mut rhai::Engine) {
330 engine
331 .register_type_with_name::<Axis>("Axis")
332 .register_fn("to_string", |t: &mut Axis| print_axis(*t))
333 .register_fn(
334 "axis",
335 |ctx: rhai::NativeCallContext,
336 v: rhai::Dynamic|
337 -> Result<Axis, Box<EvalAltResult>> {
338 Axis::from_dynamic(&ctx, v, None)
339 },
340 );
341}
342
343impl FromDynamic for Plane {
344 fn from_dynamic(
345 ctx: &rhai::NativeCallContext,
346 d: rhai::Dynamic,
347 _default: Option<&Self>,
348 ) -> Result<Self, Box<EvalAltResult>> {
349 let r = if let Some(v) = d.clone().try_cast() {
350 Some(v)
351 } else if let Ok(axis) = Axis::from_dynamic(ctx, d.clone(), None) {
352 Some(Self { axis, offset: 0.0 })
353 } else if let Ok(s) = d.clone().into_immutable_string() {
354 match s.as_str() {
355 "xy" | "XY" => Some(Plane::XY),
356 "yz" | "YZ" => Some(Plane::YZ),
357 "zx" | "ZX" => Some(Plane::ZX),
358 _ => None,
359 }
360 } else {
361 None
362 };
363
364 r.ok_or_else(|| {
365 EvalAltResult::ErrorMismatchDataType(
366 "axis or plane name".to_owned(),
367 d.type_name().to_owned(),
368 ctx.position(),
369 )
370 .into()
371 })
372 }
373}
374
375fn register_plane(engine: &mut rhai::Engine) {
376 engine
377 .register_type_with_name::<Plane>("Plane")
378 .register_fn("to_string", |t: &mut Plane| {
379 if t == &Plane::XY {
380 "plane(\"xy\")".to_owned()
381 } else if t == &Plane::YZ {
382 "plane(\"yz\")".to_owned()
383 } else if t == &Plane::ZX {
384 "plane(\"zx\")".to_owned()
385 } else {
386 let ax = print_axis(t.axis);
387 if t.offset == 0.0 {
388 format!("plane({ax})")
389 } else {
390 format!("plane({ax}, {})", t.offset)
391 }
392 }
393 })
394 .register_fn(
395 "plane",
396 |ctx: rhai::NativeCallContext,
397 v: rhai::Dynamic|
398 -> Result<Plane, Box<EvalAltResult>> {
399 Plane::from_dynamic(&ctx, v, None)
400 },
401 )
402 .register_fn(
403 "plane",
404 |ctx: rhai::NativeCallContext,
405 v: rhai::Dynamic,
406 offset: f64|
407 -> Result<Plane, Box<EvalAltResult>> {
408 let plane = Plane::from_dynamic(&ctx, v, None)?;
409 Ok(Plane {
410 axis: plane.axis,
411 offset,
412 })
413 },
414 );
415}
416
417#[cfg(test)]
418mod test {
419 use super::*;
420 use std::sync::{Arc, Mutex};
421
422 #[test]
423 fn type_constructors() {
424 let mut e = rhai::Engine::new();
425 register(&mut e);
426 assert_eq!(
427 e.eval::<Vec2>("vec2([5, 10.0])").unwrap(),
428 Vec2::new(5.0, 10.0),
429 );
430 assert_eq!(
431 e.eval::<Vec3>("vec3([1, 2, 3])").unwrap(),
432 Vec3::new(1.0, 2.0, 3.0),
433 );
434 assert_eq!(
435 e.eval::<Vec3>("vec3([1, 2])").unwrap(),
436 Vec3::new(1.0, 2.0, 0.0),
437 );
438
439 assert_eq!(e.eval::<Axis>("axis([1, 0])").unwrap(), Axis::X);
440 assert_eq!(e.eval::<Axis>("axis([0, 0, 1])").unwrap(), Axis::Z);
441 assert_eq!(e.eval::<Axis>("axis('z')").unwrap(), Axis::Z);
442 assert_eq!(e.eval::<Axis>("axis(\"z\")").unwrap(), Axis::Z);
443 assert!(e.eval::<Axis>("axis([0, 0, 0])").is_err());
444
445 assert_eq!(e.eval::<Plane>("plane([1, 0])").unwrap(), Plane::YZ);
446 assert_eq!(
447 e.eval::<Plane>("plane([1, 0], 0.5)").unwrap(),
448 Plane {
449 axis: Axis::X,
450 offset: 0.5
451 }
452 );
453 assert_eq!(e.eval::<Plane>("plane(\"yz\")").unwrap(), Plane::YZ);
454 }
455
456 #[test]
457 fn type_printing() {
458 let mut e = rhai::Engine::new();
459 register(&mut e);
460 let lines = Arc::new(Mutex::new(vec![]));
461 let lines_ = lines.clone();
462 e.on_print(move |s| lines_.lock().unwrap().push(s.to_string()));
463
464 e.eval::<()>("print(vec2(1, 0))").unwrap();
465 assert_eq!(
466 lines.lock().unwrap().last().map(|s| s.as_str()),
467 Some("[1, 0]")
468 );
469
470 e.eval::<()>("print(vec3(1, 2, 3))").unwrap();
471 assert_eq!(
472 lines.lock().unwrap().last().map(|s| s.as_str()),
473 Some("[1, 2, 3]")
474 );
475
476 e.eval::<()>("print(axis([1, 0]))").unwrap();
477 assert_eq!(
478 lines.lock().unwrap().last().map(|s| s.as_str()),
479 Some("axis(\"x\")")
480 );
481
482 e.eval::<()>("print(plane([1, 0]))").unwrap();
483 assert_eq!(
484 lines.lock().unwrap().last().map(|s| s.as_str()),
485 Some("plane(\"yz\")")
486 );
487 }
488
489 #[test]
490 fn type_ops() {
491 let mut e = rhai::Engine::new();
492 register(&mut e);
493 let v = e.eval::<Vec2>("-vec2(1, 2)").unwrap();
494 assert_eq!(v.x, -1.0);
495 assert_eq!(v.y, -2.0);
496 }
497}