Streets: sidewalks, road curves, junctions and roundabouts
Deploy web Studio / deploy (push) Successful in 4m31s
Deploy web Studio / deploy (push) Successful in 4m31s
Roads take sidewalks (none, both, left, right): 12 cm high paving with kerb faces drawn where they face the viewer. Road curve (radius, angle, left/right) carries a street round a bend; centre lines are true arcs when cut and offset, so curve joints are seamless. A road ending on another road's centre line opens that road's kerb and sidewalk (T junction); two roads ending at one corner close it. Roundabout: ring radius and width, one or two lanes, up to four arms with flared kerbs, splitter islands, give-way lines on the entry side (traffic on the left or right) and a central island with apron. Studio: a road run drawn corner by corner gets a curve in each corner (prefab meta `corner`). Tooltips list the streets each one meets. Engine: open `polyline` primitive (Prim::Poly open) so dashes run on round curves. examples/streets and a Streets demo in the web Studio; MCP guidance, docs. ROADMAP: backlog 5-7 done; multi-select and the hosted copy marked done. Tests: Rust 72, Studio 113, web 95.
This commit is contained in:
1 parent
2442966743
commit
c91d63663b
24 files changed
+989
-29
No files matched your search
@@ -321,6 +321,16 @@ pub fn register_sprite(e: &mut Engine) {
|
||||
ok(s)
|
||||
},
|
||||
);
|
||||
// open polylines through plan points at height z (or 3D points without z)
|
||||
e.register_fn(
|
||||
"polyline",
|
||||
|s: &mut SpriteH, p: D, z: D, cls: &str| -> RR<SpriteH> {
|
||||
let z = n(&z)?;
|
||||
let lines = polys3_or_flat(&p, z)?;
|
||||
s.0.borrow_mut().polyline(lines, &classes(cls), None);
|
||||
ok(s)
|
||||
},
|
||||
);
|
||||
e.register_fn("poly", |s: &mut SpriteH, p: D, cls: &str| -> RR<SpriteH> {
|
||||
s.0.borrow_mut().poly(polys3(&p)?, &classes(cls), None);
|
||||
ok(s)
|
||||
|
||||
@@ -90,6 +90,15 @@ pub fn polys3(d: &Dynamic) -> RR<Vec<Vec<P3>>> {
|
||||
}
|
||||
}
|
||||
|
||||
/// One line or several, of plan points `[x, y]` (put at height z) or 3D points.
|
||||
pub fn polys3_or_flat(d: &Dynamic, z: f64) -> RR<Vec<Vec<P3>>> {
|
||||
polys(d).map(|ls| {
|
||||
ls.into_iter()
|
||||
.map(|l| l.into_iter().map(|p| [p[0], p[1], z]).collect())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// `[[a, b, c, d], ...]` (segments, opening lists)
|
||||
pub fn quads(d: &Dynamic, what: &str) -> RR<Vec<[f64; 4]>> {
|
||||
arr(d, what)?
|
||||
|
||||
@@ -123,6 +123,10 @@ pub struct PrefabMeta {
|
||||
/// turns it to point from where the drag starts to where it ends.
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub directed: bool,
|
||||
/// A prefab that rounds the corners of a run (roads: "road_curve"): the
|
||||
/// Studio puts one in each corner of a run drawn corner by corner.
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub corner: String,
|
||||
#[serde(default)]
|
||||
pub props: Vec<PropDef>,
|
||||
/// Module the prefab comes from ("isokit/site", "lib/kiosk.rhai").
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
pub const MODULES: &[(&str, &str)] = &[
|
||||
("isokit/util", include_str!("../stdlib/util.rhai")),
|
||||
("isokit/ground", include_str!("../stdlib/ground.rhai")),
|
||||
("isokit/streets", include_str!("../stdlib/streets.rhai")),
|
||||
("isokit/nature", include_str!("../stdlib/nature.rhai")),
|
||||
("isokit/building", include_str!("../stdlib/building.rhai")),
|
||||
("isokit/vehicles", include_str!("../stdlib/vehicles.rhai")),
|
||||
|
||||
@@ -732,6 +732,122 @@ fn robot_arms_pass_goods_from_belt_to_belt() {
|
||||
assert!(out.info["robot1"].rows[1].1.contains("waiting"));
|
||||
}
|
||||
|
||||
fn street_doc() -> Doc {
|
||||
let mut doc = Doc::default();
|
||||
let walk = json!({"walk": "both"});
|
||||
for (id, kind, x, y, rot, props) in [
|
||||
(
|
||||
"main",
|
||||
"road",
|
||||
-10.0,
|
||||
0.0,
|
||||
0.0,
|
||||
json!({"w": 40, "walk": "both"}),
|
||||
),
|
||||
(
|
||||
"side",
|
||||
"road",
|
||||
-14.0,
|
||||
-12.0,
|
||||
90.0,
|
||||
json!({"w": 24, "walk": "both"}),
|
||||
),
|
||||
("bend", "road_curve", 10.0, 0.0, 0.0, walk.clone()),
|
||||
(
|
||||
"north",
|
||||
"road",
|
||||
20.0,
|
||||
18.0,
|
||||
90.0,
|
||||
json!({"w": 16, "walk": "both"}),
|
||||
),
|
||||
(
|
||||
"c1",
|
||||
"road",
|
||||
-30.0,
|
||||
20.0,
|
||||
0.0,
|
||||
json!({"w": 20, "walk": "both"}),
|
||||
),
|
||||
(
|
||||
"c2",
|
||||
"road",
|
||||
-20.0,
|
||||
30.0,
|
||||
90.0,
|
||||
json!({"w": 20, "walk": "both"}),
|
||||
),
|
||||
("rb", "roundabout", -54.0, 0.0, 0.0, json!({"arm_n": false})),
|
||||
] {
|
||||
let mut o = obj(id, kind, x, y, props);
|
||||
o.rot = rot;
|
||||
doc.objects.push(o);
|
||||
}
|
||||
doc
|
||||
}
|
||||
|
||||
/// Sidewalk strips of object id (paving pieces).
|
||||
fn walks_of(b: &crate::Built, id: &str) -> usize {
|
||||
b.scene
|
||||
.layers
|
||||
.iter()
|
||||
.flat_map(|l| l.sprites.iter())
|
||||
.filter(|s| s.obj.as_deref() == Some(id))
|
||||
.flat_map(|s| s.prims.iter())
|
||||
.filter(|p| matches!(p, isokit_core::Prim::Flat { cls, .. } if cls.contains("g-walk")))
|
||||
.count()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn streets_curve_join_and_meet_at_roundabouts() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
assert_eq!(r.prefab("road").unwrap().corner, "road_curve");
|
||||
let doc = street_doc();
|
||||
for view in 0..4u8 {
|
||||
let b = r.build("site", 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, "view {view}: depth-sort cycles");
|
||||
assert!(!out.html.contains("NaN"));
|
||||
}
|
||||
let b = r.build("site", 0, &cfg(), &doc).unwrap();
|
||||
let near = |a: [f64; 6], want: [f64; 6]| a.iter().zip(want).all(|(a, w)| (a - w).abs() < 1e-3);
|
||||
// the side road ends on the main street's centre line; the main street's south
|
||||
// sidewalk leaves a gap for it (two pieces), its north one runs through
|
||||
assert_eq!(walks_of(&b, "main"), 3);
|
||||
assert_eq!(walks_of(&b, "side"), 2);
|
||||
let sd = b.boxes["side"];
|
||||
assert!(near(sd, [-19.0, -24.0, 0.0, -9.0, 0.0, 0.12]), "{sd:?}");
|
||||
// the curve carries the street on round 90 degrees, overlapping both by 4 cm
|
||||
// along its centre line (6 cm at its outer edge); the main street meets the
|
||||
// roundabout's east arm end to end
|
||||
let bd = b.boxes["bend"];
|
||||
assert!(near(bd, [9.94, -5.0, 0.0, 25.0, 10.06, 0.12]), "{bd:?}");
|
||||
let mn = b.boxes["main"];
|
||||
assert!(near(mn, [-30.04, -5.0, 0.0, 10.04, 5.0, 0.12]), "{mn:?}");
|
||||
// two roads ending at one corner close it: the outer sidewalks go round it
|
||||
let (c1, c2) = (b.boxes["c1"], b.boxes["c2"]);
|
||||
assert!(
|
||||
(c1[3] + 15.0).abs() < 1e-6 && (c1[1] - 15.0).abs() < 1e-6,
|
||||
"{c1:?}"
|
||||
);
|
||||
assert!(
|
||||
(c2[1] - 15.0).abs() < 1e-6 && (c2[3] + 15.0).abs() < 1e-6,
|
||||
"{c2:?}"
|
||||
);
|
||||
// the roundabout: arms east, west and south ending 24 m out, the ring's sidewalk to the north
|
||||
let rb = b.boxes["rb"];
|
||||
assert!(
|
||||
(rb[0] + 78.0).abs() < 1e-6
|
||||
&& (rb[3] + 30.0).abs() < 1e-6
|
||||
&& (rb[1] + 24.0).abs() < 1e-6
|
||||
&& (rb[4] - 16.0).abs() < 0.01,
|
||||
"{rb:?}"
|
||||
);
|
||||
let html = render(&b.scene, 0, &RenderOpts::default()).html;
|
||||
assert!(html.contains("iso-giveway") && html.contains("iso-kerbf"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn site_walls_join_at_corners() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
|
||||
Reference in new issue
Block a user