Diagonals: streets, walls, fences, hedges and house walls at any angle
Deploy web Studio / deploy (push) Successful in 1m5s
Deploy web Studio / deploy (push) Successful in 1m5s
Object frames turn by any angle (quarter turns stay exact, so scenes along the axes render byte for byte as before); a box in a turned frame is a prism with its own corners. Rotation is kept as given (normalised to 0..360), no longer snapped to quarter turns. Depth sort: sprites whose plan outline is not their box carry their convex outline; when boxes overlap, an outline edge that separates two sprites orders them. A long diagonal wall sorts right against both sides. Joints (line_meet, end_cut, cut_box in util.rhai): where a piece off the axes is involved, corners are mitred and T-junctions slanted; fences put a corner post or pillar in its own sprite and mitre stone bases; Y joints let the lowest id run through. Streets: every end part is a cut e + k*w, so side streets come in at any angle (slanted sidewalk gaps), two streets meet in a mitre with seam patches; fixes a curve's outer sidewalk that was skipped when its kerb and back lines had different point counts. Studio: runs and drags keep to x/y or snap to 45 degree diagonals, Ctrl gives any angle; a road run turning 45 degrees gets a 45 degree curve; Rotation is a number box. Streets demo: a diagonal lane with a hedge. MCP guidance, SCRIPTING/README/ROADMAP. Tests: Rust 76, Studio and web checks green.
This commit is contained in:
1 parent
c91d63663b
commit
96d472625f
18 files changed
+1051
-178
No files matched your search
@@ -103,19 +103,34 @@ fn meta_wall() {
|
||||
] }
|
||||
}
|
||||
fn prefab_wall(o) {
|
||||
// ends joined to the walls they meet; long walls in pieces of at most 30 m,
|
||||
// each its own sprite for the depth sort
|
||||
// ends joined to the walls they meet (mitred and slanted where a wall is
|
||||
// diagonal); long walls in pieces of at most 30 m, each its own sprite for
|
||||
// the depth sort
|
||||
let p = o.p;
|
||||
let x0 = -p.w / 2.0 - line_joint(o, -1.0, ["wall"], []).ext;
|
||||
let x1 = p.w / 2.0 + line_joint(o, 1.0, ["wall"], []).ext;
|
||||
let c0 = wall_end(o, -1.0);
|
||||
let c1 = wall_end(o, 1.0);
|
||||
let x0 = c0.x;
|
||||
let x1 = c1.x;
|
||||
let n = ((x1 - x0) / 30.0).ceiling().to_int();
|
||||
let L = (x1 - x0) / n;
|
||||
for i in 0..n {
|
||||
let a = x0 + i * L;
|
||||
sprite().box(a, -p.t / 2.0, a + L, p.t / 2.0, 0.0, p.h, "wallp", #{ top: "wallt" });
|
||||
let k0 = if i == 0 { c0.k } else { 0.0 };
|
||||
let k1 = if i == n - 1 { c1.k } else { 0.0 };
|
||||
sprite().cut_box(a, k0, a + L, k1, -p.t / 2.0, p.t / 2.0, 0.0, p.h, "wallp", #{ top: "wallt" });
|
||||
}
|
||||
}
|
||||
|
||||
/// Where site wall o ends at e (-1 or 1): #{ x, k } (see end_cut).
|
||||
fn wall_end(o, e) {
|
||||
let m = line_meet(o, e, ["wall"], []);
|
||||
let ht = o.p.t / 2.0;
|
||||
if m.kind == "axis" { return end_cut(o.p.w, e, #{ ext: line_joint(o, e, ["wall"], []).ext, k: 0.0 }); }
|
||||
if m.kind == "free" { return end_cut(o.p.w, e, #{ ext: ht, k: 0.0 }); }
|
||||
if m.kind == "run" { return end_cut(o.p.w, e, #{ ext: if m.lower { ht } else { -m.wt }, k: 0.0 }); }
|
||||
end_cut(o.p.w, e, m)
|
||||
}
|
||||
|
||||
fn fence_styles() { ["mesh", "wood", "bars", "half stone", "cobble", "brick"] }
|
||||
|
||||
/// Half the thickness of a fence design: its posts, base or pillars.
|
||||
@@ -135,10 +150,13 @@ fn meta_fence() {
|
||||
fn prefab_fence(o) {
|
||||
let p = o.p;
|
||||
let st = p.style;
|
||||
let ea = line_joint(o, -1.0, ["fence"], ["wall", "house_wall"]);
|
||||
let eb = line_joint(o, 1.0, ["fence"], ["wall", "house_wall"]);
|
||||
let x0 = -p.w / 2.0 - ea.ext;
|
||||
let x1 = p.w / 2.0 + eb.ext;
|
||||
let ea = fence_end(o, -1.0);
|
||||
let eb = fence_end(o, 1.0);
|
||||
// x0, x1: the base's ends (stone, cobble); u0, u1: the panels', rails' and boards'
|
||||
let x0 = ea.x;
|
||||
let x1 = eb.x;
|
||||
let u0 = ea.up;
|
||||
let u1 = eb.up;
|
||||
if x1 - x0 < 0.05 { return; }
|
||||
// posts (pillars) evenly along the length; the end ones only where this
|
||||
// fence draws them, kept inside the stretch it covers
|
||||
@@ -149,10 +167,17 @@ fn prefab_fence(o) {
|
||||
if st != "cobble" {
|
||||
for j in 0..(k + 1) {
|
||||
if (j == 0 && !ea.post) || (j == k && !eb.post) { continue; }
|
||||
posts.push(fmin(fmax(-p.w / 2.0 + j * p.w / k, x0 + ph), x1 - ph));
|
||||
posts.push(fmin(fmax(-p.w / 2.0 + j * p.w / k, u0 + ph), u1 - ph));
|
||||
}
|
||||
}
|
||||
if st == "brick" { brick_fence(x0, x1, posts, p.h); return; }
|
||||
// corner posts where a diagonal fence meets this one: their own sprites
|
||||
for c in [[ea, -1.0], [eb, 1.0]] {
|
||||
if c[0].corner { fence_corner_post(sprite(), c[1] * p.w / 2.0, p.h, st); }
|
||||
}
|
||||
if st == "brick" { brick_fence(u0, u1, posts, p.h); return; }
|
||||
// diagonal pieces keep a stone base and what stands on it in separate
|
||||
// sprites, so corner posts on the base sort against both
|
||||
let split = st == "half stone" && (!on_axes(o.rot) || ea.gen || eb.gen);
|
||||
// pieces of at most 30 m, each its own sprite for the depth sort
|
||||
let n = ((x1 - x0) / 30.0).ceiling().to_int();
|
||||
let L = (x1 - x0) / n;
|
||||
@@ -160,16 +185,83 @@ fn prefab_fence(o) {
|
||||
let a = x0 + i * L;
|
||||
let b = a + L;
|
||||
let ps = [];
|
||||
let ua = if i == 0 { u0 } else { a };
|
||||
let ub = if i == n - 1 { u1 } else { b };
|
||||
for x in posts {
|
||||
if x >= a - 1e-6 && (x < b - 1e-6 || i == n - 1) { ps.push(fmin(fmax(x, a + ph), b - ph)); }
|
||||
if x >= a - 1e-6 && (x < b - 1e-6 || i == n - 1) { ps.push(fmin(fmax(x, ua + ph), ub - ph)); }
|
||||
}
|
||||
fence_piece(sprite(), a, b, ps, p.h, st, i);
|
||||
let cut = #{ a: a, ka: if i == 0 { ea.k } else { 0.0 }, b: b, kb: if i == n - 1 { eb.k } else { 0.0 } };
|
||||
let s = sprite();
|
||||
fence_piece(s, if split { sprite() } else { s }, cut, ua, ub, ps, p.h, st, i);
|
||||
}
|
||||
}
|
||||
|
||||
/// One stretch a..b of a fence (not brick) with its posts ps.
|
||||
fn fence_piece(s, a, b, ps, h, st, seed) {
|
||||
/// Post half widths of the fence designs (0: none).
|
||||
fn fence_post_half(st) {
|
||||
switch st { "cobble" => 0.0, "wood" => 0.05, "brick" => 0.18, _ => 0.04 }
|
||||
}
|
||||
|
||||
/// Where fence o ends at e (-1 or 1): #{ x, k, up, post, corner, gen }: its
|
||||
/// base (stone, cobble) ends at x + k * y; panels, rails and boards at up
|
||||
/// (short of a corner post); post: it draws the end post among its own;
|
||||
/// corner: it draws the post at a diagonal corner as its own sprite; gen: a
|
||||
/// joint with a diagonal piece.
|
||||
fn fence_end(o, e) {
|
||||
let w = o.p.w;
|
||||
let st = o.p.style;
|
||||
let ht = fence_ht(st);
|
||||
let m = line_meet(o, e, ["fence"], ["wall", "house_wall"]);
|
||||
let plain = |ext, post, gen| {
|
||||
let c = end_cut(w, e, #{ ext: ext, k: 0.0 });
|
||||
#{ x: c.x, k: 0.0, up: c.x, post: post, corner: false, gen: gen }
|
||||
};
|
||||
if m.kind == "axis" {
|
||||
let j = line_joint(o, e, ["fence"], ["wall", "house_wall"]);
|
||||
return plain.call(j.ext, j.post, false);
|
||||
}
|
||||
if m.kind == "free" { return plain.call(ht, true, true); }
|
||||
if m.kind == "run" { return if m.lower { plain.call(ht, true, true) } else { plain.call(-m.wt, false, true) }; }
|
||||
let c = end_cut(w, e, m);
|
||||
let r = #{ x: c.x, k: c.k, up: c.x, post: false, corner: false, gen: true };
|
||||
if m.kind == "tee" {
|
||||
// panels stop at the face too; against a wall the fence keeps its post
|
||||
r.post = m.stop;
|
||||
return r;
|
||||
}
|
||||
// a corner: the base is mitred; the owner's post (or pillar) stands on the corner
|
||||
let pst = if m.owner { st } else { m.w.p.style };
|
||||
let pp = fence_post_half(pst);
|
||||
if pp == 0.0 { return r; }
|
||||
if m.owner && st == "brick" {
|
||||
r.up = end_cut(w, e, #{ ext: pp, k: 0.0 }).x;
|
||||
r.post = true;
|
||||
return r;
|
||||
}
|
||||
let reach = if m.owner { pp } else { pp * (m.cos + m.sin) };
|
||||
r.up = end_cut(w, e, #{ ext: -reach, k: 0.0 }).x;
|
||||
r.corner = m.owner && st != "brick";
|
||||
r
|
||||
}
|
||||
|
||||
/// The post at a diagonal corner, at local x: on the stone base for half stone.
|
||||
fn fence_corner_post(s, x, h, st) {
|
||||
if st == "wood" {
|
||||
s.box(x - 0.05, -0.05, x + 0.05, 0.05, 0.0, h + 0.06, "woodd");
|
||||
} else {
|
||||
let z0 = if st == "half stone" { fmin(0.6, h * 0.4) } else { 0.0 };
|
||||
s.box(x - 0.04, -0.04, x + 0.04, 0.04, z0, h + 0.05, "steel");
|
||||
}
|
||||
}
|
||||
|
||||
/// One stretch of a fence (not brick): the base (stone, cobble) in sprite s
|
||||
/// over cut #{ a, ka, b, kb } (slanted ends), what stands on it in sprite su
|
||||
/// from a to b, with posts ps.
|
||||
fn fence_piece(s, su, cut, a, b, ps, h, st, seed) {
|
||||
let L = b - a;
|
||||
let ca = cut.a;
|
||||
let cb = cut.b;
|
||||
let ka = cut.ka;
|
||||
let kb = cut.kb;
|
||||
if st == "wood" {
|
||||
s.box(a, -0.025, b, 0.025, 0.0, h, "wood");
|
||||
let boards = [];
|
||||
@@ -189,19 +281,22 @@ fn fence_piece(s, a, b, ps, h, st, seed) {
|
||||
for x in ps { s.box(x - 0.04, -0.04, x + 0.04, 0.04, 0.0, h + 0.05, "steel"); }
|
||||
} else if st == "half stone" {
|
||||
let hb = fmin(0.6, h * 0.4);
|
||||
s.box(a, -0.16, b, 0.16, 0.0, hb, "cobble", #{ top: "capst" });
|
||||
let j = stone_joints(L, 0.0, hb, seed);
|
||||
s.open([a, -0.16], [b, -0.16], j, "seam");
|
||||
s.open([b, 0.16], [a, 0.16], j, "seam");
|
||||
s.box(a, -0.03, b, 0.03, hb, h, "fence");
|
||||
for x in ps { s.box(x - 0.04, -0.04, x + 0.04, 0.04, hb, h + 0.05, "steel"); }
|
||||
s.cut_box(ca, ka, cb, kb, -0.16, 0.16, 0.0, hb, "cobble", #{ top: "capst" });
|
||||
let f0 = ca - ka * 0.16;
|
||||
let f1 = ca + ka * 0.16;
|
||||
let j = stone_joints(cb - kb * 0.16 - f0, 0.0, hb, seed);
|
||||
s.open([f0, -0.16], [cb - kb * 0.16, -0.16], j, "seam");
|
||||
s.open([cb + kb * 0.16, 0.16], [f1, 0.16], stone_joints(cb + kb * 0.16 - f1, 0.0, hb, seed), "seam");
|
||||
su.box(a, -0.03, b, 0.03, hb, h, "fence");
|
||||
for x in ps { su.box(x - 0.04, -0.04, x + 0.04, 0.04, hb, h + 0.05, "steel"); }
|
||||
} else if st == "cobble" {
|
||||
let hc = fmax(0.2, h - 0.08);
|
||||
s.box(a, -0.2, b, 0.2, 0.0, hc, "cobble");
|
||||
s.box(a, -0.22, b, 0.22, hc, h, "capst");
|
||||
let j = stone_joints(L, 0.0, hc, seed);
|
||||
s.open([a, -0.2], [b, -0.2], j, "seam");
|
||||
s.open([b, 0.2], [a, 0.2], j, "seam");
|
||||
s.cut_box(ca, ka, cb, kb, -0.2, 0.2, 0.0, hc, "cobble");
|
||||
s.cut_box(ca, ka, cb, kb, -0.22, 0.22, hc, h, "capst");
|
||||
let f0 = ca - ka * 0.2;
|
||||
let f1 = ca + ka * 0.2;
|
||||
s.open([f0, -0.2], [cb - kb * 0.2, -0.2], stone_joints(cb - kb * 0.2 - f0, 0.0, hc, seed), "seam");
|
||||
s.open([cb + kb * 0.2, 0.2], [f1, 0.2], stone_joints(cb + kb * 0.2 - f1, 0.0, hc, seed), "seam");
|
||||
} else {
|
||||
// mesh
|
||||
s.box(a, -0.03, b, 0.03, 0.0, h, "fence");
|
||||
|
||||
Reference in new issue
Block a user