为什么这段代码创建数组填充相同的重复值?



我一直在尝试使用HTML5 Canvas编写模拟器。我有下面的代码来生成电场的等势线。

function drawEqLines(charges) {
// don't bother if there aren't any charges
if (charges.length == 0) return;
// the Charge class contains the charge of the charge as well as its x and y coordinates

var fieldFilled = [];

for (var i = 0; i < 10; i++) {
fieldFilled.push([]);

for (var j = 0; j < 10; j++) {
fieldFilled[i].push(false);
}
}

var calculatedFields = [];
var maxForce = 0;
for (var i = 0; i < fieldFilled.length; i++) {
var direction = 1;
for (var jj=0; jj < fieldFilled[i].length; jj++) {
if (!fieldFilled[i][jj]) {
//create a path here
//Iterate at most 2 times in case the surface gets out of the area
for (var circleTimes = 0; circleTimes < 3; circleTimes+=2) {
//Define the center of the current block as a starting point of the surface
/* 
horizontalBlock and verticalBlock are the width and height of the canvas divided into 10 different sections.
_BlockHalf is half of one of the blocks
*/
var curX = i * this.horizontalBlock + this.horizontalBlockHalf;
var curY = jj * this.verticalBlock + this.verticalBlockHalf;
// Point is a class that contains an x and y value
var curPt = new Point(curX, curY);
var direction = 1 - circleTimes;
var dots = [];
dots.push(curPt);
//Superposition the fields from all charges, and get the resulting force vector
var dirX = 0;
var dirY = 0;
var totalForce = 0;
for (var j = 0; j < charges.length; j++) {
var distX = curPt.x - charges[j].x;
var distY = curPt.y - charges[j].y;
var distanceSq = distX * distX + distY * distY;
var force = charges[j].charge / distanceSq;
var distanceFactor = force / Math.sqrt(distanceSq);
//Measure the initial force in order to match the equipotential surface points
totalForce += force;
dirX += distX * distanceFactor;
dirY += distY * distanceFactor;
}
//Maximum 2000 dots per surface line
var times = 2000;
while (times-- > 0) {
var dirTotal = Math.sqrt(dirX * dirX + dirY * dirY);
var stepX = dirX / dirTotal;
var stepY = dirY / dirTotal;
//The equipotential surface moves normal to the force vector
curPt.x = curPt.x + direction * 6 * stepY;
curPt.y = curPt.y - direction * 6 * stepX;
/*
*
* ***********************LOG LINE HERE***********************************
*
*/
// this prints out unique values
console.log(curPt.x + ", " + curPt.y);
//Correct the exact point a bit to match the initial force as near it can
var minForceIndex = -1;
var minForceDiff = 0;
var minDirX = 0;
var minDirY = 0;
var minCurX = 0;
var minCurY = 0;
curPt.x -= 3 * stepX;
curPt.y -= 3 * stepY;
for (var pointIndex = 0; pointIndex < 7; pointIndex++, curPt.x += stepX, curPt.y += stepY) {
dirX = 0;
dirY = 0;
var forceSum = 0;
for (var j = 0; j < charges.length; j++) {
var distX = curPt.x - charges[j].x;
var distY = curPt.y - charges[j].y;
var distanceSq = distX ** 2 + distY ** 2;
var force = charges[j].charge / distanceSq;
var distanceFactor = force / Math.sqrt(distanceSq);

//Measure the initial force in order to match the equipotential surface points
forceSum += force;
dirX += distX * distanceFactor;
dirY += distY * distanceFactor;
}
var forceDiff = Math.abs(forceSum - totalForce);
if (minForceIndex == -1 || forceDiff < minForceDiff) {
minForceIndex = pointIndex;
minForceDiff = forceDiff;
minDirX = dirX;
minDirY = dirY;
minCurX = curPt.x;
minCurY = curPt.y;
} else {
break;
}
}
//Set the corrected equipotential point
curPt.x = minCurX;
curPt.y = minCurY;
dirX = minDirX;
dirY = minDirY;
//Mark the containing block as filled with a surface line.
var indI = parseInt(curPt.x / this.horizontalBlock);
var indJ = parseInt(curPt.y / this.verticalBlock);
if (indI >= 0 && indI < fieldFilled.length) {
if (indJ >= 0 && indJ < fieldFilled[indI].length) {
fieldFilled[indI][indJ] = true;
}
}
//Add the dot to the line (was commented out when I added the other log)
dots.push(curPt);
if (dots.length > 5) {
//If got to the begining, a full circle has been made, terminate further iterations
if (indI == i && indJ == jj) {
distX = dots[0].x - curPt.x;
distY = dots[0].y - curPt.y;
if (distX * distX + distY * distY <= 49) {
dots.push(new Point(dots[0].x, dots[0].y));
times = 0;
circleTimes = 3;
}
}
//If got out of the area, terminate further iterations for this turn.
if (curPt.x < 0 || curPt.x > this.canvas.width || curPt.y < 0 || curPt.y > this.canvas.height) {
times = 0;
}
}
}
calculatedFields.push([totalForce, dots]);
maxForce = Math.max(maxForce, Math.abs(totalForce));
}
}
}
}
console.log(calculatedFields);

// draw the lines from the arrays of dots here...
}
这段代码的结果是一个像这样的数组:
[
[
0.000007927962725256215,
[
// as you can see, these points are all the same
{x: 114.16365557137308, y: 402.6147544331975},
{x: 114.16365557137308, y: 402.6147544331975},
{x: 114.16365557137308, y: 402.6147544331975},
{x: 114.16365557137308, y: 402.6147544331975},
// etc...
]
],
[
0.000006140401131528559,
[
{x: -1.0201768243546043, y: 323.28955989370445},
{x: -1.0201768243546043, y: 323.28955989370445},
{x: -1.0201768243546043, y: 323.28955989370445},
{x: -1.0201768243546043, y: 323.28955989370445},
// etc...
]
]
]

