Files
isokit/crates/isokit-script/stdlib/furniture.rhai
T
grabowski 2810477f1b
Deploy web Studio / deploy (push) Successful in 1m4s
Data centres: multi-storey shell, racks and pods, cooling, fire safety, power
datacentre.rhai (palette groups Data centre, Cooling, Fire safety, Power):

- dc_building: multi-storey shell (storeys of storey_h), walls in pieces,
  slabs, roof with parapet, louvres, front door and loading dock; levels
  cut it open storey by storey
- raised_floor; rack_row and dc_pod (cabinets by U, width, depth,
  equipment, fill, doors, LEDs; in-row coolers; cold / hot aisle
  containment with vent tiles, roof, end doors, ceiling duct, airflow);
  dc_cage for colocation
- crah, chiller (air-cooled, dry, adiabatic), cooling_tower, pipe_run
- smoke_detectors (grid + coverage), fire_pipe (VESDA, sprinkler, gas),
  gas_bank (Novec 1230, FM-200, Inergen sized for its halls), fire_device
  (panels, call points, abort switches, beacons, extinguishers)
- power_cab (UPS, battery, PDU, switchgear, RPP), busway, cable_tray,
  transformer
- tooltips sum IT load, kW/m2, cooling redundancy per storey, UPS load,
  battery runtime, plant cooling and agent quantity (warning when short)

Engine and stdlib:

- part_hidden(o) in house.rhai: parts on closed storeys inside a
  dc_building are not drawn (they only made sort cycles under the slab
  above); house parts, furniture and people use it. Existing examples
  render byte-identical.
- overhead services sort in their own "ceiling" layer with tight boxes
- sort records which sprites each broken cycle involved; isokit check
  names them (Rendered.cycle_at)

examples/datacentre (three storeys and a roof plant), Data centre demo in
the web Studio, tests, SCRIPTING/README/ROADMAP, MCP instructions.
2026-10-27 17:08:39 +07:00

477 lines
20 KiB
Plaintext

