// 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) { house_view(o) == 2 } 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"); } }