/** * sotc-clash-arrow | ClashArrowLayer.js * * En Foundry v11, les CanvasLayer custom ne se montent pas via CONFIG.Canvas.layers. * Solution : attacher deux PIXI.Graphics directement sur canvas.interface * (le groupe PIXI qui contient tokens, drawings, etc.). * * API publique (identique à l'ancienne classe) : * ClashArrowLayer.init() – à appeler au canvasReady * ClashArrowLayer.tearDown() – à appeler au canvasTearDown * ClashArrowLayer.redraw() – redessine toutes les flèches du store * ClashArrowLayer.drawPreview() – flèche temporaire pendant le drag * ClashArrowLayer.clearPreview() */ import { ArrowStore } from "./ArrowStore.js"; // ── Constantes visuelles ────────────────────────────────────────────────────── const COLOR_BLUE = 0x4a9eff; const COLOR_YELLOW = 0xffd84a; const COLOR_PREVIEW = 0x888899; const COLOR_USED = 0x8B4513; // marron "Used" const ALPHA_USED = 0.35; // légèrement visible const LINE_WIDTH = 3; const LINE_ALPHA = 0.92; const ARROW_HEAD_SIZE = 14; const BEZIER_CURVE = 0.35; // ── Helpers géométriques ────────────────────────────────────────────────────── function bezierControlPoints(x1, y1, x2, y2, curvature = BEZIER_CURVE) { const mx = (x1 + x2) / 2; const my = (y1 + y2) / 2; const dx = x2 - x1; const dy = y2 - y1; const nx = -dy * curvature; const ny = dx * curvature; return { cp1x: mx + nx, cp1y: my + ny }; } function bezierPoint(t, p0, p1, p2, p3) { const mt = 1 - t; return mt*mt*mt*p0 + 3*mt*mt*t*p1 + 3*mt*t*t*p2 + t*t*t*p3; } function drawArrowHead(gfx, tx, ty, dx, dy, size, color, alpha) { const angle = Math.atan2(dy, dx); const spread = Math.PI / 6; const lx = tx - size * Math.cos(angle - spread); const ly = ty - size * Math.sin(angle - spread); const rx = tx - size * Math.cos(angle + spread); const ry = ty - size * Math.sin(angle + spread); gfx.lineStyle(0); gfx.beginFill(color, alpha); gfx.drawPolygon([tx, ty, lx, ly, rx, ry]); gfx.endFill(); } // ── Module singleton ────────────────────────────────────────────────────────── /** @type {PIXI.Graphics|null} */ let _arrowsGfx = null; /** @type {PIXI.Graphics|null} */ let _previewGfx = null; function _drawArrow(gfx, x1, y1, x2, y2, color, alpha = LINE_ALPHA) { const { cp1x, cp1y } = bezierControlPoints(x1, y1, x2, y2); gfx.lineStyle(LINE_WIDTH, color, alpha); gfx.moveTo(x1, y1); gfx.bezierCurveTo(cp1x, cp1y, cp1x, cp1y, x2, y2); const t = 0.98; const px = bezierPoint(t, x1, cp1x, cp1x, x2); const py = bezierPoint(t, y1, cp1y, cp1y, y2); drawArrowHead(gfx, x2, y2, x2 - px, y2 - py, ARROW_HEAD_SIZE, color, alpha); } export const ClashArrowLayer = { /** * Initialise les Graphics PIXI et les attache au canvas. * Appelé au canvasReady depuis Hooks.js. */ init() { // Si déjà initialisé et Graphics toujours valides, juste redessiner if (_arrowsGfx && !_arrowsGfx.destroyed) { this.redraw(); return; } // Nettoyer si les Graphics sont corrompus this.tearDown(); const parent = canvas.interface ?? canvas.stage; _arrowsGfx = parent.addChild(new PIXI.Graphics()); _previewGfx = parent.addChild(new PIXI.Graphics()); _arrowsGfx.zIndex = 900; _previewGfx.zIndex = 901; ArrowStore.on("change", () => this.redraw()); // Dessiner les flèches déjà présentes dans le store this.redraw(); console.log("sotc-clash-arrow | ClashArrowLayer initialisé sur canvas.interface."); }, /** Nettoie et retire les Graphics du canvas. */ tearDown() { ArrowStore.off("change"); if (_arrowsGfx) { _arrowsGfx.parent?.removeChild(_arrowsGfx); _arrowsGfx.destroy(); _arrowsGfx = null; } if (_previewGfx) { _previewGfx.parent?.removeChild(_previewGfx); _previewGfx.destroy(); _previewGfx = null; } }, /** Redessine toutes les flèches persistantes. */ redraw() { if (!_arrowsGfx) return; _arrowsGfx.clear(); for (const arrow of ArrowStore.all()) { if (arrow.used) { _drawArrow(_arrowsGfx, arrow.x1, arrow.y1, arrow.x2, arrow.y2, COLOR_USED, ALPHA_USED); } else { const color = arrow.mutual ? COLOR_YELLOW : COLOR_BLUE; _drawArrow(_arrowsGfx, arrow.x1, arrow.y1, arrow.x2, arrow.y2, color); } } }, /** * Flèche de preview pendant le drag. * @param {number} x1 @param {number} y1 Origine (coords monde) * @param {number} x2 @param {number} y2 Position courante (coords monde) * @param {number} color */ drawPreview(x1, y1, x2, y2, color = COLOR_PREVIEW) { if (!_previewGfx) return; _previewGfx.clear(); _drawArrow(_previewGfx, x1, y1, x2, y2, color, 0.55); }, /** Efface la flèche de preview. */ clearPreview() { _previewGfx?.clear(); }, };