Files
isokit/crates/isokit-script/stdlib/building.rhai
T

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 (flat roof)" },
#{ key: "floor_mat", type: "material", def: "z-floor", choices: floor_materials(), label: "Floor (cut-open levels)" },
] }
}
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");
}