Transferring 2d boundaries onto its 1d grid

257 Views Asked by At

I have a matrix defined mxn 128x128. And I have translated my 2d x,y positions onto this 1D matrix grid. My 2d coordinates accept positions using numbers 0->127 i.e. any combo in ranges {x=0,y=0}-->{x=127,y=127}. I'm implementing algorithms that take the neighboring positions of these nodes. Specifically the 8 surrounding positions of distance i (lets say i=1). So considering node={0,0}, my neighbours are generated by adding these vectors to said node:

two_d_nodes={
        {0,i*1},{0,-i*1},{-i*1,0},{i*1,0},
        {i*1,i*1},{i*1,-i*1},{-i*1,-i*1},{-i*1,i*1}
    }

In terms of 2d though I am excluding neighbours outside the boundary. So in the above for node={0,0}, only neighours {0,1},{1,1}{1,0} are generated. Setting the boundary is basically just implementing some form of:

if x>=0 and y>=0 and x<=127 and y<=127 then... 

The 1d translation of node={0,0} is node={0} and my vector additions translated to 1d are:

one_d_nodes={{128},{-128},{-1},{1},{129},{-127},{-129},{127}}

However the relationship with the 2d boundary expressions doesn't hold true here. Or at least I don't know how to translate it. In response I tried generating all the loose cases of the grid:

{0,127,16256,16383} --the 4 corner positions  
node%128==0 --right-side boundary  
node%128==1 --left-side boundary  
node>1 and node<128 --top-side boundary  
node>127*128 and node<128*128 --bottom-side boundary    

Then tried implementing special cases....where I just ignored generating the specific out of bounds neighbours. That was messy, and didn't even work for some reason. Regardless I feel I am missing a much cleaner method.

So my question is: How do I translate my 2d boundaries onto my 1d grid for the purposes of only generating neighbours within the boundary?


The following is in regards to the answer below:

function newmatrix(node) --node={x=0,y=0}
    local matrix={}
    add(matrix,{(node.y<<8)+node.x}) --matrix= {{0},...}
--lets say [1 2 3] is a width=3; height=1 matrix, 
--then the above line maps my 2d coord to a matrix of width=256, height=128 
    matrix.height, matrix.width = #node,#node[1] --1,1
    return matrix
 end

function indexmatrix(matrix, r,c)
    if r > 1 and r <= matrix.height and c > 1 and c <= matrix.width then
        return matrix[matrix.width * r + c]
    else
        return false
    end
end

function getneighbors(matrix, r, c)
    local two_d_nodes={
        {0,1},{0,-1},{-1,0},{1,0},
        {1,1},{1,-1},{-1,-1},{-1,1}
    }
    local neighbors = {}
    for index, node in ipairs(two_d_nodes) do
        table.insert(neighbors, indexmatrix(matrix, r + node[1], c + node[2]))
    end
    return neighbors
end
--Usage:
m={x=0,y=0}
matrix=newmatrix(m) --{{0}}
--here's where I'm stuck, cause idk what r and c are
--normally I'd grab my neighbors next....
neighbors=getneighbors(matrix)
--then I have indexmatrix for...?
--my understanding is that I am using indexmatrix to
--check if the nieghbors are within the bounds or not, is that right?
--can you illustrate how it would work for my code here, it should
--toss out anything with x less then 0 and y less than 0. Same as in OP's ex 
indexmatrix(matrix) ---not sure what to do here

Attempt 2 in regards to the comment sections below:

function indexmatrix(matrix, x ,y)
    if x > 1 and x <= matrix['height'] and y > 1 and y <= matrix['width'] then
        return matrix[matrix['width'] * x + y]
    else
        return false
    end
end
function getneighbors(matrix, pos_x, pos_y)
    local two_d_nodes={
        {0,1},{0,-1},{-1,0},{1,0},
        {1,1},{1,-1},{-1,-1},{-1,1}
    }
    local neighbors = {}
    for _, node in ipairs(two_d_nodes) do
        add(neighbors, indexmatrix(matrix, pos_x + node[1], pos_y + node[2]))
    end
    return neighbors
end

matrix={} --128 columns/width, 128 rows/height 
for k=1,128 do
add(matrix,{}) ----add() is same as table.insert()
    for i=1,128 do
        matrix[k][i]=i
    end
end

