Is there any way in C# to resize arrays without making a copy? What I'm specifically looking for is a way to change the Length property of an array. The array has a fixed maximum length, and is only ever resized to a size smaller or equal to the maximum size (allows reusing the array).
The actual use case is procedural mesh generation in Unity3D. The built-in method of doing this is limited to using lists (very slow) and assigning, for example, an array of Vector3s to mesh.vertices. The array assigning method is what I want, but there's no way to specify the number of array elements to copy when you assign an array to mesh.vertices (mesh.vertices isn't an array, and copies the assigned array to the GPU).
When the number of vertices in a procedurally generated mesh is unknown beforehand, it's impossible to make an array of the right size and you have to resort to slow array resizing.
I know that C# normally doesn't allow the kind of array resizing I'm looking for, but I have to ask if there isn't some half decent low-level hackish way to still get it done.
The method I'm using now is to allocate an array of the maximum number of vertices (which is simply reused for each mesh that's generated), fill that, and then allocate another array of the right size and copy the right number of array elements to the new array (this is faster than Array.Resize, and certainly faster than anything using lists).
Edit: Updated with appropriate code as requested (NOT well documented, beware).
This calls the map generator for each chunk (test code):
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
}
}
Simple test map generator:
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;
}
}
}
}
}
This is the actual mesh generator code (optimized for Mono 3.5 and not well documented right now):
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)));
}
}
}
}
}
}