// isokit/util: helpers shared by the prefabs. /// Floor index of a level id: "gf" -> 0, "1f" -> 1, "2f" -> 2; anything else /// (roof, site, "") -> -1, which means "closed buildings". fn floor_of(lv) { if lv == "gf" { return 0; } if lv.len() >= 2 && lv.ends_with("f") { let k = lv.sub_string(0, lv.len() - 1); let digits = true; for c in k.chars() { if !"0123456789".contains(c) { digits = false; } } if digits { return parse_int(k); } } -1 } /// Walls of a cut-open room over the rectangle [x0, y0, x1, y1] from height z: /// walls facing the viewer are cut low (lo), the others stay high (hi), so /// the room can be seen into from every rotation. Each wall is its own sprite /// for the depth sort. skip: list of walls to leave out (0 S, 1 N, 2 W, 3 E). fn cut_walls(x0, y0, x1, y1, z, lo, hi, t, mat, skip) { let W = [ [x0, y0, x1, y0 + t, 0, -1], [x0, y1 - t, x1, y1, 0, 1], [x0, y0 + t, x0 + t, y1 - t, -1, 0], [x1 - t, y0 + t, x1, y1 - t, 1, 0], ]; for i in 0..4 { if skip.contains(i) { continue; } let w = W[i]; let h = if vis(w[4], w[5]) { lo } else { hi }; sprite().box(w[0], w[1], w[2], w[3], z, z + h, mat, #{ top: "cut" }); } } /// Windows spread along the face a -> b (length L) for one storey: /// returns [[u0, u1, z0, z1], ...]; leaves a gap around u = gap (or -1). fn win_list(L, z0, sill, height, width, spacing, gap) { let out = []; let n = ((L - 1.0) / spacing).floor().to_int(); if n < 1 { return out; } let start = (L - (n - 1) * spacing) / 2.0; for i in 0..n { let u = start + i * spacing; if gap >= 0.0 && (u - gap).abs() < width + 0.6 { continue; } out.push([u - width / 2.0, u + width / 2.0, z0 + sill, z0 + sill + height]); } out } /// Animate the next drawing of sprite s along (dx, dy) metres of its frame: /// call before drawing, then s.end(). Only animates when the motion runs /// along the axis the depth sort compares second; returns whether it does. fn drive(dx, dy, dur, delay) { let ok = if dx != 0.0 { along_x() } else { !along_x() }; if !ok { this.begin(""); return false; } let v = dv(dx, dy, 0.0); this.begin("a drive", `--dx:${v[0]}px;--dy:${v[1]}px;animation-duration:${dur}s;animation-delay:-${delay}s`); true } /// True when a piece turned by rot runs along world x (rot 0 or 180). fn along_world_x(rot) { ((rot / 90.0).round().to_int() % 2) == 0 } /// Half the thickness of a fence or wall piece: what the joints keep apart. fn line_ht(w) { if w.type == "fence" { fence_ht(w.p.style) } else { w.p.t / 2.0 } } /// How line piece o (a fence or wall along its local x, length p.w) meets /// other pieces at its end e (-1 or 1): #{ ext, post }, how far it reaches /// past the end and whether it draws the end post. `same`: the types it joins /// with; `stops`: types it ends against. The rules of the house walls, so /// pieces never overlap (the depth sort needs them apart): /// - alone it reaches half its thickness past the end, flush with its post; /// - on a straight run the lower piece runs through and draws the shared /// post, the other starts at that post; /// - at a corner the thicker piece (equal: the one along world x) runs /// through, the other stops at its face; /// - at a T it stops at the other's face without an end post (the other /// piece has its own there); against a `stops` piece it keeps its post. fn line_joint(o, e, same, stops) { let ht = line_ht(o); let p = to_world(e * o.p.w / 2.0, 0.0); let c = to_world(0.0, 0.0); let q = to_world(e * (o.p.w / 2.0 - 0.05), 0.0); let ox = along_world_x(o.rot); let face = 0.0; let has_face = false; let face_post = false; let run = #{}; let corner = []; for w in objects_on(same + stops, p[0], p[1], 0.5) { if w.id == o.id || (w.z - o.z).abs() > 0.5 { continue; } let wt = line_ht(w); if w.v.abs() > wt + 0.02 { continue; } let at_end = (w.u.abs() - w.p.w / 2.0).abs() < 0.02; let wx = along_world_x(w.rot); let side = if local_of(w, q[0], q[1])[1] >= w.v { 1.0 } else { -1.0 }; let stop = -(wt - side * w.v); if !same.contains(w.type) || (!at_end && wx != ox) { face = stop; has_face = true; face_post = !same.contains(w.type); } else if wx == ox { if at_end { run = #{ lower: if ox { c[0] < w.x } else { c[1] < w.y }, wt: wt }; } } else { corner.push(#{ wt: wt, stop: stop }); } } if has_face { return #{ ext: face, post: face_post }; } if run.len() > 0 { return if run.lower { #{ ext: ht, post: true } } else { #{ ext: -run.wt, post: false } }; } // two pieces end here from both sides: a straight run this one meets as a T if corner.len() > 1 { return #{ ext: corner[0].stop, post: false }; } if corner.len() == 1 { let k = corner[0]; let mine = ht > k.wt + 0.005 || ((ht - k.wt).abs() <= 0.005 && ox); return if mine { #{ ext: ht, post: true } } else { #{ ext: k.stop, post: false } }; } #{ ext: ht, post: true } }