Deploy web Studio / deploy (push) Successful in 1m3s
- Robot arm (category Industry): picks goods off the end of the nearest conveyor within its reach and puts them on the start of another conveyor within reach, or on a pallet a quarter turn on (pick / put: auto or a side). One move per item coming in (cycle 0) or a set cycle; waits while the line before it stands. The belt it feeds carries its goods at its pace and starts its loop when they land (feeds carry a time offset `ph`), so arm and belts stay in step. - Drawn as a 16-pose flipbook: two-link inverse kinematics per pose, each pose shown for a sixteenth of the cycle by a stepped CSS animation (iso-flip; pose 0 alone when unanimated). Poses pointing away from the viewer are drawn before the base, the others after it. - New sprite method beam(a, b, n, w, mat): a square bar between two 3D points, six faces culled and shaded like face(). Native, so the factory demo renders in 4.7 ms instead of 18.8 ms. - Factory demo: a palletiser at the end of the carton line, a loading robot passing drums from the incline onto the loading belt into the truck. MCP instructions on robot arms. Checks: 70 Rust tests (arms and belts in step, waiting arm, beam faces from every side), Studio 109/109, web 90/90.
1036 lines
33 KiB
Rust
1036 lines
33 KiB
Rust
//! 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, Xf, 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<RefCell<Sprite>>);
|
|
|
|
type Shared = Rc<RefCell<State>>;
|
|
type D = Dynamic;
|
|
|
|
fn ok(s: &SpriteH) -> RR<SpriteH> {
|
|
Ok(s.clone())
|
|
}
|
|
|
|
fn opt_id(d: &str) -> Option<String> {
|
|
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::<SpriteH>("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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
s.0.borrow_mut()
|
|
.disc(n(&cx)?, n(&cy)?, n(&z)?, n(&r)?, &classes(cls), opt_id(id));
|
|
ok(s)
|
|
},
|
|
);
|
|
e.register_fn(
|
|
"beam",
|
|
|s: &mut SpriteH, a: D, b: D, nn: D, w: D, mat: &str| -> RR<SpriteH> {
|
|
s.0.borrow_mut()
|
|
.beam(p3(&a)?, p3(&b)?, p3(&nn)?, n(&w)?, &classes(mat));
|
|
ok(s)
|
|
},
|
|
);
|
|
e.register_fn("face", |s: &mut SpriteH, p: D, mat: &str| -> RR<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
s.0.borrow_mut().fan(n(&x)?, n(&y)?, n(&z)?, n(&r)?);
|
|
ok(s)
|
|
},
|
|
);
|
|
// fish round ellipses: list of #{ rx, ry, dur, delay, cw, len, cls }
|
|
e.register_fn(
|
|
"swim",
|
|
|s: &mut SpriteH, x: D, y: D, z: D, list: Array| -> RR<SpriteH> {
|
|
let mut fish = Vec::with_capacity(list.len());
|
|
for f in &list {
|
|
let Some(m) = f.read_lock::<Map>() else {
|
|
return err("swim: expected a list of maps");
|
|
};
|
|
fish.push(isokit_core::Fish {
|
|
rx: fnum(&m, "rx"),
|
|
ry: fnum(&m, "ry"),
|
|
dur: fnum(&m, "dur"),
|
|
delay: fnum(&m, "delay"),
|
|
cw: m.get("cw").and_then(|v| v.as_bool().ok()).unwrap_or(false),
|
|
len: m.get("len").and_then(|v| n(v).ok()).unwrap_or(0.4),
|
|
cls: m
|
|
.get("cls")
|
|
.map(|v| v.to_string())
|
|
.unwrap_or_else(|| "koi".into()),
|
|
});
|
|
}
|
|
s.0.borrow_mut().swim(n(&x)?, n(&y)?, n(&z)?, fish);
|
|
ok(s)
|
|
},
|
|
);
|
|
e.register_fn("raw", |s: &mut SpriteH, svg: &str| -> RR<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
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<SpriteH> {
|
|
s.0.borrow_mut().begin_group(&classes(cls), "");
|
|
let r = f.call_within_context::<Dynamic>(&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<SpriteH> {
|
|
s.0.borrow_mut().begin_group(&classes(cls), css);
|
|
let r = f.call_within_context::<Dynamic>(&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<SpriteH> {
|
|
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::<FnPtr>()
|
|
.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::<Dynamic>(&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
|
|
#[allow(clippy::too_many_arguments)]
|
|
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)
|
|
},
|
|
);
|
|
// a row more in the tooltip of the object being drawn
|
|
let s = st.clone();
|
|
e.register_fn("info_row", move |k: D, v: D| {
|
|
let mut b = s.borrow_mut();
|
|
if let Some(id) = b.obj.clone() {
|
|
if let Some(i) = b.scene.info.get_mut(&id) {
|
|
i.rows.push((k.to_string(), v.to_string()));
|
|
}
|
|
}
|
|
});
|
|
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::<Array>().map(|a| a.clone()))
|
|
{
|
|
let rows: Vec<(String, String)> = rows
|
|
.iter()
|
|
.filter_map(|r| r.read_lock::<Array>().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());
|
|
register_objects(e, st);
|
|
// 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<bool> {
|
|
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<Array> {
|
|
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<f64> {
|
|
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<f64> { Ok(hash(n(&x)?)) });
|
|
e.register_fn("fmt", |x: D| -> RR<String> { Ok(thousands(n(&x)?)) });
|
|
e.register_fn("round1", |x: D| -> RR<f64> { Ok(round1(n(&x)?)) });
|
|
e.register_fn("num", |x: D| -> RR<f64> { n(&x) });
|
|
e.register_fn("fmin", |a: D, b: D| -> RR<f64> { Ok(n(&a)?.min(n(&b)?)) });
|
|
e.register_fn("fmax", |a: D, b: D| -> RR<f64> { Ok(n(&a)?.max(n(&b)?)) });
|
|
e.register_fn("clamp", |x: D, lo: D, hi: D| -> RR<f64> {
|
|
Ok(n(&x)?.max(n(&lo)?).min(n(&hi)?))
|
|
});
|
|
e.register_fn("rect", |x0: D, y0: D, x1: D, y1: D| -> RR<Array> {
|
|
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<Array> {
|
|
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<Array> {
|
|
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<Map> {
|
|
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)
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------- objects and frames
|
|
|
|
/// A type name or a list of them.
|
|
fn kinds(d: &Dynamic) -> Vec<String> {
|
|
match d.read_lock::<Array>() {
|
|
Some(a) => a.iter().map(|v| v.to_string()).collect(),
|
|
None => vec![d.to_string()],
|
|
}
|
|
}
|
|
|
|
fn fnum(m: &Map, k: &str) -> f64 {
|
|
m.get(k).and_then(|v| n(v).ok()).unwrap_or(0.0)
|
|
}
|
|
|
|
fn sget(m: &Map, k: &str) -> String {
|
|
m.get(k).map(|v| v.to_string()).unwrap_or_default()
|
|
}
|
|
|
|
/// A number prop of a document object (`o.p.<k>`).
|
|
fn prop(m: &Map, k: &str) -> Option<f64> {
|
|
m.get("p").and_then(|p| {
|
|
p.read_lock::<Map>()
|
|
.and_then(|p| p.get(k).and_then(|v| n(v).ok()))
|
|
})
|
|
}
|
|
|
|
/// The placement frame of a document object.
|
|
fn xf_of(m: &Map) -> Xf {
|
|
Xf::new(fnum(m, "x"), fnum(m, "y"), fnum(m, "z"), fnum(m, "rot"))
|
|
}
|
|
|
|
fn of_type(d: &Dynamic, ks: &[String]) -> bool {
|
|
d.read_lock::<Map>()
|
|
.is_some_and(|m| ks.iter().any(|k| *k == sget(&m, "type")))
|
|
}
|
|
|
|
/// The object with (u, v), its position in another object's frame.
|
|
fn with_uv(d: &Dynamic, u: f64, v: f64) -> Dynamic {
|
|
let mut m = d.read_lock::<Map>().map(|m| m.clone()).unwrap_or_default();
|
|
m.insert("u".into(), Dynamic::from_float(u));
|
|
m.insert("v".into(), Dynamic::from_float(v));
|
|
Dynamic::from_map(m)
|
|
}
|
|
|
|
/// The host `o` sits on: of the objects of the `hosts` types at the same
|
|
/// height, the one whose centre line (local x, length `p.w`) passes within
|
|
/// `p.t / 2 + tol` of o's position; the nearest wins, then the first.
|
|
/// Returns its index and o's position (u, v) in the host's frame.
|
|
fn host_index(objs: &[Dynamic], o: &Map, hosts: &[String], tol: f64) -> Option<(usize, f64, f64)> {
|
|
let (x, y, z) = (fnum(o, "x"), fnum(o, "y"), fnum(o, "z"));
|
|
let id = sget(o, "id");
|
|
let mut best: Option<(usize, f64, f64)> = None;
|
|
for (i, d) in objs.iter().enumerate() {
|
|
let Some(h) = d.read_lock::<Map>() else {
|
|
continue;
|
|
};
|
|
if !hosts.iter().any(|k| *k == sget(&h, "type")) || sget(&h, "id") == id {
|
|
continue;
|
|
}
|
|
let xf = xf_of(&h);
|
|
if (z - xf.z).abs() > 0.5 {
|
|
continue;
|
|
}
|
|
let l = xf.inv(x, y);
|
|
let half = prop(&h, "w").unwrap_or(1.0) / 2.0;
|
|
let lim = prop(&h, "t").unwrap_or(0.0) / 2.0 + tol;
|
|
if l[0].abs() <= half + 1e-6
|
|
&& l[1].abs() <= lim + 1e-6
|
|
&& best.is_none_or(|b| l[1].abs() < b.2.abs() - 1e-9)
|
|
{
|
|
best = Some((i, l[0], l[1]));
|
|
}
|
|
}
|
|
best
|
|
}
|
|
|
|
/// Queries over the document objects shown on this level, and frames.
|
|
fn register_objects(e: &mut Engine, st: &Shared) {
|
|
// objects of a type (or a list of types) shown on this level
|
|
let s = st.clone();
|
|
e.register_fn("objects_of", move |k: D| -> Array {
|
|
let ks = kinds(&k);
|
|
s.borrow()
|
|
.objects
|
|
.iter()
|
|
.filter(|d| of_type(d, &ks))
|
|
.cloned()
|
|
.collect()
|
|
});
|
|
// ... whose footprint (p.w x p.d around the origin) holds the world point
|
|
let s = st.clone();
|
|
e.register_fn("objects_at", move |k: D, x: D, y: D| -> RR<Array> {
|
|
let (ks, x, y) = (kinds(&k), n(&x)?, n(&y)?);
|
|
Ok(s.borrow()
|
|
.objects
|
|
.iter()
|
|
.filter(|d| {
|
|
of_type(d, &ks)
|
|
&& d.read_lock::<Map>().is_some_and(|m| {
|
|
let (Some(w), Some(dd)) = (prop(&m, "w"), prop(&m, "d")) else {
|
|
return false;
|
|
};
|
|
let l = xf_of(&m).inv(x, y);
|
|
l[0].abs() <= w / 2.0 + 1e-6 && l[1].abs() <= dd / 2.0 + 1e-6
|
|
})
|
|
})
|
|
.cloned()
|
|
.collect())
|
|
});
|
|
// ... whose centre line (local x, length p.w) passes within p.t / 2 + tol
|
|
// of the world point, each with (u, v): the point in its frame
|
|
let s = st.clone();
|
|
e.register_fn("objects_on", move |k: D, x: D, y: D, tol: D| -> RR<Array> {
|
|
let (ks, x, y, tol) = (kinds(&k), n(&x)?, n(&y)?, n(&tol)?);
|
|
Ok(s.borrow()
|
|
.objects
|
|
.iter()
|
|
.filter(|d| of_type(d, &ks))
|
|
.filter_map(|d| {
|
|
let m = d.read_lock::<Map>()?;
|
|
let l = xf_of(&m).inv(x, y);
|
|
let half = prop(&m, "w").unwrap_or(1.0) / 2.0;
|
|
// half the thickness (walls), else half the width (conveyors)
|
|
let lim = prop(&m, "t").or_else(|| prop(&m, "d")).unwrap_or(0.0) / 2.0 + tol;
|
|
(l[0].abs() <= half + 1e-6 && l[1].abs() <= lim + 1e-6)
|
|
.then(|| with_uv(d, l[0], l[1]))
|
|
})
|
|
.collect())
|
|
});
|
|
// the host an object sits on (with u, v: the object's position in the
|
|
// host's frame), or ()
|
|
fn mount_of(s: &Shared, o: &Map, hosts: &D, tol: f64) -> Dynamic {
|
|
let b = s.borrow();
|
|
match host_index(&b.objects, o, &kinds(hosts), tol) {
|
|
Some((i, u, v)) => with_uv(&b.objects[i], u, v),
|
|
None => Dynamic::UNIT,
|
|
}
|
|
}
|
|
let s = st.clone();
|
|
e.register_fn("mount_of", move |o: Map, hosts: D| {
|
|
mount_of(&s, &o, &hosts, 0.3)
|
|
});
|
|
let s = st.clone();
|
|
e.register_fn("mount_of", move |o: Map, hosts: D, tol: D| -> RR<Dynamic> {
|
|
Ok(mount_of(&s, &o, &hosts, n(&tol)?))
|
|
});
|
|
// the objects of the given types that sit on host h (each with u, v)
|
|
fn mounted(s: &Shared, h: &Map, k: &D, hosts: &D, tol: f64) -> Array {
|
|
let b = s.borrow();
|
|
let (ks, hs, hid) = (kinds(k), kinds(hosts), sget(h, "id"));
|
|
b.objects
|
|
.iter()
|
|
.filter(|d| of_type(d, &ks))
|
|
.filter_map(|d| {
|
|
let m = d.read_lock::<Map>()?;
|
|
let (i, u, v) = host_index(&b.objects, &m, &hs, tol)?;
|
|
let on = b.objects[i]
|
|
.read_lock::<Map>()
|
|
.is_some_and(|x| sget(&x, "id") == hid);
|
|
on.then(|| with_uv(d, u, v))
|
|
})
|
|
.collect()
|
|
}
|
|
let s = st.clone();
|
|
e.register_fn("mounted", move |h: Map, k: D, hosts: D| {
|
|
mounted(&s, &h, &k, &hosts, 0.3)
|
|
});
|
|
let s = st.clone();
|
|
e.register_fn(
|
|
"mounted",
|
|
move |h: Map, k: D, hosts: D, tol: D| -> RR<Array> {
|
|
Ok(mounted(&s, &h, &k, &hosts, n(&tol)?))
|
|
},
|
|
);
|
|
// draw in another frame from here on (sprites made later, vis, tags)
|
|
let s = st.clone();
|
|
e.register_fn("frame", move |o: Map| {
|
|
s.borrow_mut().xf = xf_of(&o);
|
|
});
|
|
let s = st.clone();
|
|
e.register_fn("frame", move |x: D, y: D, z: D, rot: D| -> RR<()> {
|
|
s.borrow_mut().xf = Xf::new(n(&x)?, n(&y)?, n(&z)?, n(&rot)?);
|
|
Ok(())
|
|
});
|
|
let s = st.clone();
|
|
e.register_fn("to_world", move |x: D, y: D| -> RR<Array> {
|
|
let p = s.borrow().xf.p(n(&x)?, n(&y)?);
|
|
Ok(vec![Dynamic::from_float(p[0]), Dynamic::from_float(p[1])])
|
|
});
|
|
let s = st.clone();
|
|
e.register_fn("to_local", move |x: D, y: D| -> RR<Array> {
|
|
let p = s.borrow().xf.inv(n(&x)?, n(&y)?);
|
|
Ok(vec![Dynamic::from_float(p[0]), Dynamic::from_float(p[1])])
|
|
});
|
|
e.register_fn("local_of", |o: Map, x: D, y: D| -> RR<Array> {
|
|
let p = xf_of(&o).inv(n(&x)?, n(&y)?);
|
|
Ok(vec![Dynamic::from_float(p[0]), Dynamic::from_float(p[1])])
|
|
});
|
|
// the world plan point of (u, v) in object o's frame
|
|
e.register_fn("world_of", |o: Map, u: D, v: D| -> RR<Array> {
|
|
let p = xf_of(&o).p(n(&u)?, n(&v)?);
|
|
Ok(vec![Dynamic::from_float(p[0]), Dynamic::from_float(p[1])])
|
|
});
|
|
}
|