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

+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();