我有以下 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();
响应
这段代码是进行 Marble 模拟的良好起点。不过,在效率、准确性和稳健性方面还有一些改进空间:
1. 碰撞检测效率低下:当前的障碍物碰撞检测会检查每个帧中每个球的所有障碍物。对于大量的弹珠和障碍物,这会变得非常耗费计算资源。四叉树或网格等空间划分数据结构可显著提高性能。
2. 可能导致堆栈溢出(递归):虽然此代码中没有直接出现递归,但如果处理不当,递归碰撞解析(即一个弹珠同时与多个障碍物碰撞)可能会导致堆栈溢出。当前的迭代方法更安全。
3. 不准确的碰撞响应:碰撞响应使用基于法向量的一个简单反射。这对于简单形状来说效果相当不错,但可能会导致不准确和抖动,尤其是当弹珠相互碰撞或以锐角碰撞时。更稳健的方法是采用基于脉冲的碰撞解析,该方法会考虑碰撞对象的质量和速度。
4. 缺少 Marble-Marble 碰撞:模拟目前仅处理 Marble-Obstacle 碰撞。添加 Marble-Marble 碰撞检测和分辨率可使模拟更逼真。
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> 元素。此改进版本为 Marble 添加了质量,并略微简化了碰撞解析。对于更高级的功能(球与球的碰撞、空间划分),您需要实现更复杂的算法。不妨研究广泛阶段和狭窄阶段碰撞检测等技术,以便在处理多个对象时获得更好的性能。
|
型号: |
gemini-1.5-flash-002 |
温度: |
0.2 |
输出令牌数量上限: |
8192 |
TopK: |
40.0 |
TopP: |
0.95 |
|