떨어지는 구슬을 시뮬레이션하는 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 |
|