window.RM = (() => {
'use strict';
// 全局:画风常量、SVG 小工具、场景注册、确定性随机、纹理
const INK = '#222323', LW = 2.35;
const STILL = location.hash.includes('still');
const NS = 'http://www.w3.org/2000/svg';
function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
// 字符串式 SVG 图元。o: {stroke, w, op, id, cls, extra}
function A(o) {
let s = '';
if (o.id) s += ` id="${o.id}"`;
if (o.cls) s += ` class="${o.cls}"`;
if (o.op != null) s += ` opacity="${o.op}"`;
if (o.tf) s += ` transform="${o.tf}"`;
if (o.extra) s += ' ' + o.extra;
return s;
}
function SK(o) {
const st = o.stroke === undefined ? INK : o.stroke;
if (st === 'none' || st === null) return ' stroke="none"';
return ` stroke="${st}" stroke-width="${o.w ?? LW}" stroke-linejoin="round" stroke-linecap="round"`;
}
const S = {
poly: (pts, fill, o = {}) => ``,
pl: (pts, o = {}) => ``,
path: (d, fill, o = {}) => ``,
rect: (x, y, w, h, fill, o = {}) => ``,
circ: (cx, cy, r, fill, o = {}) => ``,
ell: (cx, cy, rx, ry, fill, o = {}) => ``,
line: (x1, y1, x2, y2, o = {}) => ``,
g: (inner, o = {}) => `${inner}`,
// 无描边填色(色块、阴影)
fpoly: (pts, fill, o = {}) => S.poly(pts, fill, { ...o, stroke: 'none' }),
fpath: (d, fill, o = {}) => S.path(d, fill, { ...o, stroke: 'none' }),
};
// 场景:分层。L0 建筑;L1 后墙物件;L2 按脚深度排序(家具与角色);L3 光效与前景
const SCENE = {
svg: null, defs: null, layers: {}, objs: [], updaters: [], t0: 0,
init() {
this.svg = document.getElementById('scene');
this.svg.innerHTML = '';
this.defs = this.svg.querySelector('#defs');
for (const k of ['L0', 'L1', 'L2', 'L3']) this.layers[k] = this.svg.querySelector('#' + k);
},
def(s) { this.defs.insertAdjacentHTML('beforeend', s); },
add(layer, s) { this.layers[layer].insertAdjacentHTML('beforeend', s); return this.layers[layer].lastElementChild; },
// 深度物体:z = 脚/底座在地面上的 y
addZ(id, z, s) {
const g = document.createElementNS(NS, 'g'); g.id = id; g.dataset.z = z; g.innerHTML = s;
this.layers.L2.appendChild(g); this.objs.push(g); return g;
},
sortZ() {
const arr = [...this.layers.L2.children].sort((a, b) => (+a.dataset.z) - (+b.dataset.z));
let ok = true; for (let i = 0; i < arr.length; i++) if (this.layers.L2.children[i] !== arr[i]) { ok = false; break; }
if (!ok) arr.forEach(e => this.layers.L2.appendChild(e));
},
onUpdate(fn) { this.updaters.push(fn); },
};
// 斑驳纹理:值噪声 → 半透明 PNG,做 。dark/light 两种。
function mottleURL(size, seed, cell, alpha, rgb) {
const c = document.createElement('canvas'); c.width = c.height = size;
const x = c.getContext('2d'); const img = x.createImageData(size, size);
const r = mulberry32(seed);
const octs = [cell, cell / 2, cell / 4].filter(v => v >= 1);
const grids = octs.map(cs => { const n = Math.ceil(size / cs); return { cs, n, v: Array.from({ length: n * n }, r) }; });
const sm = t => t * t * (3 - 2 * t);
for (let j = 0; j < size; j++) for (let i = 0; i < size; i++) {
let v = 0, wsum = 0, wt = 1;
for (const g of grids) {
const gx = i / g.cs, gy = j / g.cs, x0 = Math.floor(gx), y0 = Math.floor(gy);
const fx = sm(gx - x0), fy = sm(gy - y0);
const at = (a, b) => g.v[((b % g.n + g.n) % g.n) * g.n + ((a % g.n + g.n) % g.n)];
const a = at(x0, y0) * (1 - fx) + at(x0 + 1, y0) * fx, b = at(x0, y0 + 1) * (1 - fx) + at(x0 + 1, y0 + 1) * fx;
v += (a * (1 - fy) + b * fy) * wt; wsum += wt; wt *= 0.55;
}
v /= wsum;
const k = (j * size + i) * 4;
img.data[k] = rgb[0]; img.data[k + 1] = rgb[1]; img.data[k + 2] = rgb[2];
img.data[k + 3] = Math.max(0, Math.min(255, (v - 0.45) * 2.2 * alpha * 255));
}
x.putImageData(img, 0, 0); return c.toDataURL();
}
function mottlePattern(id, size, seed, cell, alpha, rgb) {
SCENE.def(``);
}
const f1 = v => (Math.round(v * 10) / 10);
const P = pts => pts.map(p => f1(p[0]) + ' ' + f1(p[1]));
// Catmull-Rom → 三次贝塞尔。pts: [[x,y],...]
function smoothPath(pts, closed = true, k = 0.5) {
const n = pts.length; if (n < 2) return '';
const g = i => closed ? pts[(i + n) % n] : pts[Math.max(0, Math.min(n - 1, i))];
let d = `M${f1(pts[0][0])} ${f1(pts[0][1])}`;
const segs = closed ? n : n - 1;
for (let i = 0; i < segs; i++) {
const p0 = g(i - 1), p1 = g(i), p2 = g(i + 1), p3 = g(i + 2);
const c1 = [p1[0] + (p2[0] - p0[0]) * k / 3, p1[1] + (p2[1] - p0[1]) * k / 3];
const c2 = [p2[0] - (p3[0] - p1[0]) * k / 3, p2[1] - (p3[1] - p1[1]) * k / 3];
d += ` C${f1(c1[0])} ${f1(c1[1])} ${f1(c2[0])} ${f1(c2[1])} ${f1(p2[0])} ${f1(p2[1])}`;
}
return d + (closed ? ' Z' : '');
}
// 分段:sharp 里给出的下标处保持尖角(头发尖刺用)
function mixedPath(pts, sharp = new Set(), k = 0.5) {
const n = pts.length; const g = i => pts[(i + n) % n];
let d = `M${f1(pts[0][0])} ${f1(pts[0][1])}`;
for (let i = 0; i < n; i++) {
const p0 = g(i - 1), p1 = g(i), p2 = g(i + 1), p3 = g(i + 2);
const k1 = sharp.has(i) ? 0 : k, k2 = sharp.has((i + 1) % n) ? 0 : k;
const c1 = [p1[0] + (p2[0] - p0[0]) * k1 / 3, p1[1] + (p2[1] - p0[1]) * k1 / 3];
const c2 = [p2[0] - (p3[0] - p1[0]) * k2 / 3, p2[1] - (p3[1] - p1[1]) * k2 / 3];
d += ` C${f1(c1[0])} ${f1(c1[1])} ${f1(c2[0])} ${f1(c2[1])} ${f1(p2[0])} ${f1(p2[1])}`;
}
return d + ' Z';
}
return { S, INK, SCENE, mulberry32, mottlePattern, smoothPath, mixedPath, f1 };
})();