225 lines
9.9 KiB
Plaintext
225 lines
9.9 KiB
Plaintext
// 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");
|
|
}
|