使用JavaScript空间分区检测对象碰撞

3

我正在尝试创建一个由多个对象组成的系统,当它们相互碰撞时执行某些操作,我正在使用P5.min.js库。

我已经为网格设置了一个数组,并为对象设置了另一个数组,但是我无法弄清如何正确遍历每个网格单元并仅检查该单元格内的对象,然后再移动到下一个单元格。

以下是我目前所拥有的内容。

let molecules = [];
const numOfMolecules = 100;
let collisions = 0;
let check = 0;
let maxR = 10; //max molecule radius
let minR = 2; //min molecule radius
let numOfCol = 5;
let numOfRow = 5;
let CellW = 600/numOfCol; //gridWidth
let CellH = 600/numOfRow; //gridHeight
let remain = numOfMolecules;
let gridArray = [];



function setup() {
    createCanvas(600, 600);
    background(127);

    for (let i = 0; i < numOfMolecules; i++) {
        molecules.push(new Molecule());
    }
}

function draw() {
    background(127);

    molecules.forEach(molecule => {
        molecule.render();
        molecule.checkEdges();
        molecule.step();
    });

    drawGrid();
    splitIntoGrid();
    collision();
    displayFR();
}

function drawGrid() {
    for (i = 0; i < numOfRow+1; i++){
        for (j = 0; j < numOfCol+1; j++){
            noFill();
            stroke(0);
            rect(CellW*(j-1), CellH*(i-1), CellW, CellH);
        }
    }
}

function splitIntoGrid(){
    for (let i = 0; i < numOfRow; i++){
        for (let j = 0; j < numOfCol; j++){
            tempArray = [];
            molecules.forEach(molecule => {
                if (molecule.position.x > (CellW*j) &&
                    molecule.position.x < (CellW*(j+1)) &&
                    molecule.position.y > (CellH*i) &&
                    molecule.position.y < (CellH*(i+1))) {
                        tempArray.push(molecule.id);
                    }
            });
        }
    }
}

我是如何检测所有物体之间碰撞的:

function collision() {
    for (let i=0; i < molecules.length; i++){
        for (let j=0; j < molecules.length; j++){
            let diff = p5.Vector.sub(molecules[j].position, molecules[i].position);
            check++;
            if (i != j && diff.mag() <= molecules[j].radius + molecules[i].radius){
                collisions++;
                molecules[j].changeColor();
            }
        }
    }
}

据我所见,我需要将这些for循环放入另一个循环中,遍历网格中的每个单元格,但我不知道如何限制搜索对象所在的任何tempArray。 如果有意义的话,这就是我想做的事情。
function collision() {
  for (let k = 0; k < gridArray.length; k++){
    for (let i=0; i < gridArray.tempArray.length; i++){
        for (let j=0; j < gridArray.tempArray.length; j++){
            let diff = p5.Vector.sub(gridArray.tempArray[j].position, gridArray.tempArray.position);
            check++;
            if (i != j && diff.mag() <= gridArray.tempArray[j].radius + gridArray.tempArray[i].radius){
                collisions++;
                gridArray.tempArray[j].changeColor();
                gridArray.tempArray[i].changeColor();
            }
        }
    }
  }
}
1个回答

3
网格单元由数组 gridArray 表示。每个网格单元都需要一组分子。我的建议是使用 Set 而不是 Array,因为顺序无关紧要。想法是能够使用以下语法访问给定网格单元 (i,j) 上的分子集合:
gridArray[i][j]

以下代码将创建一个包含 numOfRow 个数组的数组:
const numOfRow = 5;

const gridArray = (new Array(numOfRow)).fill([]);

gridArray将看起来像这样:

[ [], [], [], [], [] ]

splitIntoGrid函数中,您正在检查哪些分子位于哪个网格单元中。这是很好的。但是,对于每个网格单元,您都会覆盖全局变量tempArray。因此,在函数执行结束时,tempArray将仅保存最后一个网格单元的分子,这不是您想要的结果。对于给定的网格单元,我们将把正确的分子添加到与该网格单元相关联的Set中。 Set数据结构具有#add方法,该方法将新元素附加到集合中:
function splitIntoGrid() {
    for (let i = 0; i < numOfRow; i++) {
        for (let j = 0; j < numOfCol; j++) {
            gridArray[i][j] = new Set();
            molecules.forEach(molecule => {
                if (molecule.position.x > (CellW*j) 
                    && molecule.position.x < (CellW*(j+1))
                    && molecule.position.y > (CellH*i) 
                    && molecule.position.y < (CellH*(i+1))) {
                        gridArray[i][j].add(molecule);
                    }
            });
        }
    }
}

现在,您已经准备好检查每个网格单元格上的碰撞了。我们将有四个嵌套循环。两个用于浏览网格,另外两个用于比较包含在每个网格单元格中的分子:
function collision() {
  for (let i = 0; i < numOfRow; i++) {
    for (let j = 0; j < numOfCol; j++) {
      gridArray[i][j].forEach(moleculeA => {
        gridArray[i][j].forEach(moleculeB => {
          const diff = p5.Vector.sub(moleculeA.position, moleculeB.position);
          if (moleculeA != moleculeB && diff.mag() <= moleculeA.radius + moleculeB.radius) {
            collisions++;
            moleculeA.changeColor();
            moleculeB.changeColor();
          }
        });
      });
    }
  }
}

在上述代码中,#forEach 很方便。

谢谢Ivan,我之前不熟悉集合。我已经按照你讲解的修改了所有函数,除了碰撞检测外,其他都完美运行。我需要在哪里声明和更新moleculeA和moleculeB吗?当我运行碰撞检测时,出现了这个错误:https://imgur.com/a/YnuaIOz - Marcus
@Marcus,你能在const diff行之前添加一个console.log(i,j)并告诉我控制台中记录了什么吗?这将有助于确定gridArray是否是问题所在。打印moleculeA.positionmoleculeB.position也会有所帮助。注意:两者都可用,因为您正在遍历gridArray(这是一个二维数组)。 - Ivan
问题出现在第一个网格单元gridArray [0] [0]上。您能否注释掉两个gridArray [i] [j] forEach循环,并添加gridArray [i] [j] .forEach(console.log)?像这样:function collision() { for (let i = 0; i <numOfRow; i ++) {for (let j = 0; j <numOfCol; j ++) {gridArray [i] [j] .forEach(console.log)}}}。此外,您可能需要检查分子是否具有“position”属性。 (@Marcus) - Ivan
抱歉回复晚了,看起来它正在无误地遍历数组,并且每个分子的位置都被保存了。https://imgur.com/a/JqrczKR - Marcus
当girdArray [i] [j] forEach循环没有被注释掉时,我仍然遇到问题。 - Marcus
抱歉,我没有意识到你在这个日志中注释了循环。我刚刚注意到我正在将分子的“id”添加到集合中而不是整个对象(在splitIntoGrid内)。用gridArray[i][j].add(molecule)替换gridArray[i][j].add(molecule.id),它应该可以正常工作! - Ivan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接