Deploy web Studio / deploy (push) Successful in 3m52s
Joints: line_joint (util.rhai) applies the house-wall rules to fences and site walls, so pieces never overlap and the depth sort stays clean. - A corner is closed: the thicker piece (or, if equal, the one along world x) runs through, and the other stops at its face. - On a straight run, two pieces share one post. - At a T, the stem stops at the other piece's face without a post of its own. - A fence that ends at a site or house wall stops at the wall's face. Fence designs (prop style): mesh, wooden boards, steel bars, mesh on a stone base, cobble wall, and brick wall with pillars. Board, stone and brick joints are drawn on the face turned to the viewer. Brick pillars are separate sprites. Studio: line prefabs (walls, fences, hedges, roads) can be drawn as a run by clicking corner after corner. Double-click or Enter ends the run, clicking the first corner closes it, and Backspace drops the last corner. A run is one undo step. A drag still draws one piece. Corners and ends snap onto the ends and lines of the pieces they meet, and a marker shows where the next corner will snap. The house demo gets a brick front wall with gate pillars and wooden side fences in place of the old east fence, which ran over the road. Tests cover every design's corners, Ts, straight runs and wall stops, with no depth-sort cycles in any rotation, plus site walls. check_studio draws and closes a fence run, undoes it in one step, snaps a dragged spur, and picks a design.
123 lines
5.1 KiB
Plaintext
123 lines
5.1 KiB
Plaintext
// 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 }
|
|
}
|