openfree's picture
์‹ค์ œ ์ธ์ฒด ๋‡Œ ๋ฉ”์‹œ ๋ฐ˜์˜ (์ฝ”๋“œ/๋งค๋‹ˆํŽ˜์ŠคํŠธ)
55db436 verified
Raw History Blame Contribute Delete
1.87 kB
// ๋ถ„ํ•ด ๋ฐฐ์น˜ โ€” ๋ณด์ด๋Š” ์กฐ๊ฐ๋งŒ ๊ฒน์น˜์ง€ ์•Š๊ฒŒ ์„ ๋ฐ˜ ์ฑ„์šฐ๊ธฐ(shelf packing)
// ํก์ˆ˜ ์ถœ์ฒ˜: ashemag/human-atlas (MIT) ์˜ explosion-layout ๊ฐœ๋…. ๊ตฌํ˜„์€ ์šฐ๋ฆฌ ๊ฒƒ.
// ํ•ต์‹ฌ ๊ทœ์œจ: "๊ฒน์น˜์ง€ ์•Š๋Š”๋‹ค"๋ฅผ ๋ˆˆ์ด ์•„๋‹ˆ๋ผ **์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋‹จ์–ธ**ํ•œ๋‹ค.
/**
* @param {{id:string, w:number, h:number}[]} parts ์กฐ๊ฐ์˜ ํˆฌ์˜ ํฌ๊ธฐ
* @param {number} aspect ํ™”๋ฉด ๊ฐ€๋กœ/์„ธ๋กœ ๋น„
* @returns {{cells:Map<string,{x:number,y:number,w:number,h:number}>, width:number, height:number}}
*/
export function explodeLayout(parts, aspect = 1) {
const PAD = 0.10;
const cards = parts.map(p => ({
id: p.id,
w: Math.max(0.05, p.w) + PAD,
h: Math.max(0.05, p.h) + PAD,
}));
const area = cards.reduce((n, c) => n + c.w * c.h, 0);
const maxW = Math.max(0.3, ...cards.map(c => c.w));
// ๋ชฉํ‘œ ํญ: ๋ฉด์ ์„ ํ™”๋ฉด๋น„์— ๋งž์ถฐ ์ •์‚ฌ๊ฐ์— ๊ฐ€๊น๊ฒŒ
const targetW = Math.max(maxW, Math.sqrt(area * Math.max(0.5, Math.min(1.8, aspect))) * 1.15);
// ๋†’์€ ๊ฒƒ๋ถ€ํ„ฐ ์ฑ„์šฐ๋ฉด ์„ ๋ฐ˜ ๋‚ญ๋น„๊ฐ€ ์ค€๋‹ค. id ๋กœ ํƒ€์ด๋ธŒ๋ ˆ์ดํฌํ•ด ๊ฒฐ๊ณผ๋ฅผ ๊ฒฐ์ •์ ์œผ๋กœ.
cards.sort((a, b) => b.h - a.h || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
const cells = new Map();
let x = 0, y = 0, rowH = 0, usedW = 0;
for (const c of cards) {
if (x > 0 && x + c.w > targetW) { x = 0; y += rowH; rowH = 0; }
cells.set(c.id, { x: x + c.w / 2, y: -y - c.h / 2, w: c.w, h: c.h });
x += c.w;
usedW = Math.max(usedW, x);
rowH = Math.max(rowH, c.h);
}
const height = y + rowH;
// ์ค‘์•™ ์ •๋ ฌ
cells.forEach(c => { c.x -= usedW / 2; c.y += height / 2; });
return { cells, width: usedW, height };
}
/** ๋‘ ์นธ์ด ๊ฒน์น˜๋Š”๊ฐ€ (๊ฒฝ๊ณ„ ์ ‘์ด‰์€ ํ—ˆ์šฉ) */
export function overlaps(a, b, eps = 1e-9) {
return Math.abs(a.x - b.x) < (a.w + b.w) / 2 - eps &&
Math.abs(a.y - b.y) < (a.h + b.h) / 2 - eps;
}