// src/pages/agent-office/canvas/FurnitureRenderer.js /** * 가구 프로시저럴 렌더러 — 테마 팔레트 기반 * 각 가구 타입별 draw 함수, Y-sort를 위한 zY 반환 */ export class FurnitureRenderer { constructor(furnitureList, tileSize) { this.furnitureList = furnitureList; this.tileSize = tileSize; } /** * 모든 가구를 Y-sort 순서로 반환 (에이전트와 함께 정렬하기 위함) * @returns {Array<{type, col, row, zY, draw: Function}>} */ getRenderables(theme, scale, offsetX, offsetY) { const ts = this.tileSize * scale; return this.furnitureList.map(f => ({ ...f, zY: f.row, draw: (ctx) => this._drawFurniture(ctx, f, theme, ts, offsetX, offsetY) })); } _drawFurniture(ctx, f, theme, ts, ox, oy) { const x = f.col * ts + ox; const y = f.row * ts + oy; switch (f.type) { case 'desk_monitor': this._drawDesk(ctx, f, theme, ts, x, y); break; case 'meeting_table': this._drawMeetingTable(ctx, f, theme, ts, x, y); break; case 'sofa': this._drawSofa(ctx, theme, ts, x, y); break; case 'coffee_machine':this._drawCoffeeMachine(ctx, theme, ts, x, y); break; case 'bookshelf': this._drawBookshelf(ctx, f, theme, ts, x, y); break; case 'plant': this._drawPlant(ctx, theme, ts, x, y); break; case 'water_cooler': this._drawWaterCooler(ctx, theme, ts, x, y); break; } } _drawDesk(ctx, f, theme, ts, x, y) { // 책상 상판 const dw = ts * 2; const dh = ts * 0.6; ctx.fillStyle = theme.furniture.desk; ctx.fillRect(x, y + ts * 0.2, dw, dh); // 책상 다리 ctx.fillStyle = theme.wall.border; ctx.fillRect(x + ts * 0.1, y + dh + ts * 0.2, ts * 0.15, ts * 0.3); ctx.fillRect(x + dw - ts * 0.25, y + dh + ts * 0.2, ts * 0.15, ts * 0.3); // 모니터들 const monCount = f.monitors || 1; const monW = ts * 0.5; const monH = ts * 0.4; const totalW = monCount * monW + (monCount - 1) * ts * 0.1; let monX = x + (dw - totalW) / 2; for (let i = 0; i < monCount; i++) { // 모니터 프레임 ctx.fillStyle = theme.furniture.monitor; ctx.fillRect(monX, y - monH + ts * 0.2, monW, monH); // 화면 ctx.fillStyle = theme.furniture.monitorScreen; ctx.fillRect(monX + ts * 0.05, y - monH + ts * 0.25, monW - ts * 0.1, monH - ts * 0.1); // 모니터 받침대 ctx.fillStyle = theme.furniture.monitor; ctx.fillRect(monX + monW * 0.35, y + ts * 0.2 - ts * 0.05, monW * 0.3, ts * 0.08); monX += monW + ts * 0.1; } // 의자 (책상 아래) ctx.fillStyle = theme.furniture.chair; ctx.fillRect(x + dw * 0.35, y + ts, dw * 0.3, ts * 0.5); ctx.fillRect(x + dw * 0.3, y + ts * 0.8, dw * 0.4, ts * 0.25); // 에이전트별 악센트 소품 if (f.accent === 'instrument') { // 음표 모양 ctx.fillStyle = theme.ui.accent; ctx.fillRect(x + dw + ts * 0.2, y + ts * 0.3, ts * 0.1, ts * 0.5); ctx.beginPath(); ctx.arc(x + dw + ts * 0.2, y + ts * 0.8, ts * 0.15, 0, Math.PI * 2); ctx.fill(); } else if (f.accent === 'papers') { // 서류 더미 ctx.fillStyle = '#ffffff'; ctx.fillRect(x + dw + ts * 0.1, y + ts * 0.3, ts * 0.35, ts * 0.45); ctx.fillStyle = theme.text.label; for (let i = 0; i < 3; i++) { ctx.fillRect(x + dw + ts * 0.15, y + ts * 0.38 + i * ts * 0.1, ts * 0.25, ts * 0.02); } } else if (f.accent === 'briefcase') { ctx.fillStyle = '#8B4513'; ctx.fillRect(x + dw + ts * 0.1, y + ts * 0.5, ts * 0.4, ts * 0.3); ctx.fillStyle = '#D4A06A'; ctx.fillRect(x + dw + ts * 0.2, y + ts * 0.45, ts * 0.2, ts * 0.08); } else if (f.accent === 'dice') { ctx.fillStyle = '#ef4444'; ctx.fillRect(x + dw + ts * 0.15, y + ts * 0.4, ts * 0.3, ts * 0.3); ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.arc(x + dw + ts * 0.3, y + ts * 0.55, ts * 0.05, 0, Math.PI * 2); ctx.fill(); } } _drawMeetingTable(ctx, f, theme, ts, x, y) { const w = (f.width || 4) * ts; const h = (f.height || 2) * ts; // 테이블 상판 ctx.fillStyle = theme.furniture.table; ctx.fillRect(x + ts * 0.1, y + ts * 0.1, w - ts * 0.2, h - ts * 0.2); // 테이블 그림자 ctx.fillStyle = 'rgba(0,0,0,0.15)'; ctx.fillRect(x + ts * 0.15, y + h - ts * 0.1, w - ts * 0.25, ts * 0.1); // 의자들 (상하 4개씩) for (let i = 0; i < 4; i++) { const cx = x + ts * 0.5 + i * (w - ts) / 3; ctx.fillStyle = theme.furniture.chair; ctx.fillRect(cx, y - ts * 0.3, ts * 0.4, ts * 0.35); ctx.fillRect(cx, y + h - ts * 0.05, ts * 0.4, ts * 0.35); } } _drawSofa(ctx, theme, ts, x, y) { ctx.fillStyle = theme.furniture.sofa; ctx.fillRect(x, y, ts * 2, ts * 0.8); // 등받이 ctx.fillStyle = theme.furniture.sofa; ctx.fillRect(x, y - ts * 0.3, ts * 2, ts * 0.35); // 쿠션 구분선 ctx.strokeStyle = theme.wall.border; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x + ts, y); ctx.lineTo(x + ts, y + ts * 0.8); ctx.stroke(); } _drawCoffeeMachine(ctx, theme, ts, x, y) { ctx.fillStyle = theme.furniture.coffee; ctx.fillRect(x + ts * 0.15, y, ts * 0.7, ts * 0.8); // 디스펜서 ctx.fillStyle = theme.furniture.monitor; ctx.fillRect(x + ts * 0.25, y + ts * 0.15, ts * 0.5, ts * 0.3); // 커피 잔 ctx.fillStyle = '#ffffff'; ctx.fillRect(x + ts * 0.3, y + ts * 0.55, ts * 0.2, ts * 0.15); // 스팀 ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x + ts * 0.4, y + ts * 0.5); ctx.quadraticCurveTo(x + ts * 0.45, y + ts * 0.35, x + ts * 0.4, y + ts * 0.2); ctx.stroke(); } _drawBookshelf(ctx, f, theme, ts, x, y) { const h = (f.height || 3) * ts; ctx.fillStyle = theme.furniture.shelf; ctx.fillRect(x, y, ts * 0.9, h); // 선반 및 책 const bookColors = ['#aa4444', '#4444aa', '#44aa44', '#aaaa44', '#aa44aa', '#44aaaa']; const shelfCount = f.height || 3; for (let i = 0; i < shelfCount; i++) { const sy = y + i * ts + ts * 0.1; // 선반 판 ctx.fillStyle = theme.furniture.shelf; ctx.fillRect(x, sy + ts * 0.7, ts * 0.9, ts * 0.05); // 책들 for (let b = 0; b < 4; b++) { ctx.fillStyle = bookColors[(i * 4 + b) % bookColors.length]; ctx.fillRect(x + ts * 0.05 + b * ts * 0.2, sy + ts * 0.1, ts * 0.15, ts * 0.6); } } } _drawPlant(ctx, theme, ts, x, y) { // 화분 ctx.fillStyle = theme.decor.pot; ctx.fillRect(x + ts * 0.25, y + ts * 0.6, ts * 0.5, ts * 0.35); ctx.fillRect(x + ts * 0.2, y + ts * 0.55, ts * 0.6, ts * 0.1); // 잎 ctx.fillStyle = theme.decor.plant; ctx.beginPath(); ctx.ellipse(x + ts * 0.5, y + ts * 0.35, ts * 0.3, ts * 0.25, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(x + ts * 0.35, y + ts * 0.25, ts * 0.15, ts * 0.2, -0.3, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(x + ts * 0.65, y + ts * 0.25, ts * 0.15, ts * 0.2, 0.3, 0, Math.PI * 2); ctx.fill(); } _drawWaterCooler(ctx, theme, ts, x, y) { // 본체 ctx.fillStyle = theme.furniture.shelf; ctx.fillRect(x + ts * 0.2, y + ts * 0.3, ts * 0.6, ts * 0.6); // 물통 ctx.fillStyle = 'rgba(100,180,255,0.5)'; ctx.fillRect(x + ts * 0.3, y, ts * 0.4, ts * 0.35); ctx.fillStyle = 'rgba(100,180,255,0.3)'; ctx.beginPath(); ctx.arc(x + ts * 0.5, y, ts * 0.2, 0, Math.PI * 2); ctx.fill(); } }