由于施加了不正确的力,我的网格在页面加载时迅速消失?

My grid quickly disappears on page load due to incorrect force being applied?

所以我正在重新创建我很久以前在学习 XNA Framework 时使用的 warping grid from Geometry Wars in a web page to further test my skills with JavaScript and I've hit another snag. I'm following a tutorial written in C# over on TutsPlus 来重新创建它。教程很简单,大部分代码都是不言自明的,但我认为我缺乏良好的数学教育再次让我失望。

我已经成功地在 300x300 canvas 中渲染了网格,没有任何问题,甚至复制了教程中的所有代码,但是由于他们使用 XNA Framework 库,他们有优势不必编写 Vector3 类型的数学函数。我只实现了我需要的东西,但我相信我的数学可能不正确,或者可能是实现不正确。

在我开始与它交互之前,初始网格(上图)应该是这样的,只要我禁用 GridUpdate 功能,它就会这样。我已经逐步完成了代码,这个问题似乎与我对向量大小的计算有关。 XNA Framework 库总是称它为 LengthLengthSquared,但我执行的每个 Google 搜索都返回用于计算幅度的结果:

现在,这在代码中重新创建非常简单,我的 Vector3 class 解释了 MagnitudeMagnitudeSquared,因为教程需要两者。我将我的震级计算结果与 an online calculator 的结果进行了比较,结果是一样的:

V = (2, 3, 4)

|V| = 5.385164807134504

最重要的是,此计算器的 URL 表示我正在计算向量的长度。这就是让我相信可能是我在这里的实施导致整个事情变得疯狂的原因。我在下面包含了我的代码片段,不幸的是它有点长,但我向你保证它已经被尽可能地修剪了。

