946 lines
27 KiB
C#
946 lines
27 KiB
C#
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace BlockSpace.Voxels
|
|
{
|
|
[DisallowMultipleComponent]
|
|
public sealed class VoxelGroup : MonoBehaviour
|
|
{
|
|
private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
|
|
|
|
private static readonly int ColorId = Shader.PropertyToID("_Color");
|
|
|
|
private static readonly int TintColorId = Shader.PropertyToID("_TintColor");
|
|
|
|
private static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor");
|
|
|
|
private static readonly int EmissiveColorId = Shader.PropertyToID("_EmissiveColor");
|
|
|
|
private static readonly Color SelectedTintColor = new Color(0.86f, 1f, 0.88f, 1f);
|
|
|
|
public ushort id;
|
|
|
|
[HideInInspector]
|
|
public int zoneID = -1;
|
|
|
|
public Vector3Int position;
|
|
|
|
public byte packedRotation;
|
|
|
|
public Vector3Int size;
|
|
|
|
public byte[] voxels;
|
|
|
|
[HideInInspector]
|
|
public MeshFilter meshFilter;
|
|
|
|
[HideInInspector]
|
|
public MeshRenderer meshRenderer;
|
|
|
|
[HideInInspector]
|
|
public MeshCollider meshCollider;
|
|
|
|
[HideInInspector]
|
|
public bool dirtyMesh = true;
|
|
|
|
[HideInInspector]
|
|
public bool dirtyCollider = true;
|
|
|
|
[SerializeField]
|
|
private bool creatorMode;
|
|
|
|
[SerializeField]
|
|
private bool useLocalGridTransform;
|
|
|
|
[SerializeField]
|
|
private float voxelSizeScale = 1f;
|
|
|
|
private VoxelGroupManager owner;
|
|
|
|
private Mesh renderMesh;
|
|
|
|
private Mesh renderMeshUnlit;
|
|
|
|
private Mesh renderMeshTransparent;
|
|
|
|
private Mesh renderMeshInvisible;
|
|
|
|
private Mesh collisionMesh;
|
|
|
|
private MeshFilter unlitMeshFilter;
|
|
|
|
private MeshRenderer unlitMeshRenderer;
|
|
|
|
private MeshFilter transparentMeshFilter;
|
|
|
|
private MeshRenderer transparentMeshRenderer;
|
|
|
|
private MeshFilter invisibleMeshFilter;
|
|
|
|
private MeshRenderer invisibleMeshRenderer;
|
|
|
|
private bool useGreedyRenderMeshing = true;
|
|
|
|
private bool allowColliderGeneration = true;
|
|
|
|
private MaterialPropertyBlock propertyBlock;
|
|
|
|
public bool IsSelected { get; private set; }
|
|
|
|
internal bool UsesLocalGridTransform => useLocalGridTransform;
|
|
|
|
internal float CurrentVoxelSize => ((owner != null) ? owner.VoxelSize : 0.125f) * Mathf.Max(0.001f, voxelSizeScale);
|
|
|
|
public void Initialize(VoxelGroupManager manager, ushort groupId, Vector3Int gridPosition, byte rotation, Vector3Int groupSize, byte[] voxelData, bool queueInitialRebuild = true)
|
|
{
|
|
owner = manager;
|
|
id = groupId;
|
|
position = gridPosition;
|
|
packedRotation = rotation;
|
|
size = groupSize;
|
|
voxels = voxelData;
|
|
creatorMode = owner != null && owner.CreatorMode;
|
|
EnsureComponents();
|
|
ApplyGridTransform();
|
|
BindMaterials((owner != null) ? owner.SharedMaterials : null);
|
|
if (queueInitialRebuild)
|
|
{
|
|
MarkDirty(mesh: true, collider: true);
|
|
return;
|
|
}
|
|
dirtyMesh = false;
|
|
dirtyCollider = false;
|
|
meshRenderer.enabled = false;
|
|
meshCollider.sharedMesh = null;
|
|
meshCollider.enabled = false;
|
|
}
|
|
|
|
public void SetVoxel(int x, int y, int z, byte value)
|
|
{
|
|
if (IsInBounds(x, y, z) && voxels != null)
|
|
{
|
|
int flatIndex = GetFlatIndex(x, y, z);
|
|
if (voxels[flatIndex] != value)
|
|
{
|
|
voxels[flatIndex] = value;
|
|
MarkDirty(mesh: true, collider: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public byte GetVoxel(int x, int y, int z)
|
|
{
|
|
if (!IsInBounds(x, y, z) || voxels == null)
|
|
{
|
|
return 0;
|
|
}
|
|
return voxels[GetFlatIndex(x, y, z)];
|
|
}
|
|
|
|
public Bounds GetWorldBounds()
|
|
{
|
|
if (size.x <= 0 || size.y <= 0 || size.z <= 0)
|
|
{
|
|
return new Bounds(base.transform.position, Vector3.zero);
|
|
}
|
|
Vector3 vector = new Vector3(size.x, size.y, size.z) * CurrentVoxelSize;
|
|
Vector3[] array = new Vector3[8]
|
|
{
|
|
base.transform.TransformPoint(new Vector3(0f, 0f, 0f)),
|
|
base.transform.TransformPoint(new Vector3(vector.x, 0f, 0f)),
|
|
base.transform.TransformPoint(new Vector3(0f, vector.y, 0f)),
|
|
base.transform.TransformPoint(new Vector3(0f, 0f, vector.z)),
|
|
base.transform.TransformPoint(new Vector3(vector.x, vector.y, 0f)),
|
|
base.transform.TransformPoint(new Vector3(vector.x, 0f, vector.z)),
|
|
base.transform.TransformPoint(new Vector3(0f, vector.y, vector.z)),
|
|
base.transform.TransformPoint(new Vector3(vector.x, vector.y, vector.z))
|
|
};
|
|
Bounds result = new Bounds(array[0], Vector3.zero);
|
|
for (int i = 1; i < array.Length; i++)
|
|
{
|
|
result.Encapsulate(array[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public void RebuildMesh()
|
|
{
|
|
EnsureComponents();
|
|
if (renderMesh == null)
|
|
{
|
|
renderMesh = new Mesh
|
|
{
|
|
name = $"VoxelGroup_Render_{id}"
|
|
};
|
|
renderMesh.indexFormat = IndexFormat.UInt32;
|
|
}
|
|
if (renderMeshUnlit == null)
|
|
{
|
|
renderMeshUnlit = new Mesh
|
|
{
|
|
name = $"VoxelGroup_RenderUnlit_{id}"
|
|
};
|
|
renderMeshUnlit.indexFormat = IndexFormat.UInt32;
|
|
}
|
|
if (renderMeshTransparent == null)
|
|
{
|
|
renderMeshTransparent = new Mesh
|
|
{
|
|
name = $"VoxelGroup_RenderTransparent_{id}"
|
|
};
|
|
renderMeshTransparent.indexFormat = IndexFormat.UInt32;
|
|
}
|
|
if (renderMeshInvisible == null)
|
|
{
|
|
renderMeshInvisible = new Mesh
|
|
{
|
|
name = $"VoxelGroup_RenderInvisible_{id}"
|
|
};
|
|
renderMeshInvisible.indexFormat = IndexFormat.UInt32;
|
|
}
|
|
bool flag = owner != null && owner.CreatorMode;
|
|
GreedyMesher.MeshData meshData = (useGreedyRenderMeshing ? GreedyMesher.BuildRenderMesh(size, voxels, flag, CurrentVoxelSize) : GreedyMesher.BuildCulledRenderMesh(size, voxels, flag, CurrentVoxelSize));
|
|
ApplyMeshSubmesh(renderMesh, meshData, 0, applyColors: true);
|
|
meshFilter.sharedMesh = renderMesh;
|
|
meshRenderer.enabled = meshData.SubmeshTriangles.Length != 0 && meshData.SubmeshTriangles[0].Count > 0;
|
|
ApplyMeshSubmesh(renderMeshUnlit, meshData, 1, applyColors: true);
|
|
unlitMeshFilter.sharedMesh = renderMeshUnlit;
|
|
unlitMeshRenderer.enabled = meshData.SubmeshTriangles.Length > 1 && meshData.SubmeshTriangles[1].Count > 0;
|
|
ApplyMeshSubmesh(renderMeshTransparent, meshData, 2, applyColors: true);
|
|
transparentMeshFilter.sharedMesh = renderMeshTransparent;
|
|
transparentMeshRenderer.enabled = meshData.SubmeshTriangles.Length > 2 && meshData.SubmeshTriangles[2].Count > 0;
|
|
ApplyMeshSubmesh(renderMeshInvisible, meshData, 3, applyColors: true);
|
|
invisibleMeshFilter.sharedMesh = renderMeshInvisible;
|
|
invisibleMeshRenderer.enabled = meshData.SubmeshTriangles.Length > 3 && meshData.SubmeshTriangles[3].Count > 0;
|
|
dirtyMesh = false;
|
|
}
|
|
|
|
public void RebuildCollider()
|
|
{
|
|
EnsureComponents();
|
|
if (!allowColliderGeneration)
|
|
{
|
|
meshCollider.sharedMesh = null;
|
|
meshCollider.enabled = false;
|
|
dirtyCollider = false;
|
|
return;
|
|
}
|
|
if (collisionMesh == null)
|
|
{
|
|
collisionMesh = new Mesh
|
|
{
|
|
name = $"VoxelGroup_Collider_{id}"
|
|
};
|
|
collisionMesh.indexFormat = IndexFormat.UInt32;
|
|
}
|
|
GreedyMesher.MeshData meshData = GreedyMesher.BuildColliderMesh(size, voxels, CurrentVoxelSize);
|
|
ApplyMeshSubmesh(collisionMesh, meshData, 0, applyColors: false);
|
|
meshCollider.sharedMesh = null;
|
|
meshCollider.sharedMesh = ((collisionMesh.vertexCount > 0) ? collisionMesh : null);
|
|
meshCollider.enabled = collisionMesh.vertexCount > 0;
|
|
dirtyCollider = false;
|
|
}
|
|
|
|
public bool EraseSphere(Vector3 worldCenter, float radius, bool markMeshDirty = true, bool markColliderDirty = true)
|
|
{
|
|
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
float num = radius / CurrentVoxelSize;
|
|
float radiusSquared = num * num;
|
|
Vector3 center = base.transform.InverseTransformPoint(worldCenter) / CurrentVoxelSize;
|
|
float num2 = num + 0.5f;
|
|
int num3 = Mathf.Clamp(Mathf.FloorToInt(center.x - num2), 0, size.x - 1);
|
|
int num4 = Mathf.Clamp(Mathf.FloorToInt(center.y - num2), 0, size.y - 1);
|
|
int num5 = Mathf.Clamp(Mathf.FloorToInt(center.z - num2), 0, size.z - 1);
|
|
int num6 = Mathf.Clamp(Mathf.FloorToInt(center.x + num2), 0, size.x - 1);
|
|
int num7 = Mathf.Clamp(Mathf.FloorToInt(center.y + num2), 0, size.y - 1);
|
|
int num8 = Mathf.Clamp(Mathf.FloorToInt(center.z + num2), 0, size.z - 1);
|
|
bool flag = false;
|
|
for (int i = num5; i <= num8; i++)
|
|
{
|
|
for (int j = num4; j <= num7; j++)
|
|
{
|
|
for (int k = num3; k <= num6; k++)
|
|
{
|
|
if (!PackedVoxel.IsAir(GetVoxel(k, j, i)) && SphereIntersectsCell(center, radiusSquared, k, j, i))
|
|
{
|
|
voxels[GetFlatIndex(k, j, i)] = 0;
|
|
flag = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (flag && (markMeshDirty || markColliderDirty))
|
|
{
|
|
MarkDirty(markMeshDirty, markColliderDirty);
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
private static bool SphereIntersectsCell(Vector3 center, float radiusSquared, int x, int y, int z)
|
|
{
|
|
float num = 0f;
|
|
if (center.x < (float)x)
|
|
{
|
|
num = (float)x - center.x;
|
|
}
|
|
else if (center.x > (float)x + 1f)
|
|
{
|
|
num = center.x - ((float)x + 1f);
|
|
}
|
|
float num2 = 0f;
|
|
if (center.y < (float)y)
|
|
{
|
|
num2 = (float)y - center.y;
|
|
}
|
|
else if (center.y > (float)y + 1f)
|
|
{
|
|
num2 = center.y - ((float)y + 1f);
|
|
}
|
|
float num3 = 0f;
|
|
if (center.z < (float)z)
|
|
{
|
|
num3 = (float)z - center.z;
|
|
}
|
|
else if (center.z > (float)z + 1f)
|
|
{
|
|
num3 = center.z - ((float)z + 1f);
|
|
}
|
|
return num * num + num2 * num2 + num3 * num3 <= radiusSquared;
|
|
}
|
|
|
|
public bool PaintSphere(Vector3 worldCenter, float radius, byte value, bool markMeshDirty = true, bool markColliderDirty = true)
|
|
{
|
|
if (PackedVoxel.IsAir(value) || voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
float num = radius / CurrentVoxelSize;
|
|
float num2 = num * num;
|
|
Vector3 vector = base.transform.InverseTransformPoint(worldCenter) / CurrentVoxelSize;
|
|
int num3 = Mathf.Clamp(Mathf.FloorToInt(vector.x - num), 0, size.x - 1);
|
|
int num4 = Mathf.Clamp(Mathf.FloorToInt(vector.y - num), 0, size.y - 1);
|
|
int num5 = Mathf.Clamp(Mathf.FloorToInt(vector.z - num), 0, size.z - 1);
|
|
int num6 = Mathf.Clamp(Mathf.FloorToInt(vector.x + num), 0, size.x - 1);
|
|
int num7 = Mathf.Clamp(Mathf.FloorToInt(vector.y + num), 0, size.y - 1);
|
|
int num8 = Mathf.Clamp(Mathf.FloorToInt(vector.z + num), 0, size.z - 1);
|
|
bool flag = false;
|
|
for (int i = num5; i <= num8; i++)
|
|
{
|
|
for (int j = num4; j <= num7; j++)
|
|
{
|
|
for (int k = num3; k <= num6; k++)
|
|
{
|
|
int flatIndex = GetFlatIndex(k, j, i);
|
|
byte b = voxels[flatIndex];
|
|
if (!PackedVoxel.IsAir(b) && b != value && !((new Vector3((float)k + 0.5f, (float)j + 0.5f, (float)i + 0.5f) - vector).sqrMagnitude > num2))
|
|
{
|
|
voxels[flatIndex] = value;
|
|
flag = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (flag && (markMeshDirty || markColliderDirty))
|
|
{
|
|
MarkDirty(markMeshDirty, markColliderDirty);
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
public bool RepaintAll(byte value, bool markMeshDirty = true, bool markColliderDirty = false)
|
|
{
|
|
if (PackedVoxel.IsAir(value) || voxels == null || voxels.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
bool flag = false;
|
|
for (int i = 0; i < voxels.Length; i++)
|
|
{
|
|
byte b = voxels[i];
|
|
if (!PackedVoxel.IsAir(b) && b != value)
|
|
{
|
|
voxels[i] = value;
|
|
flag = true;
|
|
}
|
|
}
|
|
if (flag && (markMeshDirty || markColliderDirty))
|
|
{
|
|
MarkDirty(markMeshDirty, markColliderDirty);
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
public bool IntersectsSphere(Vector3 worldCenter, float radius)
|
|
{
|
|
if (size.x <= 0 || size.y <= 0 || size.z <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
Vector3 vector = base.transform.InverseTransformPoint(worldCenter) / CurrentVoxelSize;
|
|
Vector3 zero = Vector3.zero;
|
|
Vector3 vector2 = size;
|
|
float num = 0f;
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
float num2 = vector[i];
|
|
if (num2 < zero[i])
|
|
{
|
|
float num3 = zero[i] - num2;
|
|
num += num3 * num3;
|
|
}
|
|
else if (num2 > vector2[i])
|
|
{
|
|
float num4 = num2 - vector2[i];
|
|
num += num4 * num4;
|
|
}
|
|
}
|
|
float num5 = radius / CurrentVoxelSize;
|
|
return num <= num5 * num5;
|
|
}
|
|
|
|
public bool TrimToContents()
|
|
{
|
|
if (voxels == null || voxels.Length == 0)
|
|
{
|
|
size = Vector3Int.zero;
|
|
return false;
|
|
}
|
|
int num = size.x;
|
|
int num2 = size.y;
|
|
int num3 = size.z;
|
|
int num4 = -1;
|
|
int num5 = -1;
|
|
int num6 = -1;
|
|
for (int i = 0; i < size.z; i++)
|
|
{
|
|
for (int j = 0; j < size.y; j++)
|
|
{
|
|
for (int k = 0; k < size.x; k++)
|
|
{
|
|
if (!PackedVoxel.IsAir(voxels[GetFlatIndex(k, j, i)]))
|
|
{
|
|
if (k < num)
|
|
{
|
|
num = k;
|
|
}
|
|
if (j < num2)
|
|
{
|
|
num2 = j;
|
|
}
|
|
if (i < num3)
|
|
{
|
|
num3 = i;
|
|
}
|
|
if (k > num4)
|
|
{
|
|
num4 = k;
|
|
}
|
|
if (j > num5)
|
|
{
|
|
num5 = j;
|
|
}
|
|
if (i > num6)
|
|
{
|
|
num6 = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (num4 < 0 || num5 < 0 || num6 < 0)
|
|
{
|
|
size = Vector3Int.zero;
|
|
return false;
|
|
}
|
|
if (num == 0 && num2 == 0 && num3 == 0 && num4 == size.x - 1 && num5 == size.y - 1 && num6 == size.z - 1)
|
|
{
|
|
return true;
|
|
}
|
|
Vector3Int localOffset = new Vector3Int(num, num2, num3);
|
|
Vector3Int vector3Int = new Vector3Int(num4 - num + 1, num5 - num2 + 1, num6 - num3 + 1);
|
|
byte[] array = new byte[vector3Int.x * vector3Int.y * vector3Int.z];
|
|
for (int l = num3; l <= num6; l++)
|
|
{
|
|
for (int m = num2; m <= num5; m++)
|
|
{
|
|
for (int n = num; n <= num4; n++)
|
|
{
|
|
byte b = voxels[GetFlatIndex(n, m, l)];
|
|
if (!PackedVoxel.IsAir(b))
|
|
{
|
|
int num7 = n - num;
|
|
int num8 = m - num2;
|
|
int num9 = l - num3;
|
|
int num10 = num7 + num8 * vector3Int.x + num9 * vector3Int.x * vector3Int.y;
|
|
array[num10] = b;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
voxels = array;
|
|
size = vector3Int;
|
|
position += GridRotationUtility.Rotate(localOffset, packedRotation);
|
|
ApplyGridTransform();
|
|
return true;
|
|
}
|
|
|
|
internal void SetOwner(VoxelGroupManager manager)
|
|
{
|
|
owner = manager;
|
|
creatorMode = owner != null && owner.CreatorMode;
|
|
ApplyGridTransform();
|
|
BindMaterials((owner != null) ? owner.SharedMaterials : null);
|
|
}
|
|
|
|
internal void SetUseLocalGridTransform(bool useLocalGrid)
|
|
{
|
|
if (useLocalGridTransform != useLocalGrid)
|
|
{
|
|
useLocalGridTransform = useLocalGrid;
|
|
ApplyGridTransform();
|
|
}
|
|
}
|
|
|
|
internal void OverwriteData(Vector3Int gridPosition, byte rotation, Vector3Int groupSize, byte[] voxelData, bool queueRebuild = true)
|
|
{
|
|
position = gridPosition;
|
|
packedRotation = rotation;
|
|
size = groupSize;
|
|
voxels = voxelData ?? new byte[0];
|
|
creatorMode = owner != null && owner.CreatorMode;
|
|
EnsureComponents();
|
|
ApplyGridTransform();
|
|
BindMaterials((owner != null) ? owner.SharedMaterials : null);
|
|
SetSelected(selected: false);
|
|
if (queueRebuild)
|
|
{
|
|
MarkDirty(mesh: true, collider: true);
|
|
return;
|
|
}
|
|
dirtyMesh = false;
|
|
dirtyCollider = false;
|
|
}
|
|
|
|
internal void SetGridPose(Vector3Int gridPosition, byte rotation)
|
|
{
|
|
if (!(position == gridPosition) || packedRotation != rotation)
|
|
{
|
|
position = gridPosition;
|
|
packedRotation = rotation;
|
|
ApplyGridTransform();
|
|
}
|
|
}
|
|
|
|
internal bool SetWorldVoxel(Vector3Int worldCell, byte value, bool markMeshDirty, bool markColliderDirty)
|
|
{
|
|
EnsureStorageForStoredCell(WorldGridCellToStoredCell(worldCell));
|
|
if (size.x <= 0 || size.y <= 0 || size.z <= 0 || voxels == null || voxels.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
Vector3Int vector3Int = StoredCellToLocalCell(WorldGridCellToStoredCell(worldCell));
|
|
if (!IsInBounds(vector3Int.x, vector3Int.y, vector3Int.z))
|
|
{
|
|
return false;
|
|
}
|
|
int flatIndex = GetFlatIndex(vector3Int.x, vector3Int.y, vector3Int.z);
|
|
if (voxels[flatIndex] == value)
|
|
{
|
|
return false;
|
|
}
|
|
voxels[flatIndex] = value;
|
|
if (markMeshDirty || markColliderDirty)
|
|
{
|
|
MarkDirty(markMeshDirty, markColliderDirty);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
internal bool SetStoredVoxel(Vector3Int storedCell, byte value, bool markMeshDirty, bool markColliderDirty)
|
|
{
|
|
EnsureStorageForStoredCell(storedCell);
|
|
if (size.x <= 0 || size.y <= 0 || size.z <= 0 || voxels == null || voxels.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
Vector3Int vector3Int = StoredCellToLocalCell(storedCell);
|
|
if (!IsInBounds(vector3Int.x, vector3Int.y, vector3Int.z))
|
|
{
|
|
return false;
|
|
}
|
|
int flatIndex = GetFlatIndex(vector3Int.x, vector3Int.y, vector3Int.z);
|
|
if (voxels[flatIndex] == value)
|
|
{
|
|
return false;
|
|
}
|
|
voxels[flatIndex] = value;
|
|
if (markMeshDirty || markColliderDirty)
|
|
{
|
|
MarkDirty(markMeshDirty, markColliderDirty);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
internal Vector3Int WorldPointToStoredGridCell(Vector3 worldPoint)
|
|
{
|
|
if (useLocalGridTransform && base.transform.parent != null)
|
|
{
|
|
Vector3 vector = base.transform.parent.InverseTransformPoint(worldPoint) / CurrentVoxelSize;
|
|
return new Vector3Int(Mathf.RoundToInt(vector.x), Mathf.RoundToInt(vector.y), Mathf.RoundToInt(vector.z));
|
|
}
|
|
Vector3 vector2 = worldPoint / CurrentVoxelSize;
|
|
return new Vector3Int(Mathf.RoundToInt(vector2.x), Mathf.RoundToInt(vector2.y), Mathf.RoundToInt(vector2.z));
|
|
}
|
|
|
|
internal void SetCreatorMode(bool enabled)
|
|
{
|
|
if (creatorMode != enabled)
|
|
{
|
|
creatorMode = enabled;
|
|
MarkDirty(mesh: true, collider: false);
|
|
}
|
|
}
|
|
|
|
internal bool TryPickNearestVoxelValue(Vector3 worldPoint, float maxRadius, out byte voxelValue, out Vector3 pickedWorldCenter, out float pickedDistanceSquared)
|
|
{
|
|
voxelValue = 0;
|
|
pickedWorldCenter = Vector3.zero;
|
|
pickedDistanceSquared = 0f;
|
|
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
float currentVoxelSize = CurrentVoxelSize;
|
|
if (currentVoxelSize <= 0f)
|
|
{
|
|
return false;
|
|
}
|
|
float num = maxRadius * maxRadius;
|
|
Vector3 vector = base.transform.InverseTransformPoint(worldPoint) / currentVoxelSize;
|
|
float num2 = maxRadius / currentVoxelSize;
|
|
int num3 = Mathf.Max(0, Mathf.FloorToInt(vector.x - num2));
|
|
int num4 = Mathf.Max(0, Mathf.FloorToInt(vector.y - num2));
|
|
int num5 = Mathf.Max(0, Mathf.FloorToInt(vector.z - num2));
|
|
int num6 = Mathf.Min(size.x - 1, Mathf.CeilToInt(vector.x + num2));
|
|
int num7 = Mathf.Min(size.y - 1, Mathf.CeilToInt(vector.y + num2));
|
|
int num8 = Mathf.Min(size.z - 1, Mathf.CeilToInt(vector.z + num2));
|
|
float num9 = num;
|
|
bool result = false;
|
|
for (int i = num5; i <= num8; i++)
|
|
{
|
|
for (int j = num4; j <= num7; j++)
|
|
{
|
|
for (int k = num3; k <= num6; k++)
|
|
{
|
|
byte voxel = GetVoxel(k, j, i);
|
|
if (!PackedVoxel.IsAir(voxel))
|
|
{
|
|
Vector3 vector2 = new Vector3(((float)k + 0.5f) * currentVoxelSize, ((float)j + 0.5f) * currentVoxelSize, ((float)i + 0.5f) * currentVoxelSize);
|
|
Vector3 vector3 = base.transform.TransformPoint(vector2);
|
|
float sqrMagnitude = (vector3 - worldPoint).sqrMagnitude;
|
|
if (!(sqrMagnitude > num9))
|
|
{
|
|
result = true;
|
|
num9 = sqrMagnitude;
|
|
voxelValue = voxel;
|
|
pickedWorldCenter = vector3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pickedDistanceSquared = num9;
|
|
return result;
|
|
}
|
|
|
|
public void SetVoxelSizeScale(float scale, bool rebuild = true)
|
|
{
|
|
scale = Mathf.Max(0.001f, scale);
|
|
if (!Mathf.Approximately(voxelSizeScale, scale))
|
|
{
|
|
voxelSizeScale = scale;
|
|
ApplyGridTransform();
|
|
if (rebuild)
|
|
{
|
|
RebuildMesh();
|
|
RebuildCollider();
|
|
}
|
|
else
|
|
{
|
|
dirtyMesh = true;
|
|
dirtyCollider = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void SetSelected(bool selected)
|
|
{
|
|
if (IsSelected != selected)
|
|
{
|
|
IsSelected = selected;
|
|
ApplySelectionVisual();
|
|
}
|
|
}
|
|
|
|
internal void SetGreedyRenderMeshing(bool enabled)
|
|
{
|
|
useGreedyRenderMeshing = enabled;
|
|
if (!enabled)
|
|
{
|
|
EnsureComponents();
|
|
meshCollider.sharedMesh = null;
|
|
meshCollider.enabled = false;
|
|
dirtyCollider = false;
|
|
}
|
|
}
|
|
|
|
internal void SetColliderGeneration(bool enabled)
|
|
{
|
|
allowColliderGeneration = enabled;
|
|
if (!enabled)
|
|
{
|
|
EnsureComponents();
|
|
meshCollider.sharedMesh = null;
|
|
meshCollider.enabled = false;
|
|
dirtyCollider = false;
|
|
}
|
|
}
|
|
|
|
internal void BindMaterials(Material[] materials)
|
|
{
|
|
EnsureComponents();
|
|
if (materials != null && materials.Length != 0)
|
|
{
|
|
meshRenderer.sharedMaterial = ((materials.Length != 0) ? materials[0] : null);
|
|
unlitMeshRenderer.sharedMaterial = ((materials.Length > 1) ? materials[1] : meshRenderer.sharedMaterial);
|
|
transparentMeshRenderer.sharedMaterial = ((materials.Length > 2) ? materials[2] : meshRenderer.sharedMaterial);
|
|
invisibleMeshRenderer.sharedMaterial = ((materials.Length > 3) ? materials[3] : transparentMeshRenderer.sharedMaterial);
|
|
ApplySelectionVisual();
|
|
}
|
|
}
|
|
|
|
internal void MarkDirty(bool mesh, bool collider)
|
|
{
|
|
if (mesh)
|
|
{
|
|
dirtyMesh = true;
|
|
}
|
|
if (collider)
|
|
{
|
|
dirtyCollider = true;
|
|
}
|
|
owner?.QueueRebuild(this);
|
|
}
|
|
|
|
internal int GetFlatIndex(int x, int y, int z)
|
|
{
|
|
return x + y * size.x + z * size.x * size.y;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
EnsureComponents();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (renderMesh != null)
|
|
{
|
|
Object.Destroy(renderMesh);
|
|
}
|
|
if (renderMeshUnlit != null)
|
|
{
|
|
Object.Destroy(renderMeshUnlit);
|
|
}
|
|
if (renderMeshTransparent != null)
|
|
{
|
|
Object.Destroy(renderMeshTransparent);
|
|
}
|
|
if (renderMeshInvisible != null)
|
|
{
|
|
Object.Destroy(renderMeshInvisible);
|
|
}
|
|
if (collisionMesh != null)
|
|
{
|
|
Object.Destroy(collisionMesh);
|
|
}
|
|
}
|
|
|
|
private void EnsureComponents()
|
|
{
|
|
if (meshFilter == null && !TryGetComponent<MeshFilter>(out meshFilter))
|
|
{
|
|
meshFilter = base.gameObject.AddComponent<MeshFilter>();
|
|
}
|
|
if (meshRenderer == null && !TryGetComponent<MeshRenderer>(out meshRenderer))
|
|
{
|
|
meshRenderer = base.gameObject.AddComponent<MeshRenderer>();
|
|
}
|
|
EnsureChildRenderer("Render_Unlit", ref unlitMeshFilter, ref unlitMeshRenderer);
|
|
EnsureChildRenderer("Render_Transparent", ref transparentMeshFilter, ref transparentMeshRenderer);
|
|
EnsureChildRenderer("Render_Invisible", ref invisibleMeshFilter, ref invisibleMeshRenderer);
|
|
if (meshCollider == null && !TryGetComponent<MeshCollider>(out meshCollider))
|
|
{
|
|
meshCollider = base.gameObject.AddComponent<MeshCollider>();
|
|
}
|
|
meshRenderer.shadowCastingMode = ShadowCastingMode.On;
|
|
meshRenderer.receiveShadows = true;
|
|
unlitMeshRenderer.shadowCastingMode = ShadowCastingMode.On;
|
|
unlitMeshRenderer.receiveShadows = true;
|
|
transparentMeshRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
|
transparentMeshRenderer.receiveShadows = false;
|
|
invisibleMeshRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
|
invisibleMeshRenderer.receiveShadows = false;
|
|
ApplySelectionVisual();
|
|
}
|
|
|
|
private void EnsureChildRenderer(string name, ref MeshFilter filter, ref MeshRenderer renderer)
|
|
{
|
|
if (!(filter != null) || !(renderer != null))
|
|
{
|
|
Transform transform = base.transform.Find(name);
|
|
if (transform == null)
|
|
{
|
|
GameObject obj = new GameObject(name);
|
|
obj.transform.SetParent(base.transform, worldPositionStays: false);
|
|
transform = obj.transform;
|
|
}
|
|
if (!transform.TryGetComponent<MeshFilter>(out filter))
|
|
{
|
|
filter = transform.gameObject.AddComponent<MeshFilter>();
|
|
}
|
|
if (!transform.TryGetComponent<MeshRenderer>(out renderer))
|
|
{
|
|
renderer = transform.gameObject.AddComponent<MeshRenderer>();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyGridTransform()
|
|
{
|
|
Vector3 localPosition = (Vector3)position * CurrentVoxelSize;
|
|
Quaternion quaternion = GridRotationUtility.ToQuaternion(packedRotation);
|
|
if (useLocalGridTransform)
|
|
{
|
|
base.transform.localPosition = localPosition;
|
|
base.transform.localRotation = quaternion;
|
|
}
|
|
else
|
|
{
|
|
base.transform.SetPositionAndRotation(localPosition, quaternion);
|
|
}
|
|
}
|
|
|
|
private void ApplyMeshSubmesh(Mesh mesh, GreedyMesher.MeshData meshData, int submeshIndex, bool applyColors)
|
|
{
|
|
mesh.Clear();
|
|
mesh.indexFormat = IndexFormat.UInt32;
|
|
mesh.subMeshCount = 1;
|
|
mesh.SetVertices(meshData.Vertices);
|
|
mesh.SetNormals(meshData.Normals);
|
|
if (applyColors)
|
|
{
|
|
mesh.SetColors(meshData.Colors);
|
|
mesh.SetUVs(0, meshData.Uvs);
|
|
}
|
|
if (submeshIndex >= 0 && submeshIndex < meshData.SubmeshTriangles.Length)
|
|
{
|
|
mesh.SetTriangles(meshData.SubmeshTriangles[submeshIndex], 0, calculateBounds: false);
|
|
}
|
|
mesh.RecalculateBounds();
|
|
}
|
|
|
|
private void ApplySelectionVisual()
|
|
{
|
|
if (!(meshRenderer == null))
|
|
{
|
|
if (propertyBlock == null)
|
|
{
|
|
propertyBlock = new MaterialPropertyBlock();
|
|
}
|
|
propertyBlock.Clear();
|
|
Color color = (IsSelected ? SelectedTintColor : Color.white);
|
|
propertyBlock.SetColor(BaseColorId, color);
|
|
propertyBlock.SetColor(ColorId, color);
|
|
propertyBlock.SetColor(TintColorId, color);
|
|
propertyBlock.SetColor(EmissionColorId, IsSelected ? (color * 0.06f) : Color.black);
|
|
propertyBlock.SetColor(EmissiveColorId, IsSelected ? (color * 0.06f) : Color.black);
|
|
meshRenderer.SetPropertyBlock(propertyBlock);
|
|
unlitMeshRenderer?.SetPropertyBlock(propertyBlock);
|
|
transparentMeshRenderer?.SetPropertyBlock(propertyBlock);
|
|
invisibleMeshRenderer?.SetPropertyBlock(propertyBlock);
|
|
}
|
|
}
|
|
|
|
private bool IsInBounds(int x, int y, int z)
|
|
{
|
|
if (x >= 0 && y >= 0 && z >= 0 && x < size.x && y < size.y)
|
|
{
|
|
return z < size.z;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void EnsureStorageForStoredCell(Vector3Int storedCell)
|
|
{
|
|
if (voxels == null || voxels.Length == 0 || size.x <= 0 || size.y <= 0 || size.z <= 0)
|
|
{
|
|
position = storedCell;
|
|
size = Vector3Int.one;
|
|
voxels = new byte[1];
|
|
ApplyGridTransform();
|
|
return;
|
|
}
|
|
Vector3Int vector3Int = StoredCellToLocalCell(storedCell);
|
|
Vector3Int vector3Int2 = new Vector3Int(Mathf.Min(0, vector3Int.x), Mathf.Min(0, vector3Int.y), Mathf.Min(0, vector3Int.z));
|
|
Vector3Int vector3Int3 = new Vector3Int(Mathf.Max(size.x - 1, vector3Int.x), Mathf.Max(size.y - 1, vector3Int.y), Mathf.Max(size.z - 1, vector3Int.z));
|
|
if (vector3Int2 == Vector3Int.zero && vector3Int3.x == size.x - 1 && vector3Int3.y == size.y - 1 && vector3Int3.z == size.z - 1)
|
|
{
|
|
return;
|
|
}
|
|
Vector3Int vector3Int4 = new Vector3Int(vector3Int3.x - vector3Int2.x + 1, vector3Int3.y - vector3Int2.y + 1, vector3Int3.z - vector3Int2.z + 1);
|
|
byte[] array = new byte[vector3Int4.x * vector3Int4.y * vector3Int4.z];
|
|
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 b = voxels[GetFlatIndex(k, j, i)];
|
|
if (!PackedVoxel.IsAir(b))
|
|
{
|
|
int num = k - vector3Int2.x;
|
|
int num2 = j - vector3Int2.y;
|
|
int num3 = i - vector3Int2.z;
|
|
int num4 = num + num2 * vector3Int4.x + num3 * vector3Int4.x * vector3Int4.y;
|
|
array[num4] = b;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
voxels = array;
|
|
size = vector3Int4;
|
|
position += GridRotationUtility.Rotate(vector3Int2, packedRotation);
|
|
ApplyGridTransform();
|
|
}
|
|
|
|
private Vector3Int StoredCellToLocalCell(Vector3Int storedCell)
|
|
{
|
|
return GridRotationUtility.InverseRotate(storedCell - position, packedRotation);
|
|
}
|
|
|
|
private Vector3Int WorldGridCellToStoredCell(Vector3Int worldCell)
|
|
{
|
|
if (!useLocalGridTransform || base.transform.parent == null)
|
|
{
|
|
return worldCell;
|
|
}
|
|
Vector3 vector = base.transform.parent.InverseTransformPoint((Vector3)worldCell * CurrentVoxelSize) / CurrentVoxelSize;
|
|
return new Vector3Int(Mathf.RoundToInt(vector.x), Mathf.RoundToInt(vector.y), Mathf.RoundToInt(vector.z));
|
|
}
|
|
}
|
|
}
|