Simulação de física do JavaScript

Como modificar e explicar uma simulação de bolinhas de JavaScript.

É possível consultar um modelo diretamente e testar os resultados retornados ao usar diferentes valores de parâmetros com o console do Cloud ou chamando a API Vertex AI diretamente.

Comando Formato livre

Tenho este código JavaScript que simula bolinhas caindo:

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();

Resposta

Esse código é um bom ponto de partida para uma simulação de bolinhas. No entanto, há algumas áreas que podem ser melhoradas em termos de eficiência, precisão e robustez:

1. Detecção de colisão ineficiente:a detecção de colisão atual com obstáculos verifica todos os obstáculos em cada bolinha em cada frame. Para um grande número de bolinhas e obstáculos, isso se torna caro computacionalmente. Uma estrutura de dados de particionamento espacial, como uma quadtree ou uma grade, melhoraria significativamente a performance.

2. Possível estouro de pilha (recursão): embora não esteja presente diretamente neste código, a resolução de colisão recursiva (em que uma bolinha colide com vários obstáculos simultaneamente) pode levar a um estouro de pilha se não for tratada com cuidado. A abordagem iterativa atual é mais segura.

3. Resposta de colisão imprecisa:a resposta de colisão usa uma reflexão simples com base no vetor normal. Isso funciona razoavelmente bem para formas simples, mas pode levar a imprecisões e oscilações, especialmente quando as bolinhas colidem entre si ou em ângulos acentuados. Uma abordagem mais robusta envolveria a resolução de colisões baseada em impulso, que considera as massas e velocidades dos objetos em colisão.

4. Falta de colisões entre bolas de gude:no momento, a simulação só processa colisões entre bolas de gude e obstáculos. Adicionar a detecção e resolução de colisão entre as bolas de gude tornaria a simulação mais realista.

5. Elemento canvas ausente:o código pressupõe que um elemento de tela com o ID simulationCanvas existe no HTML. Você precisa incluir isso no arquivo HTML.

Confira uma versão aprimorada que resolve alguns desses problemas:

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ão se esqueça de incluir o elemento <canvas id="simulationCanvas"></canvas> no arquivo HTML. Essa versão aprimorada adiciona massa às bolinhas e simplifica um pouco a resolução de colisão. Para recursos mais avançados (colisões de bolinhas, particionamento espacial), será necessário implementar algoritmos mais complexos. Considere pesquisar técnicas como a detecção de colisão de fase ampla e estreita para melhorar o desempenho com muitos objetos.

Modelo: gemini-1.5-flash-002
Temperatura: 0,2
Máximo de tokens de saída: 8192
TopK: 40.0
TopP: 0,95