JavaScript-Physiksimulation

Eine JavaScript-Kugelsimulation ändern und erklären

Sie können ein Modell direkt abfragen und die zurückgegebenen Ergebnisse testen, wenn Sie verschiedene Parameterwerte mit der Cloud Console oder durch direkten Aufruf der Vertex AI API verwenden.

Freeform-Prompt

Ich habe diesen JavaScript-Code, der fallende Murmeln simuliert:

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

Antwort

Dieser Code ist ein guter Ausgangspunkt für eine Kugelsimulation. Es gibt jedoch einige Verbesserungsmöglichkeiten in Bezug auf Effizienz, Genauigkeit und Robustheit:

1. Ineffiziente Kollisionserkennung: Bei der aktuellen Kollisionserkennung mit Hindernissen werden alle Hindernisse für jeden Kugel in jedem Frame geprüft. Bei einer großen Anzahl von Murmeln und Hindernissen wird dies jedoch rechenintensiv. Eine räumliche Partitionierungsdatenstruktur wie ein Quadtree oder ein Raster würde die Leistung erheblich verbessern.

2. Potenzielles Risiko für Stack-Overflow (Rekursion): Die rekursive Kollisionsauflösung (bei der ein Kugel mit mehreren Hindernissen gleichzeitig kollidiert) ist in diesem Code zwar nicht direkt vorhanden, kann aber bei unsachgemäßer Handhabung zu einem Stack-Overflow führen. Der aktuelle iterative Ansatz ist sicherer.

3. Falsche Kollisionsreaktion: Für die Kollisionsreaktion wird eine einfache Reflexion basierend auf dem Normalvektor verwendet. Das funktioniert bei einfachen Formen recht gut, kann aber zu Ungenauigkeiten und Rucklern führen, insbesondere wenn Murmeln miteinander oder in spitzen Winkeln kollidieren. Ein robusterer Ansatz wäre eine impulsbasierte Kollisionsbehebung, bei der die Massen und Geschwindigkeiten der kollidierenden Objekte berücksichtigt werden.

4. Fehlende Kollisionen zwischen Kugeln: Die Simulation verarbeitet derzeit nur Kollisionen zwischen Kugeln und Hindernissen. Die Simulation wäre realistischer, wenn die Kollisionen zwischen den Kugeln erkannt und aufgelöst würden.

5. Fehlendes canvas-Element:Im Code wird davon ausgegangen, dass im HTML-Code ein Canvas-Element mit der ID simulationCanvas vorhanden ist. Du musst diesen Code in deine HTML-Datei einfügen.

Hier ist eine verbesserte Version, in der einige dieser Probleme behoben wurden:

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

Denken Sie daran, das Element <canvas id="simulationCanvas"></canvas> in Ihre HTML-Datei aufzunehmen. In dieser verbesserten Version haben die Murmeln eine Masse und die Kollisionsbehebung wird etwas vereinfacht. Für erweiterte Funktionen (Kollisionserkennung zwischen Kugeln, räumliche Partitionierung) müssen Sie komplexere Algorithmen implementieren. Sie sollten sich mit Techniken wie der Kollisionserkennung in der Grob- und der Feinphase befassen, um die Leistung bei vielen Objekten zu verbessern.

Modell: gemini-1.5-flash-002
Temperatur: 0,2
Max. Ausgabetokens: 8.192
TopK: 40,0
TopP: 0,95