// isokit/ground: site plate, paved and green areas, roads, parking, walls, // fences and lamps. Ground surfaces go to the unsorted "ground" layer in // document order (later objects lie on top). fn ground_materials() { ["g-grass", "g-grass2", "g-green", "g-garden", "g-asph", "g-conc", "g-pad", "g-path", "g-sand", "g-court", "dirt"] } fn meta_site() { #{ label: "Site ground", category: "Ground", icon: "▭", shape: "rect", help: "The ground plate: a surface with earth sides. Frames the view.", props: [ #{ key: "w", type: "float", def: 60.0, min: 4.0, max: 1000.0, step: 1.0, label: "Width (m)" }, #{ key: "d", type: "float", def: 40.0, min: 4.0, max: 1000.0, step: 1.0, label: "Depth (m)" }, #{ key: "mat", type: "material", def: "g-grass", choices: ground_materials(), label: "Surface" }, #{ key: "base", type: "float", def: 1.6, min: 0.2, max: 10.0, step: 0.1, label: "Earth depth (m)" }, ] } } fn prefab_site(o) { let p = o.p; let w = p.w / 2.0; let d = p.d / 2.0; layer("ground"); sprite().box(-w, -d, w, d, -p.base, 0.0, "dirt", #{ top: p.mat }); bounds(-w, -d, w, d, -p.base); } fn meta_area() { #{ label: "Area", category: "Ground", icon: "▱", shape: "rect", help: "A paved or planted area lying on the ground.", props: [ #{ key: "w", type: "float", def: 10.0, min: 0.5, max: 1000.0, step: 0.5, label: "Width (m)" }, #{ key: "d", type: "float", def: 6.0, min: 0.5, max: 1000.0, step: 0.5, label: "Depth (m)" }, #{ key: "mat", type: "material", def: "g-conc", choices: ground_materials(), label: "Surface" }, ] } } fn prefab_area(o) { let p = o.p; layer("ground"); let s = sprite(); if o.pts != () && o.pts.len() >= 3 { s.flat(o.pts, 0.006, p.mat + " t"); } else { s.flat(rect(-p.w / 2.0, -p.d / 2.0, p.w / 2.0, p.d / 2.0), 0.006, p.mat + " t"); } } fn meta_road() { #{ label: "Road", category: "Ground", icon: "═", shape: "line", corner: "road_curve", help: "A straight road along its length, with a dashed centre line and sidewalks if you like (left and right looking along local +x). Roads ending on another road's side make a junction; a run drawn corner by corner gets a curve in each corner.", props: [ #{ key: "w", type: "float", def: 40.0, min: 2.0, max: 2000.0, step: 1.0, label: "Length (m)" }, #{ key: "d", type: "float", def: 6.0, min: 2.0, max: 40.0, step: 0.5, label: "Width (m)" }, #{ key: "dashed", type: "bool", def: true, label: "Centre line" }, #{ key: "kerb", type: "bool", def: true, label: "Kerb lines" }, #{ key: "walk", type: "choice", def: "none", choices: walk_sides(), label: "Sidewalks" }, #{ key: "walk_w", type: "float", def: 2.0, min: 0.8, max: 8.0, step: 0.1, label: "Walk width (m)" }, ] } } fn prefab_road(o) { let p = o.p; layer("ground"); let others = other_street_ends(o); let ends = street_ends(o); let c = line_path([[-p.w / 2.0, 0.0], [p.w / 2.0, 0.0]]); let j0 = street_join(o, ends[0], others); let j1 = street_join(o, ends[1], others); let gaps = street_gaps(o, others); street_path(o, c, j0, j1, gaps); street_info(p, `${num1(p.w)} m long, ${num1(p.d)} m wide`, [j0.meet, j1.meet], gaps[2]); } fn meta_parking() { #{ label: "Parking bays", category: "Ground", icon: "P", props: [ #{ key: "n", type: "int", def: 6, min: 1, max: 60, step: 1, label: "Bays" }, #{ key: "bay_w", type: "float", def: 2.5, min: 2.0, max: 4.0, step: 0.1, label: "Bay width (m)" }, #{ key: "bay_d", type: "float", def: 5.0, min: 4.0, max: 7.0, step: 0.1, label: "Bay depth (m)" }, ] } } fn prefab_parking(o) { let p = o.p; let w = p.n * p.bay_w / 2.0; let d = p.bay_d / 2.0; layer("ground"); let s = sprite(); s.flat(rect(-w, -d, w, d), 0.008, "g-pad t"); let segs = []; for i in 0..(p.n + 1) { let x = -w + i * p.bay_w; segs.push([x, -d, x, d]); } s.lines(segs, 0.014, "bayline"); } fn meta_wall() { #{ label: "Wall", category: "Site", icon: "▬", shape: "line", help: "Drag along the map, or click corner after corner for a run around a plot. Walls that meet join at corners and T-junctions.", props: [ #{ key: "w", type: "float", def: 20.0, min: 0.5, max: 500.0, step: 0.5, label: "Length (m)" }, #{ key: "h", type: "float", def: 2.2, min: 0.3, max: 8.0, step: 0.1, label: "Height (m)" }, #{ key: "t", type: "float", def: 0.24, min: 0.1, max: 1.0, step: 0.02, label: "Thickness (m)" }, ] } } 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 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 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" }); } } fn fence_styles() { ["mesh", "wood", "bars", "half stone", "cobble", "brick"] } /// Half the thickness of a fence design: its posts, base or pillars. fn fence_ht(style) { switch style { "wood" => 0.05, "half stone" => 0.16, "cobble" => 0.22, "brick" => 0.18, _ => 0.04 } } fn meta_fence() { #{ label: "Fence", category: "Site", icon: "┼", shape: "line", help: "Drag along the map, or click corner after corner for a run around a plot. Fences that meet join at corners and T-junctions and stop at walls. Designs: mesh, wooden boards, steel bars, mesh on a stone base, cobble wall, brick wall with pillars.", props: [ #{ key: "w", type: "float", def: 20.0, min: 1.0, max: 500.0, step: 0.5, label: "Length (m)" }, #{ key: "h", type: "float", def: 1.8, min: 0.5, max: 4.0, step: 0.1, label: "Height (m)" }, #{ key: "style", type: "choice", def: "mesh", choices: fence_styles(), label: "Design" }, ] } } 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; 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 let ph = fence_ht(st); let gap = if st == "brick" { 3.0 } else { 2.5 }; let k = fmax(1.0, (p.w / gap).round()).to_int(); let posts = []; 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)); } } if st == "brick" { brick_fence(x0, x1, posts, p.h); return; } // 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; for i in 0..n { let a = x0 + i * L; let b = a + L; let ps = []; 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)); } } fence_piece(sprite(), a, b, 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) { let L = b - a; if st == "wood" { s.box(a, -0.025, b, 0.025, 0.0, h, "wood"); let boards = []; let m = fmax(1.0, (L / 0.2).round()).to_int(); for j in 0..m { boards.push([j * L / m, (j + 1) * L / m, 0.04, h - 0.02]); } s.open([a, -0.025], [b, -0.025], boards, "seam"); s.open([b, 0.025], [a, 0.025], boards, "seam"); for x in ps { s.box(x - 0.05, -0.05, x + 0.05, 0.05, 0.0, h + 0.06, "woodd"); } } else if st == "bars" { let bars = []; let m = fmax(2.0, (L / 0.16).round()).to_int(); for j in 1..m { let u = j * L / m; bars.push([u - 0.014, u + 0.014, 0.0, h - 0.05]); } s.open([a, 0.0], [b, 0.0], bars, "steel"); s.open([b, 0.0], [a, 0.0], bars, "steel"); s.box(a, -0.025, b, 0.025, 0.1, 0.15, "steel"); s.box(a, -0.025, b, 0.025, h - 0.06, h, "steel"); 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"); } } 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"); } else { // mesh s.box(a, -0.03, b, 0.03, 0.0, h, "fence"); for x in ps { s.box(x - 0.04, -0.04, x + 0.04, 0.04, 0.0, h + 0.05, "steel"); } } } /// A brick wall between pillars: pillars and wall stretches are separate /// sprites (the pillars are thicker), with a coping on top and brick courses. fn brick_fence(x0, x1, posts, h) { let hw = fmax(0.3, h - 0.06); let u = x0; for x in posts + [x1 + 1.0] { let b = fmin(x - 0.18, x1); if b > u + 0.02 { let n = ((b - u) / 30.0).ceiling().to_int(); let L = (b - u) / n; for k in 0..n { let s = sprite(); let a = u + k * L; s.box(a, -0.12, a + L, 0.12, 0.0, hw, "brick"); s.box(a, -0.14, a + L, 0.14, hw, h, "plinth"); let j = brick_joints(L, 0.0, hw, a); s.open([a, -0.12], [a + L, -0.12], j, "seam"); s.open([a + L, 0.12], [a, 0.12], j, "seam"); } } if x <= x1 { let s = sprite(); s.box(x - 0.17, -0.17, x + 0.17, 0.17, 0.0, h + 0.1, "brick"); s.box(x - 0.18, -0.18, x + 0.18, 0.18, h + 0.1, h + 0.17, "plinth"); u = x + 0.18; } } } /// Joints of a stone face L long from z0 to z1: rows of stones of varying length. fn stone_joints(L, z0, z1, seed) { let out = []; let rows = fmax(1.0, ((z1 - z0) / 0.22).round()).to_int(); let rh = (z1 - z0) / rows; for r in 0..rows { let k = seed * 31 + r * 17 + 7; let u = 0.0; while u < L - 0.05 { k = (k * 1103 + 12345) % 9973; let u1 = fmin(L, u + 0.28 + (k % 100) / 100.0 * 0.34); if L - u1 < 0.12 { u1 = L; } out.push([u, u1, z0 + r * rh, z0 + (r + 1) * rh]); u = u1; } } out } /// Brick courses on a face L long from z0 to z1, in running bond; `at` /// shifts the bond so neighbouring stretches line up. fn brick_joints(L, z0, z1, at) { let out = []; let rows = fmax(1.0, ((z1 - z0) / 0.25).round()).to_int(); let rh = (z1 - z0) / rows; for r in 0..rows { let off = if r % 2 == 0 { 0.0 } else { 0.25 }; let u = 0.0; let first = 0.5 - ((at + off) % 0.5 + 0.5) % 0.5; let u1 = if first < 0.05 { first + 0.5 } else { first }; while u < L - 0.02 { let e = fmin(L, u1); out.push([u, e, z0 + r * rh, z0 + (r + 1) * rh]); u = e; u1 = e + 0.5; } } out } fn meta_lamp() { #{ label: "Street lamp", category: "Site", icon: "†", help: "Keep lamps on verges and kerbs, not on drive areas.", props: [ #{ key: "h", type: "float", def: 5.0, min: 2.0, max: 12.0, step: 0.5, label: "Height (m)" } ] } } fn prefab_lamp(o) { let s = sprite(); s.box(-0.07, -0.07, 0.07, 0.07, 0.0, o.p.h, "lampost", #{ no_top: true }); s.circle(0.0, 0.0, o.p.h + 0.1, 1.2, "lamp"); } fn meta_marker() { #{ label: "Marker", category: "Site", icon: "⚑", help: "A pin with a label: set the label in Tag and the tooltip in Info.", props: [ #{ key: "h", type: "float", def: 1.5, min: 0.3, max: 20.0, step: 0.1, label: "Height (m)" } ] } } fn prefab_marker(o) { let s = sprite(); s.box(-0.05, -0.05, 0.05, 0.05, 0.0, o.p.h, "steel", #{ no_top: true }); s.circle(0.0, 0.0, o.p.h, 2.0, "led ledo"); }