For my game, I am dealing with a 2D voxel map stored as a 2D array where 1 represents ground and 0 represents sky.
In the map, areas marked as 1 (ground) are represented by green boxes https://i.sstatic.net/rG7N5.png
The algorithm initiates at the leftmost ground voxel touching the sky (marked red in the picture).
It scans the 8 neighboring positions to identify if any of them are ground voxels that also touch sky voxels. These points are added to the groundline.
The algorithm works efficiently even when navigating through 'caves'. https://i.sstatic.net/Gk2Wa.png
However, there are instances where the algorithm abruptly stops like on this particular map: https://i.sstatic.net/oQOYL.png
After around 10 iterations, it fails to continue creating the line.
Below is the code along with explanatory comments:
voxelToLine() {
let voxels = this.voxels.length, //this.voxels is the 2d array
lineGround = [],
checkedVoxels = [],
nowChecking,
toCheck = [],
otherPaths = [],
done = false;
for (let y = 1; y < voxels - 1; y++) //sets first coordinate for line
if (this.voxels[0][y] && (!this.voxels[0][y - 1] || !this.voxels[1][y] || !this.voxels[0][y + 1])) {
lineGround[0] = [0, y / voxels];
nowChecking = [1, y]; //search starts from this point
}
let looped = 0;
while (!done) { //continues search until right side is located or gets stuck (max 10*voxelmap width loops)
toCheck = nowChecking.neighbours(8, (n) => n[0] > 0 && n[0] < voxels - 1); //gets 8 neighbour points around the current point
let foundNew = false;
for (let i = 0; i < toCheck.length; i++) {
let x = toCheck[i][0],
y = toCheck[i][1],
index = y * voxels + x;
if (!checkedVoxels.includes(index)) {
if (this.voxels[x][y] && (!this.voxels[x][y - 1] || !this.voxels[x + 1][y] || !this.voxels[x - 1][y] || !this.voxels[x][y + 1])) {
checkedVoxels.push(index);
if (foundNew) {
otherPaths.push([x, y]);
} else {
lineGround.push([x / voxels, y / voxels]);
nowChecking = [x, y];
foundNew = true;
}
if (x >= voxels) done = true;
}
} else if (i == toCheck.length - 1 && !foundNew) {
if (otherPaths.length > 0) {
nowChecking = otherPaths.pop();
foundNew = true;
}
}
}
if (!foundNew || looped++ > voxels * 10) {
console.log('loops: ', looped);
break;
}
}
if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]);
if (lineGround[lineGround.length - 1][0] !== 1) lineGround.push([1, lineGround[lineGround.length - 1][1]);
return lineGround;
}
You can test it here: game. Clicking removes some voxels within a radius and recalculates the line.
I am facing a challenge as to why the line discontinues in certain scenarios. All code is available here, with the relevant file being js/Level.js.