【问题标题】:Array shrinking in C#C#中的数组缩小
【发布时间】:2017-09-12 21:49:38
【问题描述】:

在 C# 中有什么方法可以在不复制的情况下调整数组的大小?我特别要寻找的是一种更改数组的 Length 属性的方法。数组具有固定的最大长度,并且只会调整为小于或等于最大大小的大小(允许重复使用数组)。

实际用例是 Unity3D 中的程序网格生成。执行此操作的内置方法仅限于使用列表(非常慢)并将例如 Vector3 数组分配给 mesh.vertices。数组分配方法是我想要的,但是当您将数组分配给mesh.vertices时,无法指定要复制的数组元素的数量(mesh.vertices不是数组,并将分配的数组复制到GPU) .

如果事先不知道程序生成的网格中的顶点数,则无法制作正确大小的数组,您必须采用缓慢的数组调整大小。

我知道 C# 通常不允许我正在寻找的那种数组大小调整,但我不得不问是否有一些半体面的低级黑客方式仍然可以完成它。

我现在使用的方法是分配一个具有最大顶点数的数组(它只是为生成的每个网格重复使用),填充它,然后分配另一个正确大小的数组并复制正确的数字将数组元素添加到新数组中(这比 Array.Resize 快,而且肯定比使用列表的任何东西都快)。

编辑:根据要求使用适当的代码进行了更新(文档不完整,请注意)。

这会为每个块调用地图生成器(测试代码):

for (int z = 0; z < size; z++)
{
    for (int x = 0; x < size; x++)
    {
        Vector3 position = new Vector3(x * 32, 0, z * 32);

        GameObject a = Instantiate(baseObject);
        a.transform.position = position;

        map.GenMap(position, model); // procedural model generation

        blockMesh.SetModel(model); // set the model to generate the mesh from
        blockMesh.GenMesh(); // generate actual mesh
        blockMesh.CopyMesh(a.GetComponent<MeshFilter>().mesh); // copy mesh data do the Unity mesh
    }
}

简单的测试图生成器:

public void GenMap(Vector3 position, Model map)
{
    map.Clear();

    double par1 = 1000;
    double par2 = 600;
    double par3 = 500;

    double par4 = 1.0 / 50.0;
    double par5 = 1.0 / 25.0;
    double par6 = 1.0 / 12.5;

    double factor = (par1 + par2 + par3) / 16;

    if (factor != 0.0)
    {
        par1 /= factor;
        par2 /= factor;
        par3 /= factor;
    }

    Color32 color150 = map.palette[150];

    for (int z = 0; z < map.realDepth; z++)
    {
        double zPos = position.z + z;

        for (int x = 0; x < map.realWidth; x++)
        {
            double xPos = position.x + x;

            int e = 16 + (int)
            (
                (noise.Noise(xPos * par4, zPos * par4) * par1) +
                (noise.Noise(xPos * par5, zPos * par5) * par2) +
                (noise.Noise(xPos * par6, zPos * par6) * par3)
            );

            for (int y = 0; y < map.realHeight; y++)
            {
                if (y <= e)
                {
                    int a = (255 - (32 - y) * 9);

                    if (a < 1)
                        a = 1;

                    color150.a = (byte)(a | 1);
                    map.voxel[x][y][z] = color150;
                }
            }
        }
    }
}

这是实际的网格生成器代码(针对 Mono 3.5 进行了优化,目前没有很好的文档记录):

using UnityEngine;
using System;

public class BlockMesh
{
    public Vector3[] vertices = new Vector3[65532];
    public Vector3[] normals = new Vector3[65532];
    public Vector2[] uvs = new Vector2[65532];
    public Color32[] colors = new Color32[65532];
    public int[] triangles = new int[65532 * 6 / 4];
    public int index = 0;
    public Model model;

    // set model and init arrays

    public void SetModel(Model model)
    {
        this.model = model;
    }

    // copy mesh data to mesh

