如何让向中心点引力聚集的圆形均匀分布且无重叠?
问题:圆形向中心聚集时重叠严重,调整排斥力导致抖动的解决办法
当前代码实现了圆形向画布中心聚集的效果,但圆形之间存在严重重叠。希望修改processCollision函数,让圆形不再明显重叠,大致均匀分布在中心点周围;同时避免增大排斥力后出现的抖动问题,保持运动流畅。
原代码
JavaScript
const rng = (min, max) => Math.random() * (max - min + 1) + min; const canvas = document.querySelector("canvas"); const ctx = canvas.getContext("2d"); ctx.strokeStyle = "#000"; ctx.lineWidth = 4; ctx.fillStyle = "#ff0000"; function drawCircle(c) { ctx.beginPath(); ctx.arc(c.x, c.y, c.r, 0, 2 * Math.PI); ctx.stroke(); ctx.fill(); } class Circle { constructor(x, y, r) { this.x = x; this.y = y; this.r = r; this.vX = 0; this.vY = 0; } } const circles = []; for (let i = 0; i < 300; i++) { circles.push(new Circle(rng(0, canvas.width), rng(0, canvas.height), rng(12, 14))); } function processCollision(c1, c2) { const deltaX = c2.x - c1.x; const deltaY = c2.y - c1.y; const sumRadius = c1.r + c2.r; const centerDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (centerDistance === 0 || centerDistance > sumRadius) { return; } // not colliding const circleDistance = centerDistance - sumRadius; const aX = deltaX / centerDistance; const aY = deltaY / centerDistance; const force = 5; c1.vX += aX * circleDistance * force; c1.vY += aY * circleDistance * force; } function update() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (const c of circles) { c.vX = (canvas.width / 2) - c.x; // move towards center x c.vY = (canvas.height / 2) - c.y; // move towards center y } for (const c1 of circles) { for (const c2 of circles) { c1 !== c2 && processCollision(c1, c2); } } for (const c of circles) { c.x += c.vX * (1 / 60); c.y += c.vY * (1 / 60); drawCircle(c); } } setInterval(update, 16.6666);
HTML
<canvas width="600" height="600" style="border:1px solid #d3d3d3;">
解决方案
问题根源有三点:
- 每次
update直接重置速度为中心方向向量,覆盖了碰撞产生的排斥速度,导致排斥力效果被抵消 - 碰撞处理只修改了
c1的速度,没有对称修改c2的速度,物理逻辑不严谨 - 缺少阻尼机制,速度累积后容易出现抖动
修改后的代码如下,重点调整了processCollision函数和速度更新逻辑:
const rng = (min, max) => Math.random() * (max - min + 1) + min; const canvas = document.querySelector("canvas"); const ctx = canvas.getContext("2d"); ctx.strokeStyle = "#000"; ctx.lineWidth = 4; ctx.fillStyle = "#ff0000"; function drawCircle(c) { ctx.beginPath(); ctx.arc(c.x, c.y, c.r, 0, 2 * Math.PI); ctx.stroke(); ctx.fill(); } class Circle { constructor(x, y, r) { this.x = x; this.y = y; this.r = r; this.vX = 0; this.vY = 0; } } const circles = []; const centerX = canvas.width / 2; const centerY = canvas.height / 2; const attractionForce = 0.1; // 中心吸引力强度 const repulsionForce = 0.5; // 碰撞排斥力强度 const damping = 0.95; // 速度阻尼,用于平滑运动 for (let i = 0; i < 300; i++) { circles.push(new Circle(rng(0, canvas.width), rng(0, canvas.height), rng(12, 14))); } function processCollision(c1, c2) { const deltaX = c2.x - c1.x; const deltaY = c2.y - c1.y; const sumRadius = c1.r + c2.r; const centerDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (centerDistance < 0.01 || centerDistance > sumRadius) { return; } // 避免除零或未碰撞 // 计算重叠深度 const overlap = sumRadius - centerDistance; // 单位方向向量 const dirX = deltaX / centerDistance; const dirY = deltaY / centerDistance; // 给两个圆形施加对称的排斥加速度 const pushX = dirX * overlap * repulsionForce; const pushY = dirY * overlap * repulsionForce; c1.vX -= pushX; c1.vY -= pushY; c2.vX += pushX; c2.vY += pushY; } function update() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (const c of circles) { // 叠加中心吸引力,而不是直接重置速度 const deltaCenterX = centerX - c.x; const deltaCenterY = centerY - c.y; c.vX += deltaCenterX * attractionForce; c.vY += deltaCenterY * attractionForce; } // 优化碰撞检测:只检测i<j的组合,避免重复计算 for (let i = 0; i < circles.length; i++) { const c1 = circles[i]; for (let j = i + 1; j < circles.length; j++) { const c2 = circles[j]; processCollision(c1, c2); } } for (const c of circles) { // 添加阻尼,平滑速度 c.vX *= damping; c.vY *= damping; c.x += c.vX * (1 / 60); c.y += c.vY * (1 / 60); drawCircle(c); } } setInterval(update, 16.6666);
修改说明
- 速度逻辑调整:不再直接重置速度为中心方向,而是叠加中心吸引力的加速度,保留碰撞产生的排斥速度效果
- 对称碰撞处理:碰撞时同时修改两个圆形的速度,符合物理规律,避免单向力导致的不平衡
- 添加阻尼:每次更新时对速度乘以阻尼系数,减少速度突变,消除抖动
- 碰撞检测优化:只检测
i<j的圆形对,减少一半计算量,提升性能 - 参数分离:将吸引力、排斥力、阻尼拆分为独立参数,方便调整平滑度和聚集效果
内容的提问来源于stack exchange,提问作者Ryan Peschel
相关产品推荐
相关产品推荐

