繁体   English   中英

如何在 1D 数组中“展平”或“索引”3D 数组?

[英]How to "flatten" or "index" 3D-array in 1D array?

我正在尝试将 3D 数组展平为我游戏中“块”系统的一维数组。 这是一个 3D 块游戏,基本上我希望块系统与 Minecraft 的系统几乎相同(但是,这不是 Minecraft 的克隆)。 在我之前的 2D 游戏中,我使用以下算法访问了展平数组:

Tiles[x + y * WIDTH]

但是,这显然不适用于 3D,因为它缺少 Z 轴。 我不知道如何在 3D 空间中实现这种算法。 宽度、高度和深度都是常量(宽度与高度一样大)。

只是x + y*WIDTH + Z*DEPTH吗? 我的数学很差,而且我才刚刚开始 3D 编程,所以我很迷茫:|

附言。 这样做的原因是我循环并通过索引从中获取很多东西。 我知道一维 arrays 比多维 arrays 快(原因我不记得了:P)。 尽管这可能不是必需的,但我希望性能尽可能好:)

这是 Java 中的解决方案,可为您提供:

  • 从 3D 到 1D
  • 从 1D 到 3D

下面是我选择遍历 3D 矩阵的路径的图形说明,单元格按其遍历顺序编号:

2 3D 矩阵示例

转换函数:

public int to1D( int x, int y, int z ) {
    return (z * xMax * yMax) + (y * xMax) + x;
}

public int[] to3D( int idx ) {
    final int z = idx / (xMax * yMax);
    idx -= (z * xMax * yMax);
    final int y = idx / xMax;
    final int x = idx % xMax;
    return new int[]{ x, y, z };
}

算法大致相同。 如果你有一个 3D 数组Original[HEIGHT, WIDTH, DEPTH]那么你可以把它变成Flat[HEIGHT * WIDTH * DEPTH]

Flat[x + WIDTH * (y + DEPTH * z)] = Original[x, y, z]

顺便说一句,在 .NET 中,您应该更喜欢数组数组而不是多维数组。 性能差异显着

我认为以上需要一点修正。 假设你的 HEIGHT 为 10,WIDTH 为 90,一维数组将是 900。根据上面的逻辑,如果你在数组 9 + 89*89 的最后一个元素,显然这大于 900。正确的算法是:

Flat[x + HEIGHT* (y + WIDTH* z)] = Original[x, y, z], assuming Original[HEIGHT,WIDTH,DEPTH] 

具有讽刺意味的是,如果您的高度>宽度,您将不会遇到溢出,只需完成疯狂的结果;)

x + y*WIDTH + Z*WIDTH*DEPTH 将其可视化为一个长方体:首先沿着x遍历,然后每个y是一个“线” width步长,每个z是一个“平面” WIDTH*DEPTH步长区域。

您快到了。 您需要将 Z 乘以WIDTHDEPTH

Tiles[x + y*WIDTH + Z*WIDTH*DEPTH] = elements[x][y][z]; // or elements[x,y,z]

TL; 博士

正确答案可以有多种书写方式,但我最喜欢以易于理解和可视化的方式书写。 这是确切的答案:

(width * height * z) + (width * y) + x

TS;DR

可视化它:

someNumberToRepresentZ + someNumberToRepresentY + someNumberToRepresentX

someNumberToRepresentZ表示我们在哪个矩阵上( depth )。 要知道我们在哪个矩阵上,我们必须知道每个矩阵有多大。 矩阵是 2d 大小为width * height ,简单。 要问的问题是“我所在的矩阵之前有多少个矩阵?” 答案是z

someNumberToRepresentZ = width * height * z

someNumberToRepresentY表示我们在哪一行( height )。 要知道我们在哪一行,我们必须知道每行有多大:每行是 1d,大小为width 要问的问题是“我所在的行之前有多少行?”。 答案是y

someNumberToRepresentY = width * y

someNumberToRepresentX表示我们在哪一列( width )。 要知道我们在哪一列,我们只需使用x

someNumberToRepresentX = x

我们的可视化然后

