Wildlife and nature: animals, beehives, bushes, flowers, rocks, logs
Deploy web Studio / deploy (push) Successful in 1m5s

New stdlib module wildlife.rhai:
- Animals (new palette category): Deer (stag with antlers, does, fawns),
  Wild boar, Fox, Elephant (bull with tusks, cow, calf), Rabbits. Low-poly
  solids from faces (loft, tube), parts drawn back to front by depth, faces
  edged in their own shade (class beast). Herds scatter round a spot, each
  animal its own sprite facing about the object's rotation. Grazing
  animals switch between two head poses (iso-pose2 groups), elephants curl
  their trunks, rabbits hop.
- Birds and Butterflies as flocks: swim() takes a shape (bird, fly, bee);
  wings flap out of step. Fish output unchanged.
- Beehives on stands with supers, lids, entrances and bees flying round;
  Bush (round, flowering, berries); Flowers (bed in rows or meadow); Rocks;
  Log or stump. g-meadow and g-soil ground materials.
- polyline() takes 3D points as documented (antlers).

Theme colours for dark and light, viewer CSS for poses, flapping, hopping.
examples/country: elephants in a paddock with a cut corner, deer at a
forest edge, boar, fox, rabbits, an apiary by a meadow, a pond, a lane,
fence and hedgerow at angles; Country demo in the web Studio and deploy.
MCP guidance, README, SCRIPTING, ROADMAP.

Tests: Rust 78, Studio 122; existing examples draw the same (only the
embedded theme CSS grew).
This commit is contained in:
grabowski committed 2026-10-25 13:57:23 +07:00
1 parent 96d472625f
commit e840d36000
22 files changed
+1211 -16

No files matched your search

