Created
July 3, 2023 16:04
-
-
Save ssell/652dfcd726a8ae57bd17cfdd21dfc260 to your computer and use it in GitHub Desktop.
Simple Unity Collider Voxelizer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using UnityEngine; | |
namespace VertexFragment | |
{ | |
/// <summary> | |
/// Generates a list of voxel points for a given collider. | |
/// </summary> | |
public static class VoxelGenerator | |
{ | |
/// <summary> | |
/// Returns an empty list if no valid collider is found attached to the game object. | |
/// </summary> | |
/// <param name="go"></param> | |
/// <param name="isConcave"></param> | |
/// <param name="xSlices"></param> | |
/// <param name="ySlices"></param> | |
/// <param name="zSlices"></param> | |
/// <returns></returns> | |
public static List<Vector3> Voxelize(GameObject go, bool isConcave, int xSlices, int ySlices, int zSlices) | |
{ | |
if (go == null) | |
{ | |
return new List<Vector3>(); | |
} | |
if (isConcave) | |
{ | |
return VoxelizeConcave(go.GetComponent<MeshCollider>(), xSlices, ySlices, zSlices); | |
} | |
else | |
{ | |
return VoxelizeConvex(go.GetComponent<Collider>(), xSlices, ySlices, zSlices); | |
} | |
} | |
/// <summary> | |
/// Voxelizes a concave collider, which must be done using a <see cref="MeshCollider"/>. | |
/// </summary> | |
/// <param name="collider"></param> | |
/// <param name="xSlices"></param> | |
/// <param name="ySlices"></param> | |
/// <param name="zSlices"></param> | |
/// <returns></returns> | |
public static List<Vector3> VoxelizeConcave(MeshCollider collider, int xSlices, int ySlices, int zSlices) | |
{ | |
List<Vector3> voxels = new List<Vector3>(xSlices * ySlices * zSlices); | |
if (collider == null) | |
{ | |
return voxels; | |
} | |
bool prevConvex = collider.convex; | |
collider.convex = false; | |
var bounds = collider.bounds; | |
xSlices = (xSlices <= 0) ? 1 : xSlices; | |
ySlices = (xSlices <= 0) ? 1 : ySlices; | |
zSlices = (xSlices <= 0) ? 1 : zSlices; | |
float xStep = bounds.size.x / xSlices; | |
float yStep = bounds.size.y / ySlices; | |
float zStep = bounds.size.z / zSlices; | |
for (int iz = 0; iz < zSlices; ++iz) | |
{ | |
for (int iy = 0; iy < ySlices; ++iy) | |
{ | |
for (int ix = 0; ix < xSlices; ++ix) | |
{ | |
Vector3 voxelCenter = new Vector3( | |
bounds.min.x + (xStep * (0.5f + ix)), | |
bounds.min.y + (yStep * (0.5f + iy)), | |
bounds.min.z + (zStep * (0.5f + iz))); | |
voxelCenter = collider.gameObject.transform.InverseTransformPoint(voxelCenter); | |
if (IsPointInCollider(collider, voxelCenter)) | |
{ | |
voxels.Add(voxelCenter); | |
} | |
} | |
} | |
} | |
if (voxels.Count == 0) | |
{ | |
// Just in case we somehow failed to generate a single point within the mesh. | |
voxels.Add(bounds.center); | |
} | |
collider.convex = prevConvex; | |
return voxels; | |
} | |
/// <summary> | |
/// Voxelizes a convex collider, which can be done with any collider. | |
/// </summary> | |
/// <param name="collider"></param> | |
/// <param name="xSlices"></param> | |
/// <param name="ySlices"></param> | |
/// <param name="zSlices"></param> | |
/// <returns></returns> | |
public static List<Vector3> VoxelizeConvex(Collider collider, int xSlices, int ySlices, int zSlices) | |
{ | |
List<Vector3> voxels = new List<Vector3>(xSlices * ySlices * zSlices); | |
if (collider == null) | |
{ | |
return voxels; | |
} | |
var bounds = collider.bounds; | |
xSlices = (xSlices <= 0) ? 1 : xSlices; | |
ySlices = (xSlices <= 0) ? 1 : ySlices; | |
zSlices = (xSlices <= 0) ? 1 : zSlices; | |
float xStep = bounds.size.x / xSlices; | |
float yStep = bounds.size.y / ySlices; | |
float zStep = bounds.size.z / zSlices; | |
for (int iz = 0; iz < zSlices; ++iz) | |
{ | |
for (int iy = 0; iy < ySlices; ++iy) | |
{ | |
for (int ix = 0; ix < xSlices; ++ix) | |
{ | |
Vector3 voxelCenter = new Vector3( | |
bounds.min.x + (xStep * (0.5f + ix)), | |
bounds.min.y + (yStep * (0.5f + iy)), | |
bounds.min.z + (zStep * (0.5f + iz))); | |
voxelCenter = collider.gameObject.transform.InverseTransformPoint(voxelCenter); | |
voxels.Add(voxelCenter); | |
} | |
} | |
} | |
return voxels; | |
} | |
/// <summary> | |
/// Uses raycasts to check if the specified point is within the collider. | |
/// </summary> | |
/// <param name="collider"></param> | |
/// <param name="point"></param> | |
/// <param name="maxDistance"></param> | |
/// <returns></returns> | |
private static bool IsPointInCollider(Collider collider, Vector3 point, float maxDistance = 1000.0f) | |
{ | |
Vector3[] directions = { | |
Vector3.up, | |
Vector3.down, | |
Vector3.left, | |
Vector3.right, | |
Vector3.forward, | |
Vector3.back | |
}; | |
foreach (var direction in directions) | |
{ | |
// If just one of the ray directions fail, then we are not inside the collider. | |
if (!collider.Raycast(new Ray(point, direction), out _, maxDistance)) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment