Algorithm for converting a 2D voxel map into a line may encounter occasional difficulty in progressing

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

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'.

However, there are instances where the algorithm abruptly stops like on this particular map:

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.

Answer №1

Upon exploring your website, I noticed several other issues aside from the one you mentioned. While trying to comprehend your code's logic, I found myself getting lost in the complexities. Consequently, I took the liberty of rewriting a significant portion of your code. The crux of my modification revolves around keeping track of the direction (slope) traveled along the ground, allowing for the accurate scanning of neighboring cells.

To elaborate further on this concept, envision each neighbor being sequentially numbered from 0 to 7:

+---+---+---+
| 7 | 0 | 1 |
+---+---+---+
| 6 | * | 2 |
+---+---+---+
| 5 | 4 | 3 |
+---+---+---+

In this scenario, the cell marked with * denotes the most recent instance of locating solid ground. Consider an example where the preceding discovery was at position 6; subsequently, the search among neighbors should commence at positions 7, 0, 1, 2, and so forth up until 5. The initial solid ground encountered during this pursuit represents the next addition to the ground level.

For another illustration: if the latest finding occurred at position 4, indicating an upward trajectory, the subsequent scan should begin from 5, continuing through 6, 7, 0, 1, 2, and concluding at 3.

The primary objective is to identify the first solid neighbor (ground) and include it as the subsequent component of the ground line. This method ensures meticulous tracking along every curve, including traversals through "caves," either ascending or descending, leftward or rightward.

Naturally, outliers may still arise, especially when commencing from isolated locations such as islands. However, addressing those specific cases was beyond the scope of my current endeavor.

The proposed approach has been integrated into the revised iteration of your method as provided below:

voxelToLine() {
    let voxels = this.voxels.length, x, y, i;
    // Neighbor coordinates listed clockwise    
    const neighbor = [ [0,-1], [1,-1], [1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1] ];

    for (y = 0; y < voxels; y++) // Sets initial coordinate for the line.
        if (this.voxels[0][y]) break; // Ground found, cease downward search
    let lineGround = [[0, y / voxels]];
    let [curX, curY] = [0, y]; // Initiate search here
    let direction = 0; // Ascending

    let looped = 0;
    do {// Iterates until right side is identified or maximum loops reached
        for (i = 0; i < 8; i++) {// Inspects each neighbor, starting from `direction`
            [x, y] = [curX + neighbor[direction][0], curY + neighbor[direction][1]];
            // Upon encountering solid ground, designate as the subsequent line entry
            if (x>=0 && x<voxels && y>=0 && y<voxels && this.voxels[x][y]) break;
            direction = (direction + 1) % 8; // Clockwise rotation to access next neighbor
        }
        if (i === 8) break; // In case no valid neighbor is found
        lineGround.push([x / voxels, y / voxels]);
        // Prepare for next iteration
        [curX, curY] = [x, y];
        direction = (direction + 5) % 8;
    } while (looped++ <= voxels*10 && curX < voxels - 1);

    // Ensure existence of x=0 and x=1 entries; add them if absent
    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;
}

Answer №2

It appears as though the voxel directly below the last genuine ground voxel is being skipped over because it has already been marked as "checked" and added to the checkedVoxels array.

Curiously, this skipping behavior would result in your ground path never making a 90-degree turn (as evidenced by the absence of such a voxel pattern in your example picture).

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Incorporate a local asciinema file into an HTML document

Is there a way to embed a local asciinema session into an html file? Here is how my directory is structured: $ tree . ├── asciinema │ └── demo.cast ├── css │ └── asciinema-player.css ├── index.html ├── js │ ...

Discover the color value within an array that begins with the "#" symbol

In my PHP code, I have written a function that extracts values from a CSS file and creates an array. Now, I need to loop through this array and create another array that only contains color values (strings starting with #). The challenge is that the length ...

Is there a way to invoke a php function in javascript with the help of Ajax?

I'm a beginner in javascript and ajax, attempting to call a php function that retrieves a patient's age in a javascript file. Despite looking into solutions on this topic, I haven't been able to figure it out yet. Here is the content of the ...

Accessing table cell value when clicked using JavaScript or jQuery

I am currently working on an ASP.NET MVC application where I have a table displaying records from the database in razor code. My goal is to extract the record ID "FeeZoneID" from a cell value when the delete link in the last column is clicked, and then t ...

Design a CreateJS/EaselJS website, comprised of multiple web pages, that is not focused on gaming

I have developed an existing HTML5 Canvas webpage composed of multiple pages, buttons, and hotspots using pure canvas javascript code. The reason I refer to 'buttons' and 'hotspots' in quotes is because I created them from scratch in j ...

I am encountering an issue with my Javascript file not running due to a bigint error

I'm attempting to utilize @metaplex/js for NFT minting. Usually, my .js files function correctly, but when I execute the file, this particular error arises. bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?) The meaning of ...

Scrolling text box utilizing Jquery

Currently, I am utilizing a scrolling box that functions well * view here * under normal circumstances. However, when there is an extensive amount of content below it, such as: <article class="content"> ...

Tips on changing the name of a property within an object using JavaScript

While this question may appear to be a duplicate, there is actually a distinction. I am attempting to provide a new key that does not contain any spaces. {order_id :"123" , order_name : "bags" , pkg_no : "00123#"} My goal is ...

Tips on invoking a method from a JavaScript object within an AJAX request

Considering the following code snippet: var submit = { send:function (form_id) { var url = $(form_id).attr("action"); $.ajax({ type: "POST", url: url, data: $(form_id).serialize(), dataType: 'json', succes ...

Modify the NAME attribute when clicked using Jquery

I am attempting to modify the NAME attribute of a DIV with the text from a textbox using jQuery. Take a look at my code snippet: http://jsfiddle.net/e6kCH/ Can anyone help me troubleshoot this issue? ...

Is there a way to make the primary button on the previous page function correctly again, just like it did before it was clicked?

Issue Description: I am facing an issue on the order page where I need to link the "Continue" button to a booking page. After reaching the booking page, I expect users to be able to navigate between the two pages seamlessly even when they use the browser& ...

What is the best way to retrieve a lengthy HTML page string parameter from a Java request?

While working with Javascript, I encountered an issue with sending HTML pages in post data. In my Java code, I used String html = request.getParameter("templateHtml"); and during debugging, I could see the HTML string in the request. However, the "html" va ...

Experience seamless one-to-many broadcasting with WebRTC/Kurento, featuring server-side recording capabilities

I am currently exploring Kurento to determine if it fits my needs. I am interested in developing a mobile application that can record and stream video to a server in real-time, with the server saving the video on its file system as it is being transmitted. ...

Conceal a div until reaching the end of the webpage by scrolling

Currently, I am working on a web page inspired by music release pages (check out an example here). My goal is to have certain hidden divs at the bottom of the page only reveal themselves once the user has scrolled all the way down, with a delay of a few se ...

Isolating an array from an object?

I am working with a component that receives props: The data received is logged on the console. What is the best way to extract the array from this object? Before I pass the array to my component, it appears like this: ...

Difficulty Establishing a Connection with SQL Server Using TypeORM

My local machine is running an SQL Server instance, but I'm encountering an error when trying to connect a database from TypeORM. The error message reads: originalError: ConnectionError: Failed to connect to localhost:1433 - Could not connect (seque ...

How can the value be accessed when using getElementById in Angular for <mat-select> elements that do not have a value attribute?

Within a loop, I have an element that has a dynamically generated id: <mat-select multiple class="dw-input" [value]="element.txn_type_id ? element.txn_type_id.split(',') : []" id="field-{{element.Name}}-txn_type_id&quo ...

Ways to determine if a new set of input values duplicates previous rows in an array

My form has an array of input fields. Is there a way to validate each row's inputs and ensure that they all have at least one unique value in any field, preventing identical rows? For example, the 2nd row should only be allowed to have a maximum of ...

Using JQuery to reverse the action of hiding an image

Having some trouble with my Javascript and JQuery skills, so bear with me. I've set up a gallery of images where users can drag and drop them into a designated drop zone. Everything works fine when dragging an image from the gallery to the drop zone a ...

Calculating the hour difference between two time stamps (HH:MM:SS a) using moment.js

I have two time without date var startTime="12:16:59 am"; var endTime="06:12:07 pm"; I need to calculate the total hours between the above times using a library like moment.js. If it's not achievable with moment.js, then please provide a solution u ...