// isokit/building: buildings with storeys, windows, roofs and cut-open // levels, plus carports and sheds. // // Levels: "gf", "1f", "2f", ... cut a building open at that storey (lower // storeys closed, the storey's floor shown, walls facing the viewer cut low); // any other level (e.g. "roof", "site") shows it closed. A level above the // building's top storey shows it ghosted. fn wall_materials() { ["render", "render2", "wall", "wall2", "office", "yardb", "plinth", "wallp", "concb", "firewall"] } fn roof_materials() { ["roof", "roof2", "roof3", "rooft", "roofcap", "canopy"] } fn floor_materials() { ["z-floor", "z-living", "z-offices", "z-kitchen", "z-shop", "z-lab", "z-wh", "z-plant", "z-garage", "z-hall", "z-sport"] } fn meta_building() { #{ label: "Building", category: "Buildings", icon: "🏢", help: "Storeys with windows, a door on the front (local -y) face, a flat or gable roof and optional solar. The GF / 1F levels cut it open.", props: [ #{ key: "w", type: "float", def: 20.0, min: 2.0, max: 300.0, step: 0.5, label: "Width (m)" }, #{ key: "d", type: "float", def: 12.0, min: 2.0, max: 300.0, step: 0.5, label: "Depth (m)" }, #{ key: "floors", type: "int", def: 2, min: 1, max: 20, step: 1, label: "Storeys" }, #{ key: "floor_h", type: "float", def: 3.4, min: 2.4, max: 12.0, step: 0.1, label: "Storey height (m)" }, #{ key: "plinth", type: "float", def: 0.4, min: 0.0, max: 1.5, step: 0.05, label: "Plinth (m)" }, #{ key: "mat", type: "material", def: "render", choices: wall_materials(), label: "Walls" }, #{ key: "roof", type: "choice", def: "flat", choices: ["flat", "gable"], label: "Roof" }, #{ key: "roof_mat", type: "material", def: "roof2", choices: roof_materials(), label: "Roof colour" }, #{ key: "rise", type: "float", def: 2.2, min: 0.5, max: 8.0, step: 0.1, label: "Gable rise (m)" }, #{ key: "windows", type: "bool", def: true, label: "Windows" }, #{ key: "lit", type: "bool", def: true, label: "Lit windows" }, #{ key: "door", type: "bool", def: true, label: "Front door" }, #{ key: "pv", type: "bool", def: false, label: "Solar panels" }, #{ key: "ac", type: "bool", def: false, label: "Roof AC units" }, #{ key: "floor_mat", type: "material", def: "z-floor", choices: floor_materials(), label: "Cut-open floor" }, ] } } fn prefab_building(o) { let p = o.p; let w = p.w / 2.0; let d = p.d / 2.0; let n = p.floors; let H = p.floor_h; let z0 = p.plinth; let k = floor_of(o.level); if k < 0 || k >= n { let s = if k >= n { sprite(o.id, "dim") } else { sprite() }; s.box(-w, -d, w, d, 0.0, z0, "plinth"); s.box(-w, -d, w, d, z0, z0 + n * H, p.mat, #{ top: p.roof_mat }); building_windows(s, p, 0, n); if p.roof == "gable" { gable_roof(s, p, z0 + n * H); } else { flat_roof_kit(s, p, z0 + n * H); } return; } // cut open at storey k: lower storeys closed, the floor of storey k, cut walls let zf = z0 + k * H; if k == 0 { layer("floor"); } let f = sprite(); f.box(-w, -d, w, d, 0.0, z0, "plinth"); if k > 0 { f.box(-w, -d, w, d, z0, zf, p.mat, #{ top: p.floor_mat }); building_windows(f, p, 0, k); } f.flat(rect(-w, -d, w, d), zf + 0.005, p.floor_mat + " t"); layer("main"); // walls start just above the floor so the depth sort draws them over it cut_walls(-w, -d, w, d, zf + 0.02, 0.9, fmin(H - 0.4, 2.6), 0.25, p.mat, []); } /// Windows (and the front door) on all four faces for storeys from..to. fn building_windows(s, p, from, to) { let w = p.w / 2.0; let d = p.d / 2.0; let sd = sides(-w, -d, w, d); for f in ["S", "E", "N", "W"] { let e = sd[f]; let L = if f == "S" || f == "N" { p.w } else { p.d }; let dark = []; let lit = []; if p.windows { for k in from..to { let z = p.plinth + k * p.floor_h; let gap = if f == "S" && k == 0 && p.door { L / 2.0 } else { -1.0 }; let i = 0; for q in win_list(L, z, 0.9, fmin(1.5, p.floor_h - 1.3), 1.6, 3.4, gap) { if p.lit && hash(i * 7.3 + k * 3.1 + L + f.len()) > 0.55 { lit.push(q); } else { dark.push(q); } i += 1; } } } if dark.len() > 0 { s.open(e[0], e[1], dark, "win"); } if lit.len() > 0 { s.open(e[0], e[1], lit, "win lit"); } if f == "S" && p.door && from == 0 { s.open(e[0], e[1], [[L / 2.0 - 0.9, L / 2.0 + 0.9, p.plinth, p.plinth + 2.3]], "door"); } } } /// Gable roof with the ridge along local x, eaves on the -y and +y sides; /// solar modules on the -y slope. fn gable_roof(s, p, ze) { let a = p.w / 2.0; let w = a + 0.4; let d = p.d / 2.0; let e = 0.6; let r = p.rise; let k = r / d; let za = ze - e * k; let zr = ze + r; // gable ends first, then the two roof slabs over them s.face([[a, -d, ze], [a, d, ze], [a, 0.0, zr - 0.05]], p.mat); s.face([[-a, d, ze], [-a, -d, ze], [-a, 0.0, zr - 0.05]], p.mat); s.prism(rect(-w, -d - e, w, 0.0), [za - 0.2, za - 0.2, zr - 0.2, zr - 0.2], [za, za, zr, zr], p.roof_mat, #{ side: "fascia" }); s.prism(rect(-w, 0.0, w, d + e), [zr - 0.2, zr - 0.2, za - 0.2, za - 0.2], [zr, zr, za, za], p.roof_mat, #{ side: "fascia" }); if p.pv { let cw = 1.13; let rh = 1.7; let cols = ((p.w - 1.0) / cw).floor().to_int(); let rows = ((d - 0.4) / rh).floor().to_int(); let u0 = -cols * cw / 2.0; let mods = []; for i in 0..rows { for j in 0..cols { let va = -(0.3 + i * rh + 0.03); let vb = -(0.3 + (i + 1) * rh - 0.03); let ua = u0 + j * cw + 0.03; let ub = u0 + (j + 1) * cw - 0.03; let zA = zr + va * k + 0.08; let zB = zr + vb * k + 0.08; mods.push([[ua, va, zA], [ub, va, zA], [ub, vb, zB], [ua, vb, zB]]); } } if mods.len() > 0 { s.poly(mods, "pv"); } } } /// Parapet, solar rows and AC units on a flat roof at height zt, drawn back /// to front. fn flat_roof_kit(s, p, zt) { let w = p.w / 2.0; let d = p.d / 2.0; let t = 0.2; let ph = 0.5; let mat = p.mat; let items = [ [0.0, d, |q| q.box(-w, d - t, w, d, zt, zt + ph, mat, #{ top: "cut" })], [-w, 0.0, |q| q.box(-w, -d + t, -w + t, d - t, zt, zt + ph, mat, #{ top: "cut" })], [w, 0.0, |q| q.box(w - t, -d + t, w, d - t, zt, zt + ph, mat, #{ top: "cut" })], [0.0, -d, |q| q.box(-w, -d, w, -d + t, zt, zt + ph, mat, #{ top: "cut" })], ]; if p.pv && p.w > 4.0 && p.d > 4.0 { let y = -d + 1.2; while y + 1.0 < d - 1.2 { let y0 = y; let x0 = -w + 1.0; let x1 = w - 1.0; items.push([0.0, y0 + 0.5, |q| q.poly([[[x0, y0, zt + 0.35], [x1, y0, zt + 0.35], [x1, y0 + 1.0, zt + 0.85], [x0, y0 + 1.0, zt + 0.85]]], "pv")]); y += 2.2; } } if p.ac && p.w > 6.0 && p.d > 4.0 { for i in 0..2 { let x = w - 2.2 - i * 2.0; let y = d - 1.4; items.push([x, y, |q| { q.box(x - 0.7, y - 0.5, x + 0.7, y + 0.5, zt, zt + 0.9, "ac"); q.fan(x, y, zt + 0.9, 0.38); }]); } } s.seq(items); } fn meta_carport() { #{ label: "Carport", category: "Buildings", icon: "⌂", help: "A canopy on posts with cars parked under it.", props: [ #{ key: "w", type: "float", def: 6.0, min: 2.5, max: 60.0, step: 0.5, label: "Width (m)" }, #{ key: "d", type: "float", def: 5.5, min: 2.5, max: 12.0, step: 0.5, label: "Depth (m)" }, #{ key: "h", type: "float", def: 2.6, min: 2.2, max: 5.0, step: 0.1, label: "Height (m)" }, #{ key: "cars", type: "int", def: 2, min: 0, max: 20, step: 1, label: "Cars" }, #{ key: "pv", type: "bool", def: false, label: "Solar on the canopy" }, ] } } fn prefab_carport(o) { let p = o.p; let w = p.w / 2.0; let d = p.d / 2.0; let h = p.h; let s = sprite(); // cars and posts back to front inside the canopy's sprite, then the roof over all of them let items = []; for c in [[-w + 0.1, -d + 0.1], [w - 0.1, -d + 0.1], [w - 0.1, d - 0.1], [-w + 0.1, d - 0.1]] { let x = c[0]; let y = c[1]; items.push([x, y, |q| q.box(x - 0.08, y - 0.08, x + 0.08, y + 0.08, 0.0, h, "steel")]); } let cols = ["car1", "car2", "car3", "car4"]; let n = p.cars; for i in 0..n { let x = -w + (i + 0.5) * p.w / n; let col = cols[(i + (hash(o.x + o.y) * 4.0).floor().to_int()) % 4]; items.push([x, 0.0, |q| q.car(x - 0.9, -2.2, x + 0.9, 2.2, col)]); } s.seq(items); s.box(-w, -d, w, d, h, h + 0.15, "canopy"); if p.pv { s.poly([[[-w + 0.2, -d + 0.2, h + 0.2], [w - 0.2, -d + 0.2, h + 0.2], [w - 0.2, d - 0.2, h + 0.2], [-w + 0.2, d - 0.2, h + 0.2]]], "pv"); } } fn meta_shed() { #{ label: "Shed / kiosk", category: "Buildings", icon: "▣", props: [ #{ key: "w", type: "float", def: 4.0, min: 1.0, max: 30.0, step: 0.5, label: "Width (m)" }, #{ key: "d", type: "float", def: 3.0, min: 1.0, max: 30.0, step: 0.5, label: "Depth (m)" }, #{ key: "h", type: "float", def: 2.8, min: 1.8, max: 8.0, step: 0.1, label: "Height (m)" }, #{ key: "mat", type: "material", def: "render2", choices: wall_materials(), label: "Walls" }, #{ key: "roof_mat", type: "material", def: "roof3", choices: roof_materials(), label: "Roof" }, ] } } fn prefab_shed(o) { let p = o.p; let w = p.w / 2.0; let d = p.d / 2.0; let s = sprite(); s.box(-w, -d, w, d, 0.0, 0.15, "plinth"); s.box(-w, -d, w, d, 0.15, p.h, p.mat, #{ top: p.roof_mat }); let sd = sides(-w, -d, w, d); s.open(sd.S[0], sd.S[1], [[p.w / 2.0 - 0.5, p.w / 2.0 + 0.5, 0.15, 2.2]], "door2"); }