【问题标题】:How to evenly spread circles gravitating towards a point?如何均匀分布吸引点的圆圈?
【发布时间】:2022-09-30 21:55:30
【问题描述】:

我已经创建了我在下面遇到的问题的完整演示:

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);
&lt;canvas width=\"600\" height=\"600\" style=\"border:1px solid #d3d3d3;\"&gt;

注意所有的圆圈是如何围绕中心吸引的。但是,它们都在彼此剧烈碰撞。我想修改processCollision 函数,使圆圈不再相互显着重叠,而是大致均匀地分布在中心点周围。

我尝试增加force 变量,但不幸的是,虽然这确实会导致更大的传播,但它也会产生很多摇晃和生涩的运动。解决方案必须是平滑的,类似于上面的示例。我已经搞砸了好几个星期,但不幸的是似乎无法找到解决方案。

  • 您可以尝试实现en.wikipedia.org/wiki/Lennard-Jones_potential 之类的东西 - 传统吸引力 + 排斥力仅在小距离内起作用(总最小能量为 sumRadius)
  • @MBo 你好!嗯,我不太确定这将如何工作。我尝试为它查找一些代码示例,我找到了this relevant answer 并实际上在本地运行了发布的 pygame 示例,但它看起来就像是从墙上弹回的普通圆圈。他们似乎并没有真正相互吸引或排斥。这仅仅是因为他们的示例对变量使用了不同的参数吗?如果是这样,我应该调整什么以获得我正在寻找的结果?我对这个算法很陌生。
  • Lennard-Jones 只是示例 - 您可以拟合一些函数 F(r),该函数对于小距离具有非常高的正值(排斥),对于较大距离具有负值(吸引力),并且在 sumradius 处具有最小值。我试图用你的代码做到这一点,但还没有得到可靠的结果。
  • 啊,非常感谢你调查这个!是的,我一直在尝试类似的调整,当物体严重碰撞时会产生很大的排斥力,但它似乎也只会引起力的爆炸和非常刺耳、生涩的运动。我很难让一些既能工作又能工作的东西既高性能(简单的代码,因为它必须非常频繁地运行)而且也很流畅。

标签: javascript geometry html5-canvas collision-detection physics


【解决方案1】:

这似乎表现出您可能想要(或接近它)的方式......它使用控制理论模型与物理模型相结合,并且需要调整常量k0, k1, strength, buffer, step_size...

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';

const k0 = 1.5;
const k1 = 5;

const strength = 5000000;
const buffer = 5;

class Disc {
  constructor(x, y, r) {
    this.x = x;
    this.y = y;
    this.r = r;
    this.vX = 0;
    this.vY = 0;
    return;
  }
  drawDisc(ctx) {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.fill();
    return;
  }
  addVelocity(step_size) {
    this.x = this.x + step_size * this.vX;
    this.y = this.y + step_size * this.vY;
    return;
  }
  addAcceleration(aX, aY, step_size) {
    this.vX = this.vX + step_size * aX;
    this.vY = this.vY + step_size * aY;
    return;
  }
  applyCentralAcceleration(step_size) {
    const accelX = -k1 * this.vX - k0 * (this.x - canvas.width / 2);
    const accelY = -k1 * this.vY - k0 * (this.y - canvas.height / 2);
    this.addAcceleration(accelX, accelY, step_size);
    return;
  }
  applyInteractionAcceleration(that, step_size) {
    let dX = this.x - that.x;
    let dY = this.y - that.y;
    const dist = dX * dX + dY * dY;
    const magnitude = strength / (dist - (this.r + buffer + that.r) ** 2) ** 2;
    dX = magnitude * dX;
    dY = magnitude * dY;
    this.addAcceleration(dX, dY, step_size);
    return;
  }
}

class System {
  constructor(numDiscs) {
    this.n = numDiscs;
    this.discs = [];
    for (let i = 0; i < numDiscs; i++) {
      this.discs.push(
        new Disc(rng(0, canvas.width), rng(0, canvas.height), rng(12, 14))
      );
    }
    return;
  }
  applyCentralAcceleration(step_size) {
    for (let i = 0; i < this.n; i++) {
      this.discs[i].applyCentralAcceleration(step_size);
    }
  }
  applyInteractionAcceleration(step_size) {
    for (let i = 0; i < this.n; i++) {
      for (let j = 0; j < this.n; j++) {
        if (i === j) {
          continue;
        }
        this.discs[i].applyInteractionAcceleration(this.discs[j], step_size);
      }
    }
  }
  applyVelocity(step_size) {
    for (let i = 0; i < this.n; i++) {
      this.discs[i].addVelocity(step_size);
    }
  }
  updateSystemState(step_size) {
    this.applyCentralAcceleration(step_size);
    this.applyInteractionAcceleration(step_size);
    this.applyVelocity(step_size);
    return;
  }
}

systemOfDiscs = new System(50);

function update() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  const step_size = 1 / 100;
  systemOfDiscs.updateSystemState(step_size);
  for (let i = 0; i < systemOfDiscs.n; i++) {
    systemOfDiscs.discs[i].drawDisc(ctx);
  }
  return;
}

setInterval(update, 16.6666);
&lt;canvas width="1000" height="1000" style="border: 1px solid #d3d3d3"&gt;&lt;/canvas&gt;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多