JavaScript 물리 시뮬레이션

JavaScript 마블 시뮬레이션을 수정하고 설명합니다.

모델을 직접 쿼리하고 Cloud 콘솔에서 다른 매개변수 값을 사용하거나 Vertex AI API를 직접 호출하여 반환된 결과를 테스트할 수 있습니다.

자유 형식 프롬프트

떨어지는 구슬을 시뮬레이션하는 JavaScript 코드가 있습니다.

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

응답

이 코드는 구슬 시뮬레이션을 시작하는 데 좋은 출발점입니다. 하지만 효율성, 정확성, 견고성 측면에서 개선할 수 있는 몇 가지 영역이 있습니다.

1. 비효율적인 충돌 감지: 현재 장애물과의 충돌 감지는 모든 프레임의 모든 구슬에 대해 모든 장애물을 확인합니다. 구슬과 장애물이 많으면 계산 비용이 많이 듭니다. 쿼드트리 또는 그리드와 같은 공간 파티셔닝 데이터 구조를 사용하면 성능이 크게 개선됩니다.

2. 스택 오버플로 가능성 (재귀): 이 코드에는 직접적으로 표시되지 않지만, 재귀 충돌 해결 (구슬이 여러 장애물과 동시에 충돌하는 경우)을 신중하게 처리하지 않으면 스택 오버플로가 발생할 수 있습니다. 현재 반복적인 접근 방식이 더 안전합니다.

3. 부정확한 충돌 응답: 충돌 응답은 법선 벡터를 기반으로 간단한 반사를 사용합니다. 이는 단순한 도형에는 상당히 잘 작동하지만, 특히 구슬이 서로 충돌하거나 급격한 각도로 충돌할 때는 부정확성과 지터링이 발생할 수 있습니다. 더 강력한 접근 방식은 충돌하는 물체의 질량과 속도를 고려하는 충격 기반 충돌 해결을 포함합니다.

4. 마블-마블 충돌 없음: 현재 시뮬레이션은 마블-장애물 충돌만 처리합니다. 구슬-구슬 충돌 감지 및 해결을 추가하면 시뮬레이션이 더 사실적으로 보일 것입니다.

5. canvas 요소 누락: 코드는 HTML에 ID가 simulationCanvas인 캔버스 요소가 있다고 가정합니다. HTML 파일에 이를 포함해야 합니다.

다음은 이러한 문제 중 일부를 해결한 개선된 버전입니다.

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

HTML 파일에 <canvas id="simulationCanvas"></canvas> 요소를 포함해야 합니다. 이 개선된 버전은 구슬에 질량을 추가하고 충돌 해결을 약간 간소화합니다. 고급 기능 (마블-마블 충돌, 공간 파티셔닝)의 경우 더 복잡한 알고리즘을 구현해야 합니다. 여러 객체에서 성능을 개선하려면 브로드 페즈 및 너로우 페즈 충돌 감지와 같은 기술을 연구해 보세요.

모델: gemini-1.5-flash-002
강도: 0.2
최대 출력 토큰: 8192
최상위 K: 40.0
최상위 P: 0.95