isokit: project layer, Studio GUI, MCP server, viewer, CLI, demo project and docs
This commit is contained in:
1 parent
95c80ec6b1
commit
564aeb75f6
47 files changed
+5978
-366
No files matched your search
+485
-186
@@ -27,9 +27,17 @@ fn opt_id(d: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_sprite(st: &Shared, ctx: Option<&NativeCallContext>, id: Option<&str>, cls: &str) -> SpriteH {
|
||||
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 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 {
|
||||
@@ -50,210 +58,432 @@ pub fn register_sprite(e: &mut Engine) {
|
||||
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)));
|
||||
|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("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));
|
||||
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> {
|
||||
|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));
|
||||
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> {
|
||||
|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)));
|
||||
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));
|
||||
|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(
|
||||
"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("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(
|
||||
"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));
|
||||
"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("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)?);
|
||||
"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)
|
||||
},
|
||||
);
|
||||
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)
|
||||
});
|
||||
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)
|
||||
});
|
||||
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)
|
||||
});
|
||||
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(
|
||||
"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, 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> {
|
||||
@@ -265,36 +495,51 @@ pub fn register_sprite(e: &mut Engine) {
|
||||
},
|
||||
);
|
||||
// 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]");
|
||||
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);
|
||||
}
|
||||
}
|
||||
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)
|
||||
});
|
||||
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)
|
||||
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 || {
|
||||
@@ -325,8 +570,14 @@ pub(crate) fn register_scene(e: &mut Engine, st: &Shared) {
|
||||
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()),
|
||||
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());
|
||||
@@ -336,33 +587,62 @@ pub(crate) fn register_scene(e: &mut Engine, st: &Shared) {
|
||||
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<()> {
|
||||
#[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();
|
||||
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)
|
||||
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("info", move |id: &str, name: &str| s.borrow_mut().scene.info(id, name, ""));
|
||||
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("info", move |id: &str, name: &str, detail: &str| s.borrow_mut().scene.info(id, name, detail));
|
||||
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::<Array>().map(|a| a.clone())) {
|
||||
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()))
|
||||
@@ -391,16 +671,23 @@ pub(crate) fn register_scene(e: &mut Engine, st: &Shared) {
|
||||
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(())
|
||||
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("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()));
|
||||
e.register_fn("warn", move |msg: &str| {
|
||||
s.borrow_mut().scene.warnings.push(msg.to_string())
|
||||
});
|
||||
|
||||
// view and inputs
|
||||
let s = st.clone();
|
||||
@@ -410,7 +697,9 @@ pub(crate) fn register_scene(e: &mut Engine, st: &Shared) {
|
||||
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));
|
||||
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?
|
||||
@@ -425,7 +714,10 @@ pub(crate) fn register_scene(e: &mut Engine, st: &Shared) {
|
||||
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]))])
|
||||
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();
|
||||
@@ -449,10 +741,17 @@ pub(crate) fn register_scene(e: &mut Engine, st: &Shared) {
|
||||
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("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])])
|
||||
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);
|
||||
|
||||
@@ -59,7 +59,9 @@ pub fn pts3(d: &Dynamic) -> RR<Vec<P3>> {
|
||||
}
|
||||
|
||||
fn is_point(d: &Dynamic) -> bool {
|
||||
d.read_lock::<Array>().map(|a| !a.is_empty() && (a[0].is_int() || a[0].is_float())).unwrap_or(false)
|
||||
d.read_lock::<Array>()
|
||||
.map(|a| !a.is_empty() && (a[0].is_int() || a[0].is_float()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// One polygon `[[x, y], ...]` or several `[[[x, y], ...], ...]`.
|
||||
@@ -114,18 +116,27 @@ pub fn rect4(d: &Dynamic) -> RR<[f64; 4]> {
|
||||
/// A height: one number or one per vertex.
|
||||
pub fn zs(d: &Dynamic) -> RR<Zs> {
|
||||
if d.is_array() {
|
||||
Ok(Zs::Each(arr(d, "heights")?.iter().map(n).collect::<RR<Vec<f64>>>()?))
|
||||
Ok(Zs::Each(
|
||||
arr(d, "heights")?.iter().map(n).collect::<RR<Vec<f64>>>()?,
|
||||
))
|
||||
} else {
|
||||
Ok(Zs::One(n(d)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_str(m: &Map, k: &str) -> Option<String> {
|
||||
m.get(k).and_then(|v| if v.is_unit() { None } else { Some(v.to_string()) })
|
||||
m.get(k).and_then(|v| {
|
||||
if v.is_unit() {
|
||||
None
|
||||
} else {
|
||||
Some(v.to_string())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_bool(m: &Map, keys: &[&str]) -> bool {
|
||||
keys.iter().any(|k| m.get(*k).and_then(|v| v.as_bool().ok()).unwrap_or(false))
|
||||
keys.iter()
|
||||
.any(|k| m.get(*k).and_then(|v| v.as_bool().ok()).unwrap_or(false))
|
||||
}
|
||||
|
||||
/// Style from a material and an optional options map
|
||||
|
||||
@@ -129,13 +129,27 @@ impl PrefabMeta {
|
||||
pub fn resolve(&self, props: &Map<String, Value>) -> Map<String, Value> {
|
||||
let mut out = props.clone();
|
||||
for d in &self.props {
|
||||
let v = out.get(&d.key).cloned().unwrap_or_else(|| d.default.clone());
|
||||
let v = out
|
||||
.get(&d.key)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| d.default.clone());
|
||||
let v = match (d.kind.as_str(), &v) {
|
||||
("float", Value::Number(n)) => n.as_f64().map(Value::from).unwrap_or(v),
|
||||
("float", Value::String(s)) => s.parse::<f64>().map(Value::from).unwrap_or(d.default.clone()),
|
||||
("int", Value::Number(n)) => n.as_f64().map(|f| Value::from(f.round() as i64)).unwrap_or(v),
|
||||
("int", Value::String(s)) => s.parse::<f64>().map(|f| Value::from(f.round() as i64)).unwrap_or(d.default.clone()),
|
||||
("bool", Value::String(s)) => Value::Bool(matches!(s.as_str(), "true" | "1" | "yes" | "on")),
|
||||
("float", Value::String(s)) => s
|
||||
.parse::<f64>()
|
||||
.map(Value::from)
|
||||
.unwrap_or(d.default.clone()),
|
||||
("int", Value::Number(n)) => n
|
||||
.as_f64()
|
||||
.map(|f| Value::from(f.round() as i64))
|
||||
.unwrap_or(v),
|
||||
("int", Value::String(s)) => s
|
||||
.parse::<f64>()
|
||||
.map(|f| Value::from(f.round() as i64))
|
||||
.unwrap_or(d.default.clone()),
|
||||
("bool", Value::String(s)) => {
|
||||
Value::Bool(matches!(s.as_str(), "true" | "1" | "yes" | "on"))
|
||||
}
|
||||
("bool", Value::Number(n)) => Value::Bool(n.as_f64().unwrap_or(0.0) != 0.0),
|
||||
_ => v,
|
||||
};
|
||||
@@ -156,13 +170,26 @@ impl Doc {
|
||||
pub fn free_id(&self, base: &str) -> String {
|
||||
let base: String = base
|
||||
.chars()
|
||||
.map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c.to_ascii_lowercase() } else { '-' })
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphanumeric() || c == '-' || c == '_' {
|
||||
c.to_ascii_lowercase()
|
||||
} else {
|
||||
'-'
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let base = if base.is_empty() { "obj".to_string() } else { base };
|
||||
let base = if base.is_empty() {
|
||||
"obj".to_string()
|
||||
} else {
|
||||
base
|
||||
};
|
||||
if self.get(&base).is_none() {
|
||||
return base;
|
||||
}
|
||||
(2..).map(|i| format!("{base}-{i}")).find(|id| self.get(id).is_none()).unwrap()
|
||||
(2..)
|
||||
.map(|i| format!("{base}-{i}"))
|
||||
.find(|id| self.get(id).is_none())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,19 +200,38 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn object_round_trips_compactly() {
|
||||
let o: Object = serde_json::from_value(json!({"id": "t1", "type": "tree", "x": 3, "y": 4.5})).unwrap();
|
||||
let o: Object =
|
||||
serde_json::from_value(json!({"id": "t1", "type": "tree", "x": 3, "y": 4.5})).unwrap();
|
||||
assert_eq!(o.kind, "tree");
|
||||
let back = serde_json::to_value(&o).unwrap();
|
||||
assert_eq!(back, json!({"id": "t1", "type": "tree", "x": 3.0, "y": 4.5}));
|
||||
assert_eq!(
|
||||
back,
|
||||
json!({"id": "t1", "type": "tree", "x": 3.0, "y": 4.5})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defaults_and_coercion() {
|
||||
let m = PrefabMeta {
|
||||
props: vec![
|
||||
PropDef { key: "r".into(), kind: "float".into(), default: json!(1.5), ..Default::default() },
|
||||
PropDef { key: "n".into(), kind: "int".into(), default: json!(2), ..Default::default() },
|
||||
PropDef { key: "on".into(), kind: "bool".into(), default: json!(false), ..Default::default() },
|
||||
PropDef {
|
||||
key: "r".into(),
|
||||
kind: "float".into(),
|
||||
default: json!(1.5),
|
||||
..Default::default()
|
||||
},
|
||||
PropDef {
|
||||
key: "n".into(),
|
||||
kind: "int".into(),
|
||||
default: json!(2),
|
||||
..Default::default()
|
||||
},
|
||||
PropDef {
|
||||
key: "on".into(),
|
||||
kind: "bool".into(),
|
||||
default: json!(false),
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
@@ -199,8 +245,16 @@ mod tests {
|
||||
fn free_ids() {
|
||||
let mut d = Doc::default();
|
||||
assert_eq!(d.free_id("Tree"), "tree");
|
||||
d.objects.push(Object { id: "tree".into(), kind: "tree".into(), ..Default::default() });
|
||||
d.objects.push(Object { id: "tree-2".into(), kind: "tree".into(), ..Default::default() });
|
||||
d.objects.push(Object {
|
||||
id: "tree".into(),
|
||||
kind: "tree".into(),
|
||||
..Default::default()
|
||||
});
|
||||
d.objects.push(Object {
|
||||
id: "tree-2".into(),
|
||||
kind: "tree".into(),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(d.free_id("tree"), "tree-3");
|
||||
}
|
||||
}
|
||||
+120
-33
@@ -77,14 +77,20 @@ fn to_error(e: &EvalAltResult, file: &str) -> ScriptError {
|
||||
loop {
|
||||
match cur {
|
||||
EvalAltResult::ErrorInFunctionCall(name, src, inner, p) => {
|
||||
trace.push(format!("in {name}() called at {file}:{}", p.line().unwrap_or(0)));
|
||||
trace.push(format!(
|
||||
"in {name}() called at {file}:{}",
|
||||
p.line().unwrap_or(0)
|
||||
));
|
||||
if !src.is_empty() {
|
||||
file = src.clone();
|
||||
}
|
||||
cur = inner;
|
||||
}
|
||||
EvalAltResult::ErrorInModule(path, inner, p) => {
|
||||
trace.push(format!("in module {path} imported at {file}:{}", p.line().unwrap_or(0)));
|
||||
trace.push(format!(
|
||||
"in module {path} imported at {file}:{}",
|
||||
p.line().unwrap_or(0)
|
||||
));
|
||||
file = path.clone();
|
||||
cur = inner;
|
||||
}
|
||||
@@ -96,7 +102,13 @@ fn to_error(e: &EvalAltResult, file: &str) -> ScriptError {
|
||||
if let Some(i) = message.rfind(" (line ") {
|
||||
message.truncate(i);
|
||||
}
|
||||
ScriptError { message, file, line: pos.line(), col: pos.position(), trace }
|
||||
ScriptError {
|
||||
message,
|
||||
file,
|
||||
line: pos.line(),
|
||||
col: pos.position(),
|
||||
trace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared state of one build, read and written by the registered functions.
|
||||
@@ -241,16 +253,38 @@ fn register_driver(e: &mut Engine, st: &Rc<RefCell<State>>) {
|
||||
b.obj_start = b.sprites.len();
|
||||
let kind = g("type").to_string();
|
||||
let name = g("name").to_string();
|
||||
let label = b.catalog.get(&kind).map(|m| m.label.clone()).unwrap_or_else(|| kind.clone());
|
||||
let name = if name.is_empty() || g("name").is_unit() { label } else { name };
|
||||
let detail = if g("info").is_unit() { String::new() } else { g("info").to_string() };
|
||||
let label = b
|
||||
.catalog
|
||||
.get(&kind)
|
||||
.map(|m| m.label.clone())
|
||||
.unwrap_or_else(|| kind.clone());
|
||||
let name = if name.is_empty() || g("name").is_unit() {
|
||||
label
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let detail = if g("info").is_unit() {
|
||||
String::new()
|
||||
} else {
|
||||
g("info").to_string()
|
||||
};
|
||||
b.scene.info(id, name, detail);
|
||||
if !b.catalog.contains_key(&kind) {
|
||||
b.scene.warnings.push(format!("object {}: unknown type \"{kind}\"", g("id")));
|
||||
b.scene
|
||||
.warnings
|
||||
.push(format!("object {}: unknown type \"{kind}\"", g("id")));
|
||||
drop(b);
|
||||
// a placeholder so the object can still be found and fixed
|
||||
let sp = api::new_sprite(&s, None, None, "");
|
||||
sp.0.borrow_mut().cuboid(-0.5, -0.5, 0.5, 0.5, 0.0, 1.0, isokit_core::Style::mat("iso-warn"));
|
||||
sp.0.borrow_mut().cuboid(
|
||||
-0.5,
|
||||
-0.5,
|
||||
0.5,
|
||||
0.5,
|
||||
0.0,
|
||||
1.0,
|
||||
isokit_core::Style::mat("iso-warn"),
|
||||
);
|
||||
s.borrow_mut().obj = None;
|
||||
s.borrow_mut().xf = Xf::default();
|
||||
return false;
|
||||
@@ -273,7 +307,13 @@ fn register_driver(e: &mut Engine, st: &Rc<RefCell<State>>) {
|
||||
if bx[0] <= bx[3] {
|
||||
let tag = o.get("tag").map(|v| v.to_string()).unwrap_or_default();
|
||||
if !tag.is_empty() && o.get("tag").is_some_and(|v| !v.is_unit()) {
|
||||
b.scene.tag([(bx[0] + bx[3]) / 2.0, (bx[1] + bx[4]) / 2.0, bx[5] + 0.6], tag, 6.0, id.clone(), "");
|
||||
b.scene.tag(
|
||||
[(bx[0] + bx[3]) / 2.0, (bx[1] + bx[4]) / 2.0, bx[5] + 0.6],
|
||||
tag,
|
||||
6.0,
|
||||
id.clone(),
|
||||
"",
|
||||
);
|
||||
}
|
||||
b.boxes.insert(id, bx);
|
||||
}
|
||||
@@ -291,7 +331,10 @@ fn register_driver(e: &mut Engine, st: &Rc<RefCell<State>>) {
|
||||
None => e.to_string(),
|
||||
};
|
||||
let id = o.get("id").map(|v| v.to_string()).unwrap_or_default();
|
||||
s.borrow_mut().scene.warnings.push(format!("object {id}: {msg}"));
|
||||
s.borrow_mut()
|
||||
.scene
|
||||
.warnings
|
||||
.push(format!("object {id}: {msg}"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -305,14 +348,19 @@ impl Runtime {
|
||||
engine.set_max_operations(80_000_000);
|
||||
engine.set_max_call_levels(64);
|
||||
engine.set_max_expr_depths(512, 512);
|
||||
engine.set_module_resolver(SourceResolver { sources: Rc::new(bundle.sources.clone()) });
|
||||
engine.set_module_resolver(SourceResolver {
|
||||
sources: Rc::new(bundle.sources.clone()),
|
||||
});
|
||||
api::register_sprite(&mut engine);
|
||||
api::register_scene(&mut engine, &state);
|
||||
register_driver(&mut engine, &state);
|
||||
|
||||
// global modules: built-in library, then the project's prelude
|
||||
let mut asts: Vec<(String, AST)> = vec![];
|
||||
let mut mods: Vec<(String, String)> = stdlib::MODULES.iter().map(|(n, s)| (n.to_string(), s.to_string())).collect();
|
||||
let mut mods: Vec<(String, String)> = stdlib::MODULES
|
||||
.iter()
|
||||
.map(|(n, s)| (n.to_string(), s.to_string()))
|
||||
.collect();
|
||||
for p in &bundle.prelude {
|
||||
let src = bundle.sources.get(p).ok_or_else(|| ScriptError {
|
||||
message: format!("prelude module {p} not found"),
|
||||
@@ -324,7 +372,8 @@ impl Runtime {
|
||||
for (name, src) in &mods {
|
||||
let mut ast = engine.compile(src).map_err(|e| to_error(&e.into(), name))?;
|
||||
ast.set_source(name.as_str());
|
||||
let m = Module::eval_ast_as_new(Scope::new(), &ast, &engine).map_err(|e| to_error(&e, name))?;
|
||||
let m = Module::eval_ast_as_new(Scope::new(), &ast, &engine)
|
||||
.map_err(|e| to_error(&e, name))?;
|
||||
engine.register_global_module(m.into());
|
||||
asts.push((name.clone(), ast));
|
||||
}
|
||||
@@ -344,24 +393,32 @@ impl Runtime {
|
||||
None => AST::empty(),
|
||||
};
|
||||
asts.push((main.clone(), ast.clone()));
|
||||
let driver = engine.compile(DRIVER).map_err(|e| to_error(&e.into(), "driver"))?;
|
||||
let driver = engine
|
||||
.compile(DRIVER)
|
||||
.map_err(|e| to_error(&e.into(), "driver"))?;
|
||||
ast = ast.merge(&driver);
|
||||
ast.set_source(main.as_str());
|
||||
|
||||
// prefab catalog: prefab_<type>(o) with an optional meta_<type>()
|
||||
let mut catalog = vec![];
|
||||
for (name, a) in &asts {
|
||||
let mut catalog: Vec<(usize, PrefabMeta)> = vec![];
|
||||
for (mi, (name, a)) in asts.iter().enumerate() {
|
||||
for f in a.iter_functions() {
|
||||
let Some(kind) = f.name.strip_prefix("prefab_") else { continue };
|
||||
let Some(kind) = f.name.strip_prefix("prefab_") else {
|
||||
continue;
|
||||
};
|
||||
if f.params.len() != 1 {
|
||||
continue;
|
||||
}
|
||||
let meta_fn = format!("meta_{kind}");
|
||||
let mut meta = if a.iter_functions().any(|g| g.name == meta_fn && g.params.is_empty()) {
|
||||
let mut meta = if a
|
||||
.iter_functions()
|
||||
.any(|g| g.name == meta_fn && g.params.is_empty())
|
||||
{
|
||||
let v: Dynamic = engine
|
||||
.call_fn(&mut Scope::new(), a, &meta_fn, ())
|
||||
.map_err(|e| to_error(&e, name))?;
|
||||
let j: serde_json::Value = rhai::serde::from_dynamic(&v).map_err(|e| to_error(&e, name))?;
|
||||
let j: serde_json::Value =
|
||||
rhai::serde::from_dynamic(&v).map_err(|e| to_error(&e, name))?;
|
||||
serde_json::from_value::<PrefabMeta>(j).map_err(|e| ScriptError {
|
||||
message: format!("{meta_fn}(): {e}"),
|
||||
file: name.clone(),
|
||||
@@ -378,12 +435,26 @@ impl Runtime {
|
||||
meta.category = "Other".into();
|
||||
}
|
||||
meta.source = name.clone();
|
||||
catalog.retain(|m: &PrefabMeta| m.kind != meta.kind); // later modules override
|
||||
catalog.push(meta);
|
||||
catalog.retain(|(_, m)| m.kind != meta.kind); // later modules override
|
||||
catalog.push((mi, meta));
|
||||
}
|
||||
}
|
||||
state.borrow_mut().catalog = catalog.iter().map(|m| (m.kind.clone(), m.clone())).collect();
|
||||
Ok(Runtime { engine, ast, state, catalog, main })
|
||||
// stable palette order: library modules first, then by category and label
|
||||
catalog.sort_by(|(ia, a), (ib, b)| {
|
||||
(ia, &a.category, &a.label).cmp(&(ib, &b.category, &b.label))
|
||||
});
|
||||
let catalog: Vec<PrefabMeta> = catalog.into_iter().map(|(_, m)| m).collect();
|
||||
state.borrow_mut().catalog = catalog
|
||||
.iter()
|
||||
.map(|m| (m.kind.clone(), m.clone()))
|
||||
.collect();
|
||||
Ok(Runtime {
|
||||
engine,
|
||||
ast,
|
||||
state,
|
||||
catalog,
|
||||
main,
|
||||
})
|
||||
}
|
||||
|
||||
/// Object types available to the document.
|
||||
@@ -397,7 +468,13 @@ impl Runtime {
|
||||
|
||||
/// Build the scene for a level and rotation. `cfg` holds the parameter
|
||||
/// values (already merged with the defaults).
|
||||
pub fn build(&self, level: &str, rot: u8, cfg: &serde_json::Map<String, serde_json::Value>, doc: &Doc) -> Result<Built, ScriptError> {
|
||||
pub fn build(
|
||||
&self,
|
||||
level: &str,
|
||||
rot: u8,
|
||||
cfg: &serde_json::Map<String, serde_json::Value>,
|
||||
doc: &Doc,
|
||||
) -> Result<Built, ScriptError> {
|
||||
{
|
||||
let mut b = self.state.borrow_mut();
|
||||
let catalog = std::mem::take(&mut b.catalog);
|
||||
@@ -407,23 +484,28 @@ impl Runtime {
|
||||
b.main = main;
|
||||
b.level = level.to_string();
|
||||
b.proj = Proj::new(rot as i64);
|
||||
b.cfg = match dyn_json(&serde_json::Value::Object(cfg.clone())).try_cast::<Map>() {
|
||||
Some(m) => m,
|
||||
None => Map::new(),
|
||||
};
|
||||
b.cfg = dyn_json(&serde_json::Value::Object(cfg.clone()))
|
||||
.try_cast::<Map>()
|
||||
.unwrap_or_default();
|
||||
for o in &doc.objects {
|
||||
let mut j = serde_json::to_value(o).unwrap_or_default();
|
||||
let meta = b.catalog.get(&o.kind);
|
||||
let props = meta.map(|m| m.resolve(&o.props)).unwrap_or_else(|| o.props.clone());
|
||||
let props = meta
|
||||
.map(|m| m.resolve(&o.props))
|
||||
.unwrap_or_else(|| o.props.clone());
|
||||
if let Some(m) = j.as_object_mut() {
|
||||
for k in ["name", "info", "tag"] {
|
||||
m.entry(k).or_insert(serde_json::Value::String(String::new()));
|
||||
m.entry(k)
|
||||
.or_insert(serde_json::Value::String(String::new()));
|
||||
}
|
||||
for k in ["z", "rot"] {
|
||||
m.entry(k).or_insert(serde_json::Value::from(0.0));
|
||||
}
|
||||
m.insert("p".into(), serde_json::Value::Object(props));
|
||||
m.insert("__fn".into(), serde_json::Value::String(format!("prefab_{}", o.kind)));
|
||||
m.insert(
|
||||
"__fn".into(),
|
||||
serde_json::Value::String(format!("prefab_{}", o.kind)),
|
||||
);
|
||||
m.insert("level".into(), serde_json::Value::String(level.into()));
|
||||
}
|
||||
let d = dyn_json(&j);
|
||||
@@ -445,11 +527,16 @@ impl Runtime {
|
||||
let mut sp = std::mem::take(&mut *h.0.borrow_mut());
|
||||
let open = sp.finish();
|
||||
if open > 0 {
|
||||
scene.warnings.push(format!("sprite {}: {open} group(s) left open", sp.id));
|
||||
scene
|
||||
.warnings
|
||||
.push(format!("sprite {}: {open} group(s) left open", sp.id));
|
||||
}
|
||||
scene.add_sprite(&layer, sp);
|
||||
}
|
||||
Ok(Built { scene, boxes: std::mem::take(&mut b.boxes) })
|
||||
Ok(Built {
|
||||
scene,
|
||||
boxes: std::mem::take(&mut b.boxes),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ use isokit_core::{render, RenderOpts};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
fn rt(main: &str) -> Runtime {
|
||||
let mut b = Bundle { main: Some("scene.rhai".into()), ..Default::default() };
|
||||
let mut b = Bundle {
|
||||
main: Some("scene.rhai".into()),
|
||||
..Default::default()
|
||||
};
|
||||
b.sources.insert("scene.rhai".into(), main.into());
|
||||
Runtime::new(&b).unwrap_or_else(|e| panic!("{e}"))
|
||||
}
|
||||
@@ -16,7 +19,14 @@ fn cfg() -> Map<String, Value> {
|
||||
}
|
||||
|
||||
fn obj(id: &str, kind: &str, x: f64, y: f64, props: Value) -> Object {
|
||||
Object { id: id.into(), kind: kind.into(), x, y, props: props.as_object().cloned().unwrap_or_default(), ..Default::default() }
|
||||
Object {
|
||||
id: id.into(),
|
||||
kind: kind.into(),
|
||||
x,
|
||||
y,
|
||||
props: props.as_object().cloned().unwrap_or_default(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -59,17 +69,29 @@ fn seq_group_and_closures() {
|
||||
let b = r.build("site", 0, &cfg(), &Doc::default()).unwrap();
|
||||
let out = render(&b.scene, 0, &RenderOpts::default());
|
||||
// from the south-east the box at x = 5 is nearer, so it is drawn after the one at x = 0
|
||||
let (a, c) = (out.html.find("iso-wall").unwrap(), out.html.find("iso-roof").unwrap());
|
||||
let (a, c) = (
|
||||
out.html.find("iso-wall").unwrap(),
|
||||
out.html.find("iso-roof").unwrap(),
|
||||
);
|
||||
assert!(a < c);
|
||||
assert!(out.html.contains("<g class=\"iso-a iso-drive\">"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_point_at_the_line() {
|
||||
let mut b = Bundle { main: Some("scene.rhai".into()), ..Default::default() };
|
||||
b.sources.insert("scene.rhai".into(), "let s = sprite(\"a\");\n\ns.box(0, 0, 1, 1, 0, \"high\", \"wall\");\n".into());
|
||||
let mut b = Bundle {
|
||||
main: Some("scene.rhai".into()),
|
||||
..Default::default()
|
||||
};
|
||||
b.sources.insert(
|
||||
"scene.rhai".into(),
|
||||
"let s = sprite(\"a\");\n\ns.box(0, 0, 1, 1, 0, \"high\", \"wall\");\n".into(),
|
||||
);
|
||||
let r = Runtime::new(&b).unwrap();
|
||||
let e = r.build("site", 0, &cfg(), &Doc::default()).err().expect("error");
|
||||
let e = r
|
||||
.build("site", 0, &cfg(), &Doc::default())
|
||||
.err()
|
||||
.expect("error");
|
||||
assert_eq!(e.file, "scene.rhai");
|
||||
assert_eq!(e.line, Some(3), "{e}");
|
||||
assert!(e.message.contains("number"), "{e}");
|
||||
@@ -81,9 +103,18 @@ fn errors_point_at_the_line() {
|
||||
|
||||
#[test]
|
||||
fn imports_resolve_from_the_bundle() {
|
||||
let mut b = Bundle { main: Some("scene.rhai".into()), ..Default::default() };
|
||||
b.sources.insert("lib/kiosk.rhai".into(), "fn kiosk(x, y) { sprite(\"kiosk\").box(x, y, x + 2, y + 2, 0, 2.5, \"wall\"); }".into());
|
||||
b.sources.insert("scene.rhai".into(), "import \"lib/kiosk\" as k;\nk::kiosk(1, 2);".into());
|
||||
let mut b = Bundle {
|
||||
main: Some("scene.rhai".into()),
|
||||
..Default::default()
|
||||
};
|
||||
b.sources.insert(
|
||||
"lib/kiosk.rhai".into(),
|
||||
"fn kiosk(x, y) { sprite(\"kiosk\").box(x, y, x + 2, y + 2, 0, 2.5, \"wall\"); }".into(),
|
||||
);
|
||||
b.sources.insert(
|
||||
"scene.rhai".into(),
|
||||
"import \"lib/kiosk\" as k;\nk::kiosk(1, 2);".into(),
|
||||
);
|
||||
let r = Runtime::new(&b).unwrap();
|
||||
let s = r.build("site", 0, &cfg(), &Doc::default()).unwrap().scene;
|
||||
assert_eq!(s.sprite_count(), 1);
|
||||
@@ -91,7 +122,11 @@ fn imports_resolve_from_the_bundle() {
|
||||
|
||||
#[test]
|
||||
fn catalog_lists_library_and_project_prefabs() {
|
||||
let mut b = Bundle { main: None, prelude: vec!["lib/kiosk.rhai".into()], ..Default::default() };
|
||||
let mut b = Bundle {
|
||||
main: None,
|
||||
prelude: vec!["lib/kiosk.rhai".into()],
|
||||
..Default::default()
|
||||
};
|
||||
b.sources.insert(
|
||||
"lib/kiosk.rhai".into(),
|
||||
r#"
|
||||
@@ -102,7 +137,9 @@ fn catalog_lists_library_and_project_prefabs() {
|
||||
);
|
||||
let r = Runtime::new(&b).unwrap();
|
||||
let kinds: Vec<&str> = r.catalog().iter().map(|m| m.kind.as_str()).collect();
|
||||
for k in ["site", "area", "road", "building", "tree", "car", "person", "tank", "kiosk"] {
|
||||
for k in [
|
||||
"site", "area", "road", "building", "tree", "car", "person", "tank", "kiosk",
|
||||
] {
|
||||
assert!(kinds.contains(&k), "{k} missing from {kinds:?}");
|
||||
}
|
||||
let k = r.prefab("kiosk").unwrap();
|
||||
@@ -115,7 +152,13 @@ fn catalog_lists_library_and_project_prefabs() {
|
||||
fn document_objects_are_placed_in_their_frame() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let mut doc = Doc::default();
|
||||
let mut b = obj("hall", "building", 30.0, 20.0, json!({"w": 10, "d": 6, "floors": 1}));
|
||||
let mut b = obj(
|
||||
"hall",
|
||||
"building",
|
||||
30.0,
|
||||
20.0,
|
||||
json!({"w": 10, "d": 6, "floors": 1}),
|
||||
);
|
||||
b.rot = 90.0;
|
||||
b.name = "Main hall".into();
|
||||
b.tag = "Hall".into();
|
||||
@@ -123,23 +166,37 @@ fn document_objects_are_placed_in_their_frame() {
|
||||
doc.objects.push(obj("t1", "tree", 5.0, 5.0, json!({})));
|
||||
let built = r.build("roof", 1, &cfg(), &doc).unwrap();
|
||||
// turned a quarter: 6 m along x, 10 m along y, centred on (30, 20)
|
||||
let bx = *built.boxes.get("hall").unwrap_or_else(|| panic!("{:?}", built.scene.warnings));
|
||||
assert!((bx[0] - 27.0).abs() < 1e-9 && (bx[3] - 33.0).abs() < 1e-9, "{bx:?}");
|
||||
assert!((bx[1] - 15.0).abs() < 1e-9 && (bx[4] - 25.0).abs() < 1e-9, "{bx:?}");
|
||||
let bx = *built
|
||||
.boxes
|
||||
.get("hall")
|
||||
.unwrap_or_else(|| panic!("{:?}", built.scene.warnings));
|
||||
assert!(
|
||||
(bx[0] - 27.0).abs() < 1e-9 && (bx[3] - 33.0).abs() < 1e-9,
|
||||
"{bx:?}"
|
||||
);
|
||||
assert!(
|
||||
(bx[1] - 15.0).abs() < 1e-9 && (bx[4] - 25.0).abs() < 1e-9,
|
||||
"{bx:?}"
|
||||
);
|
||||
let out = render(&built.scene, 1, &RenderOpts::default());
|
||||
assert!(out.html.contains("data-obj=\"hall\""));
|
||||
assert!(out.html.contains("data-obj=\"t1\""));
|
||||
assert_eq!(out.info["hall"].name, "Main hall");
|
||||
assert_eq!(out.info["t1"].name, "Tree");
|
||||
assert!(out.html.contains(">Hall<"));
|
||||
assert!(built.scene.warnings.is_empty(), "{:?}", built.scene.warnings);
|
||||
assert!(
|
||||
built.scene.warnings.is_empty(),
|
||||
"{:?}",
|
||||
built.scene.warnings
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levels_cut_buildings_open_and_filter_objects() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let mut doc = Doc::default();
|
||||
doc.objects.push(obj("b", "building", 0.0, 0.0, json!({"floors": 2})));
|
||||
doc.objects
|
||||
.push(obj("b", "building", 0.0, 0.0, json!({"floors": 2})));
|
||||
let mut desk = obj("desk", "tree", 0.0, 0.0, json!({}));
|
||||
desk.levels = vec!["gf".into()];
|
||||
doc.objects.push(desk);
|
||||
@@ -153,7 +210,10 @@ fn levels_cut_buildings_open_and_filter_objects() {
|
||||
let html = render(&gf.scene, 0, &RenderOpts::default()).html;
|
||||
assert!(html.contains("iso-z-floor iso-t"));
|
||||
let html = render(&up.scene, 0, &RenderOpts::default()).html;
|
||||
assert!(html.contains("class=\"iso-dim\""), "ghosted above the top storey");
|
||||
assert!(
|
||||
html.contains("class=\"iso-dim\""),
|
||||
"ghosted above the top storey"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -172,13 +232,26 @@ fn every_library_prefab_renders_cleanly_in_every_rotation() {
|
||||
}
|
||||
for level in ["site", "gf", "1f"] {
|
||||
for rot in 0..4 {
|
||||
let b = r.build(level, rot, &cfg(), &doc).unwrap_or_else(|e| panic!("{level}/{rot}: {e}"));
|
||||
assert!(b.scene.warnings.is_empty(), "{level}/{rot}: {:?}", b.scene.warnings);
|
||||
let b = r
|
||||
.build(level, rot, &cfg(), &doc)
|
||||
.unwrap_or_else(|e| panic!("{level}/{rot}: {e}"));
|
||||
assert!(
|
||||
b.scene.warnings.is_empty(),
|
||||
"{level}/{rot}: {:?}",
|
||||
b.scene.warnings
|
||||
);
|
||||
let out = render(&b.scene, rot, &RenderOpts::default());
|
||||
assert!(!out.html.contains("NaN"), "{level}/{rot}");
|
||||
for m in r.catalog() {
|
||||
let id = format!("o{}", r.catalog().iter().position(|q| q.kind == m.kind).unwrap());
|
||||
assert!(b.boxes.contains_key(&id), "{} drew nothing in {level}/{rot}", m.kind);
|
||||
let id = format!(
|
||||
"o{}",
|
||||
r.catalog().iter().position(|q| q.kind == m.kind).unwrap()
|
||||
);
|
||||
assert!(
|
||||
b.boxes.contains_key(&id),
|
||||
"{} drew nothing in {level}/{rot}",
|
||||
m.kind
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,8 +259,15 @@ fn every_library_prefab_renders_cleanly_in_every_rotation() {
|
||||
|
||||
#[test]
|
||||
fn unknown_types_and_failing_prefabs_warn_but_render() {
|
||||
let mut b = Bundle { main: None, prelude: vec!["lib/bad.rhai".into()], ..Default::default() };
|
||||
b.sources.insert("lib/bad.rhai".into(), "fn prefab_bad(o) { sprite().box(0, 0, 1, 1, 0, o.p.nope, \"wall\"); }".into());
|
||||
let mut b = Bundle {
|
||||
main: None,
|
||||
prelude: vec!["lib/bad.rhai".into()],
|
||||
..Default::default()
|
||||
};
|
||||
b.sources.insert(
|
||||
"lib/bad.rhai".into(),
|
||||
"fn prefab_bad(o) { sprite().box(0, 0, 1, 1, 0, o.p.nope, \"wall\"); }".into(),
|
||||
);
|
||||
let r = Runtime::new(&b).unwrap();
|
||||
let mut doc = Doc::default();
|
||||
doc.objects.push(obj("u", "ufo", 0.0, 0.0, json!({})));
|
||||
@@ -202,7 +282,9 @@ fn unknown_types_and_failing_prefabs_warn_but_render() {
|
||||
|
||||
#[test]
|
||||
fn cfg_reaches_the_script() {
|
||||
let r = rt(r#"if cfg("big") { sprite("a").box(0, 0, 9, 9, 0, 9, "wall"); } else { sprite("a").box(0, 0, 1, 1, 0, 1, "wall"); }"#);
|
||||
let r = rt(
|
||||
r#"if cfg("big") { sprite("a").box(0, 0, 9, 9, 0, 9, "wall"); } else { sprite("a").box(0, 0, 1, 1, 0, 1, "wall"); }"#,
|
||||
);
|
||||
let mut c = Map::new();
|
||||
c.insert("big".into(), json!(true));
|
||||
let b = r.build("site", 0, &c, &Doc::default()).unwrap();
|
||||
|
||||
@@ -27,8 +27,8 @@ fn meta_building() {
|
||||
#{ key: "lit", type: "bool", def: true, label: "Lit windows" },
|
||||
#{ key: "door", type: "bool", def: true, label: "Front door" },
|
||||
#{ key: "pv", type: "bool", def: false, label: "Solar panels" },
|
||||
#{ key: "ac", type: "bool", def: false, label: "Roof AC units (flat roof)" },
|
||||
#{ key: "floor_mat", type: "material", def: "z-floor", choices: floor_materials(), label: "Floor (cut-open levels)" },
|
||||
#{ key: "ac", type: "bool", def: false, label: "Roof AC units" },
|
||||
#{ key: "floor_mat", type: "material", def: "z-floor", choices: floor_materials(), label: "Cut-open floor" },
|
||||
] }
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ fn prefab_tank(o) {
|
||||
}
|
||||
|
||||
fn meta_container() {
|
||||
#{ label: "Shipping container", category: "Equipment", icon: "▭",
|
||||
#{ label: "Container", category: "Equipment", icon: "▭",
|
||||
props: [
|
||||
#{ key: "size", type: "choice", def: "20ft", choices: ["10ft", "20ft", "40ft"], label: "Size" },
|
||||
#{ key: "colour", type: "choice", def: "rack", choices: ["rack", "truckbox", "classic", "steel", "firewall"], label: "Colour" },
|
||||
@@ -75,7 +75,7 @@ fn prefab_pv_field(o) {
|
||||
}
|
||||
|
||||
fn meta_chimney() {
|
||||
#{ label: "Chimney / stack", category: "Equipment", icon: "▮",
|
||||
#{ label: "Chimney", category: "Equipment", icon: "▮",
|
||||
props: [
|
||||
#{ key: "h", type: "float", def: 12.0, min: 2.0, max: 120.0, step: 0.5, label: "Height (m)" },
|
||||
#{ key: "r", type: "float", def: 0.5, min: 0.1, max: 5.0, step: 0.05, label: "Radius (m)" },
|
||||
|
||||
@@ -15,7 +15,7 @@ fn prefab_person(o) {
|
||||
}
|
||||
|
||||
fn meta_crowd() {
|
||||
#{ label: "Group of people", category: "People", icon: "👥",
|
||||
#{ label: "Crowd", category: "People", icon: "👥",
|
||||
props: [
|
||||
#{ key: "n", type: "int", def: 5, min: 2, max: 40, step: 1, label: "People" },
|
||||
#{ key: "r", type: "float", def: 2.0, min: 0.6, max: 15.0, step: 0.1, label: "Spread (m)" },
|
||||
|
||||
Reference in new issue
Block a user