Voici un code JavaScript qui simule la chute de billes :
const canvas = document.getElementById('simulationCanvas');
const ctx = canvas.getContext('2d');
const marbles = [];
const obstacles = [];
const gravity = 0.1; // Adjust this value to change the speed of the simulation
const friction = 0.99;
const restitution = 0.8;
class Marble {
constructor(x, y, radius, color) {
Object.assign(this, { x, y, radius, color });
this.dx = (Math.random() - 0.5) * 2;
this.dy = 0;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
update() {
// Apply gravity to the vertical velocity
this.dy += gravity;
// Apply friction to the horizontal velocity
this.dx *= friction;
// Update the marble's position based on its velocity
this.x += this.dx;
this.y += this.dy;
// Check for collisions with the bottom of the canvas
if (this.y + this.radius > canvas.height) {
// Keep the marble within the canvas boundaries
this.y = canvas.height - this.radius;
// Reverse the vertical velocity and apply restitution for bouncing effect
this.dy = -this.dy * restitution;
}
// Check for collisions with the sides of the canvas
if (this.x + this.radius > canvas.width || this.x - this.radius < 0) {
// Reverse the horizontal velocity to make the marble bounce off the walls
this.dx = -this.dx;
}
// Check for collisions with obstacles
obstacles.forEach(obstacle => {
const { normalX, normalY, isColliding, penetrationDepth } = obstacle.checkCollision(this.x, this.y, this.radius);
if (isColliding) {
// Calculate the dot product of the velocity and the collision normal
const dotProduct = this.dx * normalX + this.dy * normalY;
// Reflect the velocity vector off the surface normal
this.dx -= 2 * dotProduct * normalX;
this.dy -= 2 * dotProduct * normalY;
// Apply restitution to the reflected velocity
this.dx *= restitution;
this.dy *= restitution;
// Resolve the collision by moving the marble outside of the obstacle
this.x += normalX * penetrationDepth;
this.y += normalY * penetrationDepth;
}
});
this.draw();
}
}
class Obstacle {
constructor(x, y, width, height, angle, color) {
Object.assign(this, { x, y, width, height, angle, color });
}
draw() {
ctx.save();
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
ctx.rotate(this.angle * Math.PI / 180);
ctx.fillStyle = this.color;
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
ctx.restore();
}
checkCollision(mx, my, mr) {
const cos = Math.cos(-this.angle * Math.PI / 180);
const sin = Math.sin(-this.angle * Math.PI / 180);
const dx = mx - (this.x + this.width / 2);
const dy = my - (this.y + this.height / 2);
const localX = cos * dx - sin * dy + this.width / 2;
const localY = sin * dx + cos * dy + this.height / 2;
const closestX = Math.max(0, Math.min(this.width, localX));
const closestY = Math.max(0, Math.min(this.height, localY));
const distanceX = localX - closestX;
const distanceY = localY - closestY;
const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
const isColliding = distance < mr;
const normalX = cos * distanceX / distance + sin * distanceY / distance;
const normalY = -sin * distanceX / distance + cos * distanceY / distance;
const penetrationDepth = mr - distance;
return { normalX, normalY, isColliding, penetrationDepth };
}
}
for (let i = 0; i < 10; i++) {
const radius = 10;
const x = Math.random() * (canvas.width - radius * 2) + radius;
const y = radius;
const color = 'blue';
marbles.push(new Marble(x, y, radius, color));
}
obstacles.push(new Obstacle(100, 500, 150, 20, -20, 'black'));
obstacles.push(new Obstacle(600, 400, 150, 20, -20, 'black'));
obstacles.push(new Obstacle(200, 200, 100, 20, 45, 'black'));
obstacles.push(new Obstacle(400, 150, 120, 20, -45, 'black'));
obstacles.push(new Obstacle(500, 500, 200, 20, 15, 'black'));
obstacles.push(new Obstacle(800, 300, 150, 20, -15, 'black'));
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
marbles.forEach(marble => marble.update());
obstacles.forEach(obstacle => obstacle.draw());
requestAnimationFrame(animate);
}
animate();
Réponse
Ce code est un bon point de départ pour une simulation de billes. Toutefois, il existe quelques domaines à améliorer en termes d'efficacité, de justesse et de robustesse :
1. Détection de collision inefficace : la détection actuelle de collision avec des obstacles vérifie tous les obstacles, pour chaque bille et dans chaque image. Avec un nombre conséquent de billes et d'obstacles, cela devient coûteux en termes de calcul. Une structure de données de partitionnement spatial, comme un arbre quaternaire ou une grille, améliorerait considérablement les performances.
2. Risque de dépassement de pile (récursion) : bien que cela ne soit pas directement présent dans ce code, la résolution de collision récursive (lorsqu'une bille entre en collision avec plusieurs obstacles simultanément) peut entraîner un dépassement de pile si elle n'est pas gérée correctement. L'approche itérative actuelle est plus sûre.
3. Réponse à une collision inexacte : la réponse à une collision utilise une réflexion simple basée sur le vecteur normal. Cette méthode fonctionne assez bien pour les formes simples, mais elle peut entraîner des imprécisions et des saccades, en particulier lorsque les billes se heurtent les unes aux autres ou à des angles vifs. Une approche plus robuste implique une résolution des collisions basée sur l'impulsion, qui tient compte des masses et des vitesses des objets entrant en collision.
4. Absence de collisions entre billes : la simulation ne gère actuellement que les collisions entre billes et obstacles. Ajouter la détection et la résolution des collisions entre billes rendrait la simulation plus réaliste.
5. Élément canvas manquant : le code suppose qu'un élément de canevas avec l'ID simulationCanvas existe dans le code HTML. Vous devez l'inclure dans votre fichier HTML.
Voici une version améliorée qui résout certains de ces problèmes :
const canvas = document.getElementById('simulationCanvas');
const ctx = canvas.getContext('2d');
const gravity = 0.1;
const friction = 0.99;
const restitution = 0.8;
class Marble {
constructor(x, y, radius, color) {
Object.assign(this, { x, y, radius, color });
this.dx = (Math.random() - 0.5) * 2;
this.dy = 0;
this.mass = 1; // Add mass for more realistic collisions
}
// ... (draw method remains the same) ...
update() {
this.dy += gravity;
this.dx *= friction;
this.x += this.dx;
this.y += this.dy;
this.handleCollisions();
this.draw();
}
handleCollisions() {
// Bottom collision
if (this.y + this.radius > canvas.height) {
this.y = canvas.height - this.radius;
this.dy = -this.dy * restitution;
}
// Side collisions
if (this.x + this.radius > canvas.width || this.x - this.radius < 0) {
this.dx = -this.dx * restitution;
}
// Obstacle collisions (iterative approach)
obstacles.forEach(obstacle => {
const collisionData = obstacle.checkCollision(this.x, this.y, this.radius);
if (collisionData.isColliding) {
this.resolveCollision(collisionData);
}
});
}
resolveCollision(collisionData) {
const { normalX, normalY, penetrationDepth } = collisionData;
const dotProduct = this.dx * normalX + this.dy * normalY;
this.dx -= 2 * dotProduct * normalX * restitution;
this.dy -= 2 * dotProduct * normalY * restitution;
this.x += normalX * penetrationDepth;
this.y += normalY * penetrationDepth;
}
}
class Obstacle {
// ... (Obstacle class remains largely the same) ...
}
// ... (marble and obstacle creation remains the same) ...
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
marbles.forEach(marble => marble.update());
obstacles.forEach(obstacle => obstacle.draw());
requestAnimationFrame(animate);
}
animate();
N'oubliez pas d'inclure l'élément <canvas id="simulationCanvas"></canvas> dans votre fichier HTML. Cette version améliorée ajoute de la masse aux billes et simplifie légèrement la résolution des collisions. Pour des fonctionnalités plus avancées (collisions entre billes, partitionnement spatial), vous devrez implémenter des algorithmes plus complexes. Envisagez d'étudier des techniques telles que la détection des collisions en phase large et en phase étroite, pour améliorer les performances en présence de nombreux objets.
|
Modèle : |
gemini-1.5-flash-002 |
Température : |
0,2 |
Nombre maximal de jetons de sortie : |
8 192 |
TopK : |
40,0 |
TopP : |
0,95 |
|