Runtime assets
Models, textures and animations prepared for use in a scene or application.
549,584 bytes (0.55 MB)
Download runtime assetsCream masonry house with a charcoal gabled roof, olive shutters and a timber porch.
Shapes & Seasons Farm · Buildings

Drag to orbit after opening. The model starts stationary.
Revision r_01f97f5e943e4c2693b6db8596f3e353
Metres · +X forward · +Y up · +Z right
The poster is this revision’s sealed GLB rendered under the Kiln review lighting rig (review-neutral-v1) on the neutral backdrop, with nothing retouched. The 3D view applies the same rig (review-neutral-v1): its light directions and strengths, exposure, tone mapping and backdrop.
For your engine. The download is a standard glTF 2.0 binary (GLB) with PBR metallic-roughness materials. glTF stores materials, not lighting or tone mapping, so your engine decides how they read. This 3D view tone-maps with Review Neutral, the Khronos PBR Neutral construction with a smaller glare offset (0.015 instead of 0.04), at exposure 0.9. Its Tone mapping control also shows ACES and Linear, for comparison.
Six views of this revision, rendered on the GPU from its source.

This is the source extracted from the sealed editable archive for the displayed revision.
// Farmhouse — cream masonry, charcoal roof, front dormer, gable chimney, olive shutters,
// honey-wood covered porch and steps. Metres; +X front, +Y up, +Z right; body centred on origin.
const meta = { name: 'Farmhouse', role: 'building' };
const P = {
halfX: 3.0, halfZ: 4.0, wallT: 0.3, floorY: 0.4, eaveY: 3.6, pitchDeg: 40,
roofT: 0.18, eaveOver: 0.35, gableOver: 0.35,
doorHalfW: 0.5, doorTop: 2.55, winY0: 1.15, winY1: 2.45, shutterW: 0.55,
porchX: 5.0, porchHalfZ: 3.45, postX: 4.8, postZ: [-3.3, -0.95, 0.95, 3.3],
porchLedgerY: 3.3, porchSlope: 0.2, porchRoofT: 0.12, porchRoofX: 5.25,
beamTop: 2.80, beamD: 0.18, stepHalfZ: 0.8,
};
const TH = P.pitchDeg * Math.PI / 180;
const K = Math.tan(TH);
const RIDGE_Y = P.eaveY + P.halfX * K; // roof underside at ridge
// Stripe-based wood maps run grain lines along texture V; set false if review shows cross grain.
const WOOD_GRAIN_ALONG_V = true;
const MAT = {
masonry: { schemaVersion: 2, model: 'pbrMetallicRoughness', name: 'Farm cream masonry (restrained blocks)', baseColor: 16777215, roughness: 1, metalness: 0, emissiveIntensity: 1, alphaMode: 'opaque', alphaCutoff: 0.5, doubleSided: false, textures: { baseColor: { kind: 'resource', resourceId: 'kiln.library.d1c42bd60d214b06715046b8d7bfdcd7b6591c427e02cf8b3eecf7a7f61772b6.base-color' }, normal: { kind: 'resource', resourceId: 'kiln.library.d1c42bd60d214b06715046b8d7bfdcd7b6591c427e02cf8b3eecf7a7f61772b6.normal' }, metallicRoughness: { kind: 'resource', resourceId: 'kiln.library.d1c42bd60d214b06715046b8d7bfdcd7b6591c427e02cf8b3eecf7a7f61772b6.metallic-roughness' } } },
roof: { schemaVersion: 2, model: 'pbrMetallicRoughness', name: 'Farm charcoal roof shingles', baseColor: 16777215, roughness: 1, metalness: 0, emissiveIntensity: 1, alphaMode: 'opaque', alphaCutoff: 0.5, doubleSided: false, textures: { baseColor: { kind: 'resource', resourceId: 'kiln.library.ff552c543e787c67a870286f2eb1fab3a342891a4f7b41675a07943fad550f85.base-color' }, normal: { kind: 'resource', resourceId: 'kiln.library.ff552c543e787c67a870286f2eb1fab3a342891a4f7b41675a07943fad550f85.normal' }, metallicRoughness: { kind: 'resource', resourceId: 'kiln.library.ff552c543e787c67a870286f2eb1fab3a342891a4f7b41675a07943fad550f85.metallic-roughness' } } },
wood: { schemaVersion: 2, model: 'pbrMetallicRoughness', name: 'Farm honey wood (subdued grain derivative)', baseColor: 16777215, roughness: 1, metalness: 0, emissiveIntensity: 1, alphaMode: 'opaque', alphaCutoff: 0.5, doubleSided: false, textures: { baseColor: { kind: 'resource', resourceId: 'kiln.library.710f9ab0183deb86923b3c398286f9cbcb0eaea7ec50657a72fc83c2bd3c38cd.base-color' }, normal: { kind: 'resource', resourceId: 'kiln.library.710f9ab0183deb86923b3c398286f9cbcb0eaea7ec50657a72fc83c2bd3c38cd.normal' }, metallicRoughness: { kind: 'resource', resourceId: 'kiln.library.710f9ab0183deb86923b3c398286f9cbcb0eaea7ec50657a72fc83c2bd3c38cd.metallic-roughness' } } },
metal: { schemaVersion: 2, model: 'pbrMetallicRoughness', name: 'Brushed neutral metal', roughness: 1, metalness: 1, emissiveIntensity: 1, alphaMode: 'opaque', alphaCutoff: 0.5, doubleSided: false, textures: { baseColor: { kind: 'resource', resourceId: 'kiln.library.67c9159b792a918da293bffc9468ed8834957dd51d83ce290af980a72a9065d0.base-color' }, normal: { kind: 'resource', resourceId: 'kiln.library.67c9159b792a918da293bffc9468ed8834957dd51d83ce290af980a72a9065d0.normal' }, metallicRoughness: { kind: 'resource', resourceId: 'kiln.library.67c9159b792a918da293bffc9468ed8834957dd51d83ce290af980a72a9065d0.metallic-roughness' } } },
};
const plain = (name, baseColor, roughness, metalness = 0) =>
({ schemaVersion: 2, model: 'pbrMetallicRoughness', name, baseColor, roughness, metalness });
// ---------- small vector helpers ----------
const add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
const mul = (a, s) => [a[0] * s, a[1] * s, a[2] * s];
const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
const len = (a) => Math.hypot(a[0], a[1], a[2]);
const norm = (a) => mul(a, 1 / (len(a) || 1));
function newell(vs) {
const n = [0, 0, 0];
for (let i = 0; i < vs.length; i++) {
const a = vs[i], b = vs[(i + 1) % vs.length];
n[0] += (a[1] - b[1]) * (a[2] + b[2]);
n[1] += (a[2] - b[2]) * (a[0] + b[0]);
n[2] += (a[0] - b[0]) * (a[1] + b[1]);
}
return norm(n);
}
// ---------- per-material mesh builder with metre-scaled UVs ----------
// tile = physical texture repeat in metres; grain = member direction mapped to U (or V for wood).
function MB(tile, grainAlongV = false) {
const bld = { tile, gv: grainAlongV, pos: [], nor: [], uv: [], idx: [] };
bld.face = (vs, inside, grain) => {
let n = newell(vs);
if (dot(n, sub(vs[0], inside)) < 0) { vs = vs.slice().reverse(); n = mul(n, -1); }
let g = sub(grain, mul(n, dot(grain, n)));
if (len(g) < 0.3) g = Math.abs(n[1]) < 0.9 ? cross([0, 1, 0], n) : [1, 0, 0];
g = norm(g);
let u, v;
if (bld.gv) { v = g; u = cross(v, n); }
else { u = g; v = cross(n, u); if (Math.abs(n[1]) < 0.9 && v[1] < 0) { u = mul(u, -1); v = mul(v, -1); } }
const base = bld.pos.length / 3;
for (const p of vs) { bld.pos.push(...p); bld.nor.push(...n); bld.uv.push(dot(p, u) / bld.tile, dot(p, v) / bld.tile); }
for (let i = 1; i < vs.length - 1; i++) bld.idx.push(base, base + i, base + i + 1);
};
bld.obox = (c, ex, ey, ez, hx, hy, hz, grain) => {
const C = (i, j, k) => add(add(add(c, mul(ex, i ? hx : -hx)), mul(ey, j ? hy : -hy)), mul(ez, k ? hz : -hz));
const g = grain || (hx >= hy && hx >= hz ? ex : hy >= hz ? ey : ez);
const F = [
[[1, 0, 0], [1, 1, 0], [1, 1, 1], [1, 0, 1]], [[0, 0, 0], [0, 1, 0], [0, 1, 1], [0, 0, 1]],
[[0, 1, 0], [1, 1, 0], [1, 1, 1], [0, 1, 1]], [[0, 0, 0], [1, 0, 0], [1, 0, 1], [0, 0, 1]],
[[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]], [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]],
];
for (const f of F) bld.face(f.map(([i, j, k]) => C(i, j, k)), c, g);
};
bld.aabb = (x0, x1, y0, y1, z0, z1, grain) => {
bld.obox([(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2], [1, 0, 0], [0, 1, 0], [0, 0, 1],
(x1 - x0) / 2, (y1 - y0) / 2, (z1 - z0) / 2, grain);
};
// convex polygon pts [a,b] mapped by map(a,b,d) and extruded d0..d1
bld.prism = (pts, map, d0, d1, grain) => {
const c0 = pts.map(([a, b]) => map(a, b, d0)), c1 = pts.map(([a, b]) => map(a, b, d1));
const all = c0.concat(c1);
const inside = mul(all.reduce((s, p) => add(s, p), [0, 0, 0]), 1 / all.length);
bld.face(c0, inside, grain); bld.face(c1, inside, grain);
for (let i = 0; i < pts.length; i++) {
const j = (i + 1) % pts.length;
bld.face([c0[i], c0[j], c1[j], c1[i]], inside, grain);
}
};
bld.strut = (a, b, w, h, side, ext = 0.03) => {
const d = norm(sub(b, a));
const ey = norm(cross(d, side)), ez = cross(d, ey);
bld.obox(mul(add(a, b), 0.5), d, ey, ez, len(sub(b, a)) / 2 + ext, h / 2, w / 2, d);
};
bld.geo = () => meshGeo({ positions: bld.pos, normals: bld.nor, uvs: bld.uv, indices: bld.idx });
return bld;
}
// Solid wall split into blocks around real openings. axis 'x': wall faces ±X and runs along Z.
function wall(mb, axis, t0, t1, s0, s1, y0, y1, holes) {
const cuts = [...new Set([s0, s1, ...holes.flatMap((h) => [h.s0, h.s1])])].sort((a, b) => a - b);
const emit = (a, b, ya, yb) => axis === 'x'
? mb.aabb(t0, t1, ya, yb, a, b, [0, 0, 1]) : mb.aabb(a, b, ya, yb, t0, t1, [1, 0, 0]);
for (let i = 0; i < cuts.length - 1; i++) {
const a = cuts[i], b = cuts[i + 1];
if (b - a < 1e-6) continue;
const hs = holes.filter((h) => h.s0 <= a + 1e-6 && h.s1 >= b - 1e-6).sort((p, q) => p.y0 - q.y0);
let y = y0;
for (const h of hs) { if (h.y0 > y + 1e-6) emit(a, b, y, h.y0); y = Math.max(y, h.y1); }
if (y1 > y + 1e-6) emit(a, b, y, y1);
}
}
async function build() {
const root = createRoot('Farmhouse');
const M = {
masonry: await compilePortableMaterialSpecV2({ ...MAT.masonry, baseColor: 0xeadcbf }),
stone: await compilePortableMaterialSpecV2({ ...MAT.masonry, name: 'Farm stone (masonry maps, stone tint)', baseColor: 0xaea796 }),
roof: await compilePortableMaterialSpecV2({ ...MAT.roof, baseColor: 0x8c8c8c }),
wood: await compilePortableMaterialSpecV2({ ...MAT.wood, baseColor: 0xdcae78 }),
metal: await compilePortableMaterialSpecV2({ ...MAT.metal, name: 'Hinge iron (brushed metal, dark tint)', baseColor: 0x8a8c88 }),
trim: await compilePortableMaterialSpecV2(plain('Painted cream trim', 0xe6dcc6, 0.8)),
olive: await compilePortableMaterialSpecV2(plain('Olive shutter paint', 0x55622e, 0.72)),
red: await compilePortableMaterialSpecV2(plain('Painted red door', 0x9f3f2b, 0.68)),
// Clear, lightly tinted glazing: portable core-glTF alpha BLEND (no transmission extension).
// Single-sided on closed pane boxes so each view crosses exactly one glazed face.
glass: await materialRecipe('kiln.material.glass.v1', { baseColor: '#a9cdd4', roughness: 0.05, opacity: 0.14, doubleSided: false }),
dark: await compilePortableMaterialSpecV2(plain('Flue soot', 0x1c1a18, 1)),
};
const B = {
masonry: MB(2), stone: MB(3), trim: MB(1), glass: MB(1), dark: MB(1),
roof: MB(1), porchWood: MB(1, WOOD_GRAIN_ALONG_V), porchStone: MB(3), porchRoof: MB(1),
steps: MB(1, WOOD_GRAIN_ALONG_V), olive: MB(1), hinge: MB(1),
doorRed: MB(1), doorMetal: MB(1), ceiling: MB(1),
floor: MB(1, WOOD_GRAIN_ALONG_V),
};
const { halfX: HX, halfZ: HZ, wallT: WT, floorY: FY, eaveY: E } = P;
const ix = HX - WT, iz = HZ - WT;
// ---------- window unit: frame, glass, sill, lintel, optional shutters with strap hinges ----------
function windowUnit(o) {
const P3 = (s, y, d) => (o.axis === 'x' ? [o.face - o.out * d, y, s] : [s, y, o.face - o.out * d]);
const box = (mb, s0, s1, y0, y1, d0, d1, grain) => {
const a = P3(s0, y0, d0), b = P3(s1, y1, d1);
mb.aabb(Math.min(a[0], b[0]), Math.max(a[0], b[0]), Math.min(a[1], b[1]), Math.max(a[1], b[1]),
Math.min(a[2], b[2]), Math.max(a[2], b[2]), grain);
};
const { s0, s1, y0, y1 } = o, f = 0.06;
box(B.glass, s0 + f, s1 - f, y0 + f, y1 - f, 0.15, 0.17);
box(B.trim, s0, s1, y0, y0 + f, 0.08, 0.2); box(B.trim, s0, s1, y1 - f, y1, 0.08, 0.2);
box(B.trim, s0, s0 + f, y0 + f, y1 - f, 0.08, 0.2); box(B.trim, s1 - f, s1, y0 + f, y1 - f, 0.08, 0.2);
const sm = (s0 + s1) / 2, ym = y0 + (y1 - y0) * 0.58;
box(B.trim, sm - 0.02, sm + 0.02, y0 + f, y1 - f, 0.12, 0.15);
box(B.trim, s0 + f, s1 - f, ym - 0.02, ym + 0.02, 0.12, 0.15);
box(B.stone, s0 - 0.07, s1 + 0.07, y0 - 0.06, y0 + 0.02, -0.07, 0.08);
box(B.stone, s0 - 0.12, s1 + 0.12, y1 + 0.001, y1 + 0.16, -0.03, 0.1);
if (!o.shutters) return;
const w = o.shutterW || P.shutterW;
for (const side of [-1, 1]) {
const inner = side < 0 ? s0 - 0.04 : s1 + 0.04, outer = inner + side * w;
const a = Math.min(inner, outer), b = Math.max(inner, outer);
box(B.olive, a, b, y0, y1, -0.045, 0);
for (const yb of [y0 + 0.16, y1 - 0.16]) {
box(B.olive, a + 0.03, b - 0.03, yb - 0.045, yb + 0.045, -0.065, -0.045);
const strapEnd = inner + side * 0.32;
box(B.hinge, Math.min(inner - side * 0.03, strapEnd), Math.max(inner - side * 0.03, strapEnd), yb - 0.02, yb + 0.02, -0.075, -0.064);
box(B.hinge, Math.min(inner, inner - side * 0.035), Math.max(inner, inner - side * 0.035), yb - 0.03, yb + 0.03, -0.078, 0);
}
}
}
// ---------- Shell: foundation, walls with real openings, gables, dormer walls, chimney ----------
// Perimeter stone foundation under walls; exterior plinth and seating preserved exactly
B.stone.aabb(ix, HX + 0.08, 0, FY, -HZ - 0.08, HZ + 0.08);
B.stone.aabb(-HX - 0.08, -ix, 0, FY, -HZ - 0.08, HZ + 0.08);
B.stone.aabb(-ix, ix, 0, FY, -HZ - 0.08, -iz);
B.stone.aabb(-ix, ix, 0, FY, iz, HZ + 0.08);
// Main room wooden plank floor spanning interior between walls
B.floor.aabb(-ix, ix, 0, FY - 0.02, -iz, iz, [0, 0, 1]);
const numBoards = 18, boardW = (2 * ix) / numBoards;
for (let i = 0; i < numBoards; i++) {
const xa = i === 0 ? -ix : -ix + i * boardW + 0.002;
const xb = i === numBoards - 1 ? ix : -ix + (i + 1) * boardW - 0.002;
B.floor.aabb(xa, xb, FY - 0.02, FY, -iz, iz, [0, 0, 1]);
}
const frontWins = [-2.0, 2.0].map((c) => ({ s0: c - 0.55, s1: c + 0.55, y0: P.winY0, y1: P.winY1 }));
const door = { s0: -P.doorHalfW, s1: P.doorHalfW, y0: FY, y1: P.doorTop };
wall(B.masonry, 'x', HX - WT, HX, -HZ + WT, HZ - WT, FY, E, [door, ...frontWins]);
wall(B.masonry, 'x', -HX, -HX + WT, -HZ + WT, HZ - WT, FY, E, frontWins);
for (const w of frontWins) {
windowUnit({ axis: 'x', out: 1, face: HX, ...w, shutters: true });
windowUnit({ axis: 'x', out: -1, face: -HX, ...w, shutters: true });
}
const sideWin = { '1': { s0: -0.55, s1: 0.55 }, '-1': { s0: 0.35, s1: 1.45 } };
const gableWin = { s0: -0.4, s1: 0.4, y0: 4.1, y1: 4.9 };
for (const sz of [1, -1]) {
const t0 = sz > 0 ? HZ - WT : -HZ, t1 = sz > 0 ? HZ : -HZ + WT;
const sw = { ...sideWin[String(sz)], y0: P.winY0, y1: P.winY1 };
wall(B.masonry, 'z', t0, t1, -HX, HX, FY, E, [sw]);
windowUnit({ axis: 'z', out: sz, face: sz * HZ, ...sw, shutters: true });
// gable above the eave, meeting the roof underside, with a real gable window
const map = (a, b, d) => [a, b, d];
const yAt = (x) => E + (HX - Math.abs(x)) * K;
B.masonry.prism([[-HX, E], [-0.4, E], [-0.4, yAt(-0.4)]], map, t0, t1, [1, 0, 0]);
B.masonry.prism([[0.4, E], [HX, E], [0.4, yAt(0.4)]], map, t0, t1, [1, 0, 0]);
B.masonry.aabb(-0.4, 0.4, E, gableWin.y0, t0, t1, [1, 0, 0]);
B.masonry.prism([[-0.4, gableWin.y1], [0.4, gableWin.y1], [0.4, yAt(0.4)], [0, RIDGE_Y], [-0.4, yAt(-0.4)]], map, t0, t1, [1, 0, 0]);
windowUnit({ axis: 'z', out: sz, face: sz * HZ, ...gableWin, shutters: false });
}
// Front dormer walls (roof part in Roof assembly)
const D = { x0: 2.15, x1: 2.3, hz: 0.9, y0: 4.0, eave: 5.25, win: { s0: -0.4, s1: 0.4, y0: 4.55, y1: 5.05 } };
const dormerPeak = D.eave + D.hz * K;
wall(B.masonry, 'x', D.x0, D.x1, -D.hz, D.hz, D.y0, D.eave, [D.win]);
B.masonry.prism([[-D.hz, D.eave], [D.hz, D.eave], [0, dormerPeak]], (a, b, d) => [d, b, a], D.x0, D.x1, [0, 0, 1]);
B.masonry.aabb(0.8, D.x0, D.y0, D.eave, D.hz - 0.15, D.hz, [1, 0, 0]);
B.masonry.aabb(0.8, D.x0, D.y0, D.eave, -D.hz, -D.hz + 0.15, [1, 0, 0]);
windowUnit({ axis: 'x', out: 1, face: D.x1, ...D.win, shutters: true, shutterW: 0.38 });
// Finished interior ceiling: one continuous solid slab seated 0.05 m into all four walls
// (hides roof/dormer construction), with a plain cornice strip closing each wall join.
const CEIL = { y: 3.2, t: 0.1, cornice: 0.07 };
const cy = CEIL.y, cc = CEIL.cornice;
B.ceiling.aabb(-ix - 0.05, ix + 0.05, cy, cy + CEIL.t, -iz - 0.05, iz + 0.05, [0, 0, 1]);
B.ceiling.aabb(ix - cc, ix, cy - cc, cy, -iz, iz, [0, 0, 1]);
B.ceiling.aabb(-ix, -ix + cc, cy - cc, cy, -iz, iz, [0, 0, 1]);
B.ceiling.aabb(-ix + cc, ix - cc, cy - cc, cy, iz - cc, iz, [1, 0, 0]);
B.ceiling.aabb(-ix + cc, ix - cc, cy - cc, cy, -iz, -iz + cc, [1, 0, 0]);
// Door surround
B.trim.aabb(HX, HX + 0.04, FY, P.doorTop, -0.64, -0.5); B.trim.aabb(HX, HX + 0.04, FY, P.doorTop, 0.5, 0.64);
B.trim.aabb(HX, HX + 0.05, P.doorTop, P.doorTop + 0.16, -0.66, 0.66);
// Gable-end stone chimney on -Z with a real hollow flue crown
const ch = { x0: -2.1, x1: -1.1, z0: -4.65, z1: -HZ, sx0: -1.95, sx1: -1.25, sz0: -4.55, top: 6.85 };
B.stone.aabb(ch.x0, ch.x1, 0, 3.4, ch.z0, ch.z1);
B.stone.prism([[ch.x0, 3.4], [ch.x1, 3.4], [ch.sx1, 3.8], [ch.sx0, 3.8]], (a, b, d) => [a, b, d], ch.z0, ch.z1, [1, 0, 0]);
B.stone.aabb(ch.sx0, ch.sx1, 3.8, ch.top, ch.sz0, ch.z1);
const fx = -1.6, fz = (ch.sz0 + ch.z1) / 2, fh = 0.15, cy0 = ch.top, cy1 = ch.top + 0.12;
B.stone.aabb(ch.sx0 - 0.05, ch.sx1 + 0.05, cy0, cy1, ch.sz0 - 0.05, fz - fh);
B.stone.aabb(ch.sx0 - 0.05, ch.sx1 + 0.05, cy0, cy1, fz + fh, ch.z1 + 0.05);
B.stone.aabb(ch.sx0 - 0.05, fx - fh, cy0, cy1, fz - fh, fz + fh);
B.stone.aabb(fx + fh, ch.sx1 + 0.05, cy0, cy1, fz - fh, fz + fh);
B.dark.aabb(fx - fh, fx + fh, cy0, cy0 + 0.02, fz - fh, fz + fh);
// ---------- Roof: two slabs meeting under a ridge cap, dormer gable roof ----------
const c = Math.cos(TH), s = Math.sin(TH), T = P.roofT;
for (const sx of [1, -1]) {
const eu = [sx * c, -s, 0], en = [sx * s, c, 0];
const top = [0, RIDGE_Y, 0], low = [sx * (HX + P.eaveOver), E - P.eaveOver * K, 0];
const mid = add(mul(add(top, low), 0.5), mul(en, T / 2));
B.roof.obox(mid, eu, en, [0, 0, 1], len(sub(low, top)) / 2, T / 2, HZ + P.gableOver, [0, 0, 1]);
}
const r45 = Math.SQRT1_2;
B.roof.obox([0, RIDGE_Y + 0.1, 0], [r45, r45, 0], [-r45, r45, 0], [0, 0, 1], 0.15, 0.15, HZ + P.gableOver + 0.05, [0, 0, 1]);
const dx0 = 0.1, dx1 = D.x1 + 0.15, dOver = 0.15;
for (const sz of [1, -1]) {
const eu = [0, -s, sz * c], en = [0, c, sz * s];
const top = [0, dormerPeak, 0], low = [0, D.eave - dOver * K, sz * (D.hz + dOver)];
const mid = add(mul(add(top, low), 0.5), mul(en, T / 2));
B.roof.obox(add(mid, [(dx0 + dx1) / 2, 0, 0]), eu, en, [1, 0, 0], len(sub(low, top)) / 2, T / 2, (dx1 - dx0) / 2, [1, 0, 0]);
}
B.roof.obox([(dx0 + dx1) / 2, dormerPeak + 0.09, 0], [1, 0, 0], [0, r45, r45], [0, -r45, r45], (dx1 - dx0) / 2 + 0.03, 0.12, 0.12, [1, 0, 0]);
// ---------- Porch: stone plinth, deck, posts, beam, knee braces, rafters, ledger, railings, shed roof ----------
const W = B.porchWood, PZ = P.porchHalfZ, PX = P.postX;
B.porchStone.aabb(HX + 0.08, P.porchX, 0, 0.3, -PZ + 0.05, PZ - 0.05);
W.aabb(HX + 0.08, P.porchX + 0.05, 0.3, FY, -PZ, PZ, [0, 0, 1]);
const beamBot = P.beamTop - P.beamD;
for (const z of P.postZ) W.aabb(PX - 0.08, PX + 0.08, FY, beamBot, z - 0.08, z + 0.08, [0, 1, 0]);
W.aabb(PX - 0.08, PX + 0.08, beamBot, P.beamTop, -PZ, PZ, [0, 0, 1]);
const braceDir = [1, -1, 1, -1];
P.postZ.forEach((z, i) => {
const dz = braceDir[i];
W.strut([PX, beamBot - 0.44, z + dz * 0.04], [PX, beamBot + 0.05, z + dz * 0.49], 0.08, 0.08, [1, 0, 0]);
});
const ps = P.porchSlope, pc = 1 / Math.hypot(1, ps);
const peu = [pc, -ps * pc, 0], pen = [ps * pc, pc, 0];
const underY = (x) => P.porchLedgerY - (x - HX) * ps - P.porchRoofT / pc;
W.aabb(HX, HX + 0.12, 3.0, underY(HX + 0.12), -PZ, PZ, [0, 0, 1]);
for (const z of [-3.3, -1.65, 0, 1.65, 3.3]) {
const a = [HX, underY(HX), z], b = [P.porchRoofX - 0.05, underY(P.porchRoofX - 0.05), z];
W.obox(add(mul(add(a, b), 0.5), mul(pen, -0.06)), peu, pen, [0, 0, 1], len(sub(b, a)) / 2, 0.06, 0.04, peu);
}
{
const a = [HX, P.porchLedgerY, 0], b = [P.porchRoofX, P.porchLedgerY - (P.porchRoofX - HX) * ps, 0];
B.porchRoof.obox(add(mul(add(a, b), 0.5), mul(pen, -P.porchRoofT / 2)), peu, pen, [0, 0, 1],
len(sub(b, a)) / 2, P.porchRoofT / 2, PZ + 0.1, [0, 0, 1]);
}
// Porch soffit: boarded ceiling under the rafter line, split around the beam (posts, beam
// and braces stay exposed), bay closures at both ends and a fascia over the rafter tails.
const rafterD = 0.12, soffitT = 0.025, rafterEnd = P.porchRoofX - 0.05;
const rafterBot = (x) => add([x, underY(x), 0], mul(pen, -rafterD));
const soffit = (xa, xb) => {
const a = rafterBot(xa), b = rafterBot(xb);
W.obox(add(mul(add(a, b), 0.5), mul(pen, -soffitT / 2)), peu, pen, [0, 0, 1], len(sub(b, a)) / 2, soffitT / 2, PZ, [0, 0, 1]);
};
soffit(HX, PX - 0.08); soffit(PX + 0.08, rafterEnd);
for (const zc of [-PZ + 0.02, PZ - 0.02]) {
const a = [HX, underY(HX), zc], b = [rafterEnd, underY(rafterEnd), zc];
W.obox(add(mul(add(a, b), 0.5), mul(pen, -(rafterD + soffitT) / 2)), peu, pen, [0, 0, 1], len(sub(b, a)) / 2, (rafterD + soffitT) / 2, 0.02, peu);
}
W.aabb(rafterEnd, rafterEnd + 0.03, rafterBot(rafterEnd)[1] - soffitT, underY(rafterEnd + 0.03), -PZ, PZ, [0, 0, 1]);
function railing(axis, fixed, a0, a1) {
const R = (lo, hi, y0, y1, w) => axis === 'z'
? W.aabb(fixed - w, fixed + w, y0, y1, lo, hi) : W.aabb(lo, hi, y0, y1, fixed - w, fixed + w);
R(a0, a1, 1.25, 1.33, 0.045); R(a0, a1, 0.5, 0.56, 0.035);
const n = Math.max(1, Math.round((a1 - a0) / 0.22));
for (let i = 1; i < n; i++) { const t = a0 + (a1 - a0) * i / n; R(t - 0.022, t + 0.022, 0.56, 1.25, 0.022); }
}
railing('z', PX, -3.3 + 0.06, -0.95 - 0.06); railing('z', PX, 0.95 + 0.06, 3.3 - 0.06);
railing('x', -3.3, HX, PX - 0.06); railing('x', 3.3, HX, PX - 0.06);
// ---------- Steps: three risers from ground to deck ----------
const rise = FY / 3;
B.steps.aabb(P.porchX, P.porchX + 0.65, 0, rise, -P.stepHalfZ, P.stepHalfZ, [0, 0, 1]);
B.steps.aabb(P.porchX, P.porchX + 0.35, rise, 2 * rise, -P.stepHalfZ, P.stepHalfZ, [0, 0, 1]);
B.porchStone.aabb(P.porchX + 0.05, P.porchX + 0.65, 0, rise - 0.001, -P.stepHalfZ - 0.1, -P.stepHalfZ);
B.porchStone.aabb(P.porchX + 0.05, P.porchX + 0.65, 0, rise - 0.001, P.stepHalfZ, P.stepHalfZ + 0.1);
// ---------- Door leaf on a hinge pivot (static, closed; pivot ready for a later clip) ----------
const L = { x0: -0.025, x1: 0.025, h: P.doorTop - FY - 0.02, w: 2 * P.doorHalfW - 0.02 };
B.doorRed.aabb(L.x0, L.x1, 0.01, 0.01 + L.h, 0, L.w, [0, 1, 0]);
for (const [za, zb] of [[0.1, 0.44], [0.54, 0.88]]) for (const [ya, yb] of [[0.22, 1.0], [1.18, 1.98]])
B.doorRed.aabb(L.x1, L.x1 + 0.018, ya, yb, za, zb, [0, 1, 0]);
for (const yb of [0.12, 2.02]) {
B.doorMetal.aabb(L.x1, L.x1 + 0.012, yb - 0.025, yb + 0.025, -0.01, 0.46);
B.doorMetal.aabb(-0.03, 0.03, yb - 0.08, yb + 0.08, -0.012, 0.012);
}
B.doorMetal.aabb(L.x1, L.x1 + 0.012, 0.95, 1.2, 0.84, 0.92);
B.doorMetal.aabb(L.x1 + 0.012, L.x1 + 0.07, 1.06, 1.1, 0.86, 0.9);
// ---------- assemble named reusable groups ----------
const group = (name) => { const g = new THREE.Group(); g.name = name; root.add(g); return g; };
const shell = group('Shell'), roof = group('Roof'), porch = group('Porch'), steps = group('Steps'),
shutters = group('Shutters'), doorG = group('Door');
createPart('ShellMasonry', B.masonry.geo(), M.masonry, { parent: shell });
createPart('ShellStone', B.stone.geo(), M.stone, { parent: shell });
createPart('ShellTrim', B.trim.geo(), M.trim, { parent: shell });
createPart('ShellGlass', B.glass.geo(), M.glass, { parent: shell });
createPart('ChimneyFlue', B.dark.geo(), M.dark, { parent: shell });
createPart('InteriorCeiling', B.ceiling.geo(), M.trim, { parent: shell });
createPart('InteriorFloor', B.floor.geo(), M.wood, { parent: shell });
createPart('RoofShingles', B.roof.geo(), M.roof, { parent: roof });
createPart('PorchTimber', B.porchWood.geo(), M.wood, { parent: porch });
createPart('PorchPlinth', B.porchStone.geo(), M.stone, { parent: porch });
createPart('PorchRoof', B.porchRoof.geo(), M.roof, { parent: porch });
createPart('StepTreads', B.steps.geo(), M.wood, { parent: steps });
createPart('ShutterPanels', B.olive.geo(), M.olive, { parent: shutters });
createPart('ShutterHinges', B.hinge.geo(), M.metal, { parent: shutters });
const hinge = createPivot('FrontDoor', [HX - 0.2, FY, -P.doorHalfW + 0.01], doorG);
createPart('DoorLeaf', B.doorRed.geo(), M.red, { parent: hinge });
createPart('DoorHardware', B.doorMetal.geo(), M.metal, { parent: hinge });
return root;
}Scroll code horizontally
Source SHA-256: 74fda08c6343b2a257c0da185f1486a0ca23392d6574fc8f55eeb37c74fd9fde
Originally authored by Claude Opus 5.5. Refinements in this delivery: Claude Opus 5.5, Gemini 3.8 Flash High.
Claude Opus 5.5 · Claude Code 2.1.280
Requested effort: high. Independently confirmed: not recorded.
r_8420afabf8fb4d58997ea50b7d13c4c9
Claude Opus 5.5 · Claude Code 2.1.280
Requested effort: high. Independently confirmed: not recorded.
r_1cf8330fa49e41c1b19e691ee80d0aad
Parent: r_8420afabf8fb4d58997ea50b7d13c4c9
Claude Opus 5.5 · Claude Code 2.1.280
Requested effort: high. Independently confirmed: not recorded.
r_37f835d35a1040fc9f64d6f3b271eac5
Parent: r_1cf8330fa49e41c1b19e691ee80d0aad
Gemini 3.8 Flash High · Antigravity CLI (agy) 1.2.12
Requested effort: high. Independently confirmed: not recorded.
r_01f97f5e943e4c2693b6db8596f3e353
Parent: r_37f835d35a1040fc9f64d6f3b271eac5
Displayed revision’s parent: r_37f835d35a1040fc9f64d6f3b271eac5
Models, textures and animations prepared for use in a scene or application.
549,584 bytes (0.55 MB)
Download runtime assetsModels plus Kiln source, editing metadata, materials and included revisions, so you can reopen and continue editing.
3,615,481 bytes (3.62 MB)
Download editable assetsCC0-1.0 covers authored asset content only, to the extent of the owner’s rights. Kiln, the scene code, Three.js, BVH, Field Grass and other components retain their own licenses.