//! Functions and the `Sprite` type registered into the Rhai engine. use crate::conv::*; use crate::State; use isokit_core::fmt::{hash, round1, thousands}; use isokit_core::{classes, LayerOpts, Sprite, C}; use rhai::{Array, Dynamic, Engine, FnPtr, Map, NativeCallContext}; use std::cell::RefCell; use std::rc::Rc; /// Script handle to a sprite: copies share the same sprite. #[derive(Clone)] pub struct SpriteH(pub Rc>); type Shared = Rc>; type D = Dynamic; fn ok(s: &SpriteH) -> RR { Ok(s.clone()) } fn opt_id(d: &str) -> Option { if d.is_empty() { None } else { Some(d.to_string()) } } pub(crate) fn new_sprite(st: &Shared, ctx: Option<&NativeCallContext>, id: Option<&str>, cls: &str) -> SpriteH { let mut s = st.borrow_mut(); let id = id.map(str::to_string).or_else(|| s.obj.clone()).unwrap_or_default(); let mut sp = Sprite::new(id, classes(cls)).with_frame(s.xf); sp.obj = s.obj.clone(); if let Some(ctx) = ctx { if let Some(line) = ctx.call_position().line() { let src = ctx.call_source().unwrap_or(&s.main).to_string(); sp.src = Some(format!("{src}:{line}")); } } let h = SpriteH(Rc::new(RefCell::new(sp))); let layer = s.layer.clone(); s.sprites.push((layer, h.clone())); h } /// Sprite methods (no scene state needed). pub fn register_sprite(e: &mut Engine) { e.register_type_with_name::("Sprite"); e.register_get("id", |s: &mut SpriteH| s.0.borrow().id.clone()); // solids e.register_fn("box", |s: &mut SpriteH, x0: D, y0: D, x1: D, y1: D, z0: D, z1: D, mat: &str| -> RR { s.0.borrow_mut().cuboid(n(&x0)?, n(&y0)?, n(&x1)?, n(&y1)?, n(&z0)?, n(&z1)?, style(mat, None)); ok(s) }); e.register_fn( "box", |s: &mut SpriteH, x0: D, y0: D, x1: D, y1: D, z0: D, z1: D, mat: &str, o: Map| -> RR { s.0.borrow_mut().cuboid(n(&x0)?, n(&y0)?, n(&x1)?, n(&y1)?, n(&z0)?, n(&z1)?, style(mat, Some(&o))); ok(s) }, ); e.register_fn("prism", |s: &mut SpriteH, p: D, z0: D, z1: D, mat: &str| -> RR { s.0.borrow_mut().prism(pts(&p)?, zs(&z0)?, zs(&z1)?, style(mat, None)); ok(s) }); e.register_fn("prism", |s: &mut SpriteH, p: D, z0: D, z1: D, mat: &str, o: Map| -> RR { s.0.borrow_mut().prism(pts(&p)?, zs(&z0)?, zs(&z1)?, style(mat, Some(&o))); ok(s) }); e.register_fn("cyl", |s: &mut SpriteH, cx: D, cy: D, r: D, z0: D, z1: D, mat: &str| -> RR { s.0.borrow_mut().cyl(n(&cx)?, n(&cy)?, n(&r)?, n(&z0)?, n(&z1)?, style(mat, None)); ok(s) }); e.register_fn("cyl", |s: &mut SpriteH, cx: D, cy: D, r: D, z0: D, z1: D, mat: &str, o: Map| -> RR { s.0.borrow_mut().cyl(n(&cx)?, n(&cy)?, n(&r)?, n(&z0)?, n(&z1)?, style(mat, Some(&o))); ok(s) }); e.register_fn( "frust", |s: &mut SpriteH, cx: D, cy: D, r0: D, r1: D, z0: D, z1: D, mat: &str| -> RR { s.0.borrow_mut().frust(n(&cx)?, n(&cy)?, n(&r0)?, n(&r1)?, n(&z0)?, n(&z1)?, 10, style(mat, None)); ok(s) }, ); e.register_fn( "frust", |s: &mut SpriteH, cx: D, cy: D, r0: D, r1: D, z0: D, z1: D, mat: &str, sides: D| -> RR { let k = n(&sides)? as u32; s.0.borrow_mut().frust(n(&cx)?, n(&cy)?, n(&r0)?, n(&r1)?, n(&z0)?, n(&z1)?, k, style(mat, None)); ok(s) }, ); e.register_fn( "frust", |s: &mut SpriteH, cx: D, cy: D, r0: D, r1: D, z0: D, z1: D, mat: &str, sides: D, o: Map| -> RR { let k = n(&sides)? as u32; s.0.borrow_mut().frust(n(&cx)?, n(&cy)?, n(&r0)?, n(&r1)?, n(&z0)?, n(&z1)?, k, style(mat, Some(&o))); ok(s) }, ); e.register_fn( "slope", |s: &mut SpriteH, x0: D, x1: D, y0: D, y1: D, za: D, zb: D, th: D, mat: &str| -> RR { s.0.borrow_mut().slope(n(&x0)?, n(&x1)?, n(&y0)?, n(&y1)?, n(&za)?, n(&zb)?, n(&th)?, &classes(mat)); ok(s) }, ); // surfaces e.register_fn("open", |s: &mut SpriteH, a: D, b: D, list: D, cls: &str| -> RR { s.0.borrow_mut().open(p2(&a)?, p2(&b)?, quads(&list, "openings")?, &classes(cls), None); ok(s) }); e.register_fn("open", |s: &mut SpriteH, a: D, b: D, list: D, cls: &str, id: &str| -> RR { s.0.borrow_mut().open(p2(&a)?, p2(&b)?, quads(&list, "openings")?, &classes(cls), opt_id(id)); ok(s) }); e.register_fn("flat", |s: &mut SpriteH, p: D, z: D, cls: &str| -> RR { s.0.borrow_mut().flat(polys(&p)?, n(&z)?, &classes(cls), None); ok(s) }); e.register_fn("flat", |s: &mut SpriteH, p: D, z: D, cls: &str, id: &str| -> RR { s.0.borrow_mut().flat(polys(&p)?, n(&z)?, &classes(cls), opt_id(id)); ok(s) }); e.register_fn("lines", |s: &mut SpriteH, segs: D, z: D, cls: &str| -> RR { s.0.borrow_mut().lines(quads(&segs, "segments")?, n(&z)?, &classes(cls)); ok(s) }); e.register_fn("disc", |s: &mut SpriteH, cx: D, cy: D, z: D, r: D, cls: &str| -> RR { s.0.borrow_mut().disc(n(&cx)?, n(&cy)?, n(&z)?, n(&r)?, &classes(cls), None); ok(s) }); e.register_fn("disc", |s: &mut SpriteH, cx: D, cy: D, z: D, r: D, cls: &str, id: &str| -> RR { s.0.borrow_mut().disc(n(&cx)?, n(&cy)?, n(&z)?, n(&r)?, &classes(cls), opt_id(id)); ok(s) }); e.register_fn("face", |s: &mut SpriteH, p: D, mat: &str| -> RR { s.0.borrow_mut().face(pts3(&p)?, &classes(mat), None, false); ok(s) }); e.register_fn("face", |s: &mut SpriteH, p: D, mat: &str, o: Map| -> RR { let id = o.get("id").map(|v| v.to_string()); let two = o.get("two_sided").and_then(|v| v.as_bool().ok()).unwrap_or(false); s.0.borrow_mut().face(pts3(&p)?, &classes(mat), id, two); ok(s) }); e.register_fn("poly", |s: &mut SpriteH, p: D, cls: &str| -> RR { s.0.borrow_mut().poly(polys3(&p)?, &classes(cls), None); ok(s) }); e.register_fn("poly", |s: &mut SpriteH, p: D, cls: &str, id: &str| -> RR { s.0.borrow_mut().poly(polys3(&p)?, &classes(cls), opt_id(id)); ok(s) }); // screen-space details e.register_fn("circle", |s: &mut SpriteH, x: D, y: D, z: D, r: D, cls: &str| -> RR { s.0.borrow_mut().circle([n(&x)?, n(&y)?, n(&z)?], n(&r)?, [0.0, 0.0], &classes(cls)); ok(s) }); e.register_fn( "circle", |s: &mut SpriteH, x: D, y: D, z: D, r: D, cls: &str, dx: D, dy: D| -> RR { s.0.borrow_mut().circle([n(&x)?, n(&y)?, n(&z)?], n(&r)?, [n(&dx)?, n(&dy)?], &classes(cls)); ok(s) }, ); e.register_fn("stem", |s: &mut SpriteH, x: D, y: D, z0: D, z1: D, w: D, cls: &str| -> RR { s.0.borrow_mut().stem(n(&x)?, n(&y)?, n(&z0)?, n(&z1)?, n(&w)?, &classes(cls)); ok(s) }); e.register_fn("head", |s: &mut SpriteH, x: D, y: D, z: D, r: D| -> RR { s.0.borrow_mut().head(n(&x)?, n(&y)?, n(&z)?, n(&r)?); ok(s) }); e.register_fn("person", |s: &mut SpriteH, x: D, y: D, z: D| -> RR { s.0.borrow_mut().person(n(&x)?, n(&y)?, n(&z)?, "iso-hivis"); ok(s) }); e.register_fn("person", |s: &mut SpriteH, x: D, y: D, z: D, cls: &str| -> RR { s.0.borrow_mut().person(n(&x)?, n(&y)?, n(&z)?, &classes(cls)); ok(s) }); e.register_fn("sit", |s: &mut SpriteH, x: D, y: D, z: D, cls: &str| -> RR { s.0.borrow_mut().sit(n(&x)?, n(&y)?, n(&z)?, &classes(cls)); ok(s) }); e.register_fn("led", |s: &mut SpriteH, x: D, y: D, z: D| -> RR { s.0.borrow_mut().led(n(&x)?, n(&y)?, n(&z)?, ""); ok(s) }); e.register_fn("led", |s: &mut SpriteH, x: D, y: D, z: D, cls: &str| -> RR { s.0.borrow_mut().led(n(&x)?, n(&y)?, n(&z)?, &classes(cls)); ok(s) }); e.register_fn("puffs", |s: &mut SpriteH, x: D, y: D, z: D| -> RR { s.0.borrow_mut().puffs(n(&x)?, n(&y)?, n(&z)?, "iso-puff", 3, 3.2); ok(s) }); e.register_fn("puffs", |s: &mut SpriteH, x: D, y: D, z: D, kind: &str| -> RR { s.0.borrow_mut().puffs(n(&x)?, n(&y)?, n(&z)?, &classes(kind), 3, 3.2); ok(s) }); e.register_fn( "puffs", |s: &mut SpriteH, x: D, y: D, z: D, kind: &str, k: D, dur: D| -> RR { s.0.borrow_mut().puffs(n(&x)?, n(&y)?, n(&z)?, &classes(kind), n(&k)? as u32, n(&dur)?); ok(s) }, ); e.register_fn("fan", |s: &mut SpriteH, x: D, y: D, z: D, r: D| -> RR { s.0.borrow_mut().fan(n(&x)?, n(&y)?, n(&z)?, n(&r)?); ok(s) }); e.register_fn("raw", |s: &mut SpriteH, svg: &str| -> RR { s.0.borrow_mut().raw(None, svg); ok(s) }); e.register_fn("raw", |s: &mut SpriteH, x: D, y: D, z: D, svg: &str| -> RR { s.0.borrow_mut().raw(Some([n(&x)?, n(&y)?, n(&z)?]), svg); ok(s) }); // extents e.register_fn("ext", |s: &mut SpriteH, x: D, y: D, z: D| -> RR { s.0.borrow_mut().ext(n(&x)?, n(&y)?, n(&z)?); ok(s) }); e.register_fn("ext_box", |s: &mut SpriteH, x0: D, y0: D, x1: D, y1: D, z0: D, z1: D| -> RR { s.0.borrow_mut().ext_box(n(&x0)?, n(&y0)?, n(&x1)?, n(&y1)?, n(&z0)?, n(&z1)?); ok(s) }); // structure e.register_fn("begin", |s: &mut SpriteH, cls: &str| -> SpriteH { s.0.borrow_mut().begin_group(&classes(cls), ""); s.clone() }); e.register_fn("begin", |s: &mut SpriteH, cls: &str, css: &str| -> SpriteH { s.0.borrow_mut().begin_group(&classes(cls), css); s.clone() }); e.register_fn("end", |s: &mut SpriteH| -> SpriteH { s.0.borrow_mut().end_group(); s.clone() }); e.register_fn("group", |ctx: NativeCallContext, s: &mut SpriteH, cls: &str, f: FnPtr| -> RR { s.0.borrow_mut().begin_group(&classes(cls), ""); let r = f.call_within_context::(&ctx, (s.clone(),)); s.0.borrow_mut().end_group(); let _ = r?; ok(s) }); e.register_fn( "group", |ctx: NativeCallContext, s: &mut SpriteH, cls: &str, css: &str, f: FnPtr| -> RR { s.0.borrow_mut().begin_group(&classes(cls), css); let r = f.call_within_context::(&ctx, (s.clone(),)); s.0.borrow_mut().end_group(); let _ = r?; ok(s) }, ); // items: [[x, y, |s| { ... }], ...] drawn back to front by the anchor's depth e.register_fn("seq", |ctx: NativeCallContext, s: &mut SpriteH, items: Array| -> RR { s.0.borrow_mut().begin_seq(); for it in items { let a = it.into_array().map_err(|t| format!("seq: expected [x, y, fn], got {t}"))?; if a.len() < 3 { s.0.borrow_mut().end_seq(); return err("seq: expected [x, y, fn]"); } let f = a[2].clone().try_cast::().ok_or("seq: the third entry must be a function")?; s.0.borrow_mut().seq_item([n(&a[0])?, n(&a[1])?]); if let Err(e) = f.call_within_context::(&ctx, (s.clone(),)) { s.0.borrow_mut().end_seq(); return Err(e); } } s.0.borrow_mut().end_seq(); ok(s) }); } /// Scene-level functions (they read and write the shared build state). pub(crate) fn register_scene(e: &mut Engine, st: &Shared) { let s = st.clone(); e.register_fn("sprite", move |ctx: NativeCallContext| new_sprite(&s, Some(&ctx), None, "")); let s = st.clone(); e.register_fn("sprite", move |ctx: NativeCallContext, id: &str| new_sprite(&s, Some(&ctx), Some(id), "")); let s = st.clone(); e.register_fn("sprite", move |ctx: NativeCallContext, id: &str, cls: &str| { new_sprite(&s, Some(&ctx), Some(id), cls) }); // the shared ground sprite (unsorted layer "ground", no pick id) let s = st.clone(); e.register_fn("ground", move || { if let Some(g) = s.borrow().ground.clone() { return g; } let saved = std::mem::replace(&mut s.borrow_mut().layer, "ground".into()); let (obj, xf) = { let mut b = s.borrow_mut(); (b.obj.take(), std::mem::take(&mut b.xf)) }; let g = new_sprite(&s, None, Some(""), ""); let mut b = s.borrow_mut(); b.layer = saved; b.obj = obj; b.xf = xf; b.ground = Some(g.clone()); g }); let s = st.clone(); e.register_fn("layer", move |name: &str| { let mut b = s.borrow_mut(); b.scene.layer_mut(name); b.layer = name.to_string(); }); let s = st.clone(); e.register_fn("layer", move |name: &str, o: Map| { let mut b = s.borrow_mut(); let opts = LayerOpts { sort: o.get("sort").and_then(|v| v.as_bool().ok()).unwrap_or(true), zfirst: o.get("zfirst").and_then(|v| v.as_bool().ok()).unwrap_or(false), cls: o.get("cls").map(|v| classes(&v.to_string())).unwrap_or_else(|| "iso-sprites".into()), }; let before = o.get("before").map(|v| v.to_string()); b.scene.add_layer(name, opts, before.as_deref()); b.layer = name.to_string(); }); let s = st.clone(); e.register_fn("current_layer", move || s.borrow().layer.clone()); // tags and info fn tag(s: &Shared, x: D, y: D, z: D, text: &str, prio: f64, id: Option<&str>, kind: &str) -> RR<()> { let mut b = s.borrow_mut(); let p = b.xf.p3([n(&x)?, n(&y)?, n(&z)?]); let id = id.map(str::to_string).or_else(|| b.obj.clone()).unwrap_or_default(); b.scene.tag(p, text, prio, id, classes(kind)); Ok(()) } let s = st.clone(); e.register_fn("tag", move |x: D, y: D, z: D, t: &str| tag(&s, x, y, z, t, 1.0, None, "")); let s = st.clone(); e.register_fn("tag", move |x: D, y: D, z: D, t: &str, p: D| tag(&s, x, y, z, t, n(&p)?, None, "")); let s = st.clone(); e.register_fn("tag", move |x: D, y: D, z: D, t: &str, p: D, id: &str| tag(&s, x, y, z, t, n(&p)?, Some(id), "")); let s = st.clone(); e.register_fn("tag", move |x: D, y: D, z: D, t: &str, p: D, id: &str, k: &str| { tag(&s, x, y, z, t, n(&p)?, Some(id), k) }); let s = st.clone(); e.register_fn("info", move |id: &str, name: &str| s.borrow_mut().scene.info(id, name, "")); let s = st.clone(); e.register_fn("info", move |id: &str, name: &str, detail: &str| s.borrow_mut().scene.info(id, name, detail)); let s = st.clone(); e.register_fn("info", move |id: &str, m: Map| { let g = |k: &str| m.get(k).map(|v| v.to_string()).unwrap_or_default(); let mut b = s.borrow_mut(); b.scene.info(id, g("name"), g("detail")); if let Some(rows) = m.get("rows").and_then(|r| r.read_lock::().map(|a| a.clone())) { let rows: Vec<(String, String)> = rows .iter() .filter_map(|r| r.read_lock::().map(|a| a.clone())) .filter(|a| a.len() >= 2) .map(|a| (a[0].to_string(), a[1].to_string())) .collect(); if let Some(i) = b.scene.info.get_mut(id) { i.rows = rows; } } }); // framing let s = st.clone(); e.register_fn("focus", move |x: D, y: D, z: D| -> RR<()> { let mut b = s.borrow_mut(); let p = b.xf.p3([n(&x)?, n(&y)?, n(&z)?]); b.scene.focus = Some(p); Ok(()) }); let s = st.clone(); e.register_fn("bounds", move |x0: D, y0: D, x1: D, y1: D| -> RR<()> { let mut b = s.borrow_mut(); let r = b.xf.rect(n(&x0)?, n(&y0)?, n(&x1)?, n(&y1)?); b.scene.bounds = Some((r, -1.6)); Ok(()) }); let s = st.clone(); e.register_fn("bounds", move |x0: D, y0: D, x1: D, y1: D, zb: D| -> RR<()> { let mut b = s.borrow_mut(); let r = b.xf.rect(n(&x0)?, n(&y0)?, n(&x1)?, n(&y1)?); b.scene.bounds = Some((r, -n(&zb)?.abs())); Ok(()) }); let s = st.clone(); e.register_fn("defs", move |svg: &str| s.borrow_mut().scene.defs.push(svg.to_string())); let s = st.clone(); e.register_fn("warn", move |msg: &str| s.borrow_mut().scene.warnings.push(msg.to_string())); // view and inputs let s = st.clone(); e.register_fn("level", move || s.borrow().level.clone()); let s = st.clone(); e.register_fn("rot", move || s.borrow().proj.rot as i64); let s = st.clone(); e.register_fn("cfg", move || s.borrow().cfg.clone()); let s = st.clone(); e.register_fn("cfg", move |k: &str| s.borrow().cfg.get(k).cloned().unwrap_or(Dynamic::UNIT)); let s = st.clone(); e.register_fn("objects", move || s.borrow().all_objects.clone()); // is a face with outward normal (nx, ny) (object coordinates) turned towards the viewer? let s = st.clone(); e.register_fn("vis", move |nx: D, ny: D| -> RR { let b = s.borrow(); let w = b.xf.v(n(&nx)?, n(&ny)?); Ok(b.proj.vis(w[0], w[1]).is_some()) }); let s = st.clone(); e.register_fn("dv", move |dx: D, dy: D, dz: D| -> RR { let b = s.borrow(); let w = b.xf.v(n(&dx)?, n(&dy)?); let v = b.proj.dv(w[0], w[1], n(&dz)?); Ok(vec![Dynamic::from_float(round1(v[0])), Dynamic::from_float(round1(v[1]))]) }); // should a mover run along its x axis? (x is then the axis the depth sort compares second) let s = st.clone(); e.register_fn("along_x", move || { let b = s.borrow(); let w = b.xf.v(1.0, 0.0); b.proj.r(w[0], w[1])[0].abs() < 1e-9 }); let s = st.clone(); e.register_fn("depth", move |x: D, y: D| -> RR { let b = s.borrow(); let p = b.xf.p(n(&x)?, n(&y)?); Ok(b.proj.depth(p[0], p[1])) }); // helpers e.register_fn("px", || C); e.register_fn("hash", |x: D| -> RR { Ok(hash(n(&x)?)) }); e.register_fn("fmt", |x: D| -> RR { Ok(thousands(n(&x)?)) }); e.register_fn("round1", |x: D| -> RR { Ok(round1(n(&x)?)) }); e.register_fn("num", |x: D| -> RR { n(&x) }); e.register_fn("fmin", |a: D, b: D| -> RR { Ok(n(&a)?.min(n(&b)?)) }); e.register_fn("fmax", |a: D, b: D| -> RR { Ok(n(&a)?.max(n(&b)?)) }); e.register_fn("clamp", |x: D, lo: D, hi: D| -> RR { Ok(n(&x)?.max(n(&lo)?).min(n(&hi)?)) }); e.register_fn("rect", |x0: D, y0: D, x1: D, y1: D| -> RR { let (x0, y0, x1, y1) = (n(&x0)?, n(&y0)?, n(&x1)?, n(&y1)?); Ok(vec![arr_f(&[x0, y0]), arr_f(&[x1, y0]), arr_f(&[x1, y1]), arr_f(&[x0, y1])]) }); e.register_fn("ellipse", |cx: D, cy: D, a: D, b: D, k: D| -> RR { let (cx, cy, a, b, k) = (n(&cx)?, n(&cy)?, n(&a)?, n(&b)?, n(&k)?.max(3.0) as usize); Ok((0..k) .map(|i| { let t = i as f64 / k as f64 * std::f64::consts::TAU; arr_f(&[cx + a * t.cos(), cy + b * t.sin()]) }) .collect()) }); // outline segments of a rectangle [x0, y0, x1, y1] e.register_fn("rsegs", |r: D| -> RR { let r = rect4(&r)?; Ok(vec![ arr_f(&[r[0], r[1], r[2], r[1]]), arr_f(&[r[2], r[1], r[2], r[3]]), arr_f(&[r[2], r[3], r[0], r[3]]), arr_f(&[r[0], r[3], r[0], r[1]]), ]) }); // the four faces of a rectangle as [from, to] with the outward normal on the right e.register_fn("sides", |x0: D, y0: D, x1: D, y1: D| -> RR { let (x0, y0, x1, y1) = (n(&x0)?, n(&y0)?, n(&x1)?, n(&y1)?); let mut m = Map::new(); m.insert("S".into(), arr_pts(&[[x0, y0], [x1, y0]])); m.insert("E".into(), arr_pts(&[[x1, y0], [x1, y1]])); m.insert("N".into(), arr_pts(&[[x1, y1], [x0, y1]])); m.insert("W".into(), arr_pts(&[[x0, y1], [x0, y0]])); Ok(m) }); }