You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让向中心点引力聚集的圆形均匀分布且无重叠?

问题:圆形向中心聚集时重叠严重,调整排斥力导致抖动的解决办法

当前代码实现了圆形向画布中心聚集的效果,但圆形之间存在严重重叠。希望修改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);

修改说明

  1. 速度逻辑调整:不再直接重置速度为中心方向,而是叠加中心吸引力的加速度,保留碰撞产生的排斥速度效果
  2. 对称碰撞处理:碰撞时同时修改两个圆形的速度,符合物理规律,避免单向力导致的不平衡
  3. 添加阻尼:每次更新时对速度乘以阻尼系数,减少速度突变,消除抖动
  4. 碰撞检测优化:只检测i<j的圆形对,减少一半计算量,提升性能
  5. 参数分离:将吸引力、排斥力、阻尼拆分为独立参数,方便调整平滑度和聚集效果

内容的提问来源于stack exchange,提问作者Ryan Peschel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 01:15:52