Files
BlockSpace-v1.5.0b/Assets/Scripts/Assembly-CSharp/BlockSpace/Voxels/GreedyMesher.cs
T
2026-06-01 17:19:55 +02:00

433 lines
13 KiB
C#

using System.Collections.Generic;
using UnityEngine;
namespace BlockSpace.Voxels
{
public static class GreedyMesher
{
public sealed class MeshData
{
public List<Vector3> Vertices { get; } = new List<Vector3>(256);
public List<Vector3> Normals { get; } = new List<Vector3>(256);
public List<Color32> Colors { get; } = new List<Color32>(256);
public List<Vector2> Uvs { get; } = new List<Vector2>(256);
public List<int>[] SubmeshTriangles { get; }
public MeshData(int submeshCount)
{
SubmeshTriangles = new List<int>[submeshCount];
for (int i = 0; i < submeshCount; i++)
{
SubmeshTriangles[i] = new List<int>(256);
}
}
}
private struct FaceDescriptor
{
public bool Valid;
public int Submesh;
public Color32 Color;
public bool Matches(FaceDescriptor other)
{
if (Valid == other.Valid && Submesh == other.Submesh && Color.r == other.Color.r && Color.g == other.Color.g && Color.b == other.Color.b)
{
return Color.a == other.Color.a;
}
return false;
}
}
public static MeshData BuildRenderMesh(Vector3Int size, byte[] voxels, bool creatorMode, float voxelSize)
{
MeshData meshData = new MeshData(4);
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return meshData;
}
for (int i = 0; i < 3; i++)
{
int num = (i + 1) % 3;
int num2 = (i + 2) % 3;
int num3 = size[num];
int num4 = size[num2];
FaceDescriptor[] mask = new FaceDescriptor[num3 * num4];
BuildRenderFacesForDirection(size, voxels, creatorMode, voxelSize, meshData, mask, i, 1, num3, num4, num, num2);
BuildRenderFacesForDirection(size, voxels, creatorMode, voxelSize, meshData, mask, i, -1, num3, num4, num, num2);
}
return meshData;
}
public static MeshData BuildCulledRenderMesh(Vector3Int size, byte[] voxels, bool creatorMode, float voxelSize)
{
MeshData meshData = new MeshData(4);
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return meshData;
}
for (int i = 0; i < size.z; i++)
{
for (int j = 0; j < size.y; j++)
{
for (int k = 0; k < size.x; k++)
{
byte voxel = GetVoxel(size, voxels, k, j, i);
if (!PackedVoxel.IsAir(voxel))
{
EmitCulledFaces(meshData, size, voxels, creatorMode, voxelSize, k, j, i, voxel);
}
}
}
}
return meshData;
}
public static MeshData BuildColliderMesh(Vector3Int size, byte[] voxels, float voxelSize)
{
MeshData meshData = new MeshData(1);
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
{
return meshData;
}
for (int i = 0; i < 3; i++)
{
int num = (i + 1) % 3;
int num2 = (i + 2) % 3;
int num3 = size[num];
int num4 = size[num2];
bool[] mask = new bool[num3 * num4];
BuildColliderFacesForDirection(size, voxels, voxelSize, meshData, mask, i, 1, num3, num4, num, num2);
BuildColliderFacesForDirection(size, voxels, voxelSize, meshData, mask, i, -1, num3, num4, num, num2);
}
return meshData;
}
private static void BuildRenderFacesForDirection(Vector3Int size, byte[] voxels, bool creatorMode, float voxelSize, MeshData meshData, FaceDescriptor[] mask, int axis, int direction, int width, int height, int u, int v)
{
for (int i = 0; i <= size[axis]; i++)
{
int num = 0;
for (int j = 0; j < height; j++)
{
for (int k = 0; k < width; k++)
{
Vector3Int zero = Vector3Int.zero;
zero[axis] = ((direction > 0) ? (i - 1) : i);
zero[u] = k;
zero[v] = j;
byte voxel = GetVoxel(size, voxels, zero.x, zero.y, zero.z);
FaceDescriptor descriptor = default(FaceDescriptor);
if (!PackedVoxel.IsAir(voxel))
{
Vector3Int vector3Int = zero;
vector3Int[axis] += direction;
byte voxel2 = GetVoxel(size, voxels, vector3Int.x, vector3Int.y, vector3Int.z);
if (TryCreateRenderFace(voxel, voxel2, creatorMode, out descriptor))
{
descriptor.Valid = true;
}
}
mask[num++] = descriptor;
}
}
GreedyMergeRenderMask(meshData, mask, axis, direction, i, width, height, u, v, voxelSize);
}
}
private static void BuildColliderFacesForDirection(Vector3Int size, byte[] voxels, float voxelSize, MeshData meshData, bool[] mask, int axis, int direction, int width, int height, int u, int v)
{
for (int i = 0; i <= size[axis]; i++)
{
int num = 0;
for (int j = 0; j < height; j++)
{
for (int k = 0; k < width; k++)
{
Vector3Int zero = Vector3Int.zero;
zero[axis] = ((direction > 0) ? (i - 1) : i);
zero[u] = k;
zero[v] = j;
if (PackedVoxel.IsAir(GetVoxel(size, voxels, zero.x, zero.y, zero.z)))
{
mask[num++] = false;
continue;
}
Vector3Int vector3Int = zero;
vector3Int[axis] += direction;
byte voxel = GetVoxel(size, voxels, vector3Int.x, vector3Int.y, vector3Int.z);
mask[num++] = PackedVoxel.IsAir(voxel);
}
}
GreedyMergeColliderMask(meshData, mask, axis, direction, i, width, height, u, v, voxelSize);
}
}
private static bool TryCreateRenderFace(byte current, byte neighbor, bool creatorMode, out FaceDescriptor descriptor)
{
descriptor = default(FaceDescriptor);
if (PackedVoxel.IsAir(current))
{
return false;
}
VoxelMaterial material = PackedVoxel.GetMaterial(current);
VoxelMaterial voxelMaterial = ((!PackedVoxel.IsAir(neighbor)) ? PackedVoxel.GetMaterial(neighbor) : VoxelMaterial.Lit);
bool flag = PackedVoxel.IsAir(neighbor);
bool flag2 = !flag && voxelMaterial == VoxelMaterial.Invisible;
switch (material)
{
case VoxelMaterial.Lit:
if (!flag && !flag2 && voxelMaterial != VoxelMaterial.Transparent)
{
return false;
}
descriptor.Submesh = 0;
descriptor.Color = Palette.GetPreviewColor(PackedVoxel.GetColorIndex(current), material);
return true;
case VoxelMaterial.Unlit:
if (!flag && !flag2 && voxelMaterial != VoxelMaterial.Transparent)
{
return false;
}
descriptor.Submesh = 1;
descriptor.Color = Palette.GetPreviewColor(PackedVoxel.GetColorIndex(current), material);
return true;
case VoxelMaterial.Transparent:
if (!flag && !flag2 && voxelMaterial == VoxelMaterial.Transparent)
{
return false;
}
descriptor.Submesh = 2;
descriptor.Color = Palette.GetPreviewColor(PackedVoxel.GetColorIndex(current), material);
return true;
case VoxelMaterial.Invisible:
if (!creatorMode || !flag)
{
return false;
}
descriptor.Submesh = 3;
descriptor.Color = Palette.GetPreviewColor(PackedVoxel.GetColorIndex(current), material);
return true;
default:
return false;
}
}
private static void EmitCulledFaces(MeshData meshData, Vector3Int size, byte[] voxels, bool creatorMode, float voxelSize, int x, int y, int z, byte current)
{
Vector3Int vector3Int = new Vector3Int(x, y, z);
for (int i = 0; i < 3; i++)
{
int num = (i + 1) % 3;
int num2 = (i + 2) % 3;
for (int j = -1; j <= 1; j += 2)
{
Vector3Int vector3Int2 = vector3Int;
vector3Int2[i] += j;
byte voxel = GetVoxel(size, voxels, vector3Int2.x, vector3Int2.y, vector3Int2.z);
if (TryCreateRenderFace(current, voxel, creatorMode, out var descriptor))
{
int slice = ((j > 0) ? (vector3Int[i] + 1) : vector3Int[i]);
EmitRenderQuad(meshData, descriptor, i, j, slice, vector3Int[num], vector3Int[num2], 1, 1, num, num2, voxelSize);
}
}
}
}
private static void GreedyMergeRenderMask(MeshData meshData, FaceDescriptor[] mask, int axis, int direction, int slice, int width, int height, int u, int v, float voxelSize)
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int num = j + i * width;
FaceDescriptor descriptor = mask[num];
if (!descriptor.Valid)
{
continue;
}
int k;
for (k = 1; j + k < width && descriptor.Matches(mask[num + k]); k++)
{
}
int num2 = 1;
bool flag = true;
while (i + num2 < height && flag)
{
for (int l = 0; l < k; l++)
{
if (!descriptor.Matches(mask[num + l + num2 * width]))
{
flag = false;
break;
}
}
if (flag)
{
num2++;
}
}
EmitRenderQuad(meshData, descriptor, axis, direction, slice, j, i, k, num2, u, v, voxelSize);
for (int m = 0; m < num2; m++)
{
for (int n = 0; n < k; n++)
{
mask[num + n + m * width] = default(FaceDescriptor);
}
}
j += k - 1;
}
}
}
private static void GreedyMergeColliderMask(MeshData meshData, bool[] mask, int axis, int direction, int slice, int width, int height, int u, int v, float voxelSize)
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int num = j + i * width;
if (!mask[num])
{
continue;
}
int k;
for (k = 1; j + k < width && mask[num + k]; k++)
{
}
int num2 = 1;
bool flag = true;
while (i + num2 < height && flag)
{
for (int l = 0; l < k; l++)
{
if (!mask[num + l + num2 * width])
{
flag = false;
break;
}
}
if (flag)
{
num2++;
}
}
EmitColliderQuad(meshData, axis, direction, slice, j, i, k, num2, u, v, voxelSize);
for (int m = 0; m < num2; m++)
{
for (int n = 0; n < k; n++)
{
mask[num + n + m * width] = false;
}
}
j += k - 1;
}
}
}
private static void EmitRenderQuad(MeshData meshData, FaceDescriptor descriptor, int axis, int direction, int slice, int startU, int startV, int width, int height, int u, int v, float voxelSize)
{
int count = meshData.Vertices.Count;
Vector3 item = AxisVector(axis) * direction;
BuildQuad(axis, direction, slice, startU, startV, width, height, u, v, voxelSize, out var p, out var p2, out var p3, out var p4);
meshData.Vertices.Add(p);
meshData.Vertices.Add(p2);
meshData.Vertices.Add(p3);
meshData.Vertices.Add(p4);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Colors.Add(descriptor.Color);
meshData.Colors.Add(descriptor.Color);
meshData.Colors.Add(descriptor.Color);
meshData.Colors.Add(descriptor.Color);
meshData.Uvs.Add(new Vector2(0f, 0f));
meshData.Uvs.Add(new Vector2(0f, height));
meshData.Uvs.Add(new Vector2(width, 0f));
meshData.Uvs.Add(new Vector2(width, height));
List<int> obj = meshData.SubmeshTriangles[descriptor.Submesh];
obj.Add(count);
obj.Add(count + 2);
obj.Add(count + 1);
obj.Add(count + 2);
obj.Add(count + 3);
obj.Add(count + 1);
}
private static void EmitColliderQuad(MeshData meshData, int axis, int direction, int slice, int startU, int startV, int width, int height, int u, int v, float voxelSize)
{
int count = meshData.Vertices.Count;
Vector3 item = AxisVector(axis) * direction;
BuildQuad(axis, direction, slice, startU, startV, width, height, u, v, voxelSize, out var p, out var p2, out var p3, out var p4);
meshData.Vertices.Add(p);
meshData.Vertices.Add(p2);
meshData.Vertices.Add(p3);
meshData.Vertices.Add(p4);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
meshData.Normals.Add(item);
List<int> obj = meshData.SubmeshTriangles[0];
obj.Add(count);
obj.Add(count + 2);
obj.Add(count + 1);
obj.Add(count + 2);
obj.Add(count + 3);
obj.Add(count + 1);
}
private static void BuildQuad(int axis, int direction, int slice, int startU, int startV, int width, int height, int u, int v, float voxelSize, out Vector3 p0, out Vector3 p1, out Vector3 p2, out Vector3 p3)
{
Vector3 zero = Vector3.zero;
zero[axis] = (float)slice * voxelSize;
zero[u] = (float)startU * voxelSize;
zero[v] = (float)startV * voxelSize;
Vector3 zero2 = Vector3.zero;
zero2[u] = (float)width * voxelSize;
Vector3 zero3 = Vector3.zero;
zero3[v] = (float)height * voxelSize;
if (direction > 0)
{
p0 = zero;
p1 = zero + zero3;
p2 = zero + zero2;
p3 = zero + zero2 + zero3;
}
else
{
p0 = zero;
p1 = zero + zero2;
p2 = zero + zero3;
p3 = zero + zero2 + zero3;
}
}
private static byte GetVoxel(Vector3Int size, byte[] voxels, int x, int y, int z)
{
if (x < 0 || y < 0 || z < 0 || x >= size.x || y >= size.y || z >= size.z)
{
return 0;
}
int num = x + y * size.x + z * size.x * size.y;
return voxels[num];
}
private static Vector3 AxisVector(int axis)
{
return axis switch
{
0 => Vector3.right,
1 => Vector3.up,
_ => Vector3.forward,
};
}
}
}