// isokit/nature: trees, hedges, shrubs. /// A tree with a round crown of radius r (m) standing at (x, y) of this sprite. fn tree_at(x, y, r) { let h = 1.4 + r * 0.6; this.ext_box(x - r, y - r, x + r, y + r, 0.0, h + r * 1.8); this.stem(x, y, 0.0, h, 0.7, "trunk"); let R = r * px() * 1.18; this.circle(x, y, h + r * 0.9, R, "leaf l"); this.circle(x, y, h + r * 0.9, R * 0.58, "leaf t", -R * 0.28, -R * 0.3); this } fn meta_tree() { #{ label: "Tree", category: "Nature", icon: "🌳", props: [ #{ key: "r", type: "float", def: 1.6, min: 0.5, max: 6.0, step: 0.1, label: "Crown radius (m)" } ] } } fn prefab_tree(o) { sprite().tree_at(0.0, 0.0, o.p.r); } fn meta_tree_row() { #{ label: "Row of trees", category: "Nature", icon: "🌲", shape: "line", props: [ #{ key: "w", type: "float", def: 30.0, min: 2.0, max: 500.0, step: 1.0, label: "Length (m)" }, #{ key: "spacing", type: "float", def: 6.0, min: 2.0, max: 30.0, step: 0.5, label: "Spacing (m)" }, #{ key: "r", type: "float", def: 1.5, min: 0.5, max: 6.0, step: 0.1, label: "Crown radius (m)" }, ] } } fn prefab_tree_row(o) { let p = o.p; let n = (p.w / p.spacing).floor().to_int() + 1; let step = if n > 1 { p.w / (n - 1) } else { 0.0 }; for i in 0..n { // one sprite per tree; small deterministic size variation sprite().tree_at(-p.w / 2.0 + i * step, 0.0, p.r * (0.85 + 0.3 * hash(i + p.w))); } } fn meta_hedge() { #{ label: "Hedge", category: "Nature", icon: "▬", shape: "line", props: [ #{ key: "w", type: "float", def: 10.0, min: 0.5, max: 300.0, step: 0.5, label: "Length (m)" }, #{ key: "d", type: "float", def: 0.6, min: 0.3, max: 3.0, step: 0.1, label: "Thickness (m)" }, #{ key: "h", type: "float", def: 1.4, min: 0.3, max: 4.0, step: 0.1, label: "Height (m)" }, ] } } fn prefab_hedge(o) { let p = o.p; let c0 = hedge_end(o, -1.0); let c1 = hedge_end(o, 1.0); sprite().cut_box(c0.x, c0.k, c1.x, c1.k, -p.d / 2.0, p.d / 2.0, 0.0, p.h, "hedge"); } /// Where hedge o ends at e: mitred or slanted where a diagonal hedge meets it. fn hedge_end(o, e) { let m = line_meet(o, e, ["hedge"], []); if m.kind == "corner" || m.kind == "tee" { end_cut(o.p.w, e, m) } else { end_cut(o.p.w, e, #{ ext: 0.0, k: 0.0 }) } } // ---------------------------------------------------------------- pond fn meta_pond() { #{ label: "Pond", category: "Nature", icon: "◍", shape: "rect", help: "A garden pond with fish swimming round under lily pads, reeds at the back and a stone or grass edge. Drag a rectangle on the map.", props: [ #{ key: "w", type: "float", def: 6.0, min: 1.5, max: 60.0, step: 0.5, label: "Width (m)" }, #{ key: "d", type: "float", def: 4.0, min: 1.5, max: 60.0, step: 0.5, label: "Length (m)" }, #{ key: "shape", type: "choice", def: "natural", choices: ["natural", "round", "square"], label: "Shape" }, #{ key: "fish", type: "int", def: 5, min: 0, max: 16, step: 1, label: "Fish" }, #{ key: "lilies", type: "int", def: 3, min: 0, max: 12, step: 1, label: "Lily pads" }, #{ key: "reeds", type: "bool", def: true, label: "Reeds" }, #{ key: "edge", type: "choice", def: "stone", choices: ["stone", "grass"], label: "Edge" }, ] } } /// The pond's outline inset by `ins` metres: a kidney ("natural"), an /// ellipse ("round") or a rectangle with round corners ("square"). fn pond_outline(p, ins) { let a = p.w / 2.0 - ins; let b = p.d / 2.0 - ins; let pts = []; if p.shape == "square" { let r = fmax(0.05, fmin(a, b) * 0.35); for k in 0..4 { let cx = if k == 0 || k == 3 { a - r } else { -a + r }; let cy = if k < 2 { b - r } else { -b + r }; for j in 0..7 { let t = (k * 90 + j * 15) * PI() / 180.0; pts.push([cx + r * t.cos(), cy + r * t.sin()]); } } return pts; } for j in 0..48 { let t = j * PI() / 24.0; let k = 1.0; if p.shape == "natural" { // a dent at the back (+y) and a slight wobble, never past the box let dt = (t - PI() / 2.0) / 0.6; k = (1.0 - 0.3 * (-dt * dt).exp()) * (0.965 + 0.035 * (3.0 * t + 1.0).sin()); } pts.push([a * k * t.cos(), b * k * t.sin()]); } pts } fn prefab_pond(o) { let p = o.p; let a = p.w / 2.0; let b = p.d / 2.0; let rim = if p.edge == "stone" { fmin(0.35, fmin(a, b) * 0.18) } else { fmin(0.2, fmin(a, b) * 0.1) }; layer("ground"); let s = sprite(); let outer = pond_outline(p, 0.0); let inner = pond_outline(p, rim); if p.edge == "stone" { s.flat([outer], 0.03, "capst t"); // joints across the stone rim let segs = []; let step = if outer.len() > 30 { 2 } else { 1 }; for j in range(0, outer.len(), step) { segs.push([outer[j][0], outer[j][1], inner[j][0], inner[j][1]]); } s.lines(segs, 0.031, "seam"); } else { s.flat([outer], 0.02, "pondbank t"); } s.flat([inner], 0.032, "pondw t"); s.flat([pond_outline(p, rim + fmin(a, b) * 0.28)], 0.033, "ponddp t"); // the water the fish keep to: an ellipse well inside, off the natural shape's dent let wa = (a - rim) * 0.72; let wb = (b - rim) * 0.72; let oy = if p.shape == "natural" { -0.12 * b } else { 0.0 }; if p.shape == "natural" { wb *= 0.82; } let n = p.fish; for i in 0..n { let f = 0.45 + 0.55 * ((i * 37 + 11) % 10) / 10.0; let rx = wa * f; let ry = wb * f; let cx = 0.0; // long ponds: smaller rounds spread along the long side if rx > ry * 1.6 { cx = (((i * 53) % 9) / 8.0 - 0.5) * (rx - ry * 1.6) * 2.0; rx = ry * 1.6; } if ry > rx * 1.6 { ry = rx * 1.6; } let len = fmin(0.9, fmax(0.3, fmin(a, b) * 0.24)) * (0.85 + 0.1 * (i % 3)); let dur = fmax(8.0, 2.0 * PI() * (rx + ry) / 2.0 / 0.32); s.swim(cx, oy, 0.036, [#{ rx: rx, ry: ry, dur: dur, delay: (i * 7.3) % dur, cw: i % 3 == 1, len: len, cls: ["koi", "koiw", "koig"][i % 3] }]); } // lily pads over the fish, some in flower; rings spreading where a fish came up for i in 0..p.lilies { let t = i * 2.4 + 0.7; let q = 0.35 + 0.4 * ((i * 29 + 3) % 10) / 10.0; let x = wa * q * t.cos(); let y = oy + wb * q * t.sin(); s.disc(x, y, 0.04, fmin(0.4, fmax(0.18, fmin(a, b) * 0.1)), "lily t"); if i % 2 == 0 { s.disc(x + 0.08, y + 0.05, 0.05, 0.07, "lilyf t"); } } if n > 0 { for i in 0..fmin(3, n).to_int() { let t = i * 2.1 + 1.9; s.begin("a rip", `animation-delay:-${i * 1.7}s`); s.disc(wa * 0.5 * t.cos(), oy + wb * 0.5 * t.sin(), 0.04, fmin(0.6, fmin(a, b) * 0.15), "ripple"); s.end(); } } // reeds in clumps on the back edge, upright, so with the other sprites if p.reeds { layer("main"); for t in [1.15, 1.95, 2.55] { let q = pond_outline(p, rim * 0.5 + 0.3); let j = ((t / (2.0 * PI())) * q.len()).round().to_int() % q.len(); let c = q[j]; // tapered blades leaning out from the clump, cattail heads on some let blades = []; let heads = []; for k in 0..7 { let x = c[0] + ((k * 37) % 7 - 3) * 0.04; let y = c[1] + ((k * 53) % 5 - 2) * 0.04; let h = 0.75 + ((k * 31) % 6) * 0.09; let lx = ((k * 17) % 7 - 3) * 0.06; let ly = ((k * 23) % 5 - 2) * 0.06; blades.push([[x - 0.06, y, 0.03], [x + 0.06, y, 0.03], [x + lx, y + ly, h]]); blades.push([[x, y - 0.06, 0.03], [x, y + 0.06, 0.03], [x + lx, y + ly, h]]); if k % 3 == 0 { let f = 0.62; let hx = x + lx * f; let hy = y + ly * f; heads.push([[hx - 0.035, hy, h * f], [hx + 0.035, hy, h * f], [hx + 0.035 + lx * 0.18, hy + ly * 0.18, h * (f + 0.18)], [hx - 0.035 + lx * 0.18, hy + ly * 0.18, h * (f + 0.18)]]); } } sprite().poly(blades, "reed t").poly(heads, "cattail t"); } } }