Итак, я воссоздаю деформирующую сетку из Geometry Wars на веб-странице, чтобы дополнительно проверить свои навыки с помощью JavaScript
, и я столкнулся с еще одной загадкой.Я следую учебному пособию, написанному на C#
поверх TutsPlus , которое я использовал давно, чтобы воссоздать его во время изучения XNA Framework.Это простое руководство, и большая часть кода не требует пояснений, но я думаю, что мое отсутствие высшего образования по математике подводит меня еще раз.
Я успешно преобразовал сетку в 300x300 canvas
без проблем и даже реплицировал весь код в учебнике, но поскольку они используют библиотеки XNA Framework, у них есть преимущество в том, что им не нужно писать математические функции типа Vector3
.Я реализовал только то, что мне нужно, но я считаю, что, возможно, я неправильно понял мою математику или, возможно, реализацию.
Исходная сетка (см. Выше) должен выглядеть так, пока я не начну с ним взаимодействовать, и это так, пока я отключаю функцию Update
моего Grid
.Я прошел через код, и проблема, кажется, связана с моим вычислением для величины моих векторов.Библиотеки XNA Framework всегда называли его Length
и LengthSquared
, но каждый поиск Google, который я выполнял, возвращал результаты для вычисления величины как:
Теперь это невероятно просто воссоздать в коде, и мой класс Vector3
учитывает Magnitude
и MagnitudeSquared
, так как учебник требует обоих.Я сравнил результаты моего вычисления величины с онлайн-калькулятором , и результаты были такими же:
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>
Я считаю, что проблема как-то связана с методом Update
в классах Spring
и PointMass
, как когда я проходил черезВ моем коде я обнаружил, что PointMass
объекты, похоже, ускоряются, когда не должны (как, например, я еще не взаимодействовал с ними).Честно говоря, я думаю, что именно реализация моих пользовательских функций Vector3
в этих функциях обновления вызывает проблему, но на всю жизнь я не могу понять, что я сделал неправильно здесь.
Возможно, мне просто нужно сделать перерыв и вернуться к нему, но я надеюсь, что кто-то здесь может помочь определить неправильную реализацию.
Как я могу предотвратить немедленное рассеяние моей сетки из-засилы, которые еще не были приложены (так как в них просто просчеты)?