// isokit/furniture: interior pieces for houses and offices. Every piece
// faces local -y (its front; the back goes against a wall at +y) and stands
// on its object's z, so it can sit on any storey (see isokit/house). Pieces
// on storeys above the one a level cuts open are left out.
fn furniture_hidden(o) { part_hidden(o) }
fn wood_choices() { ["wood", "woodd", "furnw", "steel", "kitchenc"] }
fn fabric_choices() { ["uph1", "uph2", "uph3", "uph4", "sofa", "bed", "cloth"] }
/// A chair at (x, y) with its back towards (bx, by) (one of the axes).
fn chair_at(x, y, bx, by, mat) {
let h = 0.22;
for c in [[-1, -1], [1, -1], [1, 1], [-1, 1]] {
let lx = x + c[0] * 0.19;
let ly = y + c[1] * 0.19;
this.box(lx - 0.02, ly - 0.02, lx + 0.02, ly + 0.02, 0.02, 0.42, mat);
}
this.box(x - h, y - h, x + h, y + h, 0.42, 0.47, mat);
if bx != 0 {
let a = x + bx * 0.18;
let b = x + bx * 0.22;
this.box(fmin(a, b), y - h, fmax(a, b), y + h, 0.47, 0.92, mat);
} else {
let a = y + by * 0.18;
let b = y + by * 0.22;
this.box(x - h, fmin(a, b), x + h, fmax(a, b), 0.47, 0.92, mat);
}
this
}
/// An office chair at (x, y) with its back towards -y.
fn office_chair(x, y, mat) {
this.disc(x, y, 0.03, 0.27, "keyb");
this.stem(x, y, 0.03, 0.45, 0.25, "metal");
this.box(x - 0.24, y - 0.22, x + 0.24, y + 0.24, 0.45, 0.52, mat);
this.box(x - 0.22, y - 0.26, x + 0.22, y - 0.21, 0.52, 1.0, mat);
this
}
fn meta_chair() {
#{ label: "Chair", category: "Furniture", icon: "🪑", storey: true,
help: "A dining chair; its back is on the +y side.",
props: [ #{ key: "mat", type: "material", def: "wood", choices: wood_choices(), label: "Colour" } ] }
}
fn prefab_chair(o) {
if furniture_hidden(o) { return; }
sprite().chair_at(0.0, 0.0, 0, 1, o.p.mat);
}
fn meta_sofa() {
#{ label: "Sofa", category: "Furniture", icon: "🛋", storey: true,
help: "Seats face -y; one seat makes an armchair.",
props: [
#{ key: "seats", type: "int", def: 3, min: 1, max: 5, step: 1, label: "Seats" },
#{ key: "mat", type: "material", def: "uph1", choices: fabric_choices(), label: "Fabric" },
] }
}
fn prefab_sofa(o) {
if furniture_hidden(o) { return; }
let m = o.p.mat;
let n = o.p.seats;
let W = n * 0.7 / 2.0 + 0.18;
let s = sprite();
s.box(-W, -0.45, W, 0.45, 0.02, 0.4, m);
s.seq([
[0.0, 0.35, |q| q.box(-W, 0.25, W, 0.45, 0.4, 0.85, m)],
[0.0, -0.1, |q| {
for i in 0..n {
let x0 = -W + 0.18 + i * 0.7;
q.box(x0 + 0.01, -0.44, x0 + 0.69, 0.25, 0.4, 0.5, m);
}
}],
[-W + 0.09, -0.1, |q| q.box(-W, -0.45, -W + 0.18, 0.25, 0.4, 0.64, m)],
[W - 0.09, -0.1, |q| q.box(W - 0.18, -0.45, W, 0.25, 0.4, 0.64, m)],
]);
}
fn meta_table() {
#{ label: "Table", category: "Furniture", icon: "🍽", storey: true,
help: "A table with chairs pulled out along its long sides (and its ends from six chairs on).",
props: [
#{ key: "w", type: "float", def: 1.6, min: 0.6, max: 6.0, step: 0.05, label: "Length (m)" },
#{ key: "d", type: "float", def: 0.9, min: 0.5, max: 2.0, step: 0.05, label: "Width (m)" },
#{ key: "h", type: "float", def: 0.75, min: 0.4, max: 1.1, step: 0.01, label: "Height (m)" },
#{ key: "chairs", type: "int", def: 4, min: 0, max: 16, step: 1, label: "Chairs" },
#{ key: "mat", type: "material", def: "wood", choices: wood_choices(), label: "Colour" },
#{ key: "chair_mat", type: "material", def: "woodd", choices: wood_choices(), label: "Chairs" },
] }
}
fn prefab_table(o) {
if furniture_hidden(o) { return; }
let p = o.p;
let w = p.w / 2.0;
let d = p.d / 2.0;
let s = sprite();
for c in [[-1, -1], [1, -1], [1, 1], [-1, 1]] {
let x = c[0] * (w - 0.07);
let y = c[1] * (d - 0.07);
s.box(x - 0.03, y - 0.03, x + 0.03, y + 0.03, 0.02, p.h - 0.04, p.mat);
}
s.box(-w, -d, w, d, p.h - 0.04, p.h, p.mat);
// each chair its own sprite, clear of the table so the depth sort can order them
let n = p.chairs;
let ends = if n >= 6 { 2 } else { 0 };
let front = (n - ends + 1) / 2;
let back = n - ends - front;
for i in 0..front { sprite().chair_at(-w + (i + 0.5) * p.w / front, -d - 0.24, 0, -1, p.chair_mat); }
for i in 0..back { sprite().chair_at(-w + (i + 0.5) * p.w / back, d + 0.24, 0, 1, p.chair_mat); }
if ends > 0 {
sprite().chair_at(-w - 0.24, 0.0, -1, 0, p.chair_mat);
sprite().chair_at(w + 0.24, 0.0, 1, 0, p.chair_mat);
}
}
fn meta_desk() {
#{ label: "Desk", category: "Furniture", icon: "🖥", storey: true,
help: "A desk with a monitor and an office chair on its -y side.",
props: [
#{ key: "w", type: "float", def: 1.4, min: 0.8, max: 4.0, step: 0.05, label: "Width (m)" },
#{ key: "d", type: "float", def: 0.7, min: 0.5, max: 1.2, step: 0.05, label: "Depth (m)" },
#{ key: "mat", type: "material", def: "furnw", choices: wood_choices(), label: "Colour" },
#{ key: "monitor", type: "bool", def: true, label: "Monitor" },
#{ key: "chair", type: "bool", def: true, label: "Chair" },
#{ key: "chair_mat", type: "material", def: "uph1", choices: fabric_choices(), label: "Chair colour" },
] }
}
fn prefab_desk(o) {
if furniture_hidden(o) { return; }
let p = o.p;
let w = p.w / 2.0;
let d = p.d / 2.0;
let h = 0.75;
let s = sprite();
s.box(-w, -d, -w + 0.04, d, 0.02, h - 0.03, p.mat);
s.box(w - 0.04, -d, w, d, 0.02, h - 0.03, p.mat);
s.box(-w + 0.04, d - 0.04, w - 0.04, d, 0.3, h - 0.03, p.mat);
s.box(-w, -d, w, d, h - 0.03, h, p.mat);
if p.monitor {
s.flat(rect(-0.22, -d + 0.1, 0.22, -d + 0.25), h + 0.004, "keyb");
s.box(-0.04, d - 0.2, 0.04, d - 0.12, h, h + 0.12, "tvbody");
s.box(-0.3, d - 0.18, 0.3, d - 0.15, h + 0.1, h + 0.45, "tvbody");
s.open([-0.3, d - 0.18], [0.3, d - 0.18], [[0.02, 0.58, h + 0.12, h + 0.43]], "tvs on");
}
if p.chair { sprite().office_chair(0.0, -d - 0.3, p.chair_mat); }
}
fn meta_tv() {
#{ label: "TV", category: "Furniture", icon: "📺", storey: true,
help: "A TV on a low cabinet (or on the wall), screen towards -y.",
props: [
#{ key: "size", type: "choice", def: "55", choices: ["32", "43", "55", "65", "75", "85"], label: "Size (inch)" },
#{ key: "stand", type: "bool", def: true, label: "On a cabinet" },
#{ key: "on", type: "bool", def: true, label: "Switched on" },
#{ key: "mat", type: "material", def: "woodd", choices: wood_choices(), label: "Cabinet" },
] }
}
fn prefab_tv(o) {
if furniture_hidden(o) { return; }
let p = o.p;
let sw = parse_float(p.size) * 0.0254 * 0.8716;
let sh = sw * 9.0 / 16.0;
let s = sprite();
let zb = 1.0;
let y0 = 0.15;
if p.stand {
let cw = fmax(1.6, sw + 0.3) / 2.0;
s.box(-cw, -0.22, cw, 0.22, 0.02, 0.5, p.mat);
s.open([-cw, -0.22], [cw, -0.22], [[0.04, cw - 0.02, 0.08, 0.44], [cw + 0.02, 2.0 * cw - 0.04, 0.08, 0.44]], "seam");
s.box(-0.15, -0.08, 0.15, 0.08, 0.5, 0.53, "tvbody");
s.box(-0.03, 0.0, 0.03, 0.04, 0.53, 0.62, "tvbody");
zb = 0.6;
y0 = 0.0;
}
s.box(-sw / 2.0, y0, sw / 2.0, y0 + 0.05, zb, zb + sh, "tvbody");
s.open([-sw / 2.0, y0], [sw / 2.0, y0], [[0.02, sw - 0.02, zb + 0.02, zb + sh - 0.02]], if p.on { "tvs on" } else { "tvs" });
}
fn meta_bed() {
#{ label: "Bed", category: "Furniture", icon: "🛏", storey: true,
help: "Head (headboard, pillows) at +y.",
props: [
#{ key: "size", type: "choice", def: "double", choices: ["single", "double", "king"], label: "Size" },
#{ key: "mat", type: "material", def: "uph2", choices: fabric_choices(), label: "Duvet" },
#{ key: "frame", type: "material", def: "woodd", choices: wood_choices(), label: "Frame" },
#{ key: "nightstands", type: "bool", def: true, label: "Nightstands" },
] }
}
fn prefab_bed(o) {
if furniture_hidden(o) { return; }
let p = o.p;
let w = (if p.size == "single" { 0.9 } else if p.size == "king" { 1.8 } else { 1.6 }) / 2.0;
let L = 1.0;
let fr = p.frame;
let duvet = p.mat;
let two = w > 0.6;
sprite().seq([
[0.0, L + 0.04, |q| q.box(-w - 0.04, L, w + 0.04, L + 0.08, 0.02, 0.95, fr)],
[0.0, 0.0, |q| {
q.box(-w - 0.04, -L, w + 0.04, L, 0.02, 0.3, fr);
q.box(-w, -L + 0.02, w, L - 0.02, 0.3, 0.5, "cloth");
if two {
q.box(-w + 0.06, L - 0.45, -0.03, L - 0.08, 0.5, 0.62, "cloth");
q.box(0.03, L - 0.45, w - 0.06, L - 0.08, 0.5, 0.62, "cloth");
} else {
q.box(-w + 0.06, L - 0.45, w - 0.06, L - 0.08, 0.5, 0.62, "cloth");
}
q.box(-w - 0.02, -L, w + 0.02, L - 0.55, 0.5, 0.58, duvet);
}],
]);
if p.nightstands {
for side in [-1, 1] {
let x = side * (w + 0.32);
let n = sprite();
n.box(x - 0.22, L - 0.42, x + 0.22, L - 0.02, 0.02, 0.5, fr);
n.cyl(x, L - 0.2, 0.08, 0.5, 0.75, "lshade");
}
}
}
fn meta_cupboard() {
#{ label: "Cupboard", category: "Furniture", icon: "▯", storey: true,
help: "A wardrobe or cabinet, doors on the -y side.",
props: [
#{ key: "w", type: "float", def: 1.2, min: 0.3, max: 6.0, step: 0.05, label: "Width (m)" },
#{ key: "d", type: "float", def: 0.6, min: 0.3, max: 1.0, step: 0.05, label: "Depth (m)" },
#{ key: "h", type: "float", def: 2.0, min: 0.4, max: 3.0, step: 0.05, label: "Height (m)" },
#{ key: "mat", type: "material", def: "wardrobe", choices: ["wardrobe", "wood", "woodd", "furnw", "kitchenc"], label: "Colour" },
] }
}
fn prefab_cupboard(o) {
if furniture_hidden(o) { return; }
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.02, p.h, p.mat);
let n = fmax(1.0, (p.w / 0.5).round()).to_int();
let dw = p.w / n;
let doors = [];
for i in 0..n { doors.push([i * dw + 0.015, (i + 1) * dw - 0.015, 0.06, p.h - 0.04]); }
s.open([-w, -d], [w, -d], doors, "seam");
}
fn meta_shelf() {
#{ label: "Bookshelf", category: "Furniture", icon: "📚", storey: true,
help: "Open shelves facing -y.",
props: [
#{ key: "w", type: "float", def: 1.0, min: 0.3, max: 6.0, step: 0.05, label: "Width (m)" },
#{ key: "d", type: "float", def: 0.35, min: 0.2, max: 0.8, step: 0.05, label: "Depth (m)" },
#{ key: "h", type: "float", def: 1.9, min: 0.4, max: 3.0, step: 0.05, label: "Height (m)" },
#{ key: "books", type: "bool", def: true, label: "Books" },
#{ key: "mat", type: "material", def: "wood", choices: wood_choices(), label: "Colour" },
] }
}
fn prefab_shelf(o) {
if furniture_hidden(o) { return; }
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.02, p.h, p.mat);
let n = fmax(1.0, ((p.h - 0.1) / 0.36).floor()).to_int();
let sh = (p.h - 0.08) / n;
let comp = [];
let bks = [[], [], []];
for i in 0..n {
let z0 = 0.06 + i * sh;
let z1 = z0 + sh - 0.03;
comp.push([0.03, p.w - 0.03, z0, z1]);
if p.books {
let u = 0.05;
let j = 0;
while u < p.w - 0.12 {
let bw = 0.04 + 0.08 * hash(i * 13.1 + j * 3.7 + p.w);
if hash(j * 7.9 + i * 2.3) > 0.88 { u += bw + 0.06; j += 1; continue; }
let bh = (z1 - z0) * (0.62 + 0.3 * hash(j * 5.3 + i));
bks[j % 3].push([u, fmin(u + bw, p.w - 0.05), z0, z0 + bh]);
u += bw + 0.006;
j += 1;
}
}
}
let a = [-w, -d];
let b = [w, -d];
s.open(a, b, comp, "open");
for k in 0..3 {
if bks[k].len() > 0 { s.open(a, b, bks[k], "bk" + (k + 1)); }
}
}
fn meta_kitchen() {
#{ label: "Kitchen", category: "Furniture", icon: "🍳", storey: true,
help: "A kitchen run: base cabinets and worktop with sink and hob, wall cabinets and a fridge at the +x end. Front towards -y.",
props: [
#{ key: "w", type: "float", def: 3.0, min: 0.6, max: 10.0, step: 0.05, label: "Length (m)" },
#{ key: "mat", type: "material", def: "kitchenc", choices: ["kitchenc", "furnw", "wood", "woodd", "doorg"], label: "Cabinets" },
#{ key: "top", type: "material", def: "worktop", choices: ["worktop", "wood", "steel", "furnw"], label: "Worktop" },
#{ key: "sink", type: "bool", def: true, label: "Sink" },
#{ key: "hob", type: "bool", def: true, label: "Hob" },
#{ key: "upper", type: "bool", def: true, label: "Wall cabinets" },
#{ key: "fridge", type: "bool", def: true, label: "Fridge" },
] }
}
fn prefab_kitchen(o) {
if furniture_hidden(o) { return; }
let p = o.p;
let w = p.w / 2.0;
let fr = p.fridge && p.w >= 1.3;
let x1 = if fr { w - 0.64 } else { w };
let L = x1 + w;
let m = p.mat;
let tp = p.top;
let items = [[(x1 - w) / 2.0, 0.0, |q| {
q.box(-w, -0.28, x1, 0.3, 0.02, 0.86, m);
let n = fmax(1.0, (L / 0.6).round()).to_int();
let dw = L / n;
let fronts = [];
let uppers = [];
for i in 0..n {
fronts.push([i * dw + 0.01, (i + 1) * dw - 0.01, 0.1, 0.84]);
uppers.push([i * dw + 0.01, (i + 1) * dw - 0.01, 1.47, 2.13]);
}
q.open([-w, -0.28], [x1, -0.28], fronts, "seam");
q.box(-w, -0.32, x1, 0.3, 0.86, 0.9, tp);
if p.sink && L >= 1.0 {
let sx = -w + fmin(0.5, L * 0.3);
q.flat(rect(sx - 0.24, -0.2, sx + 0.24, 0.14), 0.904, "sink");
q.stem(sx, 0.22, 0.9, 1.18, 0.16, "metal");
}
if p.hob && L >= 1.8 {
let hx = x1 - 0.5;
q.flat(rect(hx - 0.3, -0.24, hx + 0.3, 0.22), 0.904, "hob");
for c in [[-1, -1], [1, -1], [1, 1], [-1, 1]] { q.disc(hx + c[0] * 0.14, c[1] * 0.11, 0.906, 0.08, "hobr"); }
}
if p.upper {
q.box(-w, -0.05, x1, 0.3, 1.45, 2.15, m);
q.open([-w, -0.05], [x1, -0.05], uppers, "seam");
}
}]];
if fr {
items.push([x1 + 0.33, 0.0, |q| {
q.box(x1 + 0.02, -0.32, w, 0.3, 0.02, 1.9, "fridge");
q.open([x1 + 0.02, -0.32], [w, -0.32], [[0.03, w - x1 - 0.05, 0.06, 1.12], [0.03, w - x1 - 0.05, 1.16, 1.86]], "seam");
}]);
}
sprite().seq(items);
}
fn meta_bath() {
#{ label: "Bathtub", category: "Furniture", icon: "🛁", storey: true,
props: [
#{ key: "w", type: "float", def: 1.7, min: 1.0, max: 2.4, step: 0.05, label: "Length (m)" },
#{ key: "d", type: "float", def: 0.75, min: 0.6, max: 1.4, step: 0.05, label: "Width (m)" },
] }
}
fn prefab_bath(o) {
if furniture_hidden(o) { return; }
let w = o.p.w / 2.0;
let d = o.p.d / 2.0;
let s = sprite();
s.box(-w, -d, w, d, 0.02, 0.56, "sanit");
s.flat(rect(-w + 0.08, -d + 0.08, w - 0.08, d - 0.08), 0.562, "poolw t");
s.lines(rsegs([-w + 0.08, -d + 0.08, w - 0.08, d - 0.08]), 0.563, "seam");
s.stem(w - 0.1, 0.0, 0.56, 0.76, 0.15, "metal");
}
fn meta_shower() {
#{ label: "Shower", category: "Furniture", icon: "🚿", storey: true,
help: "A tray with glass on its -y and +x sides; the riser is in the back left corner.",
props: [
#{ key: "w", type: "float", def: 0.9, min: 0.7, max: 2.0, step: 0.05, label: "Width (m)" },
#{ key: "d", type: "float", def: 0.9, min: 0.7, max: 2.0, step: 0.05, label: "Depth (m)" },
] }
}
fn prefab_shower(o) {
if furniture_hidden(o) { return; }
let w = o.p.w / 2.0;
let d = o.p.d / 2.0;
let s = sprite();
s.box(-w, -d, w, d, 0.02, 0.08, "sanit");
s.flat(rect(-w + 0.05, -d + 0.05, w - 0.05, d - 0.05), 0.082, "shower t");
s.disc(0.0, 0.0, 0.083, 0.04, "keyb");
s.stem(-w + 0.06, d - 0.06, 0.08, 2.05, 0.14, "metal");
s.disc(-w + 0.2, d - 0.2, 2.0, 0.1, "metal");
s.box(-w, -d, w, -d + 0.02, 0.08, 2.0, "glassp");
s.box(w - 0.02, -d + 0.02, w, d, 0.08, 2.0, "glassp");
}
fn meta_toilet() {
#{ label: "Toilet", category: "Furniture", icon: "🚽", storey: true, help: "Cistern at +y.", props: [] }
}
fn prefab_toilet(o) {
if furniture_hidden(o) { return; }
sprite().seq([
[0.0, 0.25, |q| q.box(-0.19, 0.17, 0.19, 0.33, 0.02, 0.8, "sanit")],
[0.0, -0.05, |q| {
q.frust(0.0, -0.05, 0.13, 0.19, 0.02, 0.42, "sanit", 12);
q.disc(0.0, -0.05, 0.422, 0.14, "seam");
}],
]);
}
fn meta_basin() {
#{ label: "Washbasin", category: "Furniture", icon: "🚰", storey: true,
help: "A vanity unit with a basin and a mirror on the wall behind (+y).",
props: [
#{ key: "w", type: "float", def: 0.6, min: 0.4, max: 2.4, step: 0.05, label: "Width (m)" },
#{ key: "mirror", type: "bool", def: true, label: "Mirror" },
#{ key: "mat", type: "material", def: "furnw", choices: wood_choices(), label: "Cabinet" },
] }
}
fn prefab_basin(o) {
if furniture_hidden(o) { return; }
let w = o.p.w / 2.0;
let s = sprite();
s.box(-w, -0.22, w, 0.23, 0.02, 0.82, o.p.mat);
s.open([-w, -0.22], [w, -0.22], [[0.02, w - 0.01, 0.1, 0.8], [w + 0.01, 2.0 * w - 0.02, 0.1, 0.8]], "seam");
s.box(-w, -0.24, w, 0.23, 0.82, 0.86, "sanit");
s.flat(rect(-w + 0.08, -0.17, w - 0.08, 0.1), 0.862, "sink");
s.stem(0.0, 0.17, 0.86, 1.02, 0.14, "metal");
if o.p.mirror { s.box(-w + 0.04, 0.21, w - 0.04, 0.23, 1.05, 1.75, "glassp"); }
}
fn meta_plant() {
#{ label: "Plant", category: "Furniture", icon: "🪴", storey: true,
props: [
#{ key: "h", type: "float", def: 1.0, min: 0.3, max: 2.5, step: 0.05, label: "Height (m)" },
#{ key: "pot", type: "material", def: "terra", choices: ["terra", "furnw", "concb", "woodd"], label: "Pot" },
] }
}
fn prefab_plant(o) {
if furniture_hidden(o) { return; }
let h = o.p.h;
let r = 0.1 + h * 0.08;
let zp = 0.02 + r * 1.5;
let cr = 0.15 + h * 0.2;
let s = sprite();
s.frust(0.0, 0.0, r * 0.75, r, 0.02, zp, o.p.pot, 10);
s.ext_box(-cr, -cr, cr, cr, zp, zp + h);
s.stem(0.0, 0.0, zp, zp + h * 0.5, 0.25, "trunk");
let R = cr * px() * 1.18;
s.circle(0.0, 0.0, zp + h * 0.62, R, "leaf l");
s.circle(0.0, 0.0, zp + h * 0.62, R * 0.58, "leaf t", -R * 0.28, -R * 0.3);
}
fn meta_rug() {
#{ label: "Rug", category: "Furniture", icon: "▭", shape: "rect", storey: true,
props: [
#{ key: "w", type: "float", def: 2.0, min: 0.4, max: 10.0, step: 0.05, label: "Width (m)" },
#{ key: "d", type: "float", def: 1.4, min: 0.4, max: 10.0, step: 0.05, label: "Depth (m)" },
#{ key: "mat", type: "material", def: "rug1", choices: ["rug1", "rug2", "rug3", "uph1", "uph3", "cloth"], label: "Colour" },
] }
}
fn prefab_rug(o) {
if furniture_hidden(o) { return; }
let w = o.p.w / 2.0;
let d = o.p.d / 2.0;
if o.z.abs() < 0.25 { layer("floor"); }
let s = sprite();
s.flat(rect(-w, -d, w, d), 0.012, o.p.mat + " t");
let i = fmin(0.1, fmin(w, d) * 0.2);
s.lines(rsegs([-w + i, -d + i, w - i, d - i]), 0.013, "rugl");
}
fn meta_floor_lamp() {
#{ label: "Floor lamp", category: "Furniture", icon: "💡", storey: true,
props: [ #{ key: "on", type: "bool", def: true, label: "Lit" } ] }
}
fn prefab_floor_lamp(o) {
if furniture_hidden(o) { return; }
let s = sprite();
s.disc(0.0, 0.0, 0.025, 0.16, "keyb");
s.stem(0.0, 0.0, 0.02, 1.4, 0.12, "metal");
s.frust(0.0, 0.0, 0.22, 0.14, 1.32, 1.62, "lshade", 10);
if o.p.on { s.circle(0.0, 0.0, 1.5, 1.1, "lamp"); }
}