这些点应该是唯一的,然而,它似乎只是一次又一次地产生相同的点。由于某种原因,它确实决定在合理的迭代次数后停止,就好像它正在正确地进行计算,但只是将相同的点放入数组中。

为什么会发生这种情况?我该怎么修理它?

更新:这里是我正在使用的类:

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Charge {
constructor(charge, x, y) {
this.charge = charge;
this.x = x;
this.y = y;
}
}

此外,charges数组很好,因为我在一个类似的方法中使用它,它确实正确工作。例如:

[
{charge: 6, x: 50, y: 50},
{charge: -4, x: 70, y: 90.5},
// etc...
]

更新2:我已经尝试添加上面代码中所示的日志行,它只打印出唯一的值。然而,即使我尝试在这里将点推入数组,它仍然会导致相同的问题。

更新3:添加了这个可运行的代码片段:

const height = 400; // for example
const width = 600; // also for example
const horizontalBlock = width / 10;
const verticalBlock = height / 10;
const horizontalBlockHalf = horizontalBlock / 2;
const verticalBlockHalf = verticalBlock / 2;
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
function drawEqLines(charges) {
// don't bother if there aren't any charges
if (charges.length == 0) return;
// the Charge class contains the charge of the charge as well as its x and y coordinates

var fieldFilled = [];

for (var i = 0; i < 10; i++) {
fieldFilled.push([]);

for (var j = 0; j < 10; j++) {
fieldFilled[i].push(false);
}
}

var calculatedFields = [];
var maxForce = 0;
for (var i = 0; i < fieldFilled.length; i++) {
var direction = 1;
for (var jj=0; jj < fieldFilled[i].length; jj++) {
if (!fieldFilled[i][jj]) {
//create a path here
//Iterate at most 2 times in case the surface gets out of the area
for (var circleTimes = 0; circleTimes < 3; circleTimes+=2) {
//Define the center of the current block as a starting point of the surface
/* 
horizontalBlock and verticalBlock are the width and height of the canvas divided into 10 different sections.
_BlockHalf is half of one of the blocks
*/
var curX = i * horizontalBlock + horizontalBlockHalf;
var curY = jj * verticalBlock + verticalBlockHalf;
// Point is a class that contains an x and y value
var curPt = new Point(curX, curY);
var direction = 1 - circleTimes;
var dots = [];
dots.push(curPt);
//Superposition the fields from all charges, and get the resulting force vector
var dirX = 0;
var dirY = 0;
var totalForce = 0;
for (var j = 0; j < charges.length; j++) {
var distX = curPt.x - charges[j].x;
var distY = curPt.y - charges[j].y;
var distanceSq = distX * distX + distY * distY;
var force = charges[j].charge / distanceSq;
var distanceFactor = force / Math.sqrt(distanceSq);
//Measure the initial force in order to match the equipotential surface points
totalForce += force;
dirX += distX * distanceFactor;
dirY += distY * distanceFactor;
}
//Maximum 2000 dots per surface line
var times = 2000;
while (times-- > 0) {
var dirTotal = Math.sqrt(dirX * dirX + dirY * dirY);
var stepX = dirX / dirTotal;
var stepY = dirY / dirTotal;
//The equipotential surface moves normal to the force vector
curPt.x = curPt.x + direction * 6 * stepY;
curPt.y = curPt.y - direction * 6 * stepX;
/*
*
* ***********************LOG LINE HERE***********************************
*
*/
// this prints out unique values
console.log(curPt.x + ", " + curPt.y);
//Correct the exact point a bit to match the initial force as near it can
var minForceIndex = -1;
var minForceDiff = 0;
var minDirX = 0;
var minDirY = 0;
var minCurX = 0;
var minCurY = 0;
curPt.x -= 3 * stepX;
curPt.y -= 3 * stepY;
for (var pointIndex = 0; pointIndex < 7; pointIndex++, curPt.x += stepX, curPt.y += stepY) {
dirX = 0;
dirY = 0;
var forceSum = 0;
for (var j = 0; j < charges.length; j++) {
var distX = curPt.x - charges[j].x;
var distY = curPt.y - charges[j].y;
var distanceSq = distX ** 2 + distY ** 2;
var force = charges[j].charge / distanceSq;
var distanceFactor = force / Math.sqrt(distanceSq);

//Measure the initial force in order to match the equipotential surface points
forceSum += force;
dirX += distX * distanceFactor;
dirY += distY * distanceFactor;
}
var forceDiff = Math.abs(forceSum - totalForce);
if (minForceIndex == -1 || forceDiff < minForceDiff) {
minForceIndex = pointIndex;
minForceDiff = forceDiff;
minDirX = dirX;
minDirY = dirY;
minCurX = curPt.x;
minCurY = curPt.y;
} else {
break;
}
}
//Set the corrected equipotential point
curPt.x = minCurX;
curPt.y = minCurY;
dirX = minDirX;
dirY = minDirY;
//Mark the containing block as filled with a surface line.
var indI = parseInt(curPt.x / this.horizontalBlock);
var indJ = parseInt(curPt.y / this.verticalBlock);
if (indI >= 0 && indI < fieldFilled.length) {
if (indJ >= 0 && indJ < fieldFilled[indI].length) {
fieldFilled[indI][indJ] = true;
}
}
//Add the dot to the line (was commented out when I added the other log)
dots.push(curPt);
if (dots.length > 5) {
//If got to the begining, a full circle has been made, terminate further iterations
if (indI == i && indJ == jj) {
distX = dots[0].x - curPt.x;
distY = dots[0].y - curPt.y;
if (distX * distX + distY * distY <= 49) {
dots.push(new Point(dots[0].x, dots[0].y));
times = 0;
circleTimes = 3;
}
}
//If got out of the area, terminate further iterations for this turn.
if (curPt.x < 0 || curPt.x > width || curPt.y < 0 || curPt.y > height) {
times = 0;
}
}
}
calculatedFields.push([totalForce, dots]);
maxForce = Math.max(maxForce, Math.abs(totalForce));
}
}
}
}
console.log(calculatedFields);

// draw the lines from the arrays of dots here...
}
var charges = [
{charge: 6, x: 50, y: 75},
{charge: -5, x: 60, y: 254.5}
]
drawEqLines(charges);

你有一个循环的代码是:dots.push(curPt)

在这个循环中,您重新分配curPt对象的xy属性,但它是相同的对象,在每次迭代中不断推入。

如果你想把不同的点压入数组中,你必须在循环中创建一个新的Point对象