Skip to content

Instantly share code, notes, and snippets.

@ssell
Created July 3, 2023 16:04
Show Gist options
  • Save ssell/652dfcd726a8ae57bd17cfdd21dfc260 to your computer and use it in GitHub Desktop.
Save ssell/652dfcd726a8ae57bd17cfdd21dfc260 to your computer and use it in GitHub Desktop.
Simple Unity Collider Voxelizer
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