|
|
@@ -228,328 +228,6 @@ include __DIR__ . '/partials/footer.php';
|
|
|
const mapData = <?php echo json_encode($map_data); ?>;
|
|
|
const mapWidth = mapData.width;
|
|
|
const mapHeight = mapData.height;
|
|
|
-
|
|
|
-// Configuration de l'éditeur de carte
|
|
|
-class MapEditor {
|
|
|
- constructor() {
|
|
|
- this.canvas = document.getElementById('mapCanvas');
|
|
|
- this.container = document.querySelector('.map-canvas-container');
|
|
|
- this.zoomLevel = 1;
|
|
|
- this.panOffset = { x: 0, y: 0 };
|
|
|
- this.isDragging = false;
|
|
|
- this.lastMousePos = { x: 0, y: 0 };
|
|
|
- this.selectedTile = null;
|
|
|
- this.tileSize = 40; // Rayon de l'hexagone (du centre au sommet)
|
|
|
-
|
|
|
- this.init();
|
|
|
- }
|
|
|
-
|
|
|
- init() {
|
|
|
- if (!this.canvas) {
|
|
|
- console.error('MapEditor: Élément canvas non trouvé!');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!this.container) {
|
|
|
- console.error('MapEditor: Conteneur non trouvé!');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- this.setupCanvas();
|
|
|
- this.renderMap();
|
|
|
- this.setupEventListeners();
|
|
|
- this.hideLoading();
|
|
|
- this.fitToScreen();
|
|
|
- }
|
|
|
-
|
|
|
- setupCanvas() {
|
|
|
- // La taille du canvas est définie dynamiquement dans renderMap() selon l'enveloppe.
|
|
|
- this.canvas.style.transformOrigin = '0 0';
|
|
|
- }
|
|
|
-
|
|
|
- // Conversion axial -> pixels pour hexagones pointus (pointy-top)
|
|
|
- axialToPixel(q, r) {
|
|
|
- const size = this.tileSize; // rayon (centre -> sommet)
|
|
|
- const x = size * Math.sqrt(3) * (q + r / 2);
|
|
|
- const y = size * 1.5 * r; // 3/2 * size
|
|
|
- return { x, y };
|
|
|
- }
|
|
|
-
|
|
|
- renderMap() {
|
|
|
- this.canvas.innerHTML = '';
|
|
|
-
|
|
|
- // Dimensions d'un hexagone (boîte englobante)
|
|
|
- const hexWidth = this.tileSize * Math.sqrt(3);
|
|
|
- const hexHeight = this.tileSize * 2;
|
|
|
-
|
|
|
- // Rayon de la carte hexagonale (en tuiles)
|
|
|
- // On utilise le plus grand hexagone qui tient dans (mapWidth, mapHeight)
|
|
|
- const radius = Math.floor((Math.min(mapWidth, mapHeight) - 1) / 2);
|
|
|
- if (radius < 0) return;
|
|
|
-
|
|
|
- // Première passe: calculer toutes les positions et les bornes pour dimensionner le canvas
|
|
|
- const positions = [];
|
|
|
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
|
-
|
|
|
- for (let q = -radius; q <= radius; q++) {
|
|
|
- const rMin = Math.max(-radius, -q - radius);
|
|
|
- const rMax = Math.min(radius, -q + radius);
|
|
|
- for (let r = rMin; r <= rMax; r++) {
|
|
|
- const { x, y } = this.axialToPixel(q, r);
|
|
|
- positions.push({ q, r, x, y });
|
|
|
- // consider hex bounding box (center ± half size) when computing extents
|
|
|
- const halfW = hexWidth / 2;
|
|
|
- const halfH = hexHeight / 2;
|
|
|
- if (x - halfW < minX) minX = x - halfW;
|
|
|
- if (y - halfH < minY) minY = y - halfH;
|
|
|
- if (x + halfW > maxX) maxX = x + halfW;
|
|
|
- if (y + halfH > maxY) maxY = y + halfH;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Padding pour éviter des coupures visuelles
|
|
|
- const padding = 20;
|
|
|
-
|
|
|
- // Dimensionner le canvas d'après l'enveloppe + la taille d'un hex
|
|
|
- const canvasWidth = (maxX - minX) + hexWidth + padding;
|
|
|
- const canvasHeight = (maxY - minY) + hexHeight + padding;
|
|
|
- this.canvas.style.width = canvasWidth + 'px';
|
|
|
- this.canvas.style.height = canvasHeight + 'px';
|
|
|
-
|
|
|
- // Seconde passe: rendre chaque hexagone avec un offset pour commencer à (padding/2, padding/2)
|
|
|
- const offsetX = -minX + padding / 2;
|
|
|
- const offsetY = -minY + padding / 2;
|
|
|
- for (const pos of positions) {
|
|
|
- // pass center coordinates adjusted by offset; renderHexagon will place box centered on these
|
|
|
- this.renderHexagon(pos.q, pos.r, pos.x + offsetX, pos.y + offsetY, hexWidth, hexHeight);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- renderHexagon(q, r, x, y, hexWidth, hexHeight) {
|
|
|
- const hex = document.createElement('div');
|
|
|
- hex.className = 'hexagon';
|
|
|
- hex.dataset.q = q;
|
|
|
- hex.dataset.r = r;
|
|
|
-
|
|
|
- // Positionnement et dimensions : x,y sont les coordonnées du centre
|
|
|
- // Ajouter un très léger chevauchement pour compenser l'anti-aliasing et éviter les petits interstices
|
|
|
- const expansion = 0.8; // pixels (combler de très fins espaces sans créer de chevauchement visible)
|
|
|
- const adjWidth = hexWidth + expansion;
|
|
|
- const adjHeight = hexHeight + expansion;
|
|
|
- const halfW = adjWidth / 2;
|
|
|
- const halfH = adjHeight / 2;
|
|
|
- hex.style.width = Math.round(adjWidth) + 'px';
|
|
|
- hex.style.height = Math.round(adjHeight) + 'px';
|
|
|
- // Arrondir les positions pour éviter les sous-pixels qui créent des gaps visuels
|
|
|
- hex.style.left = Math.round(x - halfW) + 'px';
|
|
|
- hex.style.top = Math.round(y - halfH) + 'px';
|
|
|
-
|
|
|
- // Style de base
|
|
|
- hex.style.position = 'absolute';
|
|
|
- hex.style.cursor = 'pointer';
|
|
|
- hex.style.transition = 'background-color 0.2s';
|
|
|
- hex.style.backgroundColor = '#ffffff';
|
|
|
-
|
|
|
- // Créer la forme hexagonale avec CSS (pointy-top)
|
|
|
- hex.style.clipPath = 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)';
|
|
|
-
|
|
|
- // Gestionnaire de clic
|
|
|
- hex.addEventListener('click', (e) => {
|
|
|
- e.stopPropagation();
|
|
|
- this.selectTile(q, r);
|
|
|
- });
|
|
|
-
|
|
|
- // Gestionnaire de survol
|
|
|
- hex.addEventListener('mouseenter', () => {
|
|
|
- if (!this.selectedTile || this.selectedTile.q !== q || this.selectedTile.r !== r) {
|
|
|
- hex.style.backgroundColor = '#e3f2fd';
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- hex.addEventListener('mouseleave', () => {
|
|
|
- if (!this.selectedTile || this.selectedTile.q !== q || this.selectedTile.r !== r) {
|
|
|
- hex.style.backgroundColor = '#ffffff';
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- this.canvas.appendChild(hex);
|
|
|
- }
|
|
|
-
|
|
|
- selectTile(q, r) {
|
|
|
- // Désélectionner la tuile précédente
|
|
|
- if (this.selectedTile) {
|
|
|
- const prevHex = this.canvas.querySelector(`[data-q="${this.selectedTile.q}"][data-r="${this.selectedTile.r}"]`);
|
|
|
- if (prevHex) {
|
|
|
- prevHex.style.backgroundColor = '#ffffff';
|
|
|
- prevHex.style.borderColor = '#666';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Sélectionner la nouvelle tuile
|
|
|
- this.selectedTile = { q, r };
|
|
|
- const hex = this.canvas.querySelector(`[data-q="${q}"][data-r="${r}"]`);
|
|
|
- if (hex) {
|
|
|
- hex.style.backgroundColor = '#2196f3';
|
|
|
- hex.style.borderColor = '#1976d2';
|
|
|
- }
|
|
|
-
|
|
|
- // Afficher les informations de la tuile
|
|
|
- this.showTileInfo(q, r);
|
|
|
- }
|
|
|
-
|
|
|
- showTileInfo(q, r) {
|
|
|
- const tileInfo = document.getElementById('tileInfo');
|
|
|
- const tilePosition = document.getElementById('tilePosition');
|
|
|
- const tileType = document.getElementById('tileType');
|
|
|
- const tileProperties = document.getElementById('tileProperties');
|
|
|
-
|
|
|
- tilePosition.textContent = `Q: ${q}, R: ${r}`;
|
|
|
- tileType.textContent = 'Vide'; // Par défaut
|
|
|
-
|
|
|
- // TODO: Récupérer les vraies propriétés de la tuile depuis les données
|
|
|
- tileProperties.innerHTML = `
|
|
|
- <div class="row">
|
|
|
- <div class="col-md-6">
|
|
|
- <label class="form-label small">Type de terrain</label>
|
|
|
- <select class="form-select form-select-sm">
|
|
|
- <option>Vide</option>
|
|
|
- <option>Forêt</option>
|
|
|
- <option>Montagne</option>
|
|
|
- <option>Eau</option>
|
|
|
- <option>Ville</option>
|
|
|
- <option>Route</option>
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- <div class="col-md-6">
|
|
|
- <label class="form-label small">Élévation</label>
|
|
|
- <input type="number" class="form-control form-control-sm" placeholder="0">
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="mt-2">
|
|
|
- <label class="form-label small">Ressources</label>
|
|
|
- <div class="input-group input-group-sm">
|
|
|
- <input type="text" class="form-control" placeholder="Ajouter une ressource...">
|
|
|
- <button class="btn btn-outline-secondary" type="button">+</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- `;
|
|
|
-
|
|
|
- tileInfo.style.display = 'block';
|
|
|
- }
|
|
|
-
|
|
|
- setupEventListeners() {
|
|
|
- console.log('Configuration des event listeners de zoom');
|
|
|
-
|
|
|
- // Zoom
|
|
|
- document.getElementById('zoomIn').addEventListener('click', (e) => {
|
|
|
- e.preventDefault();
|
|
|
- console.log('Bouton zoom in cliqué');
|
|
|
- this.zoom(1.2);
|
|
|
- });
|
|
|
- document.getElementById('zoomOut').addEventListener('click', (e) => {
|
|
|
- e.preventDefault();
|
|
|
- console.log('Bouton zoom out cliqué');
|
|
|
- this.zoom(0.8);
|
|
|
- });
|
|
|
- document.getElementById('fitToScreen').addEventListener('click', (e) => {
|
|
|
- e.preventDefault();
|
|
|
- console.log('Bouton fit to screen cliqué');
|
|
|
- this.fitToScreen();
|
|
|
- });
|
|
|
-
|
|
|
- // Déplacement (pan)
|
|
|
- this.container.addEventListener('mousedown', (e) => {
|
|
|
- if (e.target === this.container) {
|
|
|
- this.isDragging = true;
|
|
|
- this.lastMousePos = { x: e.clientX, y: e.clientY };
|
|
|
- this.canvas.style.cursor = 'grabbing';
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- document.addEventListener('mousemove', (e) => {
|
|
|
- if (this.isDragging) {
|
|
|
- const deltaX = e.clientX - this.lastMousePos.x;
|
|
|
- const deltaY = e.clientY - this.lastMousePos.y;
|
|
|
-
|
|
|
- this.panOffset.x += deltaX;
|
|
|
- this.panOffset.y += deltaY;
|
|
|
-
|
|
|
- this.updateTransform();
|
|
|
-
|
|
|
- this.lastMousePos = { x: e.clientX, y: e.clientY };
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- document.addEventListener('mouseup', () => {
|
|
|
- this.isDragging = false;
|
|
|
- this.canvas.style.cursor = 'grab';
|
|
|
- });
|
|
|
-
|
|
|
- // Désélectionner en cliquant sur le fond
|
|
|
- this.container.addEventListener('click', () => {
|
|
|
- if (this.selectedTile) {
|
|
|
- this.selectTile(null, null);
|
|
|
- document.getElementById('tileInfo').style.display = 'none';
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- zoom(factor) {
|
|
|
- this.zoomLevel *= factor;
|
|
|
- this.zoomLevel = Math.max(0.1, Math.min(5, this.zoomLevel)); // Limiter le zoom entre 0.1x et 5x
|
|
|
- this.updateTransform();
|
|
|
- document.getElementById('zoomLevel').textContent = Math.round(this.zoomLevel * 100);
|
|
|
- }
|
|
|
-
|
|
|
- fitToScreen() {
|
|
|
- const containerRect = this.container.getBoundingClientRect();
|
|
|
- const canvasRect = this.canvas.getBoundingClientRect();
|
|
|
-
|
|
|
- const scaleX = containerRect.width / canvasRect.width;
|
|
|
- const scaleY = containerRect.height / canvasRect.height;
|
|
|
- const scale = Math.min(scaleX, scaleY) * 0.9; // Laisser un peu de marge
|
|
|
-
|
|
|
- this.zoomLevel = scale;
|
|
|
- this.panOffset = { x: 0, y: 0 };
|
|
|
- this.updateTransform();
|
|
|
- document.getElementById('zoomLevel').textContent = Math.round(this.zoomLevel * 100);
|
|
|
- }
|
|
|
-
|
|
|
- updateTransform() {
|
|
|
- this.canvas.style.transform = `translate(${this.panOffset.x}px, ${this.panOffset.y}px) scale(${this.zoomLevel})`;
|
|
|
- }
|
|
|
-
|
|
|
- hideLoading() {
|
|
|
- const loading = document.getElementById('mapLoading');
|
|
|
- if (loading) {
|
|
|
- // Retirer les classes Bootstrap qui forcent display: flex
|
|
|
- loading.classList.remove('d-flex');
|
|
|
- loading.style.display = 'none';
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// Initialiser l'éditeur de carte
|
|
|
-(function() {
|
|
|
- function initMapEditor() {
|
|
|
- if (typeof mapData === 'undefined') {
|
|
|
- console.error('MapEditor: mapData non défini');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- new MapEditor();
|
|
|
- } catch (error) {
|
|
|
- console.error('Erreur lors de la création de MapEditor:', error);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Attendre que le DOM soit complètement chargé
|
|
|
- if (document.readyState === 'loading') {
|
|
|
- document.addEventListener('DOMContentLoaded', initMapEditor);
|
|
|
- } else {
|
|
|
- initMapEditor();
|
|
|
- }
|
|
|
-})();
|
|
|
</script>
|
|
|
+<script src="/assets/js/map-editor.js"></script>
|
|
|
<?php endif; ?>
|