+1
View File
@@ -470,6 +470,7 @@ pub fn register_sprite(e: &mut Engine) {
.get("cls")
.map(|v| v.to_string())
.unwrap_or_else(|| "koi".into()),
shape: m.get("shape").map(|v| v.to_string()).unwrap_or_default(),
});
}
s.0.borrow_mut().swim(n(&x)?, n(&y)?, n(&z)?, fish);
+24 -6
View File
@@ -90,13 +90,31 @@ pub fn polys3(d: &Dynamic) -> RR<Vec<Vec<P3>>> {
}
}
/// One line or several, of plan points `[x, y]` (put at height z) or 3D points.
/// One line or several, of plan points `[x, y]` (put at height z) or 3D
/// points `[x, y, z]` (antlers, branches).
pub fn polys3_or_flat(d: &Dynamic, z: f64) -> RR<Vec<Vec<P3>>> {
polys(d).map(|ls| {
ls.into_iter()
.map(|l| l.into_iter().map(|p| [p[0], p[1], z]).collect())
.collect()
})
let a = arr(d, "polyline")?;
let lines: Vec<&Dynamic> = match a.first() {
None => return Ok(vec![]),
Some(f) if is_point(f) => vec![d],
_ => a.iter().collect(),
};
lines
.into_iter()
.map(|l| {
arr(l, "polyline")?
.iter()
.map(|q| {
let n = q.read_lock::<Array>().map(|v| v.len()).unwrap_or(0);
if n >= 3 {
p3(q)
} else {
p2(q).map(|p| [p[0], p[1], z])
}
})
.collect()
})
.collect()
}
/// `[[a, b, c, d], ...]` (segments, opening lists)
+1
View File
@@ -9,6 +9,7 @@ pub const MODULES: &[(&str, &str)] = &[
("isokit/util", include_str!("../stdlib/util.rhai")),
("isokit/ground", include_str!("../stdlib/ground.rhai")),
("isokit/streets", include_str!("../stdlib/streets.rhai")),
("isokit/wildlife", include_str!("../stdlib/wildlife.rhai")),
("isokit/nature", include_str!("../stdlib/nature.rhai")),
("isokit/building", include_str!("../stdlib/building.rhai")),
("isokit/vehicles", include_str!("../stdlib/vehicles.rhai")),
+95
View File
@@ -858,6 +858,101 @@ fn piece(id: &str, kind: &str, a: [f64; 2], b: [f64; 2], props: Value) -> Object
o
}
#[test]
fn wildlife_and_garden_prefabs_draw_from_every_side() {
let r = Runtime::new(&Bundle::default()).unwrap();
let doc = Doc {
objects: vec![
obj("deer", "deer", -18.0, 8.0, json!({"n": 5, "r": 4})),
obj(
"stag",
"deer",
-8.0,
12.0,
json!({"n": 1, "mix": "stag", "graze": false}),
),
obj("boar", "boar", -18.0, -6.0, json!({"n": 3})),
obj("fox", "fox", -8.0, -2.0, json!({})),
obj(
"el",
"elephant",
6.0,
10.0,
json!({"n": 3, "mix": "herd", "r": 5}),
),
obj("rab", "rabbits", -4.0, -12.0, json!({"n": 4})),
obj("birds", "birds", 0.0, 0.0, json!({"n": 7})),
obj("bf", "butterflies", 10.0, -12.0, json!({"n": 5})),
obj("hives", "beehive", 16.0, -6.0, json!({"n": 4})),
obj("bed", "flowers", 10.0, -12.0, json!({"w": 5, "d": 3})),
obj(
"mead",
"flowers",
20.0,
-14.0,
json!({"w": 8, "d": 5, "kind": "meadow"}),
),
obj("b1", "bush", 3.0, -4.0, json!({})),
obj("b2", "bush", 5.0, -6.0, json!({"kind": "berries"})),
obj("rk", "rocks", 22.0, 6.0, json!({"n": 4})),
obj("lg", "log", 14.0, 4.0, json!({})),
obj("st", "log", 18.0, 2.0, json!({"kind": "stump"})),
],
..Default::default()
};
for view in 0..4u8 {
let b = r.build("site", view, &cfg(), &doc).unwrap();
assert!(b.scene.warnings.is_empty(), "{:?}", b.scene.warnings);
let out = render(&b.scene, view, &RenderOpts::default());
assert!(!out.html.contains("NaN"));
// each animal its own sprite: five deer, three boar, one fox, three elephants, four rabbits
for (id, n) in [
("deer", 5),
("stag", 1),
("boar", 3),
("fox", 1),
("el", 3),
("rab", 4),
] {
assert_eq!(sprites_of(&b, id), n, "{id}");
}
// grazing animals carry two head poses taking turns, the first shown unanimated;
// the standing stag and the rabbits have none
assert_eq!(
out.html.matches("iso-a iso-pose2 iso-pose0").count(),
5 + 3 + 1 + 3
);
assert_eq!(out.html.matches("iso-a iso-pose2\"").count(), 5 + 3 + 1 + 3);
assert_eq!(out.html.matches("iso-a iso-hop").count(), 4);
// birds flap, butterflies flutter, bees fly round the hives
assert_eq!(out.html.matches("iso-a iso-flap").count(), 7);
assert_eq!(out.html.matches("iso-a iso-flutter").count(), 5);
assert_eq!(out.html.matches("class=\"iso-bee iso-bee\"").count(), 4 * 8);
// animal faces are edged in their own shade; antlers on the one stag
assert!(out.html.contains("iso-deer iso-beast"));
assert!(out.html.contains("iso-antler"));
}
let b = r.build("site", 0, &cfg(), &doc).unwrap();
// a stag stands about 1.5 m at the head, antlers up to 2.1 m; it fits its sort box
let st = b.boxes["stag"];
assert!(st[5] > 1.9 && st[5] < 2.3, "{st:?}");
// an elephant bull: about 3 m tall at the head
let el = b.boxes["el"];
assert!(el[5] > 2.6 && el[5] < 3.3, "{el:?}");
let out = render(&b.scene, 0, &RenderOpts::default());
let row = |id: &str, k: &str| {
out.info[id]
.rows
.iter()
.find(|r| r.0 == k)
.map(|r| r.1.clone())
.unwrap_or_default()
};
assert_eq!(row("deer", "Herd"), "1 stag, 3 does, 1 fawn");
assert_eq!(row("el", "Herd"), "1 bull, 1 cow, 1 calf");
assert_eq!(row("hives", "Apiary"), "4 hives, 2 supers each");
}
#[test]
fn diagonal_walls_fences_and_hedges_mitre_and_sort() {
let r = Runtime::new(&Bundle::default()).unwrap();