id_matrix={{}} --{ {1...16k}}
for j=1,128*128 do
    id_matrix[1][j]=j
end
id_matrix.height, id_matrix.width = 128,128 
    
position={x=0,y=0}
neighbors = getNeighbors(matrix, position.x, position.y)

Attempt 3: A working dumbed down version of the code given. Not what I wanted at all.

function indexmatrix(x,y)
    if x>=0 and y>=0 and x<127 and y<127 then
        return 128 * x + y
    else
        return false
    end
end
function getneighbors(posx,posy)
    local two_d_nodes={
        {0,1},{0,-1},{-1,0},{1,0},
        {1,1},{1,-1},{-1,-1},{-1,1}
    }
    local neighbors = {}
    for _, node in pairs(two_d_nodes) do
        add(neighbors, indexmatrix(posx+node[1], posy + node[2]))
    end
    return neighbors
end

pos={x=0,y=10}
neighbors = getneighbors(pos.x,pos.y)
1

There are 1 best solutions below

7
On

Edit: The equation to map 2D coordinates to 1D, y = mx + z, is a function of two variables. It is not possible for a multivariable equation to have a single solution unless a system of equations is given that gets x or z in terms of the other variable. Because x and z are independent of one another, the short answer to the question is: no

Instead, the constraints on x and z must be used to ensure integrity of the 1D coordinates.

What follows is an example of how to work with a 1D array as if it were a 2D matrix.

Let's say we have a constructor that maps a 2D table to a 1D matrix

local function newMatrix(m) -- m is 128x128 Matrix
    local Matrix = {}
    --logic to map m to 1D array
    -- ...
   return Matrix -- Matrix is m 1x16384 Array
end

The numeric indices are reserved, but we can add non-numeric keys to store information about the matrix. Let's store the number of rows and columns as height and width. We can do this in the constructor

local function newMatrix(m)
    local Matrix = {}
    --logic to map to 1D array
    -- ...
    -- Store row and column info in the matrix
    Matrix.height, Matrix.width = #m, #m[1] -- Not the best way
    return Matrix
end

Although the matrix is now a 1x16384 array, we can create a function that allows us to interact with the 1D array like it's still a 2D matrix. This function will get the value of a position in the matrix, but we return false/nil if the indices are out of bounds.

To be clear, the formula to map 2D coordinates to a 1D coordinate for a matrix, and can be found here:

1D position = 2D.x * Matrix-Width + 2D.y

And here's what that function could look like:

local function indexMatrix(Matrix, r,c)
    if r >= 1 and r <= Matrix.height and c >= 1 and c <= Matrix.width then
        return Matrix[Matrix.width * r + c] -- the above formula
    else
        return false -- out of bounds
    end
end

We can now index our Matrix with any bounds without fear of returning an incorrect element.

Finally, we can make a function to grab the neighbors given a 2D position. In this function, we add vectors to the given 2D position to get surrounding positions, and then index the matrix using the indexMatrix function. Because indexMatrix checks if a 2D position is within the bounds of the original Matrix (before it was converted), we only get neighbors that exist.

local function getNeighbors(Matrix, r, c) -- r,c = row, column (2D position)
    local two_d_nodes={
        {0,1},{0,-1},{-1,0},{1,0},
        {1,1},{1,-1},{-1,-1},{-1,1}
    }
    local neighbors = {}
    for index, node in ipairs(two_d_nodes) do
        -- Add each vector to the given position and get the node from the Matrix
        table.insert(neighbors, indexMatrix(Matrix, r + node[1], c + node[2]))
    end
    return neighbors
end

You can either skip elements that return false from indexMatrix or remove them after the fact. Or anything else that sounds better to you (this code is not great, it's just meant to be an example). Wrap it in a for i ... do loop and you can go out an arbitrary distance.

I hope I haven't assumed too much and that this is helpful. Just know it's not foolproof (the # operator stops counting at the first nil, for instance)

Edit: Usage

Matrix = {
    {1,2,3...128}, -- row 1
    {1,2,3...128},
    ...
    {1,2,3...128}, -- row 128
}

Array = newMatrix(Matrix) -- Convert to 1D Array ({1,2,3,...,16384})
--Array.width = 128, Array.height = 128
position = {x=0, y=0}
neighbors = getNeighbors(Array, position.x, position.y)
-- neighbors is: {{0,1}, false, false, {1,0}, {1,1}, false, false, false}