    public void CopyMesh(Mesh mesh)
    {
        Vector3[] a = new Vector3[index * 4];

        Array.Copy(vertices, a, index * 4);
        mesh.vertices = a;

        Array.Copy(normals, a, index * 4);
        mesh.normals = a;

        Color32[] b = new Color32[index * 4];
        Array.Copy(colors, b, index * 4);
        mesh.colors32 = b;

        int[] c = new int[index * 6];
        Array.Copy(triangles, c, index * 6);
        mesh.triangles = c;

        Vector2[] d = new Vector2[index * 4];

        Array.Copy(uvs, d, index * 4);
        mesh.uv = d;
    }

    // ambient occlusion uv constants

    private const float pixelSize = 1.0f / 4096.0f;
    private const float tileSize = pixelSize * 256;
    private const float adjust1 = pixelSize * 64;
    private const float adjust2 = tileSize - 2 * adjust1;

    // cube faces

    private Vector3[] faceVertices1 = { new Vector3(0, 1, 0), new Vector3(0, 0, 0), new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 0, 1), new Vector3(1, 0, 1) };
    private Vector3[] faceVertices2 = { new Vector3(1, 1, 0), new Vector3(1, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 0, 1), new Vector3(0, 0, 0), new Vector3(1, 0, 0) };
    private Vector3[] faceVertices3 = { new Vector3(0, 1, 1), new Vector3(0, 0, 1), new Vector3(0, 1, 0), new Vector3(0, 1, 1), new Vector3(0, 1, 1), new Vector3(1, 1, 1) };
    private Vector3[] faceVertices4 = { new Vector3(1, 1, 1), new Vector3(1, 0, 1), new Vector3(1, 1, 0), new Vector3(1, 1, 1), new Vector3(0, 1, 0), new Vector3(1, 1, 0) };

    private Vector3[] faceNormals = { Vector3.up, Vector3.down, Vector3.back, Vector3.forward, Vector3.left, Vector3.right };

    private void AddFace(int side, Vector3 position, Color32 color, int aop)
    {
        // mesh array indices

        int index4 = index * 4;
        int index6 = index * 6;

        // ambient occlusion uv cords

        float x1 = tileSize * (aop & 15) + adjust1;
        float y1 = tileSize * (aop >> 4) + adjust1;

        float x2 = x1 + adjust2;
        float y2 = y1 + adjust2;

        // put face into mesh

        vertices[index4 + 0] = position + faceVertices1[side];
        vertices[index4 + 1] = position + faceVertices2[side];
        vertices[index4 + 2] = position + faceVertices3[side];
        vertices[index4 + 3] = position + faceVertices4[side];

        uvs[index4 + 0].x = x1;
        uvs[index4 + 0].y = y1;
        uvs[index4 + 1].x = x2;
        uvs[index4 + 1].y = y1;
        uvs[index4 + 2].x = x1;
        uvs[index4 + 2].y = y2;
        uvs[index4 + 3].x = x2;
        uvs[index4 + 3].y = y2;

        normals[index4 + 0] = faceNormals[side];
        normals[index4 + 1] = faceNormals[side];
        normals[index4 + 2] = faceNormals[side];
        normals[index4 + 3] = faceNormals[side];

        colors[index4 + 0] = color;
        colors[index4 + 1] = color;
        colors[index4 + 2] = color;
        colors[index4 + 3] = color;

        int tri = 0x08790936 >> ((side & 1) << 4);

        triangles[index6 + 0] = index4 + ((tri >> 10) & 3);
        triangles[index6 + 1] = index4 + ((tri >> 08) & 3);
        triangles[index6 + 2] = index4 + ((tri >> 06) & 3);
        triangles[index6 + 3] = index4 + ((tri >> 04) & 3);
        triangles[index6 + 4] = index4 + ((tri >> 02) & 3);
        triangles[index6 + 5] = index4 + (tri & 3);

        index += 1;
    }

    // build mesh

    public void GenMesh()
    {
        index = 0;

        for (int z = 1; z <= model.depth; z++)
        {
            for (int y = 1; y <= model.height; y++)
            {
                for (int x = 1; x <= model.width; x++)
                {
                    Color32 voxelColor = model.voxel[x][y][z];

                    if (voxelColor.a == 0)
                        continue;

                    if (model.voxel[x][y + 1][z].a == 0) // top
                    {
                        AddFace(0, new Vector3(x, y, z), voxelColor,
                            (((model.voxel[x - 1][y + 1][z - 1].a) & 1) << 7) |
                            (((model.voxel[x + 0][y + 1][z - 1].a) & 1) << 6) |
                            (((model.voxel[x + 1][y + 1][z - 1].a) & 1) << 5) |
                            (((model.voxel[x - 1][y + 1][z + 0].a) & 1) << 4) |
                            (((model.voxel[x + 1][y + 1][z + 0].a) & 1) << 3) |
                            (((model.voxel[x - 1][y + 1][z + 1].a) & 1) << 2) |
                            (((model.voxel[x + 0][y + 1][z + 1].a) & 1) << 1) |
                            (((model.voxel[x + 1][y + 1][z + 1].a) & 1)));
                    }

                    if (model.voxel[x][y - 1][z].a == 0) // bottom
                    {
                        AddFace(1, new Vector3(x, y, z), voxelColor,
                            (((model.voxel[x - 1][y - 1][z - 1].a) & 1) << 7) |
                            (((model.voxel[x + 0][y - 1][z - 1].a) & 1) << 6) |
                            (((model.voxel[x + 1][y - 1][z - 1].a) & 1) << 5) |
                            (((model.voxel[x - 1][y - 1][z + 0].a) & 1) << 4) |
                            (((model.voxel[x + 1][y - 1][z + 0].a) & 1) << 3) |
                            (((model.voxel[x - 1][y - 1][z + 1].a) & 1) << 2) |
                            (((model.voxel[x + 0][y - 1][z + 1].a) & 1) << 1) |
                            (((model.voxel[x + 1][y - 1][z + 1].a) & 1)));
                    }

                    if (model.voxel[x][y][z - 1].a == 0) // back
                    {
                        AddFace(2, new Vector3(x, y, z), voxelColor,
                            (((model.voxel[x - 1][y - 1][z - 1].a) & 1) << 7) |
                            (((model.voxel[x + 0][y - 1][z - 1].a) & 1) << 6) |
                            (((model.voxel[x + 1][y - 1][z - 1].a) & 1) << 5) |
                            (((model.voxel[x - 1][y + 0][z - 1].a) & 1) << 4) |
                            (((model.voxel[x + 1][y + 0][z - 1].a) & 1) << 3) |
                            (((model.voxel[x - 1][y + 1][z - 1].a) & 1) << 2) |
                            (((model.voxel[x + 0][y + 1][z - 1].a) & 1) << 1) |
                            (((model.voxel[x + 1][y + 1][z - 1].a) & 1)));
                    }

                    if (model.voxel[x][y][z + 1].a == 0) // front
                    {
                        AddFace(3, new Vector3(x, y, z), voxelColor,
                            (((model.voxel[x - 1][y - 1][z + 1].a) & 1) << 7) |
                            (((model.voxel[x + 0][y - 1][z + 1].a) & 1) << 6) |
                            (((model.voxel[x + 1][y - 1][z + 1].a) & 1) << 5) |
                            (((model.voxel[x - 1][y + 0][z + 1].a) & 1) << 4) |
                            (((model.voxel[x + 1][y + 0][z + 1].a) & 1) << 3) |
                            (((model.voxel[x - 1][y + 1][z + 1].a) & 1) << 2) |
                            (((model.voxel[x + 0][y + 1][z + 1].a) & 1) << 1) |
                            (((model.voxel[x + 1][y + 1][z + 1].a) & 1)));
                    }

                    if (model.voxel[x - 1][y][z].a == 0) // left
                    {
                        AddFace(4, new Vector3(x, y, z), voxelColor,
                            (((model.voxel[x - 1][y - 1][z + 1].a) & 1) << 7) |
                            (((model.voxel[x - 1][y - 1][z + 0].a) & 1) << 6) |
                            (((model.voxel[x - 1][y - 1][z - 1].a) & 1) << 5) |
                            (((model.voxel[x - 1][y + 0][z + 1].a) & 1) << 4) |
                            (((model.voxel[x - 1][y + 0][z - 1].a) & 1) << 3) |
                            (((model.voxel[x - 1][y + 1][z + 1].a) & 1) << 2) |
                            (((model.voxel[x - 1][y + 1][z + 0].a) & 1) << 1) |
                            (((model.voxel[x - 1][y + 1][z - 1].a) & 1)));
                    }

                    if (model.voxel[x + 1][y][z].a == 0) // right
                    {
                        AddFace(5, new Vector3(x, y, z), voxelColor,
                            (((model.voxel[x + 1][y - 1][z + 1].a) & 1) << 7) |
                            (((model.voxel[x + 1][y - 1][z + 0].a) & 1) << 6) |
                            (((model.voxel[x + 1][y - 1][z - 1].a) & 1) << 5) |
                            (((model.voxel[x + 1][y + 0][z + 1].a) & 1) << 4) |
                            (((model.voxel[x + 1][y + 0][z - 1].a) & 1) << 3) |
                            (((model.voxel[x + 1][y + 1][z + 1].a) & 1) << 2) |
                            (((model.voxel[x + 1][y + 1][z + 0].a) & 1) << 1) |
                            (((model.voxel[x + 1][y + 1][z - 1].a) & 1)));
                    }
                }
            }
        }
    }
}