class Vector3 {
  constructor(x, y, z) {
    this.X = x;
    this.Y = y;
    this.Z = z;
  }
  Add(val) {
    this.X += val.X;
    this.Y += val.Y;
    this.Z += val.Z;
  }
  Subtract(val) {
    this.X -= val.X;
    this.Y -= val.Y;
    this.Z -= val.Z;
  }
  MultiplyByScalar(val) {
    let result = new Vector3(0, 0, 0);
    result.X = this.X * val;
    result.Y = this.Y * val;
    result.Z = this.Z * val;
    return result;
  }
  DivideByScalar(val) {
    let result = new Vector3(0, 0, 0);
    result.X = this.X / val;
    result.Y = this.Y / val;
    result.Z = this.Z / val;
    return result;
  }
  Magnitude() {
    if (this.X == 0 && this.Y == 0 && this.Z == 0)
      return 0;
    return Math.sqrt(Math.pow(this.X, 2) +
      Math.pow(this.Y, 2) +
      Math.pow(this.Z, 2));
  }
  MagnitudeSquared() {
    return Math.pow(this.Magnitude(), 2);
  }
  DistanceFrom(to) {
    let x = Math.pow(this.X - to.X, 2);
    let y = Math.pow(this.Y - to.Y, 2);
    let z = Math.pow(this.Z - to.Z, 2);
    return Math.sqrt(x + y + z);
  }
}
class PointMass {
  Acceleration = new Vector3(0, 0, 0);
  Velocity = new Vector3(0, 0, 0);
  Damping = 0.95;
  constructor(position, inverseMass) {
    this.Position = position;
    this.InverseMass = inverseMass;
  }
  IncreaseDamping(factor) {
    this.Damping *= factor;
  }
  ApplyForce(force) {
    this.Acceleration.Add(force.MultiplyByScalar(this.InverseMass));
  }
  Update() {
    this.Velocity.Add(this.Acceleration);
    this.Position.Add(this.Velocity);
    this.Acceleration = new Vector3(0, 0, 0);

    if (this.Velocity.MagnitudeSquared() < 0.001 * 0.001)
      Velocity = new Vector3(0, 0, 0);

    this.Velocity.MultiplyByScalar(this.Damping);
    this.Damping = 0.95;
  }
}
class Spring {
  constructor(startPoint, endPoint, stiffness, damping) {
    this.StartPoint = startPoint;
    this.EndPoint = endPoint;
    this.Stiffness = stiffness;
    this.Damping = damping;
    this.TargetLength = startPoint.Position.DistanceFrom(endPoint.Position) * 0.95;
  }
  Update() {
    let x = this.StartPoint.Position;
    x.Subtract(this.EndPoint.Position);
    let magnitude = x.Magnitude();
    if (magnitude < this.TargetLength || magnitude == 0)
      return;

    x = x.DivideByScalar(magnitude).MultiplyByScalar(magnitude - this.TargetLength);
    let dv = this.EndPoint.Velocity;
    dv.Subtract(this.StartPoint.Velocity);
    let force = x.MultiplyByScalar(this.Stiffness)
    force.Subtract(dv.MultiplyByScalar(this.Damping));
    this.StartPoint.ApplyForce(force);
    this.EndPoint.ApplyForce(force);
  }
}
class Grid {
  Springs = [];
  Points = [];
  constructor(containerID, spacing) {
    this.Container = document.getElementById(containerID);
    this.Width = this.Container.width;
    this.Height = this.Container.height;

    this.ColumnCount = this.Width / spacing + 1;
    this.RowCount = this.Height / spacing + 1;

    let columns = [];
    let fixedColumns = [];
    let rows = [];
    let fixedRows = [];
    let fixedPoints = [];
    for (let y = 0; y < this.Height; y += spacing) {
      for (let x = 0; x < this.Width; x += spacing) {
        columns.push(new PointMass(new Vector3(x, y, 0), 1));
        fixedColumns.push(new PointMass(new Vector3(x, y, 0), 0));
      }
      rows.push(columns);
      fixedRows.push(fixedColumns);
      columns = [];
      fixedColumns = [];
    }
    this.Points = rows;

    for (let y = 0; y < rows.length; y++) {
      for (let x = 0; x < rows[y].length; x++) {
        if (x == 0 || y == 0 || x == rows.length - 1 || x == rows[y].length - 1)
          this.Springs.push(new Spring(fixedRows[x][y], this.Points[x][y], 0.1, 0.1));
        else if (x % 3 == 0 && y % 3 == 0)
          this.Springs.push(new Spring(fixedRows[x][y], this.Points[x][y], 0.002, 0.002));

        const stiffness = 0.28;
        const damping = 0.06;
        if (x > 0)
          this.Springs.push(new Spring(this.Points[x - 1][y], this.Points[x][y], stiffness, damping));
        if (y > 0)
          this.Springs.push(new Spring(this.Points[x][y - 1], this.Points[x][y], stiffness, damping));
      }
    }
  }
  ApplyDirectedForce(force, position, radius) {
    this.Points.forEach(function(row) {
      row.forEach(function(point) {
        if (point.Position.DistanceFrom(position) < Math.pow(radius, 2))
          point.ApplyForce(force.MultiplyByScalar(10).DivideByScalar(10 + point.Position.DistanceFrom(position)));
      });
    });
  }
  ApplyImplosiveForce(force, position, radius) {
    this.Points.forEach(function(point) {
      let distance_squared = Math.pow(point.Position.DistanceFrom(position));
      if (distance_squared < Math.pow(radius, 2)) {

        point.ApplyForce(force.MultiplyByScalar(10).Multiply(position.Subtract(point.Position)).DivideByScalar(100 + distance_squared));
        point.IncreaseDamping(0.6);
      }
    });
  }
  ApplyExplosiveForce(force, position, radius) {
    this.Points.forEach(function(point) {
      let distance_squared = Math.pow(point.Position.DistanceFrom(position));
      if (distance_squared < Math.pow(radius, 2)) {
        point.ApplyForce(force.MultiplyByScalar(100).Multiply(point.Position.Subtract(position)).DivideByScalar(10000 + distance_squared));
        point.IncreaseDamping(0.6);
      }
    });
  }
  Update() {
    this.Springs.forEach(function(spring) {
      spring.Update();
    });
    this.Points.forEach(function(row) {
      row.forEach(function(point) {
        point.Update();
      });
    });
  }
  Draw() {
    const context = this.Container.getContext('2d');
    context.clearRect(0, 0, this.Width, this.Height);
    context.strokeStyle = "#ffffff";
    context.fillStyle = "#ffffff";
    for (let y = 1; y < this.Points.length; y++) {
      for (let x = 1; x < this.Points[y].length; x++) {
        let left = new Vector3(0, 0, 0);
        let up = new Vector3(0, 0, 0);

        if (x > 1) {
          left = this.Points[x - 1][y].Position;
          context.beginPath();
          context.moveTo(left.X, left.Y);
          context.lineTo(this.Points[x][y].Position.X, this.Points[x][y].Position.Y);
          context.stroke();
        }
        if (y > 1) {
          up = this.Points[x][y - 1].Position;
          context.beginPath();
          context.moveTo(up.X, up.Y);
          context.lineTo(this.Points[x][y].Position.X, this.Points[x][y].Position.Y);
          context.stroke();
        }

        let radius = 3;
        if (y % 3 == 1)
          radius = 5;

        context.beginPath();
        context.arc(this.Points[x][y].Position.X, this.Points[x][y].Position.Y, radius, 0, 2 * Math.PI);
        context.fill();
      }
    }
  }
}

