以下の 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 要素がない: このコードは、ID が simulationCanvas のキャンバス要素が HTML に存在することを前提としています。この要素を 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 |
TopK: |
40.0 |
TopP: |
0.95 |
|