Buildings (modern/fantasy, multi-floor, footprint shapes), caves, outdoor biomes, and town/village + city-block settlements. Auto-places Foundry walls, doors, windows, lighting; furniture as locked tiles; live preview. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
7.9 KiB
JavaScript
151 lines
7.9 KiB
JavaScript
/**
|
||
* Furniture piece registry + room-type mapping.
|
||
*
|
||
* Each piece declares a footprint (in grid cells) and a draw() that renders the
|
||
* art to FILL a box of (wpx × hpx), centred, on a transparent background. The
|
||
* same registry feeds (a) the live preview vector, (b) the sprite PNGs used for
|
||
* locked Tile placement in the scene. System-agnostic, no external art.
|
||
*/
|
||
|
||
export const PAL = {
|
||
modern: { wood: "#9a8f80", woodLine: "#5d5448", metal: "#9aa3ad", metalLine: "#5b636c", fabric: "#7d8ba0", line: "#3a3f45", soft: "#c4ccd4", seat: "#8b95a3" },
|
||
fantasy: { wood: "#9c7b50", woodLine: "#4f3a20", metal: "#8a8d92", metalLine: "#4a4d52", fabric: "#8a6b52", line: "#3a2614", soft: "#c8b48f", seat: "#7a5532" }
|
||
};
|
||
|
||
const r2 = (n) => +n.toFixed(1);
|
||
|
||
/** Map a room label to furniture kinds (in placement priority order). */
|
||
export function piecesFor(label) {
|
||
const l = label.toLowerCase();
|
||
if (/bed|chamber/.test(l)) return ["bed", "chest"];
|
||
if (/exec|study/.test(l)) return ["desk", "shelf", "chair"];
|
||
if (/office/.test(l)) return ["desk", "chair", "shelf"];
|
||
if (/meeting|common|dining|ballroom/.test(l)) return ["bigtable"];
|
||
if (/kitchen/.test(l)) return ["counter", "table"];
|
||
if (/server/.test(l)) return ["racks", "racks"];
|
||
if (/restroom|privy|bath/.test(l)) return ["toilet", "sink"];
|
||
if (/storage|store/.test(l)) return ["crates", "shelf", "crates"];
|
||
if (/strong|vault|cellar/.test(l)) return ["chest", "chest"];
|
||
if (/reception|lobby|entry|foyer|hall/.test(l)) return ["counter"];
|
||
if (/guard|barracks/.test(l)) return ["bunk", "table"];
|
||
if (/break/.test(l)) return ["table", "counter"];
|
||
return ["table"];
|
||
}
|
||
|
||
/**
|
||
* Piece registry. footprint = [wCells, hCells] in the piece's native (un-rotated)
|
||
* orientation. `wall` true → prefers sitting against a wall. `collide` false →
|
||
* decorative, never blocks (and may overlap).
|
||
*/
|
||
export const PIECES = {
|
||
bed: { footprint: [1, 2], wall: true, draw: drawBed },
|
||
bunk: { footprint: [1, 2], wall: true, draw: drawBed },
|
||
chest: { footprint: [1, 1], wall: true, draw: drawChest },
|
||
desk: { footprint: [2, 1], wall: true, draw: drawDesk },
|
||
chair: { footprint: [1, 1], wall: false, draw: drawChair },
|
||
table: { footprint: [1, 1], wall: false, draw: drawTable },
|
||
bigtable: { footprint: [3, 2], wall: false, draw: drawBigTable },
|
||
counter: { footprint: [2, 1], wall: true, draw: drawCounter },
|
||
racks: { footprint: [1, 1], wall: true, draw: drawRacks },
|
||
toilet: { footprint: [1, 1], wall: true, draw: drawToilet },
|
||
sink: { footprint: [1, 1], wall: true, draw: drawSink },
|
||
crates: { footprint: [1, 1], wall: true, draw: drawCrates },
|
||
shelf: { footprint: [1, 1], wall: true, draw: drawShelf }
|
||
};
|
||
|
||
/** Full standalone transparent SVG for one piece — used to make sprite PNGs. */
|
||
export function pieceSpriteSVG(kind, subtype, wpx, hpx) {
|
||
const def = PIECES[kind];
|
||
const pal = PAL[subtype] ?? PAL.modern;
|
||
const inner = def ? def.draw(wpx, hpx, pal) : "";
|
||
return `<svg xmlns="http://www.w3.org/2000/svg" width="${Math.round(wpx)}" height="${Math.round(hpx)}" viewBox="0 0 ${wpx} ${hpx}">${inner}</svg>`;
|
||
}
|
||
|
||
/** Inner SVG for a piece (no <svg> wrapper) — used by the preview renderer. */
|
||
export function pieceInnerSVG(kind, subtype, wpx, hpx) {
|
||
const def = PIECES[kind];
|
||
const pal = PAL[subtype] ?? PAL.modern;
|
||
return def ? def.draw(wpx, hpx, pal) : "";
|
||
}
|
||
|
||
// --- piece art (drawn to fill a wpx×hpx box, small inset, transparent bg) -----
|
||
function box(w, h, inset) {
|
||
const m = Math.min(w, h) * inset;
|
||
return { x: r2(m), y: r2(m), w: r2(w - 2 * m), h: r2(h - 2 * m), m: r2(m) };
|
||
}
|
||
|
||
function drawBed(w, h, pal) {
|
||
const b = box(w, h, 0.08);
|
||
const pillow = r2(b.h * 0.26);
|
||
return `<g stroke-linejoin="round">` +
|
||
`<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.06)}" fill="${pal.fabric}" stroke="${pal.line}" stroke-width="${r2(w * 0.03)}"/>` +
|
||
`<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${pillow}" rx="${r2(w * 0.05)}" fill="${pal.soft}" stroke="${pal.line}" stroke-width="${r2(w * 0.02)}"/></g>`;
|
||
}
|
||
function drawChest(w, h, pal) {
|
||
const b = box(w, h, 0.18);
|
||
return `<g><rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.04)}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.04)}"/>` +
|
||
`<line x1="${b.x}" y1="${r2(b.y + b.h * 0.4)}" x2="${r2(b.x + b.w)}" y2="${r2(b.y + b.h * 0.4)}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.03)}"/></g>`;
|
||
}
|
||
function drawDesk(w, h, pal) {
|
||
const b = box(w, h, 0.1);
|
||
return `<g><rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(h * 0.06)}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(h * 0.05)}"/>` +
|
||
`<rect x="${r2(b.x + b.w * 0.55)}" y="${r2(b.y + b.h * 0.18)}" width="${r2(b.w * 0.3)}" height="${r2(b.h * 0.5)}" fill="${pal.metal}" stroke="${pal.metalLine}" stroke-width="${r2(h * 0.03)}"/></g>`;
|
||
}
|
||
function drawChair(w, h, pal) {
|
||
const b = box(w, h, 0.26);
|
||
return `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.12)}" fill="${pal.seat}" stroke="${pal.line}" stroke-width="${r2(w * 0.04)}"/>`;
|
||
}
|
||
function drawTable(w, h, pal) {
|
||
const b = box(w, h, 0.16);
|
||
return `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.08)}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.05)}"/>`;
|
||
}
|
||
function drawBigTable(w, h, pal) {
|
||
const b = box(w, h, 0.2);
|
||
let s = `<g>`;
|
||
// chairs around
|
||
const cr = r2(Math.min(w, h) * 0.07);
|
||
for (let i = 0; i < 3; i += 1) {
|
||
const cx = r2(b.x + b.w * (0.2 + i * 0.3));
|
||
s += `<circle cx="${cx}" cy="${r2(b.y - cr * 1.2)}" r="${cr}" fill="${pal.seat}" stroke="${pal.line}" stroke-width="${r2(w * 0.012)}"/>`;
|
||
s += `<circle cx="${cx}" cy="${r2(b.y + b.h + cr * 1.2)}" r="${cr}" fill="${pal.seat}" stroke="${pal.line}" stroke-width="${r2(w * 0.012)}"/>`;
|
||
}
|
||
s += `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(Math.min(w, h) * 0.08)}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(Math.min(w, h) * 0.04)}"/>`;
|
||
return s + `</g>`;
|
||
}
|
||
function drawCounter(w, h, pal) {
|
||
const b = box(w, h, 0.12);
|
||
const top = r2(b.h * 0.5);
|
||
return `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${top}" fill="${pal.metal}" stroke="${pal.metalLine}" stroke-width="${r2(h * 0.05)}"/>`;
|
||
}
|
||
function drawRacks(w, h, pal) {
|
||
let s = `<g>`;
|
||
const b = box(w, h, 0.12);
|
||
for (let i = 0; i < 3; i += 1) {
|
||
const x = r2(b.x + (b.w / 3) * i + b.w * 0.04);
|
||
s += `<rect x="${x}" y="${b.y}" width="${r2(b.w / 3 * 0.7)}" height="${b.h}" fill="${pal.metal}" stroke="${pal.metalLine}" stroke-width="${r2(w * 0.025)}"/>`;
|
||
}
|
||
return s + `</g>`;
|
||
}
|
||
function drawToilet(w, h, pal) {
|
||
const cx = r2(w * 0.5);
|
||
return `<g><ellipse cx="${cx}" cy="${r2(h * 0.6)}" rx="${r2(w * 0.26)}" ry="${r2(h * 0.3)}" fill="${pal.soft}" stroke="${pal.line}" stroke-width="${r2(w * 0.035)}"/>` +
|
||
`<rect x="${r2(cx - w * 0.22)}" y="${r2(h * 0.12)}" width="${r2(w * 0.44)}" height="${r2(h * 0.2)}" rx="${r2(w * 0.04)}" fill="${pal.soft}" stroke="${pal.line}" stroke-width="${r2(w * 0.03)}"/></g>`;
|
||
}
|
||
function drawSink(w, h, pal) {
|
||
const b = box(w, h, 0.24);
|
||
return `<rect x="${b.x}" y="${b.y}" width="${b.w}" height="${b.h}" rx="${r2(w * 0.1)}" fill="${pal.soft}" stroke="${pal.line}" stroke-width="${r2(w * 0.035)}"/>`;
|
||
}
|
||
function drawCrates(w, h, pal) {
|
||
let s = `<g>`;
|
||
const sz = r2(Math.min(w, h) * 0.42);
|
||
for (const [ox, oy] of [[0.06, 0.1], [0.5, 0.16], [0.28, 0.52]]) {
|
||
s += `<rect x="${r2(w * ox)}" y="${r2(h * oy)}" width="${sz}" height="${sz}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.03)}"/>`;
|
||
}
|
||
return s + `</g>`;
|
||
}
|
||
function drawShelf(w, h, pal) {
|
||
const x = r2(w * 0.08), y = r2(h * 0.1), bw = r2(w * 0.84), bh = r2(h * 0.34);
|
||
return `<g><rect x="${x}" y="${y}" width="${bw}" height="${bh}" fill="${pal.wood}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.035)}"/>` +
|
||
`<line x1="${x}" y1="${r2(y + bh / 2)}" x2="${r2(x + bw)}" y2="${r2(y + bh / 2)}" stroke="${pal.woodLine}" stroke-width="${r2(w * 0.02)}"/></g>`;
|
||
}
|