【问题讨论】:

  • 如何实现一个包含数组的 ReducableArray 类,int 长度不超过数组的大小,并且只使用数组的第一部分不超过长度?
  • @YairHalberstadt 是的,他可以称之为List&lt;T&gt;! (你所描述的正是 List 已经做的)
  • 是的,但他说列表太慢了@ScottChamberlain
  • 所以,你的解决方案也太慢了,因为你描述了List&lt;T&gt;
  • @Thorham 你能否更新你的问题,展示一个你迄今为止尝试过的太慢的例子?

标签: c# arrays unity3d


【解决方案1】:

经过多次谷歌搜索,我偶然发现了我正在寻找的不安全的黑客方法。为了完整起见,我发布它,但它似乎不是任何人都想在生产代码中使用的解决方案。

你不想使用的非常肮脏的黑客:

using System;
using UnityEngine;

public unsafe class DirtyHack
{
    public Vector3[] vertices = new Vector3[65532];
    public int index;

    public void DirtyCopy(Mesh mesh)
    {
        fixed (void* ptr = vertices)
        {
            *((UIntPtr*)ptr - 1) = (UIntPtr)(index * 4);
            mesh.vertices = vertices;
            *((UIntPtr*)ptr - 1) = (UIntPtr)(65532);
        }
    }
}

编辑:更改代码以防止在数组调整大小期间被 GC 移动数组。

【讨论】:

  • 如果 GC 在构造函数和 DirtyCopy 调用之间移动顶点数组会怎样。然后你实际上是在写入随机存储器。不要这样做。您无法更新数组的长度,这就是您问题的答案。
  • @Asik - 是的,这就是为什么我写这篇文章只是为了完整性,而不是为了实际的生产代码。
  • @Thorham 这是一个非常有趣的 hack,尽管我同意你关于它的“肮脏”的看法。那么数组的大小存储在内存中的&amp;array[0] - 1?这就说得通了。我想知道这是否可以确保安全?有什么办法可以防止数组在临时改变大小的过程中被移动?
  • @piojo - 是的,请参阅更新的代码。基本上数组不会在固定代码块内移动。
  • @Thorham 不再那么脏了!
猜你喜欢
  • 2010-09-16
  • 2011-02-13
  • 1970-01-01
  • 1970-01-01
  • 2015-02-05
  • 2010-09-10
  • 2015-06-05
  • 2021-09-11
  • 2011-11-24
相关资源
最近更新 更多