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
@@ -848,6 +848,162 @@ fn streets_curve_join_and_meet_at_roundabouts() {
|
||||
assert!(html.contains("iso-giveway") && html.contains("iso-kerbf"));
|
||||
}
|
||||
|
||||
/// A piece of `kind` from plan point a to b: x, y = the middle, rot = its angle, w = its length.
|
||||
fn piece(id: &str, kind: &str, a: [f64; 2], b: [f64; 2], props: Value) -> Object {
|
||||
let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
|
||||
let mut p = props;
|
||||
p["w"] = json!(dx.hypot(dy));
|
||||
let mut o = obj(id, kind, (a[0] + b[0]) / 2.0, (a[1] + b[1]) / 2.0, p);
|
||||
o.rot = dy.atan2(dx).to_degrees().rem_euclid(360.0);
|
||||
o
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diagonal_walls_fences_and_hedges_mitre_and_sort() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let mut doc = Doc {
|
||||
objects: vec![
|
||||
piece("w1", "wall", [-30.0, -20.0], [-20.0, -20.0], json!({})),
|
||||
piece("w2", "wall", [-20.0, -20.0], [-12.0, -12.0], json!({})),
|
||||
piece("w3", "wall", [-12.0, -12.0], [-12.0, -2.0], json!({})),
|
||||
piece("w4", "wall", [-16.0, -16.0], [-22.0, -10.0], json!({})),
|
||||
obj("p_right", "person", -14.0, -18.0, json!({})),
|
||||
obj("p_left", "person", -18.0, -15.0, json!({})),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
for (i, st) in ["mesh", "wood", "bars", "half stone", "cobble", "brick"]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let x = i as f64 * 10.0;
|
||||
doc.objects.push(piece(
|
||||
&format!("f{i}a"),
|
||||
"fence",
|
||||
[x, 5.0],
|
||||
[x + 6.0, 5.0],
|
||||
json!({ "style": st }),
|
||||
));
|
||||
doc.objects.push(piece(
|
||||
&format!("f{i}b"),
|
||||
"fence",
|
||||
[x + 6.0, 5.0],
|
||||
[x + 10.0, 11.0],
|
||||
json!({ "style": st }),
|
||||
));
|
||||
}
|
||||
doc.objects
|
||||
.push(piece("h1", "hedge", [5.0, -20.0], [12.0, -13.0], json!({})));
|
||||
doc.objects.push(piece(
|
||||
"h2",
|
||||
"hedge",
|
||||
[12.0, -13.0],
|
||||
[20.0, -13.0],
|
||||
json!({}),
|
||||
));
|
||||
doc.objects
|
||||
.push(obj("fl", "floor", 25.0, 18.0, json!({"w": 10, "d": 8})));
|
||||
for (id, a, b) in [
|
||||
("hw1", [20.0, 14.0], [30.0, 14.0]),
|
||||
("hw2", [30.0, 14.0], [30.0, 19.0]),
|
||||
("hw3", [30.0, 19.0], [27.0, 22.0]),
|
||||
("hw4", [27.0, 22.0], [20.0, 22.0]),
|
||||
("hw5", [20.0, 22.0], [20.0, 14.0]),
|
||||
] {
|
||||
doc.objects.push(piece(id, "house_wall", a, b, json!({})));
|
||||
}
|
||||
let mut win = obj("win", "window", 28.5, 20.5, json!({"w": 1.2}));
|
||||
win.rot = 135.0;
|
||||
doc.objects.push(win);
|
||||
for view in 0..4u8 {
|
||||
for lv in ["site", "gf"] {
|
||||
let b = r.build(lv, view, &cfg(), &doc).unwrap();
|
||||
assert!(b.scene.warnings.is_empty(), "{:?}", b.scene.warnings);
|
||||
let out = render(&b.scene, view, &RenderOpts::default());
|
||||
assert_eq!(out.cycles, 0, "{lv}/{view}: depth-sort cycles");
|
||||
assert!(!out.html.contains("NaN"));
|
||||
}
|
||||
// the person on the side of the diagonal wall turned to the viewer is drawn after it,
|
||||
// the other one before
|
||||
let b = r.build("site", view, &cfg(), &doc).unwrap();
|
||||
let html = render(&b.scene, view, &RenderOpts::default()).html;
|
||||
let at = |id: &str| html.find(&format!("data-obj=\"{id}\"")).unwrap();
|
||||
// w2 runs along (1, 1); its left side (towards (-1, 1)) holds p_left. In two views the
|
||||
// wall is seen edge-on: neither side faces the viewer and the order does not matter.
|
||||
let p = isokit_core::Proj::new(view as i64);
|
||||
let (left_seen, right_seen) = (p.vis(-1.0, 1.0).is_some(), p.vis(1.0, -1.0).is_some());
|
||||
if left_seen || right_seen {
|
||||
let (front, back) = if left_seen {
|
||||
("p_left", "p_right")
|
||||
} else {
|
||||
("p_right", "p_left")
|
||||
};
|
||||
assert!(
|
||||
at(back) < at("w2") && at("w2") < at(front),
|
||||
"view {view}: {back} < w2 < {front}"
|
||||
);
|
||||
}
|
||||
}
|
||||
let b = r.build("site", 0, &cfg(), &doc).unwrap();
|
||||
// the 135-degree corner is mitred: w1's outer face reaches 0.12 * tan(22.5) past the corner
|
||||
let w1 = b.boxes["w1"];
|
||||
assert!(
|
||||
(w1[3] + 20.0 - 0.12 * 22.5f64.to_radians().tan()).abs() < 1e-6,
|
||||
"{w1:?}"
|
||||
);
|
||||
// the window on the diagonal wall cuts its opening there
|
||||
assert_eq!(sprites_of(&b, "hw3"), 2);
|
||||
let wb = b.boxes["win"];
|
||||
assert!(
|
||||
((wb[0] + wb[3]) / 2.0 - 28.5).abs() < 1e-6 && ((wb[1] + wb[4]) / 2.0 - 20.5).abs() < 1e-6,
|
||||
"{wb:?}"
|
||||
);
|
||||
// diagonal fence corners: the owner draws the corner post as its own sprite (mesh: f0a, the lower id)
|
||||
assert_eq!(sprites_of(&b, "f0a"), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diagonal_streets_meet_at_mitres_and_slanted_junctions() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let walk = json!({"walk": "both"});
|
||||
let mut doc = Doc {
|
||||
objects: vec![
|
||||
piece("a1", "road", [-60.0, -30.0], [-35.0, -30.0], walk.clone()),
|
||||
piece("a2", "road", [-35.0, -30.0], [-20.0, -15.0], walk.clone()),
|
||||
piece("m1", "road", [-10.0, -40.0], [40.0, -40.0], walk.clone()),
|
||||
piece("s1", "road", [5.0, -40.0], [17.0, -28.0], walk.clone()),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let mut bend = obj(
|
||||
"bend",
|
||||
"road_curve",
|
||||
10.0,
|
||||
0.0,
|
||||
json!({"r": 12, "angle": 45, "walk": "both"}),
|
||||
);
|
||||
bend.rot = 0.0;
|
||||
doc.objects.push(bend);
|
||||
for view in 0..4u8 {
|
||||
let b = r.build("site", view, &cfg(), &doc).unwrap();
|
||||
assert!(b.scene.warnings.is_empty(), "{:?}", b.scene.warnings);
|
||||
assert_eq!(render(&b.scene, view, &RenderOpts::default()).cycles, 0);
|
||||
}
|
||||
let b = r.build("site", 0, &cfg(), &doc).unwrap();
|
||||
// the 135-degree corner is mitred: a1's outer sidewalk back edge (5 m out) reaches
|
||||
// 5 * tan(22.5) past it, and its seam patch 6 cm on into a2 (along (1, 1))
|
||||
let a1 = b.boxes["a1"];
|
||||
let reach = 5.0 * 22.5f64.to_radians().tan() + 0.06 * 0.5f64.sqrt();
|
||||
assert!((a1[3] + 35.0 - reach).abs() < 1e-6, "{a1:?}");
|
||||
// the diagonal side road opens m1's north sidewalk (two pieces there); its asphalt ends
|
||||
// slanted on m1's centre line
|
||||
assert_eq!(walks_of(&b, "m1"), 3);
|
||||
let s1 = b.boxes["s1"];
|
||||
assert!((s1[1] + 40.0).abs() < 1e-6, "{s1:?}");
|
||||
let html = render(&b.scene, 0, &RenderOpts::default()).html;
|
||||
assert!(html.contains("iso-g-walk"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn site_walls_join_at_corners() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
|
||||
Reference in new issue
Block a user