var grid = new Grid("grid", 40);
setInterval(function() {
  grid.Update();
  grid.Draw();
}, 5);

var mouseX = 0;
var mouseY = 0;

function updateMouseCoordinates(evt) {
  var rect = grid.Container.getBoundingClientRect();
  mouseX = evt.clientX - rect.left;
  mouseY = evt.clientY - rect.top;

  const context = grid.Container.getContext('2d');
  context.clearRect(0, 0, this.Width, this.Height);
  context.strokeStyle = "#ffffff";
  context.fillStyle = "#ff3333";
  context.beginPath();
  context.arc(mouseX, mouseY, 15, 0, 2 * Math.PI);
  context.fill();
  grid.ApplyDirectedForce(new Vector3(0, 0, 5000), new Vector3(mouseX, mouseY, 0), 50);
}
html,
body {
  margin: 0;
  height: 100%;
  background: #213;
  background: linear-gradient(45deg, #213, #c13);
  background: -webkit-linear-gradient(45deg, #213, #c13);
}

.container {
  position: relative;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
<div class="container">
  <canvas onmousemove="updateMouseCoordinates(event)" id="grid" class="grid" width="300" height="300"></canvas>
</div>

我认为这个问题与 SpringPointMass class 中的 Update 方法有关,因为当我单步执行代码时,我一直发现 PointMass 对象似乎在不应该加速的情况下加速(例如,我还没有与它们进行交互)。老实说,我认为这是导致问题的那些更新函数中自定义 Vector3 函数的实现,但对于我来说,我无法弄清楚我在这里做错了什么。

也许我只是需要休息一下再回来,但我希望这里的人可以帮助发现不正确的实现。


如何防止我的网格因尚未施加的力(因为它们只是计算错误)而立即消散?

我的建议是减少问题。只有一个点,放慢间隔,逐步查看发生了什么。鼠标似乎没有做任何事情。注释掉行 grid.ApplyDirectedForce(new Vector3(0, 0, 5000), new Vector3(mouseX, mouseY, 0), 50); 不会更改输出。它在 grid.Update() 中出错,由于某种原因 grid.Update() 即使没有施加力也会做一些事情,也许这意味着 spring 代码有错误。右下角的点不会移动第一帧,这可能意味着什么。调试器是你的朋友。在 grid.Update() 中添加断点并查看代码实际执行的操作。我知道这不是一个直接的答案,但我希望这能引导您朝着正确的方向前进。

我还想指出,通常 Magnitude Squared 的全部意义在于,您可以比较向量或距离而无需进行平方根运算。也就是说,在您的 Magnitude 函数中执行平方根运算,然后在您的 Magnitude Squared 函数中对其求平方。这与简单地执行 x^2 + y^2 + z^2

相同

第 1 帧:

第 2 帧: