231 lines
9.5 KiB
C#
231 lines
9.5 KiB
C#
using System.Collections.Generic;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
|
|
namespace Obi
|
|
{
|
|
|
|
/**
|
|
* Generates a sparse distance field from a voxel representation of a mesh.
|
|
*/
|
|
public class VoxelDistanceField
|
|
{
|
|
public Vector3[,,] distanceField; // for each coordinate, stores coordinates of closest surface voxel.
|
|
|
|
private MeshVoxelizer voxelizer;
|
|
|
|
public VoxelDistanceField(MeshVoxelizer voxelizer)
|
|
{
|
|
this.voxelizer = voxelizer;
|
|
}
|
|
|
|
public Vector4 SampleUnfiltered(int x, int y, int z)
|
|
{
|
|
x = Mathf.Clamp(x, 0, voxelizer.resolution.x - 1);
|
|
y = Mathf.Clamp(y, 0, voxelizer.resolution.y - 1);
|
|
z = Mathf.Clamp(z, 0, voxelizer.resolution.z - 1);
|
|
|
|
var grad = distanceField[x, y, z];
|
|
float dist = grad.magnitude;
|
|
grad.Normalize();
|
|
|
|
return new Vector4(grad.x, grad.y, grad.z, -dist);
|
|
}
|
|
|
|
public Vector4 SampleFiltered(float x, float y, float z, Vector3Int axisMask)
|
|
{
|
|
var pos = new Vector3(x, y, z);
|
|
|
|
// clamp position inside the distance field:
|
|
var min = voxelizer.GetVoxelCenter(new Vector3Int(0, 0, 0));
|
|
var max = voxelizer.GetVoxelCenter(new Vector3Int(voxelizer.resolution.x - 1, voxelizer.resolution.y - 1, voxelizer.resolution.z - 1));
|
|
pos.x = Mathf.Clamp(pos.x, min.x, max.x);
|
|
pos.y = Mathf.Clamp(pos.y, min.y, max.y);
|
|
pos.z = Mathf.Clamp(pos.z, min.z, max.z);
|
|
|
|
var voxel = voxelizer.GetPointVoxel(pos - (Vector3)axisMask * voxelizer.voxelSize * 0.5f) - voxelizer.Origin;
|
|
var voxelCenter = voxelizer.GetVoxelCenter(voxel);
|
|
var norm = Vector3.Scale((pos - voxelCenter) / voxelizer.voxelSize, axisMask);
|
|
|
|
var xz00 = SampleUnfiltered(voxel.x, voxel.y, voxel.z);
|
|
var xz01 = SampleUnfiltered(voxel.x, voxel.y, voxel.z + 1);
|
|
var xz10 = SampleUnfiltered(voxel.x + 1, voxel.y, voxel.z);
|
|
var xz11 = SampleUnfiltered(voxel.x + 1, voxel.y, voxel.z + 1);
|
|
|
|
var yz00 = SampleUnfiltered(voxel.x, voxel.y + 1, voxel.z);
|
|
var yz01 = SampleUnfiltered(voxel.x, voxel.y + 1, voxel.z + 1);
|
|
var yz10 = SampleUnfiltered(voxel.x + 1, voxel.y + 1, voxel.z);
|
|
var yz11 = SampleUnfiltered(voxel.x + 1, voxel.y + 1, voxel.z + 1);
|
|
|
|
var X1 = Vector4.Lerp(xz00, xz10, norm.x);
|
|
var X2 = Vector4.Lerp(xz01, xz11, norm.x);
|
|
var X3 = Vector4.Lerp(yz00, yz10, norm.x);
|
|
var X4 = Vector4.Lerp(yz01, yz11, norm.x);
|
|
|
|
var Y1 = Vector4.Lerp(X1, X2, norm.z);
|
|
var Y2 = Vector4.Lerp(X3, X4, norm.z);
|
|
|
|
return Vector4.Lerp(Y1, Y2, norm.y);
|
|
}
|
|
|
|
public void Smooth()
|
|
{
|
|
// create output buffer for ping-pong.
|
|
Vector3[,,] smoothed = new Vector3[voxelizer.resolution.x,
|
|
voxelizer.resolution.y,
|
|
voxelizer.resolution.z];
|
|
|
|
for (int x = 0; x < distanceField.GetLength(0); ++x)
|
|
for (int y = 0; y < distanceField.GetLength(1); ++y)
|
|
for (int z = 0; z < distanceField.GetLength(2); ++z)
|
|
{
|
|
if (voxelizer[x, y, z] != MeshVoxelizer.Voxel.Outside)
|
|
{
|
|
var p = new Vector3Int(x, y, z);
|
|
Vector3 df = distanceField[x, y, z];
|
|
int count = 1;
|
|
foreach (var o in MeshVoxelizer.faceNeighborhood)
|
|
{
|
|
// offset voxel to get neighbor:
|
|
var n = p + o;
|
|
if (voxelizer.VoxelExists(n.x, n.y, n.z) && voxelizer[n.x, n.y, n.z] != MeshVoxelizer.Voxel.Outside)
|
|
{
|
|
df += distanceField[n.x, n.y, n.z];
|
|
count++;
|
|
}
|
|
}
|
|
df /= count;
|
|
smoothed[x, y, z] = df;
|
|
}
|
|
}
|
|
|
|
distanceField = smoothed;
|
|
}
|
|
|
|
private void CalculateGradientsAndDistances(Vector3Int[,,] buffer1)
|
|
{
|
|
distanceField = new Vector3[voxelizer.resolution.x,
|
|
voxelizer.resolution.y,
|
|
voxelizer.resolution.z];
|
|
|
|
for (int x = 0; x < buffer1.GetLength(0); ++x)
|
|
for (int y = 0; y < buffer1.GetLength(1); ++y)
|
|
for (int z = 0; z < buffer1.GetLength(2); ++z)
|
|
{
|
|
if (voxelizer[x, y, z] != MeshVoxelizer.Voxel.Outside)
|
|
{
|
|
distanceField[x, y, z] = voxelizer.GetVoxelCenter(buffer1[x, y, z]) -
|
|
voxelizer.GetVoxelCenter(new Vector3Int(x, y, z));
|
|
}
|
|
else
|
|
distanceField[x, y, z] = Vector3.zero;
|
|
}
|
|
}
|
|
|
|
public IEnumerator JumpFlood()
|
|
{
|
|
// create two buffers for ping-ponging:
|
|
Vector3Int[,,] buffer1 = new Vector3Int[voxelizer.resolution.x,
|
|
voxelizer.resolution.y,
|
|
voxelizer.resolution.z];
|
|
|
|
Vector3Int[,,] buffer2 = new Vector3Int[voxelizer.resolution.x,
|
|
voxelizer.resolution.y,
|
|
voxelizer.resolution.z];
|
|
|
|
// initialize distance field:
|
|
for (int x = 0; x < buffer1.GetLength(0); ++x)
|
|
for (int y = 0; y < buffer1.GetLength(1); ++y)
|
|
for (int z = 0; z < buffer1.GetLength(2); ++z)
|
|
{
|
|
if (voxelizer[x, y, z] == MeshVoxelizer.Voxel.Outside)
|
|
buffer1[x, y, z] = new Vector3Int(x, y, z);
|
|
else
|
|
buffer1[x, y, z] = new Vector3Int(-1, -1, -1);
|
|
}
|
|
|
|
// calculate the maximum size of the buffer:
|
|
int size = Mathf.Max(buffer1.GetLength(0),
|
|
buffer1.GetLength(1),
|
|
buffer1.GetLength(2));
|
|
int step = (int)(size / 2.0f);
|
|
|
|
yield return new CoroutineJob.ProgressInfo("Generating voxel distance field...", 0);
|
|
|
|
float numPasses = (int)Mathf.Log(size, 2);
|
|
int i = 0;
|
|
|
|
// jump flood passes:
|
|
while (step >= 1)
|
|
{
|
|
JumpFloodPass(step, buffer1, buffer2);
|
|
|
|
// halve step:
|
|
step /= 2;
|
|
|
|
// swap buffers:
|
|
Vector3Int[,,] temp = buffer1;
|
|
buffer1 = buffer2;
|
|
buffer2 = temp;
|
|
|
|
yield return new CoroutineJob.ProgressInfo("Generating voxel distance field...", ++i / numPasses);
|
|
}
|
|
|
|
CalculateGradientsAndDistances(buffer1);
|
|
}
|
|
|
|
private void JumpFloodPass(int stride, Vector3Int[,,] input, Vector3Int[,,] output)
|
|
{
|
|
// for each voxel:
|
|
for (int x = 0; x < input.GetLength(0); ++x)
|
|
for (int y = 0; y < input.GetLength(1); ++y)
|
|
for (int z = 0; z < input.GetLength(2); ++z)
|
|
{
|
|
// our position:
|
|
Vector3Int p = new Vector3Int(x, y, z);
|
|
|
|
// our seed:
|
|
Vector3Int s = input[x, y, z];
|
|
|
|
// copy the closest seed to the output, in case we do not update it this pass:
|
|
output[x, y, z] = s;
|
|
|
|
// this voxel is a seed, skip it.
|
|
if (s.x == x && s.y == y && s.z == z)
|
|
continue;
|
|
|
|
// distance to our closest seed:
|
|
float dist = float.MaxValue;
|
|
if (s.x >= 0)
|
|
dist = (s - p).sqrMagnitude;
|
|
|
|
// for each neighbor voxel:
|
|
foreach (var o in MeshVoxelizer.fullNeighborhood)
|
|
{
|
|
// offset voxel to get neighbor:
|
|
var n = p + o * stride;
|
|
|
|
if (voxelizer.VoxelExists(n.x, n.y, n.z))
|
|
{
|
|
// neighbors' closest seed.
|
|
Vector3Int nc = input[n.x, n.y, n.z];
|
|
|
|
if (nc.x >= 0)
|
|
{
|
|
// distance to neighbor's closest seed:
|
|
float newDist = (nc - p).sqrMagnitude;
|
|
|
|
// if the distance to the neighbor's closest seed is smaller than the distance to ours:
|
|
if (newDist < dist)
|
|
{
|
|
output[x, y, z] = nc;
|
|
dist = newDist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
} |