// src/pages/agent-office/canvas/TileMap.js /** * 타일맵 렌더러 — 바닥, 벽, 그리드를 테마 팔레트로 렌더링 * 가구는 FurnitureRenderer가 별도 처리 */ export class TileMap { constructor(mapData) { this.cols = mapData.cols; this.rows = mapData.rows; this.tileSize = mapData.tileSize; this.floor = mapData.floor; this.tileTypes = mapData.tileTypes; } /** * 바닥 + 벽 렌더링 * @param {CanvasRenderingContext2D} ctx * @param {object} theme - themes.js 에서 가져온 테마 객체 * @param {number} scale - 줌 레벨 * @param {number} offsetX - 패닝 X 오프셋 * @param {number} offsetY - 패닝 Y 오프셋 */ render(ctx, theme, scale, offsetX, offsetY) { const ts = this.tileSize * scale; for (let r = 0; r < this.rows; r++) { for (let c = 0; c < this.cols; c++) { const tileType = this.floor[r][c]; const x = c * ts + offsetX; const y = r * ts + offsetY; // 화면 밖이면 스킵 (CSS 공간 기준 — DPR 변환 적용된 좌표계) if (x + ts < 0 || y + ts < 0 || x > ctx.canvas.clientWidth || y > ctx.canvas.clientHeight) continue; if (tileType === 0) { // 벽 ctx.fillStyle = theme.wall.color; ctx.fillRect(x, y, ts, ts); // 벽 하단 경계선 ctx.fillStyle = theme.wall.border; ctx.fillRect(x, y + ts - scale, ts, scale); } else { // 바닥 const isBreak = this.tileTypes[String(tileType)] === 'floor_break'; ctx.fillStyle = isBreak ? theme.floor.color2 : theme.floor.color1; ctx.fillRect(x, y, ts, ts); // 체커보드 패턴 if ((r + c) % 2 === 0) { ctx.fillStyle = theme.floor.grid; ctx.fillRect(x, y, ts, ts); } // 그리드 선 ctx.strokeStyle = theme.floor.grid; ctx.lineWidth = scale * 0.5; ctx.strokeRect(x, y, ts, ts); } } } } /** 화면 좌표 → 타일 좌표 변환 */ screenToTile(screenX, screenY, scale, offsetX, offsetY) { const ts = this.tileSize * scale; const col = Math.floor((screenX - offsetX) / ts); const row = Math.floor((screenY - offsetY) / ts); return { col, row }; } /** 타일 좌표 → 화면 좌표 (타일 중앙) */ tileToScreen(col, row, scale, offsetX, offsetY) { const ts = this.tileSize * scale; return { x: col * ts + offsetX + ts / 2, y: row * ts + offsetY + ts / 2 }; } }