diff --git a/src/app/football-field/football-field.component.css b/src/app/football-field/football-field.component.css index 7e74b69..cf69b88 100644 --- a/src/app/football-field/football-field.component.css +++ b/src/app/football-field/football-field.component.css @@ -72,6 +72,11 @@ canvas { align-items: center; margin-bottom: 10px; } +.ball-timeline { + display: flex; + align-items: center; + margin-bottom: 10px; +} .timeline { position: relative; @@ -106,6 +111,6 @@ canvas { top: 0; width: 3px; /* Epaisseur du trait */ height: 100%; /* La hauteur couvre toute la timeline */ - background-color: red; /* Couleur du trait de temps */ + background-color: hsl(0, 1%, 41%); /* Couleur du trait de temps */ left: 0; /* Point de départ du trait */ } \ No newline at end of file diff --git a/src/app/football-field/football-field.component.html b/src/app/football-field/football-field.component.html index 811965a..5ac5173 100644 --- a/src/app/football-field/football-field.component.html +++ b/src/app/football-field/football-field.component.html @@ -66,15 +66,30 @@
+
+ Ballon + +
Joueur {{ player.id }}
-
{{ i + 1 }}
diff --git a/src/app/football-field/football-field.component.ts b/src/app/football-field/football-field.component.ts index c4d52fe..f4609e0 100644 --- a/src/app/football-field/football-field.component.ts +++ b/src/app/football-field/football-field.component.ts @@ -13,19 +13,19 @@ interface TimelineStep { duration: number; // Temps d'animation (en secondes ou ticks) } +interface Rectangle { + x: number; // Position X du centre du rectangle de l'élément + y: number; // Position Y du centre du rectangle de l'élément + width: number; // longueur de l'élément + height: number; // largeur de l'élément +} + interface Circle { id: number; // Identifiant unique de l'élément x: number; // Position X du centre du cercle de l'élément y: number; // Position Y du centre du cercle de l'élément - number: number; radius: number; // Rayon du cercle de l'élément isDragging: boolean; - vx: number; // vitesse en X - vy: number; // vitesse en Y - destinationX: number | null; - destinationY: number | null; - controlPointX?: number | null; // Pour les vecteurs courbés - controlPointY?: number | null; // Pour les vecteurs courbés isMoving: boolean; progress: number; // Paramètre de progression sur la courbe startX: number; @@ -34,12 +34,46 @@ interface Circle { currentStepIndex: number; // Suivre l'étape actuelle dans la timeline } -interface Rectangle { - x: number; - y: number; - width: number; - height: number; - isDragging: boolean; +interface Circle2 { + x: number; // Position X du centre du cercle de l'élément + y: number; // Position Y du centre du cercle de l'élément + radius: number; // Rayon du cercle de l'élément +} + +interface Piquet { + id: number; // Identifiant unique de l'élément + design: Rectangle; // Design du piquet + isDragging: boolean; // Flag de déplacement du piquet +} + +interface Plot { + id: number; // Identifiant unique de l'élément + design: Circle2; // Design du plot + isDragging: boolean; // Flag de déplacement du plot +} + +interface Ball { + id: number; // Identifiant unique de l'élément + design: Circle2; // Design du ballon + isDragging: boolean; // Flag de déplacement du ballon + isMoving: boolean; // Flag d'animation du ballon + progress: number; // Paramètre de progression sur la courbe + steps: TimelineStep[]; // Stockage des étapes d'animation + currentStepIndex: number; // Suivre l'étape actuelle dans la timeline + attachToPlayer: Circle | null; // Le joueur auquel le ballon est attaché +} + +interface Player { + id: number; // Identifiant unique de l'élément + design: Circle2; // Design du Joueur + isDragging: boolean; // Flag de déplacement du joueur + isMoving: boolean; // Flag d'animation du joueur + progress: number; // Paramètre de progression sur la courbe + hasBall: boolean; // Indique si le joueur a le ballon + startX: number; + startY: number; + steps: TimelineStep[]; // Stockage des étapes d'animation + currentStepIndex: number; // Suivre l'étape actuelle dans la timeline } @Component({ @@ -61,10 +95,7 @@ export class FootballFieldComponent { private startX = 0; private startY = 0; private draggingPlayer: Circle | null = null; - private draggingBall: Circle | null = null; - //private draggingPlot: Circle | null = null; - //private draggingPiquet: Rectangle | null = null; - private draggingElement: any = null; + private draggingElement: any = null; // Element (plot, piquet) en cours de déplacement sur le terrain private attachedPlayer: Circle | null = null; private magnetRadius: number = 35; // Distance à laquelle le ballon s'attache au joueur //private attachedToPlayer: boolean = false; // indique si le ballon est attache a un joueur @@ -94,31 +125,34 @@ export class FootballFieldComponent { public playerCount: number = 8; // Nombre de joueurs par défaut public plotCount: number = 2; // Nombre de plots par défaut public piquetCount: number = 2; // Nombre de plots par défaut - public ballCount: number = 1; // Nombre de ballons par defaut public players: Circle[] = []; - public plots: Circle[] = []; - public piquets: Rectangle[] = []; - public ball: Circle = { id: 0, + public plots: Plot[] = []; + public piquets: Piquet[] = []; + /*public ball: Circle = { id: 0, x:400, - y:300, - number:1, + y:300, radius:10, - isDragging:false, - vx:0, - vy:0, - destinationX: null, - destinationY: null, + isDragging:false, isMoving:false, progress: 0, startX: 150, startY: 150, - timeline: null, - currentStepIndex: 0 }; + timeline: [], + currentStepIndex: 0 };*/ + public ball: Ball = { id: 0, + design: { x:400, y:300, + radius:10, + }, + isDragging:false, + isMoving:false, + progress: 0, + steps: [], + currentStepIndex: 0, + attachToPlayer: null }; public selectedPlayer: Circle | null = null; public isDrawingLine: boolean = false; public loopAnimation: boolean = false; // Pour répéter l'animation public debugInfo: string = ""; // Variable pour les informations de debogage - public vectorType: 'linear' | 'curved' = 'linear'; // Type de vecteur sélectionné public interactionMode: 'move' | 'animate' = 'move'; // Mode d'interaction sélectionné public isTimelineDragging: boolean = false; @@ -344,7 +378,7 @@ export class FootballFieldComponent { const playerPositions = this.players.map((player, index) => `Joueur ${index + 1}: (${player.x.toFixed(1)}, ${player.y.toFixed(1)})` ).join(', '); - const ballPosition = `Ballon: (${this.ball.x.toFixed(1)}, ${this.ball.y.toFixed(1)})`; + const ballPosition = `Ballon: (${this.ball.design.x.toFixed(1)}, ${this.ball.design.y.toFixed(1)})`; this.debugInfo = `${playerPositions}, ${ballPosition}`; } @@ -357,20 +391,15 @@ export class FootballFieldComponent { const x = i * (2 * radius) + radius; const y = radius; this.players.push({ id: i + 1, - x, - y, - number: i + 1, - radius, - isDragging: false, - vx:0, vy:0, - destinationX: null, - destinationY: null, - isMoving:false, - progress: 0, - startX: 0, - startY: 0, - timeline: [], - currentStepIndex: 0 }); + x, y, + radius, + isDragging: false, + isMoving:false, + progress: 0, + startX: 0, + startY: 0, + timeline: [], + currentStepIndex: 0 }); } this.drawField(); } @@ -383,20 +412,10 @@ export class FootballFieldComponent { const x = (this.canvas.nativeElement.width - radius) - (i) * (2 * radius); const y = radius; this.plots.push({ id: i + 1, - x, - y, - number: i + 1, - radius, - isDragging: false, - vx: 0, vy: 0, - destinationX: null, - destinationY: null, - isMoving:false, - progress: 0, - startX: 0, - startY: 0, - timeline: null, - currentStepIndex: 0 }); + design: { + x, y, + radius }, + isDragging: false }); } this.drawField(); } @@ -409,7 +428,9 @@ export class FootballFieldComponent { const height = 100; const x = (i * width); const y = (this.canvas.nativeElement.height - height); - this.piquets.push({ x, y, width, height, isDragging: false }); + this.piquets.push({ id: i+1, + design: {x, y, width, height}, + isDragging: false }); } this.drawField(); } @@ -437,14 +458,14 @@ export class FootballFieldComponent { // Dessiner les cercles representant les plots private drawPlots() { - this.plots.forEach(circle => { - this.drawPlot(circle); + this.plots.forEach(plot => { + this.drawPlot(plot); }); } private drawPiquets() { - this.piquets.forEach(rectangle => { - this.drawPiquet(rectangle); + this.piquets.forEach(piquet => { + this.drawPiquet(piquet); }); } @@ -454,7 +475,7 @@ export class FootballFieldComponent { // Dessiner le cercle ctx.beginPath(); - ctx.arc(this.ball.x, this.ball.y, this.ball.radius, 0, Math.PI * 2); + ctx.arc(this.ball.design.x, this.ball.design.y, this.ball.design.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.strokeStyle = '#000000'; @@ -463,12 +484,12 @@ export class FootballFieldComponent { } // Dessiner un plot - private drawPlot(circle: Circle) { + private drawPlot(plot: Plot) { const ctx = this.ctx; // Dessiner le cercle ctx.beginPath(); - ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2); + ctx.arc(plot.design.x, plot.design.y, plot.design.radius, 0, Math.PI * 2); ctx.fillStyle = '#ffa500'; ctx.fill(); ctx.strokeStyle = '#ff4500'; @@ -477,14 +498,14 @@ export class FootballFieldComponent { } // Dessiner un piquet - private drawPiquet(rectangle: Rectangle) { + private drawPiquet(piquet: Piquet) { const ctx = this.ctx; ctx.beginPath(); ctx.fillStyle = '#ff0000'; - ctx.fillRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + ctx.fillRect(piquet.design.x, piquet.design.y, piquet.design.width, piquet.design.height); ctx.strokeStyle = '#000000'; - ctx.strokeRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + ctx.strokeRect(piquet.design.x, piquet.design.y, piquet.design.width, piquet.design.height); } // Dessiner un joueur avec un numér @@ -504,7 +525,7 @@ export class FootballFieldComponent { ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText(circle.number.toString(), circle.x, circle.y); + ctx.fillText(circle.id.toString(), circle.x, circle.y); } onCanvasMouseDown(event: MouseEvent) { @@ -579,7 +600,7 @@ export class FootballFieldComponent { duration: 1000 // Durée d'animation arbitraire, peut être ajustée }); console.log("timeline:", this.selectedPlayer.timeline); - this.selectedPlayer.currentStepIndex = 0; // Réinitialiser au début de la timeline + //this.selectedPlayer.currentStepIndex = 0; // Réinitialiser au début de la timeline this.startAnimation(); } } else if (this.interactionMode === 'move') { @@ -601,23 +622,24 @@ export class FootballFieldComponent { // Commencer a déplacer un élément du canvas startDragging(x:number, y:number) { - if (this.isInsideCircle(this.ball, x, y)) { + if (this.isInsideCircle(this.ball.design, x, y)) { console.log("inside ball"); this.ball.isDragging = true; this.attachedPlayer = null; // Libérer le ballon lors du clic - this.offsetX = x - this.ball.x; - this.offsetY = y - this.ball.y; + this.offsetX = x - this.ball.design.x; + this.offsetY = y - this.ball.design.y; return; } - this.draggingElement = this.plots.find(plot => this.isInsideCircle(plot, x, y)) || - this.piquets.find(piquet => this.isInsideRectangle(piquet, x, y)) || - (this.isInsideCircle(this.ball, x, y) ? this.ball : null); + this.draggingElement = this.plots.find(plot => this.isInsideCircle(plot.design, x, y)) || + this.piquets.find(piquet => this.isInsideRectangle(piquet.design, x, y)) || + (this.isInsideCircle(this.ball.design, x, y) ? this.ball : null); if (this.draggingElement) { this.draggingElement.isDragging = true; - this.offsetX = x - this.draggingElement.x; - this.offsetY = y - this.draggingElement.y; + this.offsetX = x - this.draggingElement.design.x; + this.offsetY = y - this.draggingElement.design.y; + console.log("inside element:", this.draggingElement); } this.draggingPlayer = this.players.find(player => { @@ -635,8 +657,8 @@ export class FootballFieldComponent { drag(x:number, y:number) { if (this.ball.isDragging) { console.log("dragging ball"); - this.ball.x = x - this.offsetX; - this.ball.y = y - this.offsetY; + this.ball.design.x = x - this.offsetX; + this.ball.design.y = y - this.offsetY; this.drawField(); return; } @@ -648,7 +670,7 @@ export class FootballFieldComponent { // Attacher le ballon si un joueur se rapproche suffisamment const nearestPlayer = this.getNearestPlayerToBall(); - if (nearestPlayer && this.getDistance(this.ball, nearestPlayer) < this.magnetRadius) { + if (nearestPlayer && this.getDistance(this.ball.design, nearestPlayer) < this.magnetRadius) { this.attachedPlayer = nearestPlayer; this.updateBallPositionOnPlayer(); } @@ -664,8 +686,8 @@ export class FootballFieldComponent { if (this.draggingElement && this.draggingElement.isDragging) { console.log("dragging element"); - this.draggingElement.x = x - this.offsetX; - this.draggingElement.y = y - this.offsetY; + this.draggingElement.design.x = x - this.offsetX; + this.draggingElement.design.y = y - this.offsetY; this.drawField(); return; } @@ -692,49 +714,12 @@ export class FootballFieldComponent { } } - private moveAlongLine(player: Circle) { - const dx = player.destinationX! - player.x; - const dy = player.destinationY! - player.y; - const distance = Math.sqrt(dx * dx + dy * dy); - - if (distance < 1) { - player.x = player.destinationX!; - player.y = player.destinationY!; - player.isMoving = false; - } else { - player.vx = (dx / distance) * 2; - player.vy = (dy / distance) * 2; - player.x += player.vx; - player.y += player.vy; - } - } - - private moveAlongCurve(player: Circle) { - player.progress += 0.01; - if (player.progress >= 1) { - player.x = player.destinationX!; - player.y = player.destinationY!; - player.isMoving = false; - player.progress = 0; - } else { - const t = player.progress; - const x = (1 - t) * (1 - t) * player.x + - 2 * (1 - t) * t * player.controlPointX! + - t * t * player.destinationX!; - const y = (1 - t) * (1 - t) * player.y + - 2 * (1 - t) * t * player.controlPointY! + - t * t * player.destinationY!; - player.x = x; - player.y = y; - } - } - private updateBallPositionOnPlayer() { const player = this.attachedPlayer; if (!player) return; - const dx = this.ball.x - player.x; - const dy = this.ball.y - player.y; + const dx = this.ball.design.x - player.x; + const dy = this.ball.design.y - player.y; let angle = 0; if (this.test) { angle = Math.atan2(dy, dx); @@ -744,25 +729,10 @@ export class FootballFieldComponent { angle = this.prevAngle; } - this.ball.x = player.x + (player.radius + this.ball.radius) * Math.cos(angle); - this.ball.y = player.y + (player.radius + this.ball.radius) * Math.sin(angle); + this.ball.design.x = player.x + (player.radius + this.ball.design.radius) * Math.cos(angle); + this.ball.design.y = player.y + (player.radius + this.ball.design.radius) * Math.sin(angle); } - // Calculer la nouvelle position du ballon a la tangente du joueur -// private updateBallPositionOnTangent() { -// const player = this.draggingPlayer; -// if (!player) return; -// -// const dx = this.ball.x - player.x; -// const dy = this.ball.y - player.y; -// const distance = Math.sqrt(dx * dy + dy * dy); // Distance entre le joueur et le ballon -// const angle = Math.atan2(dy, dx); // Calculer l'angle entre le joueur et le ballon -// -// // Positionner le ballon a la tangente du joueur -// this.ball.x = player.x + Math.cos(angle) * (player.radius + this.ball.radius); -// this.ball.y = player.y + Math.sin(angle) * (player.radius + this.ball.radius); -// } - private selectPlayer(x: number, y: number) { this.selectedPlayer = this.getNearestPlayer(x, y); if (this.selectedPlayer) { @@ -783,7 +753,7 @@ export class FootballFieldComponent { let minDistance = this.magnetRadius; this.players.forEach(player => { - const distance = this.getDistance(this.ball, player); + const distance = this.getDistance(this.ball.design, player); if (distance < minDistance) { minDistance = distance; nearestPlayer = player; @@ -807,12 +777,12 @@ export class FootballFieldComponent { return nearestPlayer; } - private getDistance(circle1: Circle, circle2: Circle): number { + private getDistance(circle1: Circle2, circle2: Circle2): number { return Math.sqrt((circle1.x - circle2.x) ** 2 + (circle1.y - circle2.y) ** 2); } // Vérifier si la souris et a l'intérieur d'un cercle - private isInsideCircle(circle: Circle, x: number, y: number) { + private isInsideCircle(circle: Circle2, x: number, y: number) { const distance = Math.sqrt((x - circle.x) ** 2 + (y - circle.y) ** 2); return distance <= circle.radius; } @@ -828,104 +798,6 @@ export class FootballFieldComponent { this.drawField(); } -// // Change la couleur sélectionnée -// setColor(color: string) { -// this.selectedColor = color; -// } - -// // Commence a dessiner quand la souris est enfoncée -// startDrawing(event: MouseEvent) { -// this.drawing = true; -// const { x, y } = this.getMousePos(event); -// this.startX = x; -// this.startY = y; -// this.lastX = x; -// this.lastY = y; -// } - -// private startDrawingVector(x:number, y:number) { -// this.startX = x; -// this.startY = y; -// this.isDrawingVector = true; -// } - -// private finishDrawingVector(endX: number, endY: number) { -// if (!this.isDrawingVector) return; -// this.isDrawingVector = false; -// -// const nearestPlayer = this.getNearestPlayer(this.startX, this.startY); -// if (nearestPlayer) { -// nearestPlayer.destinationX = endX; -// nearestPlayer.destinationY = endY; -// nearestPlayer.isMoving = true; -// nearestPlayer.progress = 0; -// -// if (this.vectorType === 'curved') { -// nearestPlayer.controlPointX = (this.startX + endX) / 2 + 50; -// nearestPlayer.controlPointY = (this.startY + endY) / 2 - 50; -// } else { -// nearestPlayer.controlPointX = null; -// nearestPlayer.controlPointY = null; -// } -// } -// } - -// // Dessiner en fonction de la forme sélectionnée -// draw(event: MouseEvent) { -// if (!this.drawing) return; -// -// const { x, y } = this.getMousePos(event); -// const ctx = this.ctx; -// -// // Effacer ce qui a été dessiné pendant le déplacement -// //this.drawField(); -// -// // Si aucune forme n'est sélectionnée, on dessine librement -// if (this.selectedShape === 'none') { -// ctx.strokeStyle = this.selectedColor; -// ctx.lineWidth = 5; -// ctx.beginPath(); -// ctx.moveTo(this.lastX, this.lastY); -// ctx.lineTo(x, y); -// ctx.stroke(); -// } else { -// // Sauvegarder le contexte pour ne pas effacer le terrain -// ctx.save(); -// ctx.strokeStyle = this.selectedColor; -// ctx.lineWidth = 5; -// ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); -// -// switch (this.selectedShape) { -// case 'rectangle': -// ctx.strokeRect(this.startX, this.startY, x - this.startX, y - this.startY); -// break; -// case 'circle': -// const radius = Math.sqrt((x - this.startX) ** 2 + (y - this.startY) ** 2); -// ctx.beginPath(); -// ctx.arc(this.startX, this.startY, radius, 0, Math.PI * 2); -// ctx.stroke(); -// break; -// case 'line': -// ctx.beginPath(); -// ctx.moveTo(this.startX, this.startY); -// ctx.lineTo(x, y); -// ctx.stroke(); -// break; -// } -// -// ctx.restore(); -// } -// -// this.lastX = x; -// this.lastY = y; -// } - - - // Arrête de dessiner quand la souris est relâchée -// stopDrawing() { -// this.drawing = false; -// } - // Obtient la position de la souris sur le canvas private getMousePos(event: MouseEvent) { const rect = this.canvas.nativeElement.getBoundingClientRect(); @@ -952,11 +824,6 @@ export class FootballFieldComponent { //this.selectedPlayer = null; // Réinitialiser la sélection de joueur pour éviter tout déplacement involontaire } - // Méthode pour définir le type de vecteur (linéaire ou courbé) - setVectorType(type: 'linear' | 'curved') { - this.vectorType = type; - } - // Méthode pour activer/désactiver la boucle de l'animation toggleLoop() { this.loopAnimation = !this.loopAnimation;