someNumberToRepresentZ + someNumberToRepresentY + someNumberToRepresentX

成为

(width * height * z) + (width * y) + x

上面 Samuel Kerrien 的正向和反向变换几乎是正确的。 下面包含一个更简洁的(基于 R 的)转换映射和示例(“a %% b”是模运算符,表示 a 除以 b 的余数):

dx=5; dy=6; dz=7  # dimensions
x1=1; y1=2; z1=3  # 3D point example
I = dx*dy*z1+dx*y1+x1; I  # corresponding 2D index
# [1] 101
x= I %% dx; x  # inverse transform recovering the x index
# [1] 1
y = ((I - x)/dx) %% dy; y  # inverse transform recovering the y index
# [1] 2
z= (I-x -dx*y)/(dx*dy); z  # inverse transform recovering the z index
# [1] 3

注意除法 (/) 和模块 (%%) 运算符。

为了更好地理解一维数组中 3D 数组的描述将是(我猜最佳答案中的深度是指 Y 大小)

IndexArray = x + y * InSizeX + z * InSizeX * InSizeY;

IndexArray = x + InSizeX * (y + z * InSizeY);

正确的算法是:

Flat[ x * height * depth + y * depth + z ] = elements[x][y][z] 
where [WIDTH][HEIGHT][DEPTH]

m[x][y][z] = 数据[xYZ + yZ + z]

x-picture:
0-YZ
.
.
x-YZ

y-picture 

0-Z
.
.
.
y-Z


summing up, it should be : targetX*YZ + targetY*Z + targetZ  

如果有人有兴趣将 nD (2D, 3D, 4D, ...) 数组展平为 1D,我编写了以下代码。 例如,如果将不同维度的数组大小存储在sizes数组中:

#  pseudo code
sizes = {size_x, size_y, size_z,...};

这个递归 function 给你系列{1, size_x, size_x*size_y, size_x*size_y*size_z, ...}

// i: number of the term
public int GetCoeff(int i){
        if (i==0)
            return 1;
        return sizes[i-1]*GetCoeff(i-1);
}

因此,我们必须将 nD 索引乘以它们相应的级数项并将它们相加得到{ix + iy*size_x + iz*size_x*size_y, ...}

// indexNd: {ix, iy, iz, ...}
public int GetIndex1d(int[] indexNd){
    int sum =0;
    for (var i=0; i<indexNd.Length;i++)
        sum += indexNd[i]*GetCoeff(i);
    return sum;
    
}

在我假设的这段代码中,nD 数组在 memory 中是连续的,首先是 x,然后是 y, z, ...。 所以你可能称你的数组为arr[z,y,x] 但是,如果你用另一种方式称呼它们,arr[x,y,z] 那么 z 是最快的索引,我们喜欢计算iz + iy*size_z + ix* size_z*size_y 在这种情况下,下面的 function 为我们提供了系列{1, size_z, size_z*size_y, ...}

// Dims is dimension of array, like 3 for 3D
public int GetReverseCoeff(int i){
       if (i==0)
            return 1;
        return sizes[Dims-i]*GetReverseCoeff(i-1);
}

系数以正确的顺序存储:

public void SetCoeffs(){
    for (int i=0;i<Dims;i++)
            coeffs[Dims-i-1] = GetReverseCoeff(i);
}

除了使用 coeffs 数组外,一维索引的计算方式与之前相同:

// indexNd: {ix, iy, iz, ...}
public int GetIndex1d(int[] indexNd){
    int sum =0;
    for (var i=0; i<indexNd.Length;i++)
        sum += indexNd[i]*coeffs[i];
    return sum;
    
}

Samuel Kerrien 对 python 的回答:

def to1D(crds,dims):
  x,y,z=crds
  xMax,yMax,zMax=dims
  return (z * xMax * yMax) + (y * xMax) + x

def to3D(idx,dims):
    xMax,yMax,zMax=dims
    z = idx // (xMax * yMax)
    idx -= (z * xMax * yMax)
    y = idx // xMax
    x = idx % xMax
    return x, y, z 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM