You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2690 lines
85 KiB
C#
2690 lines
85 KiB
C#
/******************************************************************************/
|
|
/*
|
|
Project - MudBun
|
|
Publisher - Long Bunny Labs
|
|
http://LongBunnyLabs.com
|
|
Author - Ming-Lun "Allen" Chou
|
|
http://AllenChou.net
|
|
*/
|
|
/******************************************************************************/
|
|
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
using Unity.Collections;
|
|
using Unity.Jobs;
|
|
using UnityEngine;
|
|
using UnityEngine.Jobs;
|
|
using UnityEngine.Profiling;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
|
|
#if MUDBUN_BURST
|
|
using Unity.Burst;
|
|
using Unity.Mathematics;
|
|
#endif
|
|
|
|
namespace MudBun
|
|
{
|
|
using AabbTree = NativeArray<AabbTree<MudBrushBase>.NodePod>;
|
|
using BrushArray = NativeArray<SdfBrush>;
|
|
using MaterialArray = NativeArray<SdfBrushMaterial>;
|
|
using IntStack = NativeArray<int>;
|
|
using FloatStack = NativeArray<float>;
|
|
using VecStack = NativeArray<Vector3>;
|
|
using MatStack = NativeArray<SdfBrushMaterial>;
|
|
|
|
/// <summary>
|
|
/// Low-level interface for CPU-based SDF evaluation. Used by renderers internally. Tinker at your own risk.
|
|
/// </summary>
|
|
#if MUDBUN_BURST
|
|
[BurstCompile]
|
|
#endif
|
|
public static unsafe class Sdf
|
|
{
|
|
#if MUDBUN_BURST
|
|
private static readonly int MaxAabbTreeStackSize = 16;
|
|
private static readonly int MaxBrushGroupDepth = 8;
|
|
#endif
|
|
|
|
public enum AsyncMode
|
|
{
|
|
Invalid = -1,
|
|
None,
|
|
Async,
|
|
AsyncInputCopied, // not used right now; should lean on client-side resource managerment for better performance on multi-frame async evaluation
|
|
}
|
|
|
|
/// <summary>
|
|
/// SDF value and/or normal (normalized gradient) evaluation result.
|
|
/// </summary>
|
|
public struct Result
|
|
{
|
|
public static Result New(float value, in SdfBrushMaterial material, Vector3 normal)
|
|
{
|
|
return
|
|
new Result()
|
|
{
|
|
m_value = value,
|
|
m_material = material,
|
|
m_normal = normal,
|
|
};
|
|
}
|
|
|
|
private float m_value;
|
|
private SdfBrushMaterial m_material;
|
|
private Vector3 m_normal;
|
|
|
|
/// <summary>
|
|
/// SDF value.
|
|
/// </summary>
|
|
public float Value => m_value;
|
|
/// <summary>
|
|
/// Material (if material computation is specified).
|
|
/// </summary>
|
|
public SdfBrushMaterial Material => m_material;
|
|
/// <summary>
|
|
/// SDF normal (normalized gradient).
|
|
/// </summary>
|
|
public Vector3 Normal => m_normal;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signature for static methods meant to be tagged with <c>RegisterSdfBrushEvalFuncAttribute</c> for CPU-based SDF brush computation.
|
|
/// </summary>
|
|
/// <param name="res">SDF value.</param>
|
|
/// <param name="p">Sample position.</param>
|
|
/// <param name="pRel">Relative sample position to brush.</param>
|
|
/// <param name="aBrush">Array of brush compute data.</param>
|
|
/// <param name="iBrush">Index of the current brush's first compute data element in the array.</param>
|
|
/// <returns></returns>
|
|
public delegate float SdfBrushEvalFunc(float res, ref float3 p, in float3 pRel, SdfBrush* aBrush, int iBrush);
|
|
|
|
#if MUDBUN_BURST
|
|
public struct SdfBrushEvalFuncMapEntry
|
|
{
|
|
public int BrushType;
|
|
public FunctionPointer<SdfBrushEvalFunc> Func;
|
|
}
|
|
|
|
private static readonly int DenseSdfEvalMapSize = 500;
|
|
private static NativeArray<FunctionPointer<SdfBrushEvalFunc>> s_sdfEvalFuncMapDense;
|
|
private static NativeArray<SdfBrushEvalFuncMapEntry> s_sdfEvalFuncMapSparse;
|
|
#else
|
|
public struct float3 { } // for documentation
|
|
private static bool s_warnedBurstMissing = false;
|
|
private static void WarnBurstMissing()
|
|
{
|
|
if (s_warnedBurstMissing)
|
|
return;
|
|
|
|
Debug.LogWarning("MudBun: Burst is now needed for MudBun's new raycast-based selection & CPU-based computations (SDF/normal/raycast/snap).\n"
|
|
+ "Please import the Burst package (version 1.2.3 or 1.6.6 and newer) in Unity's package manager and then click Tools > MudBun > Refresh Compatibility.");
|
|
s_warnedBurstMissing = true;
|
|
}
|
|
#endif
|
|
|
|
#if MUDBUN_BURST
|
|
[BurstCompile]
|
|
public static unsafe float DummyEvaluateSdf(float res, ref float3 p, in float3 pRel, SdfBrush* aBrush, int iBrush)
|
|
{
|
|
return float.MaxValue;
|
|
}
|
|
#endif
|
|
|
|
public static void InitEvalMap()
|
|
{
|
|
#if MUDBUN_BURST
|
|
if (!s_sdfEvalFuncMapDense.IsCreated)
|
|
{
|
|
s_sdfEvalFuncMapDense = new NativeArray<FunctionPointer<SdfBrushEvalFunc>>(DenseSdfEvalMapSize, Allocator.Persistent);
|
|
var dummy = (SdfBrushEvalFunc) DummyEvaluateSdf;
|
|
for (int i = 0; i < s_sdfEvalFuncMapDense.Length; ++i)
|
|
RegisterSdfBrushEvalFunc(i, dummy);
|
|
}
|
|
if (!s_sdfEvalFuncMapSparse.IsCreated)
|
|
{
|
|
s_sdfEvalFuncMapSparse = new NativeArray<SdfBrushEvalFuncMapEntry>(1, Allocator.Persistent);
|
|
}
|
|
|
|
var assembly = Assembly.GetExecutingAssembly();
|
|
var types = assembly.GetTypes();
|
|
var brushTypes = types.Where(x => x.IsSubclassOf(typeof(MudBrushBase)));
|
|
foreach (var brushClass in brushTypes)
|
|
{
|
|
var methods = brushClass.GetMethods();
|
|
var sdfEvalFuncs = methods.Where(x => x.IsStatic && x.GetCustomAttribute<RegisterSdfBrushEvalFuncAttribute>() != null && !x.Name.Contains("$Burst"));
|
|
foreach (var evalFunc in sdfEvalFuncs)
|
|
{
|
|
var attr = evalFunc.GetCustomAttribute<RegisterSdfBrushEvalFuncAttribute>();
|
|
var d = (SdfBrushEvalFunc) evalFunc.CreateDelegate(typeof(SdfBrushEvalFunc));
|
|
RegisterSdfBrushEvalFunc(attr.BrushType, d);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public static void DisposeEvalMap()
|
|
{
|
|
#if MUDBUN_BURST
|
|
if (s_sdfEvalFuncMapDense.IsCreated)
|
|
s_sdfEvalFuncMapDense.Dispose();
|
|
if (s_sdfEvalFuncMapSparse.IsCreated)
|
|
s_sdfEvalFuncMapSparse.Dispose();
|
|
#endif
|
|
}
|
|
|
|
#if MUDBUN_BURST
|
|
public static void RegisterSdfBrushEvalFunc(int brushType, SdfBrushEvalFunc func)
|
|
{
|
|
//var pFunc = new FunctionPointer<SdfBrushEvalFunc>(Marshal.GetFunctionPointerForDelegate(func));
|
|
var pFunc = BurstCompiler.CompileFunctionPointer(func);
|
|
|
|
if (brushType < DenseSdfEvalMapSize)
|
|
{
|
|
s_sdfEvalFuncMapDense[brushType] = pFunc;
|
|
}
|
|
else
|
|
{
|
|
var entry =
|
|
new SdfBrushEvalFuncMapEntry()
|
|
{
|
|
BrushType = brushType,
|
|
Func = pFunc,
|
|
};
|
|
|
|
int iExistingEntry = -1;
|
|
|
|
if (s_sdfEvalFuncMapSparse.IsCreated)
|
|
{
|
|
for (int i = 0; i < s_sdfEvalFuncMapSparse.Length; ++i)
|
|
{
|
|
if (s_sdfEvalFuncMapSparse[i].BrushType != brushType)
|
|
continue;
|
|
|
|
iExistingEntry = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iExistingEntry < 0)
|
|
{
|
|
if (s_sdfEvalFuncMapSparse.IsCreated)
|
|
{
|
|
int len = s_sdfEvalFuncMapSparse.Length;
|
|
var oldMap = s_sdfEvalFuncMapSparse;
|
|
s_sdfEvalFuncMapSparse = new NativeArray<SdfBrushEvalFuncMapEntry>(len + 1, Allocator.Persistent);
|
|
for (int i = 0; i < oldMap.Length; ++i)
|
|
s_sdfEvalFuncMapSparse[i] = oldMap[i];
|
|
oldMap.Dispose();
|
|
}
|
|
else
|
|
{
|
|
s_sdfEvalFuncMapSparse = new NativeArray<SdfBrushEvalFuncMapEntry>(1, Allocator.Persistent);
|
|
}
|
|
|
|
s_sdfEvalFuncMapSparse[s_sdfEvalFuncMapSparse.Length - 1] = entry;
|
|
}
|
|
else
|
|
{
|
|
s_sdfEvalFuncMapSparse[iExistingEntry] = entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
// operators
|
|
//-------------------------------------------------------------------------
|
|
|
|
[BurstCompile]
|
|
public static float DistBlendWeight(float distA, float distB, float strength)
|
|
{
|
|
float m = 1.0f / Mathf.Max(MathUtil.Epsilon, distA);
|
|
float n = 1.0f / Mathf.Max(MathUtil.Epsilon, distB);
|
|
m = Mathf.Pow(m, strength);
|
|
n = Mathf.Pow(n, strength);
|
|
return MathUtil.Saturate(n / (m + n));
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float UniQuad(float a, float b, float k)
|
|
{
|
|
float h = math.max(k - math.abs(a - b), 0.0f) / math.max(k, MathUtil.Epsilon);
|
|
return math.min(a, b) - h * h * k * MathUtil.OneOver4;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float UniCubic(float a, float b, float k)
|
|
{
|
|
float h = math.max(k - math.abs(a - b), 0.0f) / math.max(k, MathUtil.Epsilon);
|
|
return math.min(a, b) - h * h * h * k * MathUtil.OneOver6;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float UniRound(float a, float b, float r)
|
|
{
|
|
float2 u = math.max(new float2(r - a, r - b), new float2(0.0f, 0.0f));
|
|
return math.max(r, math.min(a, b)) - math.length(u);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float UniChamfer(float a, float b, float r)
|
|
{
|
|
return math.min(math.min(a, b), (a - r + b) * MathUtil.Sqrt2Inv);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float IntQuad(float a, float b, float k)
|
|
{
|
|
float h = math.max(k - math.abs(a - b), 0.0f) / math.max(k, MathUtil.Epsilon);
|
|
return math.max(a, b) + h * h * k * MathUtil.OneOver4;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float IntCubic(float a, float b, float k)
|
|
{
|
|
float h = math.max(k - math.abs(a - b), 0.0f) / math.max(k, MathUtil.Epsilon);
|
|
return math.max(a, b) + h * h * h * k * MathUtil.OneOver6;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float IntRound(float a, float b, float r)
|
|
{
|
|
float2 u = math.max(new float2(r + a, r + b), new float2(0.0f, 0.0f));
|
|
return math.min(-r, math.max(a, b)) + math.length(u);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float IntChamfer(float a, float b, float r)
|
|
{
|
|
return math.max(math.max(a, b), (a + r + b) * MathUtil.Sqrt2Inv);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float SubQuad(float a, float b, float k)
|
|
{
|
|
return IntQuad(a, -b, k);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float SubCubic(float a, float b, float k)
|
|
{
|
|
return IntCubic(a, -b, k);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float SubRound(float a, float b, float r)
|
|
{
|
|
return IntRound(a, -b, r);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float SubChamfer(float a, float b, float r)
|
|
{
|
|
return IntChamfer(a, -b, r);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Pipe(float a, float b, float r)
|
|
{
|
|
return math.length(new float2(a, b)) - r;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Engrave(float a, float b, float r)
|
|
{
|
|
return math.max(a, (a + r - math.abs(b))) * MathUtil.Sqrt2Inv;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// end: operators
|
|
|
|
|
|
// primitives
|
|
//-------------------------------------------------------------------------
|
|
|
|
[BurstCompile]
|
|
public static float Sphere(in float3 p, float r)
|
|
{
|
|
return math.length(p) - r;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Ellipsoid(in float3 p, in float3 h)
|
|
{
|
|
float k0 = math.max(MathUtil.Epsilon, math.length(p / h));
|
|
float k1 = math.max(MathUtil.Epsilon, math.length(p / (h * h)));
|
|
return k0 * (k0 - 1.0f) / k1;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Box(in float3 p, in float3 h, float r = 0.0f)
|
|
{
|
|
float3 absH = math.abs(h);
|
|
float3 d = math.abs(p) - absH;
|
|
return math.length(math.max(d, 0.0f)) + math.min(math.cmax(d), 0.0f) - r;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Capsule(in float3 p, in float3 a, in float3 b, float r)
|
|
{
|
|
float3 ab = b - a;
|
|
float3 ap = p - a;
|
|
float3 pAdj = p - a + math.saturate(math.dot(ap, ab) / math.dot(ab, ab)) * ab;
|
|
return math.length(pAdj) - r;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float CappedCone(in float3 p, float h, float r1, float r2, float r = 0.0f)
|
|
{
|
|
float2 q = new float2(math.length(p.xz), p.y);
|
|
float2 k1 = new float2(r2, h);
|
|
float2 k2 = new float2(r2 - r1, 2.0f * h);
|
|
float2 ca = new float2(q.x - math.min(q.x, (q.y < 0.0f) ? r1 : r2), math.abs(q.y) - h);
|
|
float2 cb = q - k1 + k2 * math.clamp(math.dot(k1 - q, k2) / math.dot(k2, k2), 0.0f, 1.0f);
|
|
float s = (cb.x < 0.0f && ca.y < 0.0f) ? -1.0f : 1.0f;
|
|
return s * math.sqrt(math.min(math.dot(ca, ca), math.dot(cb, cb))) - r;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float RoundCone(in float3 p, in float3 a, in float3 b, float r1, float r2)
|
|
{
|
|
// sampling independent computations (only depend on shape)
|
|
float3 ba = b - a;
|
|
float l2 = math.dot(ba, ba);
|
|
float rr = r1 - r2;
|
|
float a2 = l2 - rr * rr;
|
|
float il2 = 1.0f / l2;
|
|
|
|
// sampling dependent computations
|
|
float3 pa = p - a;
|
|
float y = math.dot(pa, ba);
|
|
float z = y - l2;
|
|
float3 g = pa * l2 - ba * y;
|
|
float x2 = math.dot(g, g);
|
|
float y2 = y * y * l2;
|
|
float z2 = z * z * l2;
|
|
|
|
// single square root!
|
|
float k = math.sign(rr) * rr * rr * x2;
|
|
if (math.sign(z) * a2 * z2 > k)
|
|
return math.sqrt(x2 + z2) * il2 - r2;
|
|
|
|
if (math.sign(y) * a2 * y2 < k)
|
|
return math.sqrt(x2 + y2) * il2 - r1;
|
|
|
|
return (math.sqrt(x2*a2*il2) + y * rr)*il2 - r1;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Cylinder(in float3 p, float h, float r, float rr = 0.0f)
|
|
{
|
|
float2 d = math.abs(new float2(math.length(p.xz), p.y)) - new float2(r, h);
|
|
return math.min(math.max(d.x, d.y), 0.0f) + math.length(math.max(d, 0.0f)) - rr;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Torus(in float3 p, float h, float r1, float r2)
|
|
{
|
|
float3 q = new float3(math.max(math.abs(p.x) - h, 0.0f), p.y, p.z);
|
|
return math.length(new float2(math.length(q.xz) - r1, q.y)) - r2;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float SolidAngle(in float3 p, in float2 c, float r, float rr = 0.0f)
|
|
{
|
|
// c is the sin/cos of the angle
|
|
float2 q = new float2(math.length(p.xz), p.y);
|
|
float l = math.length(q) - r;
|
|
float m = math.length(q - c * math.clamp(math.dot(q, c), 0.0f, r));
|
|
return math.max(l, m * math.sign(c.y * q.x - c.x * q.y)) - rr;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Segment(in float3 p, in float3 a, in float3 b, out float2 ret)
|
|
{
|
|
float3 pa = p - a, ba = b - a;
|
|
float h = math.saturate(math.dot(pa, ba) / math.dot(ba, ba));
|
|
ret = new float2(math.length(pa - ba * h), h);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Bezier(in float3 pos, in float3 A, in float3 B, in float3 C, out float2 ret)
|
|
{
|
|
float3 a = B - A;
|
|
float3 b = A - 2.0f * B + C;
|
|
float3 c = a * 2.0f;
|
|
float3 d = A - pos;
|
|
|
|
float kk = 1.0f / math.dot(b, b);
|
|
float kx = kk * math.dot(a, b);
|
|
float ky = kk * (2.0f * math.dot(a, a) + math.dot(d, b)) / 3.0f;
|
|
float kz = kk * math.dot(d,a);
|
|
|
|
ret = -1.0f;
|
|
|
|
float p = ky - kx * kx;
|
|
float p3 = p * p * p;
|
|
float q = kx * (2.0f * kx * kx - 3.0f * ky) + kz;
|
|
float h = q * q + 4.0f * p3;
|
|
|
|
if(h >= 0.0f)
|
|
{
|
|
h = math.sqrt(h);
|
|
float2 x = (new float2(h, -h) - q) / 2.0f;
|
|
float2 uv = math.sign(x) * math.pow(math.abs(x), 0.33333333f);
|
|
float t = math.clamp(uv.x + uv.y - kx, 0.0f, 1.0f);
|
|
|
|
// 1 root
|
|
float3 g = d + (c + b * t) * t;
|
|
ret = new float2(math.dot(g, g), t);
|
|
}
|
|
else
|
|
{
|
|
float z = math.sqrt(-p);
|
|
float v = math.acos(q / (p * z * 2.0f)) / 3.0f;
|
|
float m = math.cos(v);
|
|
float n = math.sin(v) * 1.732050808f;
|
|
float3 t = math.clamp(new float3(m + m,-n - m, n - m) * z - kx, 0.0f, 1.0f);
|
|
|
|
// 3 roots, but only need two
|
|
float3 g = d + (c + b * t.x) * t.x;
|
|
float dis = math.dot(g, g);
|
|
ret = new float2(dis, t.x);
|
|
|
|
g = d + (c + b * t.y) * t.y;
|
|
dis = math.dot(g, g);
|
|
if(dis < ret.x)
|
|
ret = new float2(dis, t.y);
|
|
}
|
|
|
|
ret.x = math.sqrt(ret.x);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Noise(int type, in float3 p, in float3 boundsMin, in float3 boundsMax, in float3 offset, in float3 size, float threshold, int numOctaves, float octaveOffsetFactor, in float3 period)
|
|
{
|
|
float n = 0.0f;
|
|
float f = 1.0f;
|
|
switch ((SdfBrush.NoiseTypeEnum) type)
|
|
{
|
|
case SdfBrush.NoiseTypeEnum.Perlin:
|
|
n = 0.8f * (math.saturate(PNoise(p / size, offset, numOctaves, octaveOffsetFactor, period)) - 0.5f) + 0.5f;
|
|
f = 0.9f;
|
|
break;
|
|
|
|
case SdfBrush.NoiseTypeEnum.BakedPerlin:
|
|
n = 0.9f * CachedNoise(p / size, offset, numOctaves, octaveOffsetFactor);
|
|
f = 0.8f;
|
|
break;
|
|
|
|
case SdfBrush.NoiseTypeEnum.Triangle:
|
|
n = TriangleNoise(p / size, offset, numOctaves, octaveOffsetFactor);
|
|
f = 0.4f;
|
|
break;
|
|
}
|
|
|
|
float d = threshold - n;
|
|
|
|
// noise is not an actual SDF
|
|
// we need to scale the result to make it behave like one
|
|
// making the result slightly smaller than it should be would prevent false positive voxel node culling
|
|
d *= f * math.min(math.min(size.x, size.y), size.z);
|
|
|
|
return d;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// end: primitives
|
|
|
|
|
|
// noises
|
|
//-------------------------------------------------------------------------
|
|
|
|
[BurstCompile]
|
|
public static float Rand(float s)
|
|
{
|
|
float m;
|
|
Mod(s, 6.2831853f, out m);
|
|
return math.frac(math.sin(m) * 43758.5453123f);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Rand(in float2 s)
|
|
{
|
|
float d = math.dot(s + 0.1234567f, new float2(1111112.9819837f, 78.237173f));
|
|
float m;
|
|
Mod(d, 6.2831853f, out m);
|
|
return math.frac(math.sin(m) * 43758.5453123f);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float Rand(in float3 s)
|
|
{
|
|
float d = math.dot(s + 0.1234567f, new float3(11112.9819837f, 378.237173f, 3971977.9173179f));
|
|
float m;
|
|
Mod(d, 6.2831853f, out m);
|
|
return math.frac(math.sin(m) * 43758.5453123f);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Mod(float x, float y, out float ret)
|
|
{
|
|
ret = x - y * math.floor(x / y);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Mod(in float2 x, in float2 y, out float2 ret)
|
|
{
|
|
ret = x - y * math.floor(x / y);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Mod(in float3 x, in float3 y, out float3 ret)
|
|
{
|
|
ret = x - y * math.floor(x / y);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Mod(in float4 x, in float4 y, out float4 ret)
|
|
{
|
|
ret = x - y * math.floor(x / y);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Mod289(in float2 x, out float2 ret)
|
|
{
|
|
ret = x - math.floor(x / 289.0f) * 289.0f;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Mod289(in float3 x, out float3 ret)
|
|
{
|
|
ret = x - math.floor(x / 289.0f) * 289.0f;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Mod289(in float4 x, out float4 ret)
|
|
{
|
|
ret = x - math.floor(x / 289.0f) * 289.0f;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Permute(in float3 x, out float3 ret)
|
|
{
|
|
Mod289((x * 34.0f + 1.0f) * x, out ret);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Permute(in float4 x, out float4 ret)
|
|
{
|
|
Mod289((x * 34.0f + 1.0f) * x, out ret);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void TaylorInvSqrt(in float3 r, out float3 ret)
|
|
{
|
|
ret = 1.79284291400159f - 0.85373472095314f * r;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void TaylorInvSqrt(in float4 r, out float4 ret)
|
|
{
|
|
ret = 1.79284291400159f - 0.85373472095314f * r;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Fade(in float2 t, out float2 ret)
|
|
{
|
|
ret = t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void Fade(in float3 t, out float3 ret)
|
|
{
|
|
ret = t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static int Index(in int3 id, in int3 dimension)
|
|
{
|
|
return ((id.z * dimension.z + id.y) * dimension.y) + id.x;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static void UnitTriWave(in float3 x, out float3 ret)
|
|
{
|
|
ret = math.abs(x - math.floor(x) - 0.5f);
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float PNoise(in float3 P, in float3 rep)
|
|
{
|
|
float3 Pi0, Pi1;
|
|
Mod(math.floor(P), math.max(MathUtil.Epsilon, rep), out Pi0);
|
|
Mod(Pi0 + (float3)1.0f, math.max(MathUtil.Epsilon, rep), out Pi1); // Integer part + 1, mod period
|
|
float3 Pf0 = math.frac(P); // math.fractional part for interpolation
|
|
float3 Pf1 = Pf0 - (float3)1.0f; // math.fractional part - 1.0f
|
|
float4 ix = new float4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
|
float4 iy = new float4(Pi0.y, Pi0.y, Pi1.y, Pi1.y);
|
|
float4 iz0 = (float4)Pi0.z;
|
|
float4 iz1 = (float4)Pi1.z;
|
|
|
|
float4 perRet;
|
|
Permute(ix, out perRet);
|
|
float4 ixy, ixy0, ixy1;
|
|
Permute(perRet + iy, out ixy);
|
|
Permute(ixy + iz0, out ixy0);
|
|
Permute(ixy + iz1, out ixy1);
|
|
|
|
float4 gx0 = ixy0 / 7.0f;
|
|
float4 gy0 = math.frac(math.floor(gx0) / 7.0f) - 0.5f;
|
|
gx0 = math.frac(gx0);
|
|
float4 gz0 = (float4)0.5f - math.abs(gx0) - math.abs(gy0);
|
|
float4 sz0 = math.step(gz0, (float4)0.0f);
|
|
gx0 -= sz0 * (math.step((float4)0.0f, gx0) - 0.5f);
|
|
gy0 -= sz0 * (math.step((float4)0.0f, gy0) - 0.5f);
|
|
|
|
float4 gx1 = ixy1 / 7.0f;
|
|
float4 gy1 = math.frac(math.floor(gx1) / 7.0f) - 0.5f;
|
|
gx1 = math.frac(gx1);
|
|
float4 gz1 = (float4)0.5f - math.abs(gx1) - math.abs(gy1);
|
|
float4 sz1 = math.step(gz1, (float4)0.0f);
|
|
gx1 -= sz1 * (math.step((float4)0.0f, gx1) - 0.5f);
|
|
gy1 -= sz1 * (math.step((float4)0.0f, gy1) - 0.5f);
|
|
|
|
float3 g000 = new float3(gx0.x,gy0.x,gz0.x);
|
|
float3 g100 = new float3(gx0.y,gy0.y,gz0.y);
|
|
float3 g010 = new float3(gx0.z,gy0.z,gz0.z);
|
|
float3 g110 = new float3(gx0.w,gy0.w,gz0.w);
|
|
float3 g001 = new float3(gx1.x,gy1.x,gz1.x);
|
|
float3 g101 = new float3(gx1.y,gy1.y,gz1.y);
|
|
float3 g011 = new float3(gx1.z,gy1.z,gz1.z);
|
|
float3 g111 = new float3(gx1.w,gy1.w,gz1.w);
|
|
|
|
float4 norm0;
|
|
TaylorInvSqrt(new float4(math.dot(g000, g000), math.dot(g010, g010), math.dot(g100, g100), math.dot(g110, g110)), out norm0);
|
|
g000 *= norm0.x;
|
|
g010 *= norm0.y;
|
|
g100 *= norm0.z;
|
|
g110 *= norm0.w;
|
|
float4 norm1;
|
|
TaylorInvSqrt(new float4(math.dot(g001, g001), math.dot(g011, g011), math.dot(g101, g101), math.dot(g111, g111)), out norm1);
|
|
g001 *= norm1.x;
|
|
g011 *= norm1.y;
|
|
g101 *= norm1.z;
|
|
g111 *= norm1.w;
|
|
|
|
float n000 = math.dot(g000, Pf0);
|
|
float n100 = math.dot(g100, new float3(Pf1.x, Pf0.y, Pf0.z));
|
|
float n010 = math.dot(g010, new float3(Pf0.x, Pf1.y, Pf0.z));
|
|
float n110 = math.dot(g110, new float3(Pf1.x, Pf1.y, Pf0.z));
|
|
float n001 = math.dot(g001, new float3(Pf0.x, Pf0.y, Pf1.z));
|
|
float n101 = math.dot(g101, new float3(Pf1.x, Pf0.y, Pf1.z));
|
|
float n011 = math.dot(g011, new float3(Pf0.x, Pf1.y, Pf1.z));
|
|
float n111 = math.dot(g111, Pf1);
|
|
|
|
float3 fade_xyz;
|
|
Fade(Pf0, out fade_xyz);
|
|
float4 n_z = math.lerp(new float4(n000, n100, n010, n110), new float4(n001, n101, n011, n111), fade_xyz.z);
|
|
float2 n_yz = math.lerp(n_z.xy, n_z.zw, fade_xyz.y);
|
|
float n_xyz = math.lerp(n_yz.x, n_yz.y, fade_xyz.x);
|
|
return 2.2f * n_xyz;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float PNoise(in float3 s, in float3 offset, int numOctaves, float octaveOffsetFactor, in float3 period)
|
|
{
|
|
float3 sCopy = s;
|
|
float3 offsetCopy = offset;
|
|
float3 periodCopy = period;
|
|
float o = 0.0f;
|
|
float w = 0.5f;
|
|
float wTotal = 0.0f;
|
|
int i = 0;
|
|
do
|
|
{
|
|
o += w * PNoise(sCopy - offsetCopy, periodCopy);
|
|
wTotal += w;
|
|
offsetCopy *= 2.0f * octaveOffsetFactor;
|
|
periodCopy *= 2.0f * octaveOffsetFactor;
|
|
sCopy *= 2.0f;
|
|
w *= 0.5f;
|
|
} while (++i < numOctaves);
|
|
o *= 0.5f / wTotal;
|
|
o += 0.5f;
|
|
return o;
|
|
}
|
|
|
|
[BurstCompile]
|
|
// not actually cached, but to match GPU implementation
|
|
public static float CachedNoise(in float3 s)
|
|
{
|
|
float3 dimensions = new float3(256, 128, 256);
|
|
float density = 32.0f;
|
|
float3 sampleInterval = 1.0f / density;
|
|
float3 period = dimensions * sampleInterval;
|
|
|
|
return math.saturate(0.8f * PNoise(s, period) + 0.5f) - 0.5f;
|
|
|
|
/*
|
|
float3 sQ = s / sampleInterval;
|
|
float3 sL = sampleInterval * math.floor(sQ);
|
|
float3 sH = sL + sampleInterval;
|
|
float3 sT = math.frac(sQ);
|
|
|
|
float3 s0 = new float3(sL.x, sL.y, sL.z);
|
|
float3 s1 = new float3(sH.x, sL.y, sL.z);
|
|
float3 s2 = new float3(sL.x, sH.y, sL.z);
|
|
float3 s3 = new float3(sH.x, sH.y, sL.z);
|
|
float3 s4 = new float3(sL.x, sL.y, sH.z);
|
|
float3 s5 = new float3(sH.x, sL.y, sH.z);
|
|
float3 s6 = new float3(sL.x, sH.y, sH.z);
|
|
float3 s7 = new float3(sH.x, sH.y, sH.z);
|
|
|
|
float n0 = math.saturate(0.8f * PNoise(s0, period) + 0.5f) - 0.5f;
|
|
float n1 = math.saturate(0.8f * PNoise(s1, period) + 0.5f) - 0.5f;
|
|
float n2 = math.saturate(0.8f * PNoise(s2, period) + 0.5f) - 0.5f;
|
|
float n3 = math.saturate(0.8f * PNoise(s3, period) + 0.5f) - 0.5f;
|
|
float n4 = math.saturate(0.8f * PNoise(s4, period) + 0.5f) - 0.5f;
|
|
float n5 = math.saturate(0.8f * PNoise(s5, period) + 0.5f) - 0.5f;
|
|
float n6 = math.saturate(0.8f * PNoise(s6, period) + 0.5f) - 0.5f;
|
|
float n7 = math.saturate(0.8f * PNoise(s7, period) + 0.5f) - 0.5f;
|
|
|
|
return
|
|
math.lerp
|
|
(
|
|
math.lerp
|
|
(
|
|
math.lerp(n0, n1, sT.x),
|
|
math.lerp(n2, n3, sT.x),
|
|
sT.y
|
|
),
|
|
math.lerp
|
|
(
|
|
math.lerp(n4, n5, sT.x),
|
|
math.lerp(n6, n7, sT.x),
|
|
sT.y
|
|
),
|
|
sT.z
|
|
);
|
|
*/
|
|
}
|
|
|
|
[BurstCompile]
|
|
// not actually cached, but to match GPU implementation
|
|
public static float CachedNoise(in float3 s, in float3 offset, int numOctaves, float octaveOffsetFactor)
|
|
{
|
|
float3 sCopy = s;
|
|
float3 offsetCopy = offset;
|
|
float o = 0.0f;
|
|
float w = 0.5f;
|
|
float wTotal = 0.0f;
|
|
int i = 0;
|
|
do
|
|
{
|
|
o += w * CachedNoise(sCopy - offsetCopy);
|
|
wTotal += w;
|
|
offsetCopy *= 2.0f * octaveOffsetFactor;
|
|
sCopy *= 2.0f;
|
|
w *= 0.5f;
|
|
} while (++i < numOctaves);
|
|
o *= 0.5f / wTotal;
|
|
o += 0.5f;
|
|
return o;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float TriangleNoise(in float3 p)
|
|
{
|
|
float3 utw0, utw1, utw2;
|
|
UnitTriWave(p * 0.23f, out utw0);
|
|
UnitTriWave(p * 0.41f + utw0.yzx, out utw1);
|
|
UnitTriWave(p + utw1.zxy, out utw2);
|
|
return math.dot(utw2, (float3) 1.0f) - 0.5f;
|
|
}
|
|
|
|
[BurstCompile]
|
|
public static float TriangleNoise(in float3 s, in float3 offset, int numOctaves, in float octaveOffsetFactor)
|
|
{
|
|
float3 sCopy = s;
|
|
float3 offsetCopy = offset;
|
|
float o = 0.0f;
|
|
float w = 0.5f;
|
|
float wTotal = 0.0f;
|
|
int i = 0;
|
|
do
|
|
{
|
|
o += w * TriangleNoise(sCopy - offsetCopy);
|
|
wTotal += w;
|
|
offsetCopy *= 2.0f * octaveOffsetFactor;
|
|
sCopy *= 2.0f;
|
|
w *= 0.5f;
|
|
} while (++i < numOctaves);
|
|
o *= 0.5f / wTotal;
|
|
o += 0.5f;
|
|
return o;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// end: noises
|
|
|
|
|
|
private static readonly int MaxBrushMaskInts = 32;
|
|
private static readonly int BitsPerInt = 32;
|
|
|
|
[BurstCompile]
|
|
private struct BrushMask
|
|
{
|
|
public NativeArray<uint> m_ints;
|
|
|
|
public struct BrushMaskIterator
|
|
{
|
|
private BrushMask m_mask;
|
|
private uint m_curInt;
|
|
private int m_numInts;
|
|
private int m_iInt;
|
|
private int m_iBrushBase;
|
|
|
|
public void Init(BrushMask mask)
|
|
{
|
|
m_mask = mask;
|
|
m_iInt = 0;
|
|
m_iBrushBase = 0;
|
|
}
|
|
|
|
public int First()
|
|
{
|
|
m_iInt = 0;
|
|
m_iBrushBase = 0;
|
|
m_curInt = m_mask.m_ints[m_iInt];
|
|
return Next();
|
|
}
|
|
|
|
public int Next()
|
|
{
|
|
while (m_iInt < m_mask.m_ints.Length)
|
|
{
|
|
if (m_curInt == 0)
|
|
{
|
|
++m_iInt;
|
|
m_iBrushBase += BitsPerInt;
|
|
if (m_iInt < m_mask.m_ints.Length)
|
|
{
|
|
m_curInt = m_mask.m_ints[m_iInt];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
int iFirstSetBit = (int) Mathf.Log(m_curInt & (~m_curInt + 1u), 2);
|
|
m_curInt &= ~(1u << iFirstSetBit);
|
|
return m_iBrushBase + iFirstSetBit;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
public void Init()
|
|
{
|
|
m_ints = new NativeArray<uint>(MaxBrushMaskInts, Allocator.Temp, NativeArrayOptions.ClearMemory);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (m_ints.IsCreated)
|
|
m_ints.Dispose();
|
|
}
|
|
|
|
public void SetBit(int bit)
|
|
{
|
|
m_ints[bit / BitsPerInt] |= (1u << (bit % BitsPerInt));
|
|
}
|
|
|
|
public void ClearBit(int bit)
|
|
{
|
|
m_ints[bit / BitsPerInt] &= (~(1u << (bit % BitsPerInt)));
|
|
}
|
|
|
|
public bool IsBitSet(int bit)
|
|
{
|
|
return (m_ints[bit / BitsPerInt] & (1u << (bit % BitsPerInt))) != 0;
|
|
}
|
|
|
|
public BrushMaskIterator GetIterator()
|
|
{
|
|
BrushMaskIterator iter = new BrushMaskIterator();
|
|
iter.Init(this);
|
|
return iter;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
public struct Ray
|
|
{
|
|
/// <summary>
|
|
/// The starting point of the ray.
|
|
/// </summary>
|
|
public Vector3 From;
|
|
/// <summary>
|
|
/// The direction of the ray.
|
|
/// </summary>
|
|
public Vector3 Direction;
|
|
/// <summary>
|
|
/// The maximum travel distance the ray
|
|
/// </summary>
|
|
public float MaxDistance;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raycast result.
|
|
/// </summary>
|
|
public struct Contact
|
|
{
|
|
/// <summary>
|
|
/// Whether the ray has hit the SDF zero isosurface.
|
|
/// </summary>
|
|
public bool Hit;
|
|
/// <summary>
|
|
/// Whether the ray has reached its maximum number of steps.
|
|
/// </summary>
|
|
public bool MaxStepsReached;
|
|
/// <summary>
|
|
/// Contact position (if hit).
|
|
/// </summary>
|
|
public Vector3 Position;
|
|
/// <summary>
|
|
/// Contact normal (if hit).
|
|
/// </summary>
|
|
public Vector3 Normal;
|
|
/// <summary>
|
|
/// Ratio of the ray's travel distance (until hit or miss) compared to its maximum distance.
|
|
/// <p/>
|
|
/// For a raycast, this is the same as <c>GlobalT</c>.
|
|
/// <br/>
|
|
/// For a raycast chain, this is the ratio local to the last evaluated ray segment.
|
|
/// </summary>
|
|
public float LocalT;
|
|
/// <summary>
|
|
/// Ratio of the ray's travel distance (until hit or miss) compared to its maximum distance.
|
|
/// For a raycast, this is the same as <c>LocalT</c>.
|
|
/// <br/>
|
|
/// For a raycast chain, this is overall ratio global to the entire chain.
|
|
/// </summary>
|
|
public float GlobalT;
|
|
/// <summary>
|
|
/// Material at contact point (if material computation is specified).
|
|
/// </summary>
|
|
public SdfBrushMaterial Material;
|
|
|
|
public static Contact New =>
|
|
new Contact()
|
|
{
|
|
Hit = false,
|
|
MaxStepsReached = false,
|
|
Position = Vector3.zero,
|
|
Normal = Vector3.zero,
|
|
LocalT = -1.0f,
|
|
GlobalT = -1.0f,
|
|
Material = SdfBrushMaterial.New,
|
|
};
|
|
}
|
|
|
|
private static NativeArray<Vector3> s_sampleDummy;
|
|
private static NativeArray<Ray> s_castDummy;
|
|
private static NativeArray<Vector3> s_castChainDummy;
|
|
private static NativeArray<Vector3> s_normalDummy;
|
|
private static NativeArray<Contact> s_contactDummy;
|
|
private static NativeArray<SdfBrushMaterial> s_materialDummy;
|
|
private static NativeArray<Result> s_resultDummy;
|
|
|
|
internal static void InitAsyncJobData()
|
|
{
|
|
s_sampleDummy = new NativeArray<Vector3>(1, Allocator.Persistent);
|
|
s_castDummy = new NativeArray<Ray>(1, Allocator.Persistent);
|
|
s_castChainDummy = new NativeArray<Vector3>(1, Allocator.Persistent);
|
|
s_normalDummy = new NativeArray<Vector3>(1, Allocator.Persistent);
|
|
s_contactDummy = new NativeArray<Contact>(1, Allocator.Persistent);
|
|
s_materialDummy = new NativeArray<SdfBrushMaterial>(1, Allocator.Persistent);
|
|
s_resultDummy = new NativeArray<Result>(1, Allocator.Persistent);
|
|
}
|
|
|
|
internal static void DisposeAsyncJobData()
|
|
{
|
|
s_sampleDummy.Dispose();
|
|
s_castDummy.Dispose();
|
|
s_castChainDummy.Dispose();
|
|
s_normalDummy.Dispose();
|
|
s_contactDummy.Dispose();
|
|
s_materialDummy.Dispose();
|
|
s_resultDummy.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Job handles are associated with SDF evaluation jobs running in parallel. A job handle exposes the interface to query and wait on the completion of its associated job.
|
|
/// </summary>
|
|
public struct EvalJobHandle
|
|
{
|
|
private class Shared
|
|
{
|
|
public bool m_valid = false;
|
|
public bool m_scheduled = false;
|
|
public bool m_completed = false;
|
|
public EvalJob m_job;
|
|
public JobHandle m_hJob;
|
|
public AsyncMode m_asyncMode = AsyncMode.Invalid;
|
|
public MudRendererBase m_renderer;
|
|
}
|
|
|
|
// shared data among EvalJobHandle variables assigned the same value
|
|
private Shared m_shared;
|
|
|
|
/// <summary>
|
|
/// Whether this handle has been associated with a job.
|
|
/// </summary>
|
|
public bool Valid => (m_shared != null) && m_shared.m_valid;
|
|
|
|
/// <summary>
|
|
/// Whether a call to the handle's <c>Complete</c> method has been finished, thus whether its associated job is guaranteed to have finished. At this point it's safe to process and dipose of the job's output.
|
|
/// </summary>
|
|
public bool Completed => (m_shared != null) && m_shared.m_hJob.IsCompleted;
|
|
|
|
/// <summary>
|
|
/// Invalidate this job handle, disassociating it with any job.
|
|
/// </summary>
|
|
public void Invalidate() { m_shared = null; }
|
|
|
|
#if MUDBUN_BURST
|
|
public static EvalJobHandle New(EvalJob job, AsyncMode asyncMode, MudRendererBase renderer)
|
|
{
|
|
if (asyncMode == AsyncMode.AsyncInputCopied)
|
|
job.CopyInput();
|
|
|
|
return
|
|
new EvalJobHandle()
|
|
{
|
|
m_shared =
|
|
new Shared()
|
|
{
|
|
m_valid = true,
|
|
m_scheduled = false,
|
|
m_completed = false,
|
|
m_job = job,
|
|
m_asyncMode = asyncMode,
|
|
m_renderer = renderer,
|
|
}
|
|
};
|
|
}
|
|
#endif
|
|
|
|
public static EvalJobHandle Empty => new EvalJobHandle();
|
|
|
|
internal void Schedule(bool byRenderer)
|
|
{
|
|
#if MUDBUN_BURST
|
|
if (m_shared == null)
|
|
return;
|
|
|
|
if (m_shared.m_scheduled)
|
|
return;
|
|
|
|
if (!byRenderer)
|
|
m_shared.m_renderer.UpdateComputeData();
|
|
|
|
switch (m_shared.m_job.Type)
|
|
{
|
|
case EvalJob.TypeEnum.Sdf:
|
|
case EvalJob.TypeEnum.Normal:
|
|
case EvalJob.TypeEnum.SdfAndNormal:
|
|
case EvalJob.TypeEnum.SnapToSurface:
|
|
m_shared.m_hJob = m_shared.m_job.Schedule(m_shared.m_job.Samples.Length, GetJobBatchSize(m_shared.m_job.Samples.Length));
|
|
break;
|
|
|
|
case EvalJob.TypeEnum.Raycast:
|
|
m_shared.m_hJob = m_shared.m_job.Schedule(m_shared.m_job.Casts.Length, GetJobBatchSize(m_shared.m_job.Casts.Length));
|
|
break;
|
|
|
|
case EvalJob.TypeEnum.RaycastChain:
|
|
m_shared.m_hJob = m_shared.m_job.Schedule(1, 1);
|
|
break;
|
|
}
|
|
|
|
if (!byRenderer)
|
|
JobHandle.ScheduleBatchedJobs();
|
|
|
|
m_shared.m_scheduled = true;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wait on the job handle's associated job until it completes. When this method returns, it's safe to process and dispose of the job's output.
|
|
/// </summary>
|
|
public void Complete()
|
|
{
|
|
#if MUDBUN_BURST
|
|
if (m_shared == null)
|
|
return;
|
|
|
|
if (!m_shared.m_valid)
|
|
return;
|
|
|
|
if (!m_shared.m_scheduled)
|
|
Schedule(false);
|
|
|
|
if (m_shared.m_completed)
|
|
return;
|
|
|
|
m_shared.m_hJob.Complete();
|
|
|
|
if (m_shared.m_asyncMode == AsyncMode.AsyncInputCopied)
|
|
m_shared.m_job.DisposeInput();
|
|
|
|
m_shared.m_job.Dispose();
|
|
m_shared.m_completed = true;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if MUDBUN_BURST
|
|
[BurstCompile]
|
|
#endif
|
|
public struct EvalJob : IJobParallelFor
|
|
{
|
|
#if MUDBUN_BURST
|
|
public enum TypeEnum
|
|
{
|
|
Invalid = -1,
|
|
Sdf,
|
|
Normal,
|
|
SdfAndNormal,
|
|
Raycast,
|
|
RaycastChain,
|
|
SnapToSurface,
|
|
}
|
|
|
|
// input
|
|
public TypeEnum Type;
|
|
public Matrix4x4 WorldToLocal;
|
|
public Matrix4x4 LocalToWorld;
|
|
public Matrix4x4 LocalToWorldIt;
|
|
[ReadOnly] public NativeArray<FunctionPointer<SdfBrushEvalFunc>> SdfEvalFuncMapDense;
|
|
[ReadOnly] public NativeArray<SdfBrushEvalFuncMapEntry> SdfEvalFuncMapSprase;
|
|
[ReadOnly] public NativeArray<Vector3> Samples;
|
|
[ReadOnly] public BrushArray Brushes;
|
|
[ReadOnly] public MaterialArray MaterialsIn;
|
|
[ReadOnly] public AabbTree Tree;
|
|
public int NumBrushes;
|
|
public int RootIndex;
|
|
public float MaxSurfaceDistance;
|
|
public bool ComputeMaterials;
|
|
public float SurfaceShift;
|
|
[ReadOnly] public NativeArray<Ray> Casts;
|
|
[ReadOnly] public NativeArray<Vector3> CastChain;
|
|
public int MaxSteps;
|
|
public float CastMargin;
|
|
public bool ForceZeroBlendUnion;
|
|
|
|
// output
|
|
[WriteOnly] public NativeArray<Result> SdfResults;
|
|
[WriteOnly] public NativeArray<Contact> Contacts;
|
|
|
|
public void CopyInput()
|
|
{
|
|
if (Samples.IsCreated)
|
|
Samples = new NativeArray<Vector3>(Samples, Allocator.Persistent);
|
|
|
|
if (Brushes.IsCreated)
|
|
Brushes = new BrushArray(Brushes, Allocator.Persistent);
|
|
|
|
if (MaterialsIn.IsCreated)
|
|
MaterialsIn = new MaterialArray(MaterialsIn, Allocator.Persistent);
|
|
|
|
if (Tree.IsCreated)
|
|
Tree = new AabbTree(Tree, Allocator.Persistent);
|
|
}
|
|
|
|
public void DisposeInput()
|
|
{
|
|
if (Samples.IsCreated)
|
|
Samples.Dispose();
|
|
|
|
if (Brushes.IsCreated)
|
|
Brushes.Dispose();
|
|
|
|
if (MaterialsIn.IsCreated)
|
|
MaterialsIn.Dispose();
|
|
|
|
if (Tree.IsCreated)
|
|
Tree.Dispose();
|
|
}
|
|
|
|
private bool LookUpBrushFunc(int brushType, out FunctionPointer<SdfBrushEvalFunc> pFunc)
|
|
{
|
|
pFunc = new FunctionPointer<SdfBrushEvalFunc>();
|
|
|
|
if (brushType >= 0
|
|
&& brushType < DenseSdfEvalMapSize)
|
|
{
|
|
if (!SdfEvalFuncMapDense.IsCreated)
|
|
{
|
|
//Debug.LogError($"Brush evaluation function for brush type {brushType} not registered.");
|
|
return false;
|
|
}
|
|
|
|
pFunc = SdfEvalFuncMapDense[brushType];
|
|
|
|
// TODO: return false if pFunc is not registered
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (!SdfEvalFuncMapSprase.IsCreated)
|
|
{
|
|
//Debug.LogError($"Brush evaluation function for brush type {brushType} not registered.");
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < SdfEvalFuncMapSprase.Length; ++i)
|
|
{
|
|
if (SdfEvalFuncMapSprase[i].BrushType != brushType)
|
|
continue;
|
|
|
|
pFunc = SdfEvalFuncMapSprase[i].Func;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//Debug.LogError($"Brush evaluation function for brush type {brushType} not registered.");
|
|
return false;
|
|
}
|
|
|
|
private float ApplyBrush(float res, float groupRes, in SdfBrushMaterial groupMat, ref float3 p, BrushArray aBrush, int iBrush, in SdfBrush b, MaterialArray aMaterial, ref SdfBrushMaterial oMat, bool outputMat, float surfaceShift)
|
|
{
|
|
//Profiler.BeginSample("ApplyBrush");
|
|
|
|
float d = EvalBrush(res, ref p, aBrush, iBrush, b);
|
|
|
|
if (b.Type == (int) MudBrushGroup.TypeEnum.EndGroup)
|
|
d = groupRes;
|
|
|
|
bool isGroupBrush = false;
|
|
switch ((MudBrushGroup.TypeEnum) b.Type)
|
|
{
|
|
case MudBrushGroup.TypeEnum.BeginGroup:
|
|
case MudBrushGroup.TypeEnum.EndGroup:
|
|
isGroupBrush = true;
|
|
break;
|
|
}
|
|
|
|
float tMat = 0.0f;
|
|
float blend = ForceZeroBlendUnion ? 0.0f : b.Blend;
|
|
|
|
var op = ForceZeroBlendUnion ? SdfBrush.OperatorEnum.Union : (SdfBrush.OperatorEnum) b.Operator;
|
|
var boolOpType = SdfBrush.BooleanOperatorTypeEnum.Cubic;
|
|
int opInt = (int) op;
|
|
if (opInt >= 8 /* (int) SdfBrush.BooleanOperatorTypeEnum.Quadratic */
|
|
&& opInt < 17 /* (int) SdfBrush.BooleanOperatorTypeEnum.Chamfer + 3 */)
|
|
{
|
|
if (opInt < 11 /* (int) SdfBrush.BooleanOperatorTypeEnum.Round */)
|
|
{
|
|
boolOpType = (SdfBrush.BooleanOperatorTypeEnum) 8 /* SdfBrush.BooleanOperatorTypeEnum.Quadratic */;
|
|
}
|
|
else if (opInt < 14 /* (int) SdfBrush.BooleanOperatorTypeEnum.Chamfer */)
|
|
boolOpType = (SdfBrush.BooleanOperatorTypeEnum) 11 /* SdfBrush.BooleanOperatorTypeEnum.Round */;
|
|
else
|
|
boolOpType = SdfBrush.BooleanOperatorTypeEnum.Chamfer;
|
|
|
|
op = (SdfBrush.OperatorEnum) (opInt - (int) boolOpType);
|
|
}
|
|
|
|
switch (op)
|
|
{
|
|
case SdfBrush.OperatorEnum.Union:
|
|
if (!isGroupBrush)
|
|
d -= surfaceShift;
|
|
tMat = DistBlendWeight(res, d, 1.5f);
|
|
switch (boolOpType)
|
|
{
|
|
/*
|
|
case SdfBrush.BooleanOperatorTypeEnum.Quadratic:
|
|
res = UniQuad(res, d, blend);
|
|
break;
|
|
*/
|
|
case SdfBrush.BooleanOperatorTypeEnum.Cubic:
|
|
res = UniCubic(res, d, blend);
|
|
break;
|
|
/*
|
|
case SdfBrush.BooleanOperatorTypeEnum.Round:
|
|
res = UniRound(res, d, blend);
|
|
break;
|
|
*/
|
|
case SdfBrush.BooleanOperatorTypeEnum.Chamfer:
|
|
res = UniChamfer(res, d, blend);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SdfBrush.OperatorEnum.Subtract:
|
|
if (!isGroupBrush)
|
|
d += surfaceShift;
|
|
switch (boolOpType)
|
|
{
|
|
/*
|
|
case SdfBrush.BooleanOperatorTypeEnum.Quadratic:
|
|
res = SubQuad(res, d, blend);
|
|
break;
|
|
*/
|
|
case SdfBrush.BooleanOperatorTypeEnum.Cubic:
|
|
res = SubCubic(res, d, blend);
|
|
break;
|
|
/*
|
|
case SdfBrush.BooleanOperatorTypeEnum.Round:
|
|
res = SubRound(res, d, blend);
|
|
break;
|
|
*/
|
|
case SdfBrush.BooleanOperatorTypeEnum.Chamfer:
|
|
res = SubChamfer(res, d, blend);
|
|
break;
|
|
}
|
|
tMat = 1.0f - MathUtil.Saturate(2.0f * d / Mathf.Max(MathUtil.Epsilon, blend));
|
|
break;
|
|
|
|
case SdfBrush.OperatorEnum.Intersect:
|
|
if (!isGroupBrush)
|
|
d -= surfaceShift;
|
|
switch (boolOpType)
|
|
{
|
|
/*
|
|
case SdfBrush.BooleanOperatorTypeEnum.Quadratic:
|
|
res = IntQuad(res, d, blend);
|
|
break;
|
|
*/
|
|
case SdfBrush.BooleanOperatorTypeEnum.Cubic:
|
|
res = IntCubic(res, d, blend);
|
|
break;
|
|
/*
|
|
case SdfBrush.BooleanOperatorTypeEnum.Round:
|
|
res = IntRound(res, d, blend);
|
|
break;
|
|
*/
|
|
case SdfBrush.BooleanOperatorTypeEnum.Chamfer:
|
|
res = IntChamfer(res, d, blend);
|
|
break;
|
|
}
|
|
tMat = 1.0f - MathUtil.Saturate(-2.0f * d / Mathf.Max(MathUtil.Epsilon, blend));
|
|
break;
|
|
|
|
case SdfBrush.OperatorEnum.Pipe:
|
|
if (!isGroupBrush)
|
|
d -= surfaceShift;
|
|
res = Pipe(res, d, blend);
|
|
tMat = MathUtil.Saturate(-d / Mathf.Max(MathUtil.Epsilon, blend));
|
|
break;
|
|
|
|
case SdfBrush.OperatorEnum.Engrave:
|
|
res = Engrave(res, d, blend);
|
|
tMat = 1.0f - MathUtil.Saturate(Mathf.Abs(d) / Mathf.Max(MathUtil.Epsilon, blend));
|
|
break;
|
|
|
|
/*
|
|
case SdfBrush.OperatorEnum.Dye:
|
|
if (!isGroupBrush)
|
|
d -= surfaceShift;
|
|
tMat = 1.0f - MathUtil.Saturate(Mathf.Max(0.0f, d) / Mathf.Max(MathUtil.Epsilon, blend));
|
|
break;
|
|
*/
|
|
default:
|
|
if (SdfBrush.IsDyeOperator(op))
|
|
{
|
|
if (!isGroupBrush)
|
|
d -= surfaceShift;
|
|
tMat = 1.0f - MathUtil.Saturate(Mathf.Max(0.0f, d) / Mathf.Max(MathUtil.Epsilon, blend));
|
|
}
|
|
else if (b.Operator == (int) MudDistortion.OperatorEnum.Distort)
|
|
res = Mathf.Min(res, d);
|
|
else if (b.Operator == (int) MudModifier.OperatorEnum.Modify)
|
|
res = d;
|
|
break;
|
|
}
|
|
|
|
if (b.MaterialIndex >= 0)
|
|
{
|
|
float blendTightness = aMaterial[b.MaterialIndex].MetallicSmoothnessSizeTightness.w;
|
|
if (blendTightness > 0.0f)
|
|
{
|
|
tMat -= 0.5f;
|
|
tMat = 0.5f + 0.5f * math.sign(tMat) * (1.0f - math.pow(math.abs(1.0f - math.abs(2.0f * tMat)), math.pow(1.0f + blendTightness, 5.0f)));
|
|
}
|
|
|
|
SdfBrushMaterial iMat = aMaterial[b.MaterialIndex];
|
|
iMat.EmissionHash.a = b.Hash;
|
|
iMat.BrushIndex = b.Index;
|
|
if (b.Type == (int) MudBrushGroup.TypeEnum.EndGroup)
|
|
iMat = groupMat;
|
|
|
|
if (outputMat
|
|
&& b.Flags.IsBitSet((int)SdfBrush.FlagBit.ContributeMaterial))
|
|
{
|
|
// dye blendColor modes
|
|
// https://docs.unity3d.com/Packages/com.unity.shadergraph@14.0/manual/blendColor-Node.html
|
|
if (SdfBrush.IsDyeOperator((SdfBrush.OperatorEnum)b.Operator))
|
|
{
|
|
float3 baseColor = new float3(oMat.Color.r, oMat.Color.g, oMat.Color.b);
|
|
float3 blendColor = new float3(iMat.Color.r, iMat.Color.g, iMat.Color.b);
|
|
float3 outColor = new float3(0.0f, 0.0f, 0.0f);
|
|
float3 opacity = iMat.Color.a;
|
|
switch (b.Operator)
|
|
{
|
|
case (int) SdfBrush.OperatorEnum.Dye:
|
|
// overwrite; do nothing
|
|
break;
|
|
/*
|
|
case (int) SdfBrush.DyeBlendModeEnum.Burn:
|
|
outColor = math.lerp(baseColor, 1.0f - (1.0f - blendColor) / max(1e-6f, baseColor), opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.Darken:
|
|
outColor = math.lerp(baseColor, min(blendColor, baseColor), opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.Difference:
|
|
outColor = math.lerp(baseColor, abs(blendColor - baseColor), opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.Dodge:
|
|
outColor = math.lerp(baseColor, baseColor / max(1e-6f, 1.0f - blendColor), opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.Divide:
|
|
outColor = math.lerp(baseColor, baseColor / max(1e-6f, blendColor), opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.Exclusion:
|
|
outColor = math.lerp(baseColor, blendColor + baseColor - 2.0f * blendColor * baseColor, opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.HardLight:
|
|
{
|
|
float3 result1 = 1.0f - 2.0f * (1.0f - baseColor) * (1.0f - blendColor);
|
|
float3 result2 = 2.0f * baseColor * blendColor;
|
|
float3 zeroOrOne = math.step(blendColor, 0.5f);
|
|
blendColor = result2 * zeroOrOne + (1.0f - zeroOrOne) * result1;
|
|
outColor = math.lerp(baseColor, blendColor, opacity);
|
|
}
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.HardMix:
|
|
outColor = math.lerp(baseColor, math.step(1.0f - baseColor, blendColor), opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.Lighten:
|
|
outColor = math.lerp(baseColor, max(blendColor, baseColor), opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.LightBurn:
|
|
outColor = math.lerp(baseColor, baseColor + blendColor - 1.0f, opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.LinearDodge:
|
|
outColor = math.lerp(baseColor, baseColor + blendColor, opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.LinearLight:
|
|
outColor = math.lerp(baseColor, blendColor < 0.5f ? max(baseColor + 2.0f * blendColor - 1.0f, 0.0f) : min(baseColor + 2.0f * (blendColor - 0.5f), 1.0f), opacity);
|
|
break;
|
|
*/
|
|
case (int) SdfBrush.DyeBlendModeEnum.Multiply:
|
|
outColor = math.lerp(baseColor, baseColor * blendColor, opacity);
|
|
break;
|
|
/*
|
|
case (int) SdfBrush.DyeBlendModeEnum.Negation:
|
|
outColor = math.lerp(baseColor, 1.0f - abs(1.0f - blendColor - baseColor), opacity);
|
|
break;
|
|
*/
|
|
case (int) SdfBrush.DyeBlendModeEnum.Overlay:
|
|
{
|
|
float3 result1 = 1.0f - 2.0f * (1.0f - baseColor) * (1.0f - blendColor);
|
|
float3 result2 = 2.0f * baseColor * blendColor;
|
|
float3 zeroOrOne = math.step(baseColor, 0.5f);
|
|
blendColor = result2 * zeroOrOne + (1.0f - zeroOrOne) * result1;
|
|
outColor = math.lerp(baseColor, blendColor, opacity);
|
|
}
|
|
break;
|
|
/*
|
|
case (int) SdfBrush.DyeBlendModeEnum.PinLight:
|
|
{
|
|
float3 check = math.step(0.5f, blendColor);
|
|
float3 result1 = check * max(2.0f * (baseColor - 0.5f), blendColor);
|
|
blendColor = result1 + (1.0f - check) * min(2.0f * baseColor, blendColor);
|
|
outColor = math.lerp(baseColor, blendColor, opacity);
|
|
}
|
|
break;
|
|
*/
|
|
case (int) SdfBrush.DyeBlendModeEnum.Screen:
|
|
outColor = math.lerp(baseColor, 1.0f - (1.0f - blendColor) * (1.0f - baseColor), opacity);
|
|
break;
|
|
/*
|
|
case (int) SdfBrush.DyeBlendModeEnum.SoftLight:
|
|
{
|
|
float3 result1 = 2.0f * baseColor * blendColor + baseColor * baseColor * (1.0f - 2.0f * blendColor);
|
|
float3 result2 = sqrt(baseColor) * (2.0f * blendColor - 1.0f) + 2.0f * baseColor * (1.0f - blendColor);
|
|
float3 zeroOrOne = math.step(0.5f, blendColor);
|
|
blendColor = result2 * zeroOrOne + (1.0f - zeroOrOne) * result1;
|
|
outColor = math.lerp(baseColor, blendColor, opacity);
|
|
}
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.Subtract:
|
|
outColor = math.lerp(baseColor, baseColor - blendColor, opacity);
|
|
break;
|
|
case (int) SdfBrush.DyeBlendModeEnum.VividLight:
|
|
{
|
|
float3 result1 = 1.0f - (1.0f - blendColor) / (2.0f * baseColor);
|
|
float3 result2 = blendColor / (2.0f * (1.0f - baseColor));
|
|
float3 zeroOrOne = math.step(0.5f, baseColor);
|
|
blendColor = result2 * zeroOrOne + (1.0f - zeroOrOne) * result1;
|
|
outColor = math.lerp(baseColor, blendColor, opacity);
|
|
}
|
|
break;
|
|
*/
|
|
case (int) SdfBrush.DyeBlendModeEnum.Paint:
|
|
outColor = math.lerp(baseColor, 2 * baseColor * blendColor, opacity);
|
|
break;
|
|
}
|
|
|
|
outColor = math.saturate(outColor);
|
|
iMat.Color.r = outColor.x;
|
|
iMat.Color.g = outColor.y;
|
|
iMat.Color.b = outColor.z;
|
|
|
|
if (b.Operator != (int) SdfBrush.OperatorEnum.Dye)
|
|
{
|
|
// non-overwrite blendColor modes don't affect alpha
|
|
iMat.Color.a = oMat.Color.a;
|
|
}
|
|
} // end: dye blendColor modes
|
|
|
|
SdfBrushMaterial oMatNew;
|
|
SdfBrushMaterial.Lerp(oMat, iMat, tMat, out oMatNew);
|
|
oMat = oMatNew;
|
|
}
|
|
else if (tMat > 0.5f)
|
|
{
|
|
// still record brush hash even if not computing or contributing materials (for click selection)
|
|
oMat.EmissionHash.a = iMat.EmissionHash.a;
|
|
}
|
|
}
|
|
|
|
//Profiler.EndSample();
|
|
|
|
return res;
|
|
}
|
|
|
|
private float EvalBrush(float res, ref float3 p, BrushArray aBrush, int iBrush, in SdfBrush b)
|
|
{
|
|
float d = float.MaxValue;
|
|
|
|
if (!LookUpBrushFunc(aBrush[iBrush].Type, out var pFunc))
|
|
return d;
|
|
|
|
//Profiler.BeginSample("EvalBrush");
|
|
|
|
float preMirrorX = p.x;
|
|
bool doMirrorX = b.Flags.IsBitSet((int) SdfBrush.FlagBit.MirrorX);
|
|
if (doMirrorX)
|
|
p.x = Mathf.Abs(p.x);
|
|
|
|
bool flipX = b.Flags.IsBitSet((int) SdfBrush.FlagBit.FlipX);
|
|
if (flipX)
|
|
p.x = -p.x;
|
|
|
|
Vector3 h = VectorUtil.Abs(0.5f * b.Size);
|
|
Vector3 pRel = Quaternion.Inverse(b.Rotation) * (p - (float3) b.Position);
|
|
|
|
//Profiler.BeginSample("Invoke");
|
|
|
|
res = pFunc.Invoke(res, ref p, pRel, (SdfBrush*) aBrush.GetUnsafeReadOnlyPtr(), iBrush);
|
|
|
|
//Profiler.EndSample();
|
|
|
|
if (flipX || doMirrorX)
|
|
p.x = preMirrorX;
|
|
|
|
//Profiler.EndSample();
|
|
|
|
return res;
|
|
}
|
|
|
|
private float EvalSdf(Vector3 p, BrushMask mask, ref SdfBrushMaterial materialOut, bool computeMaterials, float castRadius = 0.0f)
|
|
{
|
|
//Profiler.BeginSample("EvalSdf");
|
|
|
|
//Profiler.BeginSample("PrepareApplyBrush");
|
|
|
|
int iStack = -1;
|
|
VecStack pStack = new VecStack(MaxBrushGroupDepth, Allocator.Temp);
|
|
FloatStack resStack = new FloatStack(MaxBrushGroupDepth, Allocator.Temp);
|
|
MatStack matStack = new MatStack(MaxBrushGroupDepth, Allocator.Temp);
|
|
|
|
float res = float.MaxValue;
|
|
SdfBrushMaterial mat = SdfBrushMaterial.New;
|
|
float3 pFloat3 = p;
|
|
float groupRes = float.MaxValue;
|
|
SdfBrushMaterial groupMat = SdfBrushMaterial.New;
|
|
|
|
//Profiler.EndSample();
|
|
|
|
var iter = mask.GetIterator();
|
|
for (int iBrush = iter.First(); iBrush >= 0; iBrush = iter.Next())
|
|
{
|
|
//Profiler.BeginSample("Per Brush");
|
|
|
|
switch ((MudBrushGroup.TypeEnum) Brushes[iBrush].Type)
|
|
{
|
|
case MudBrushGroup.TypeEnum.BeginGroup:
|
|
{
|
|
iStack = Mathf.Min(MaxBrushGroupDepth - 1, iStack + 1);
|
|
pStack[iStack] = pFloat3;
|
|
resStack[iStack] = res;
|
|
matStack[iStack] = mat;
|
|
res = float.MaxValue;
|
|
mat = SdfBrushMaterial.New;
|
|
|
|
bool doMirrorX = Brushes[iBrush].Flags.IsBitSet((int) SdfBrush.FlagBit.MirrorX);
|
|
if (doMirrorX)
|
|
pFloat3.x = Mathf.Abs(pFloat3.x);
|
|
|
|
bool flipX = Brushes[iBrush].Flags.IsBitSet((int) SdfBrush.FlagBit.FlipX);
|
|
if (flipX)
|
|
pFloat3.x = -pFloat3.x;
|
|
|
|
break;
|
|
}
|
|
|
|
case MudBrushGroup.TypeEnum.EndGroup:
|
|
{
|
|
groupRes = res;
|
|
groupMat = mat;
|
|
pFloat3 = pStack[iStack];
|
|
res = resStack[iStack];
|
|
mat = matStack[iStack];
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
res = ApplyBrush(res, groupRes, groupMat, ref pFloat3, Brushes, iBrush, Brushes[iBrush], MaterialsIn, ref mat, ComputeMaterials, SurfaceShift + castRadius);
|
|
|
|
switch ((MudBrushGroup.TypeEnum) Brushes[iBrush].Type)
|
|
{
|
|
case MudBrushGroup.TypeEnum.EndGroup:
|
|
iStack = Mathf.Max(-1, iStack - 1);
|
|
break;
|
|
}
|
|
|
|
//Profiler.EndSample();
|
|
}
|
|
|
|
pStack.Dispose();
|
|
resStack.Dispose();
|
|
matStack.Dispose();
|
|
|
|
if (computeMaterials)
|
|
materialOut = mat;
|
|
|
|
//Profiler.EndSample();
|
|
|
|
return
|
|
MaxSurfaceDistance > 0.0f
|
|
? Mathf.Min(res, MaxSurfaceDistance)
|
|
: res;
|
|
}
|
|
|
|
// AABB query
|
|
private void BuildBrushMask(AabbTree tree, int iRoot, Aabb query, out BrushMask ret)
|
|
{
|
|
//Profiler.BeginSample("BuildBrushMask (AABB query)");
|
|
|
|
float margin = 0.0f;
|
|
for (int iBrush = 0; iBrush < NumBrushes; ++iBrush)
|
|
{
|
|
margin = Mathf.Max(margin, Brushes[iBrush].Blend);
|
|
}
|
|
query.Expand(margin);
|
|
|
|
ret = new BrushMask();
|
|
ret.Init();
|
|
|
|
int stackTop = 0;
|
|
IntStack stack = new IntStack(MaxAabbTreeStackSize, Allocator.Temp);
|
|
stack[stackTop] = iRoot;
|
|
|
|
while (stackTop >= 0)
|
|
{
|
|
int index = stack[stackTop--];
|
|
if (index < 0)
|
|
continue;
|
|
|
|
if (!Aabb.Intersects(tree[index].Bounds, query))
|
|
continue;
|
|
|
|
if (tree[index].ChildA < 0)
|
|
{
|
|
ret.SetBit(tree[index].UserDataIndex);
|
|
}
|
|
else
|
|
{
|
|
stackTop = Mathf.Min(stackTop + 1, MaxAabbTreeStackSize - 1);
|
|
stack[stackTop] = tree[index].ChildA;
|
|
stackTop = Mathf.Min(stackTop + 1, MaxAabbTreeStackSize - 1);
|
|
stack[stackTop] = tree[index].ChildB;
|
|
}
|
|
}
|
|
|
|
stack.Dispose();
|
|
|
|
//Profiler.EndSample();
|
|
}
|
|
|
|
// ray query
|
|
private void BuildBrushMask(AabbTree tree, int iRoot, in Ray query, float margin, out BrushMask ret, out float tMin)
|
|
{
|
|
//Profiler.BeginSample("BuildBrushMask (ray query)");
|
|
|
|
for (int iBrush = 0; iBrush < NumBrushes; ++iBrush)
|
|
{
|
|
margin = Mathf.Max(margin, Brushes[iBrush].Blend);
|
|
}
|
|
|
|
ret = new BrushMask();
|
|
ret.Init();
|
|
|
|
tMin = 1.0f;
|
|
|
|
int stackTop = 0;
|
|
IntStack stack = new IntStack(MaxAabbTreeStackSize, Allocator.Temp);
|
|
stack[stackTop] = iRoot;
|
|
|
|
while (stackTop >= 0)
|
|
{
|
|
int index = stack[stackTop--];
|
|
if (index < 0)
|
|
continue;
|
|
|
|
Aabb expandedBounds = tree[index].Bounds;
|
|
expandedBounds.Expand(margin);
|
|
float t =
|
|
expandedBounds.Contains(query.From) // starting from inside AABB is okay
|
|
? 0.0f
|
|
: expandedBounds.RayCast(query.From, query.Direction * query.MaxDistance);
|
|
if (t < 0.0f || t > 1.0f)
|
|
continue;
|
|
|
|
if (tree[index].ChildA < 0)
|
|
{
|
|
ret.SetBit(tree[index].UserDataIndex);
|
|
tMin = Mathf.Min(t, tMin);
|
|
}
|
|
else
|
|
{
|
|
stackTop = Mathf.Min(stackTop + 1, MaxAabbTreeStackSize - 1);
|
|
stack[stackTop] = tree[index].ChildA;
|
|
stackTop = Mathf.Min(stackTop + 1, MaxAabbTreeStackSize - 1);
|
|
stack[stackTop] = tree[index].ChildB;
|
|
}
|
|
}
|
|
|
|
stack.Dispose();
|
|
|
|
//Profiler.EndSample();
|
|
}
|
|
|
|
// point query
|
|
private void BuildBrushMask(AabbTree tree, int iRoot, in Vector3 query, out BrushMask ret)
|
|
{
|
|
Aabb bounds = new Aabb(query - MathUtil.Epsilon * Vector3.one, query + MathUtil.Epsilon * Vector3.one);
|
|
BuildBrushMask(tree, iRoot, bounds, out ret);
|
|
}
|
|
|
|
private float EvalSdf(Vector3 p, ref SdfBrushMaterial materialOut, bool computeMaterials, float castRadius = 0.0f)
|
|
{
|
|
Aabb maskQuery = new Aabb(p - Mathf.Max(MathUtil.Epsilon, MaxSurfaceDistance) * Vector3.one, p + Mathf.Max(MathUtil.Epsilon, MaxSurfaceDistance) * Vector3.one);
|
|
BrushMask mask;
|
|
BuildBrushMask(Tree, RootIndex, maskQuery, out mask);
|
|
float result = EvalSdf(p, mask, ref materialOut, computeMaterials, castRadius);
|
|
mask.Dispose();
|
|
return result;
|
|
}
|
|
|
|
private Vector3 EvalNormal(Vector3 p, BrushMask mask)
|
|
{
|
|
//Profiler.BeginSample("EvalNormal");
|
|
|
|
Vector3 n = Vector3.zero;
|
|
var mat = SdfBrushMaterial.New;
|
|
n += new Vector3( 1.0f, -1.0f, -1.0f) * EvalSdf(p + new Vector3( 1e-4f, -1e-4f, -1e-4f), mask, ref mat, false);
|
|
n += new Vector3(-1.0f, -1.0f, 1.0f) * EvalSdf(p + new Vector3(-1e-4f, -1e-4f, 1e-4f), mask, ref mat, false);
|
|
n += new Vector3(-1.0f, 1.0f, -1.0f) * EvalSdf(p + new Vector3(-1e-4f, 1e-4f, -1e-4f), mask, ref mat, false);
|
|
n += new Vector3( 1.0f, 1.0f, 1.0f) * EvalSdf(p + new Vector3(1e-4f * 1.0001f, 1e-4f * 1.0002f, 1e-4f * 1.0003f), mask, ref mat, false);
|
|
|
|
//Profiler.EndSample();
|
|
|
|
return VectorUtil.NormalizeSafe(n, Vector3.zero, 1e-10f);
|
|
}
|
|
|
|
private Vector3 EvalNormal(Vector3 p)
|
|
{
|
|
Aabb maskQuery = new Aabb(p - Mathf.Max(MathUtil.Epsilon, MaxSurfaceDistance) * Vector3.one, p + Mathf.Max(MathUtil.Epsilon, MaxSurfaceDistance) * Vector3.one);
|
|
BrushMask mask;
|
|
BuildBrushMask(Tree, RootIndex, maskQuery, out mask);
|
|
Vector3 n = EvalNormal(p, mask);
|
|
mask.Dispose();
|
|
return n;
|
|
}
|
|
|
|
private static readonly float RayStepRatio = 0.5f;
|
|
|
|
private Contact EvalRaycast(Ray ray, ref SdfBrushMaterial materialOut)
|
|
{
|
|
//Profiler.BeginSample("EvalCast");
|
|
|
|
/*
|
|
// make sure the query cast starts from outside the root AABB
|
|
float maskQueryOffset = Tree[RootIndex].Bounds.Size.magnitude;
|
|
maskQuery.From -= maskQuery.Direction * maskQueryOffset;
|
|
maskQuery.MaxDistance += maskQueryOffset;
|
|
*/
|
|
|
|
float tMin;
|
|
BrushMask mask;
|
|
BuildBrushMask(Tree, RootIndex, ray, CastMargin, out mask, out tMin);
|
|
|
|
Vector3 p = ray.From + tMin * ray.MaxDistance * ray.Direction;
|
|
|
|
var contact = Contact.New;
|
|
float dist = 0.0f;
|
|
for (int iStep = 0; iStep < MaxSteps; ++iStep)
|
|
{
|
|
if (iStep == MaxSteps - 1)
|
|
contact.MaxStepsReached = true;
|
|
|
|
float d = EvalSdf(p, mask, ref materialOut, false);
|
|
|
|
// within margin?
|
|
if (Mathf.Abs(d) < CastMargin)
|
|
{
|
|
dist = (p - ray.From).magnitude;
|
|
|
|
// actually within max distance?
|
|
if (dist <= ray.MaxDistance)
|
|
{
|
|
contact.Hit = true;
|
|
contact.Position = p;
|
|
contact.Normal = EvalNormal(p, mask);
|
|
contact.LocalT = contact.GlobalT = dist / ray.MaxDistance;
|
|
if (ComputeMaterials)
|
|
EvalSdf(p, mask, ref materialOut, true);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
float stepDist = RayStepRatio * d;
|
|
p += stepDist * ray.Direction;
|
|
dist += stepDist;
|
|
|
|
// exceed max distance?
|
|
if (dist > ray.MaxDistance)
|
|
{
|
|
p = ray.From + ray.MaxDistance * ray.Direction;
|
|
|
|
// still return sensible data that might be useful
|
|
contact.Hit = false;
|
|
contact.Position = p;
|
|
contact.Normal = EvalNormal(p, mask);
|
|
contact.LocalT = contact.GlobalT = 1.0f;
|
|
if (ComputeMaterials)
|
|
EvalSdf(p, mask, ref materialOut, true);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
mask.Dispose();
|
|
|
|
//Profiler.EndSample();
|
|
|
|
return contact;
|
|
}
|
|
|
|
private Contact EvalRaycastChain(NativeArray<Vector3> castChain, ref SdfBrushMaterial materialOut)
|
|
{
|
|
Aabb maskQuery = Aabb.Empty;
|
|
for (int i = 0; i < castChain.Length; ++i)
|
|
maskQuery.Include(castChain[i]);
|
|
|
|
BrushMask mask;
|
|
BuildBrushMask(Tree, RootIndex, maskQuery, out mask);
|
|
|
|
var contact = Contact.New;
|
|
|
|
float totalRayMaxDist = 0.0f;
|
|
for (int i = 0; i < castChain.Length - 1; ++i)
|
|
totalRayMaxDist = (castChain[i + 1] - castChain[i]).magnitude;
|
|
|
|
int iStep = -1;
|
|
int iCurrRay = -1;
|
|
Vector3 p = float.MaxValue * Vector3.one;
|
|
Vector3 currRayFrom = float.MaxValue * Vector3.one;
|
|
Vector3 currRayDir = float.MaxValue * Vector3.one;
|
|
float currRayMaxDist = -1.0f;
|
|
float currRayDist = 0.0f;
|
|
float totalRayDist = 0.0f;
|
|
while (++iStep < MaxSteps)
|
|
{
|
|
if (iStep == MaxSteps - 1)
|
|
contact.MaxStepsReached = true;
|
|
|
|
if (currRayDist > currRayMaxDist)
|
|
{
|
|
// advance to next ray segment
|
|
++iCurrRay;
|
|
if (iCurrRay >= castChain.Length - 1)
|
|
break;
|
|
|
|
// initialize new ray
|
|
p = castChain[iCurrRay];
|
|
currRayFrom = p;
|
|
currRayMaxDist = (castChain[iCurrRay + 1] - castChain[iCurrRay]).magnitude;
|
|
currRayDist = 0.0f;
|
|
}
|
|
|
|
float d = EvalSdf(p, mask, ref materialOut, false);
|
|
|
|
// within margin?
|
|
if (Mathf.Abs(d) < CastMargin)
|
|
{
|
|
currRayDist = (p - currRayFrom).magnitude;
|
|
|
|
// actually within max distance?
|
|
if (currRayDist <= currRayMaxDist)
|
|
{
|
|
contact.Hit = true;
|
|
contact.Position = p;
|
|
contact.Normal = EvalNormal(p, mask);
|
|
contact.LocalT = currRayDist / currRayMaxDist;
|
|
contact.GlobalT = totalRayDist / totalRayMaxDist;
|
|
if (ComputeMaterials)
|
|
EvalSdf(p, mask, ref materialOut, true);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
float stepDist = RayStepRatio * d;
|
|
p += stepDist * currRayDir;
|
|
currRayDist += stepDist;
|
|
}
|
|
|
|
// went past last ray?
|
|
if (iCurrRay == castChain.Length - 1)
|
|
{
|
|
p = castChain[iCurrRay];
|
|
|
|
// still return sensible data that might be useful
|
|
contact.Hit = false;
|
|
contact.Position = p;
|
|
contact.Normal = EvalNormal(p, mask);
|
|
contact.LocalT = contact.GlobalT = 1.0f;
|
|
if (ComputeMaterials)
|
|
EvalSdf(p, mask, ref materialOut, true);
|
|
}
|
|
|
|
mask.Dispose();
|
|
|
|
return contact;
|
|
}
|
|
|
|
private Contact EvalSnapToSurface(Vector3 p, ref SdfBrushMaterial materialOut)
|
|
{
|
|
//Profiler.BeginSample("EvalSnapToSurface");
|
|
|
|
Aabb normalMaskQuery = new Aabb(p - Mathf.Max(MathUtil.Epsilon, MaxSurfaceDistance) * Vector3.one, p + Mathf.Max(MathUtil.Epsilon, MaxSurfaceDistance) * Vector3.one);
|
|
BrushMask normalMask;
|
|
BuildBrushMask(Tree, RootIndex, normalMaskQuery, out normalMask);
|
|
Vector3 n = EvalNormal(p, normalMask);
|
|
float d = EvalSdf(p, ref materialOut, false);
|
|
normalMask.Dispose();
|
|
|
|
var cast =
|
|
new Ray()
|
|
{
|
|
From = p,
|
|
Direction = -n,
|
|
MaxDistance = MaxSurfaceDistance,
|
|
};
|
|
var contact = EvalRaycast(cast, ref materialOut);
|
|
|
|
//Profiler.EndSample();
|
|
|
|
return contact;
|
|
}
|
|
#endif
|
|
|
|
public void Execute(int index)
|
|
{
|
|
#if MUDBUN_BURST
|
|
switch (Type)
|
|
{
|
|
case TypeEnum.Sdf:
|
|
{
|
|
if (RootIndex < 0)
|
|
{
|
|
SdfResults[index] = Result.New(MaxSurfaceDistance, SdfBrushMaterial.New, Vector3.zero);
|
|
return;
|
|
}
|
|
|
|
Vector3 p = WorldToLocal.MultiplyPoint(Samples[index]);
|
|
|
|
var mat = SdfBrushMaterial.New;
|
|
float res = EvalSdf(p, ref mat, ComputeMaterials);
|
|
|
|
SdfResults[index] = Result.New(res, mat, Vector3.zero);
|
|
break;
|
|
}
|
|
|
|
case TypeEnum.Normal:
|
|
{
|
|
if (RootIndex < 0)
|
|
{
|
|
SdfResults[index] = Result.New(float.MaxValue, SdfBrushMaterial.New, Vector3.zero);
|
|
return;
|
|
}
|
|
|
|
Vector3 p = WorldToLocal.MultiplyPoint(Samples[index]);
|
|
Vector3 n = EvalNormal(p);
|
|
n = LocalToWorldIt.MultiplyVector(n);
|
|
|
|
SdfResults[index] = Result.New(float.MaxValue, SdfBrushMaterial.New, n);
|
|
break;
|
|
}
|
|
|
|
case TypeEnum.SdfAndNormal:
|
|
{
|
|
if (RootIndex < 0)
|
|
{
|
|
SdfResults[index] = Result.New(MaxSurfaceDistance, SdfBrushMaterial.New, Vector3.zero);
|
|
return;
|
|
}
|
|
|
|
Vector3 p = WorldToLocal.MultiplyPoint(Samples[index]);
|
|
|
|
Aabb maskQuery = new Aabb(p - Mathf.Max(MathUtil.Epsilon, MaxSurfaceDistance) * Vector3.one, p + Mathf.Max(MathUtil.Epsilon, MaxSurfaceDistance) * Vector3.one);
|
|
BrushMask mask;
|
|
BuildBrushMask(Tree, RootIndex, maskQuery, out mask);
|
|
|
|
var mat = SdfBrushMaterial.New;
|
|
float res = EvalSdf(p, mask, ref mat, ComputeMaterials);
|
|
|
|
Vector3 n = EvalNormal(p, mask);
|
|
n = LocalToWorldIt.MultiplyVector(n);
|
|
|
|
SdfResults[index] = Result.New(res, mat, n);
|
|
|
|
mask.Dispose();
|
|
break;
|
|
}
|
|
|
|
case TypeEnum.Raycast:
|
|
{
|
|
if (RootIndex < 0)
|
|
{
|
|
Contacts[index] = Contact.New;
|
|
return;
|
|
}
|
|
|
|
var cast = Casts[index];
|
|
cast.From = WorldToLocal.MultiplyPoint(cast.From);
|
|
cast.Direction = WorldToLocal.MultiplyVector(cast.Direction).normalized;
|
|
|
|
var mat = SdfBrushMaterial.New;
|
|
var contact = EvalRaycast(cast, ref mat);
|
|
if (ComputeMaterials)
|
|
contact.Material = mat;
|
|
contact.Position = LocalToWorld.MultiplyPoint(contact.Position);
|
|
contact.Normal = LocalToWorldIt.MultiplyVector(contact.Normal).normalized;
|
|
Contacts[index] = contact;
|
|
break;
|
|
}
|
|
|
|
case TypeEnum.RaycastChain:
|
|
{
|
|
if (RootIndex < 0)
|
|
{
|
|
Contacts[0] = Contact.New;
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < Casts.Length; ++i)
|
|
{
|
|
CastChain[i] = WorldToLocal.MultiplyPoint(CastChain[i]);
|
|
}
|
|
|
|
var mat = SdfBrushMaterial.New;
|
|
var contact = EvalRaycastChain(CastChain, ref mat);
|
|
if (ComputeMaterials)
|
|
contact.Material = mat;
|
|
contact.Position = LocalToWorld.MultiplyPoint(contact.Position);
|
|
contact.Normal = LocalToWorldIt.MultiplyVector(contact.Normal).normalized;
|
|
Contacts[0] = contact;
|
|
break;
|
|
}
|
|
|
|
case TypeEnum.SnapToSurface:
|
|
{
|
|
if (RootIndex < 0)
|
|
{
|
|
Contacts[index] = Contact.New;
|
|
return;
|
|
}
|
|
|
|
Vector3 p = WorldToLocal.MultiplyPoint(Samples[index]);
|
|
|
|
var mat = SdfBrushMaterial.New;
|
|
var contact = EvalSnapToSurface(p, ref mat);
|
|
if (ComputeMaterials)
|
|
contact.Material = mat;
|
|
contact.Position = LocalToWorld.MultiplyPoint(contact.Position);
|
|
contact.Normal = LocalToWorldIt.MultiplyVector(contact.Normal).normalized;
|
|
Contacts[index] = contact;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if MUDBUN_BURST
|
|
public void Dispose()
|
|
{
|
|
// do nothing (for now)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private static int GetJobBatchSize(int numJobs) => Mathf.Max(1, numJobs / Mathf.Max(1, SystemInfo.processorCount - 1));
|
|
|
|
/// <summary>
|
|
/// Evaluate SDF values.
|
|
/// </summary>
|
|
/// <param name="async"></param>
|
|
/// <param name="renderer"></param>
|
|
/// <param name="samples"></param>
|
|
/// <param name="results"></param>
|
|
/// <param name="aBrush"></param>
|
|
/// <param name="numBrushes"></param>
|
|
/// <param name="aMaterial"></param>
|
|
/// <param name="tree"></param>
|
|
/// <param name="iRoot"></param>
|
|
/// <param name="maxDistance"></param>
|
|
/// <param name="computeMaterials"></param>
|
|
/// <param name="surfaceShift"></param>
|
|
/// <returns></returns>
|
|
public static EvalJobHandle EvaluateSdf
|
|
(
|
|
AsyncMode asyncMode,
|
|
MudRendererBase renderer,
|
|
NativeArray<Vector3> samples,
|
|
NativeArray<Result> results,
|
|
BrushArray aBrush,
|
|
int numBrushes,
|
|
MaterialArray aMaterial,
|
|
AabbTree tree,
|
|
int iRoot,
|
|
float maxDistance,
|
|
bool computeMaterials,
|
|
float surfaceShift
|
|
)
|
|
{
|
|
#if !MUDBUN_BURST
|
|
WarnBurstMissing();
|
|
return EvalJobHandle.Empty;
|
|
#else
|
|
var job =
|
|
new EvalJob()
|
|
{
|
|
Type = EvalJob.TypeEnum.Sdf,
|
|
WorldToLocal = renderer.transform.worldToLocalMatrix,
|
|
LocalToWorld = renderer.transform.localToWorldMatrix,
|
|
LocalToWorldIt = renderer.transform.localToWorldMatrix.inverse.transpose,
|
|
SdfEvalFuncMapDense = s_sdfEvalFuncMapDense,
|
|
SdfEvalFuncMapSprase = s_sdfEvalFuncMapSparse,
|
|
Samples = samples,
|
|
Casts = s_castDummy,
|
|
CastChain = s_castChainDummy,
|
|
CastMargin = 0.0f,
|
|
ForceZeroBlendUnion = false,
|
|
Brushes = aBrush,
|
|
NumBrushes = numBrushes,
|
|
MaterialsIn = aMaterial,
|
|
Tree = tree,
|
|
RootIndex = iRoot,
|
|
MaxSurfaceDistance = maxDistance,
|
|
ComputeMaterials = computeMaterials,
|
|
SurfaceShift = surfaceShift,
|
|
SdfResults = results,
|
|
Contacts = s_contactDummy,
|
|
};
|
|
|
|
if (asyncMode != AsyncMode.None)
|
|
{
|
|
return EvalJobHandle.New(job, asyncMode, renderer);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < samples.Length; ++i)
|
|
job.Execute(i);
|
|
job.Dispose();
|
|
return EvalJobHandle.Empty;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluate SDF normals (normalized gradients).
|
|
/// </summary>
|
|
/// <param name="async"></param>
|
|
/// <param name="renderer"></param>
|
|
/// <param name="samples"></param>
|
|
/// <param name="results"></param>
|
|
/// <param name="aBrush"></param>
|
|
/// <param name="numBrushes"></param>
|
|
/// <param name="aMaterial"></param>
|
|
/// <param name="tree"></param>
|
|
/// <param name="iRoot"></param>
|
|
/// <param name="maxDistance"></param>
|
|
/// <param name="surfaceShift"></param>
|
|
/// <param name="h"></param>
|
|
/// <returns></returns>
|
|
public static EvalJobHandle EvaluateNormal
|
|
(
|
|
AsyncMode asyncMode,
|
|
MudRendererBase renderer,
|
|
NativeArray<Vector3> samples,
|
|
NativeArray<Result> results,
|
|
BrushArray aBrush,
|
|
int numBrushes,
|
|
MaterialArray aMaterial,
|
|
AabbTree tree,
|
|
int iRoot,
|
|
float maxDistance,
|
|
float surfaceShift,
|
|
float h
|
|
)
|
|
{
|
|
#if !MUDBUN_BURST
|
|
WarnBurstMissing();
|
|
return EvalJobHandle.Empty;
|
|
#else
|
|
var job =
|
|
new EvalJob()
|
|
{
|
|
Type = EvalJob.TypeEnum.Normal,
|
|
WorldToLocal = renderer.transform.worldToLocalMatrix,
|
|
LocalToWorld = renderer.transform.localToWorldMatrix,
|
|
LocalToWorldIt = renderer.transform.localToWorldMatrix.inverse.transpose,
|
|
SdfEvalFuncMapDense = s_sdfEvalFuncMapDense,
|
|
SdfEvalFuncMapSprase = s_sdfEvalFuncMapSparse,
|
|
Samples = samples,
|
|
Casts = s_castDummy,
|
|
CastChain = s_castChainDummy,
|
|
CastMargin = 0.0f,
|
|
ForceZeroBlendUnion = false,
|
|
Brushes = aBrush,
|
|
NumBrushes = numBrushes,
|
|
MaterialsIn = aMaterial,
|
|
Tree = tree,
|
|
RootIndex = iRoot,
|
|
MaxSurfaceDistance = maxDistance,
|
|
ComputeMaterials = false,
|
|
SurfaceShift = surfaceShift,
|
|
SdfResults = results,
|
|
Contacts = s_contactDummy,
|
|
};
|
|
|
|
if (asyncMode == AsyncMode.AsyncInputCopied)
|
|
job.CopyInput();
|
|
|
|
if (asyncMode != AsyncMode.None)
|
|
{
|
|
return EvalJobHandle.New(job, asyncMode, renderer);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < samples.Length; ++i)
|
|
job.Execute(i);
|
|
job.Dispose();
|
|
return EvalJobHandle.Empty;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluate SDF values and normals (normalized gradients) simultaneously. More efficient than evaluating separately.
|
|
/// </summary>
|
|
/// <param name="async"></param>
|
|
/// <param name="renderer"></param>
|
|
/// <param name="samples"></param>
|
|
/// <param name="sdfResults"></param>
|
|
/// <param name="aBrush"></param>
|
|
/// <param name="numBrushes"></param>
|
|
/// <param name="aMaterial"></param>
|
|
/// <param name="tree"></param>
|
|
/// <param name="iRoot"></param>
|
|
/// <param name="maxDistance"></param>
|
|
/// <param name="computeMaterials"></param>
|
|
/// <param name="surfaceShift"></param>
|
|
/// <param name="h"></param>
|
|
/// <returns></returns>
|
|
public static EvalJobHandle EvaluateSdfAndNormal
|
|
(
|
|
AsyncMode asyncMode,
|
|
MudRendererBase renderer,
|
|
NativeArray<Vector3> samples,
|
|
NativeArray<Result> sdfResults,
|
|
BrushArray aBrush,
|
|
int numBrushes,
|
|
MaterialArray aMaterial,
|
|
AabbTree tree,
|
|
int iRoot,
|
|
float maxDistance,
|
|
bool computeMaterials,
|
|
float surfaceShift,
|
|
float h
|
|
)
|
|
{
|
|
#if !MUDBUN_BURST
|
|
WarnBurstMissing();
|
|
return EvalJobHandle.Empty;
|
|
#else
|
|
var job =
|
|
new EvalJob()
|
|
{
|
|
Type = EvalJob.TypeEnum.SdfAndNormal,
|
|
WorldToLocal = renderer.transform.worldToLocalMatrix,
|
|
LocalToWorld = renderer.transform.localToWorldMatrix,
|
|
LocalToWorldIt = renderer.transform.localToWorldMatrix.inverse.transpose,
|
|
SdfEvalFuncMapDense = s_sdfEvalFuncMapDense,
|
|
SdfEvalFuncMapSprase = s_sdfEvalFuncMapSparse,
|
|
Samples = samples,
|
|
Casts = s_castDummy,
|
|
CastChain = s_castChainDummy,
|
|
CastMargin = 0.0f,
|
|
ForceZeroBlendUnion = false,
|
|
Brushes = aBrush,
|
|
NumBrushes = numBrushes,
|
|
MaterialsIn = aMaterial,
|
|
Tree = tree,
|
|
RootIndex = iRoot,
|
|
MaxSurfaceDistance = maxDistance,
|
|
ComputeMaterials = computeMaterials,
|
|
SurfaceShift = surfaceShift,
|
|
SdfResults = sdfResults,
|
|
Contacts = s_contactDummy,
|
|
};
|
|
|
|
if (asyncMode == AsyncMode.AsyncInputCopied)
|
|
job.CopyInput();
|
|
|
|
if (asyncMode != AsyncMode.None)
|
|
{
|
|
return EvalJobHandle.New(job, asyncMode, renderer);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < samples.Length; ++i)
|
|
job.Execute(i);
|
|
job.Dispose();
|
|
return EvalJobHandle.Empty;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluate raycasts against SDF zero isosurface.
|
|
/// </summary>
|
|
/// <param name="async"></param>
|
|
/// <param name="renderer"></param>
|
|
/// <param name="casts"></param>
|
|
/// <param name="results"></param>
|
|
/// <param name="castMargin"></param>
|
|
/// <param name="aBrush"></param>
|
|
/// <param name="numBrushes"></param>
|
|
/// <param name="aMaterial"></param>
|
|
/// <param name="tree"></param>
|
|
/// <param name="iRoot"></param>
|
|
/// <param name="computeMaterials"></param>
|
|
/// <param name="maxSteps"></param>
|
|
/// <param name="surfaceShift"></param>
|
|
/// <param name="forceZeroBlendUnion"></param>
|
|
/// <returns></returns>
|
|
public static EvalJobHandle EvaluateRaycast
|
|
(
|
|
AsyncMode asyncMode,
|
|
MudRendererBase renderer,
|
|
NativeArray<Ray> casts,
|
|
NativeArray<Contact> results,
|
|
float castMargin,
|
|
BrushArray aBrush,
|
|
int numBrushes,
|
|
MaterialArray aMaterial,
|
|
AabbTree tree,
|
|
int iRoot,
|
|
bool computeMaterials,
|
|
int maxSteps,
|
|
float surfaceShift,
|
|
bool forceZeroBlendUnion
|
|
)
|
|
{
|
|
#if !MUDBUN_BURST
|
|
WarnBurstMissing();
|
|
return EvalJobHandle.Empty;
|
|
#else
|
|
var job =
|
|
new EvalJob()
|
|
{
|
|
Type = EvalJob.TypeEnum.Raycast,
|
|
WorldToLocal = renderer.transform.worldToLocalMatrix,
|
|
LocalToWorld = renderer.transform.localToWorldMatrix,
|
|
LocalToWorldIt = renderer.transform.localToWorldMatrix.inverse.transpose,
|
|
SdfEvalFuncMapDense = s_sdfEvalFuncMapDense,
|
|
SdfEvalFuncMapSprase = s_sdfEvalFuncMapSparse,
|
|
Samples = s_sampleDummy,
|
|
Casts = casts,
|
|
CastChain = s_castChainDummy,
|
|
CastMargin = castMargin,
|
|
ForceZeroBlendUnion = forceZeroBlendUnion,
|
|
Brushes = aBrush,
|
|
NumBrushes = numBrushes,
|
|
MaterialsIn = aMaterial,
|
|
Tree = tree,
|
|
RootIndex = iRoot,
|
|
MaxSurfaceDistance = -1.0f,
|
|
MaxSteps = maxSteps,
|
|
ComputeMaterials = computeMaterials,
|
|
SurfaceShift = surfaceShift,
|
|
SdfResults = s_resultDummy,
|
|
Contacts = results,
|
|
};
|
|
|
|
if (asyncMode == AsyncMode.AsyncInputCopied)
|
|
job.CopyInput();
|
|
|
|
if (asyncMode != AsyncMode.None)
|
|
{
|
|
return EvalJobHandle.New(job, asyncMode, renderer);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < casts.Length; ++i)
|
|
job.Execute(i);
|
|
job.Dispose();
|
|
return EvalJobHandle.Empty;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluate raycast chains.
|
|
/// </summary>
|
|
/// <param name="async"></param>
|
|
/// <param name="renderer"></param>
|
|
/// <param name="castChain"></param>
|
|
/// <param name="contact"></param>
|
|
/// <param name="castMargin"></param>
|
|
/// <param name="aBrush"></param>
|
|
/// <param name="numBrushes"></param>
|
|
/// <param name="aMaterial"></param>
|
|
/// <param name="tree"></param>
|
|
/// <param name="iRoot"></param>
|
|
/// <param name="computeMaterials"></param>
|
|
/// <param name="maxSteps"></param>
|
|
/// <param name="surfaceShift"></param>
|
|
/// <returns></returns>
|
|
public static EvalJobHandle EvaluateRaycastChain
|
|
(
|
|
AsyncMode asyncMode,
|
|
MudRendererBase renderer,
|
|
NativeArray<Vector3> castChain,
|
|
NativeArray<Contact> contact,
|
|
float castMargin,
|
|
BrushArray aBrush,
|
|
int numBrushes,
|
|
MaterialArray aMaterial,
|
|
AabbTree tree,
|
|
int iRoot,
|
|
bool computeMaterials,
|
|
int maxSteps,
|
|
float surfaceShift
|
|
)
|
|
{
|
|
#if !MUDBUN_BURST
|
|
WarnBurstMissing();
|
|
return EvalJobHandle.Empty;
|
|
#else
|
|
var job =
|
|
new EvalJob()
|
|
{
|
|
Type = EvalJob.TypeEnum.RaycastChain,
|
|
WorldToLocal = renderer.transform.worldToLocalMatrix,
|
|
LocalToWorld = renderer.transform.localToWorldMatrix,
|
|
LocalToWorldIt = renderer.transform.localToWorldMatrix.inverse.transpose,
|
|
SdfEvalFuncMapDense = s_sdfEvalFuncMapDense,
|
|
SdfEvalFuncMapSprase = s_sdfEvalFuncMapSparse,
|
|
Samples = s_sampleDummy,
|
|
Casts = s_castDummy,
|
|
CastChain = castChain,
|
|
CastMargin = castMargin,
|
|
ForceZeroBlendUnion = false,
|
|
Brushes = aBrush,
|
|
NumBrushes = numBrushes,
|
|
MaterialsIn = aMaterial,
|
|
Tree = tree,
|
|
RootIndex = iRoot,
|
|
MaxSurfaceDistance = -1.0f,
|
|
MaxSteps = maxSteps,
|
|
ComputeMaterials = computeMaterials,
|
|
SurfaceShift = surfaceShift,
|
|
SdfResults = s_resultDummy,
|
|
Contacts = contact,
|
|
};
|
|
|
|
if (asyncMode == AsyncMode.AsyncInputCopied)
|
|
job.CopyInput();
|
|
|
|
if (asyncMode != AsyncMode.None)
|
|
{
|
|
return EvalJobHandle.New(job, asyncMode, renderer);
|
|
}
|
|
else
|
|
{
|
|
job.Execute(0);
|
|
job.Dispose();
|
|
return EvalJobHandle.Empty;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Snap points to closest SDF zero isosurface.
|
|
/// </summary>
|
|
/// <param name="async"></param>
|
|
/// <param name="renderer"></param>
|
|
/// <param name="samples"></param>
|
|
/// <param name="results"></param>
|
|
/// <param name="castMargin"></param>
|
|
/// <param name="aBrush"></param>
|
|
/// <param name="numBrushes"></param>
|
|
/// <param name="aMaterial"></param>
|
|
/// <param name="tree"></param>
|
|
/// <param name="iRoot"></param>
|
|
/// <param name="computeMaterials"></param>
|
|
/// <param name="maxSurfaceDistance"></param>
|
|
/// <param name="maxSteps"></param>
|
|
/// <param name="surfaceShift"></param>
|
|
/// <returns></returns>
|
|
public static EvalJobHandle EvaluateSnapToSurface
|
|
(
|
|
AsyncMode asyncMode,
|
|
MudRendererBase renderer,
|
|
NativeArray<Vector3> samples,
|
|
NativeArray<Contact> results,
|
|
float castMargin,
|
|
BrushArray aBrush,
|
|
int numBrushes,
|
|
MaterialArray aMaterial,
|
|
AabbTree tree,
|
|
int iRoot,
|
|
bool computeMaterials,
|
|
float maxSurfaceDistance,
|
|
int maxSteps,
|
|
float surfaceShift
|
|
)
|
|
{
|
|
#if !MUDBUN_BURST
|
|
WarnBurstMissing();
|
|
return EvalJobHandle.Empty;
|
|
#else
|
|
var job =
|
|
new EvalJob()
|
|
{
|
|
Type = EvalJob.TypeEnum.SnapToSurface,
|
|
WorldToLocal = renderer.transform.worldToLocalMatrix,
|
|
LocalToWorld = renderer.transform.localToWorldMatrix,
|
|
LocalToWorldIt = renderer.transform.localToWorldMatrix.inverse.transpose,
|
|
SdfEvalFuncMapDense = s_sdfEvalFuncMapDense,
|
|
SdfEvalFuncMapSprase = s_sdfEvalFuncMapSparse,
|
|
Samples = samples,
|
|
Casts = s_castDummy,
|
|
CastChain = s_castChainDummy,
|
|
CastMargin = castMargin,
|
|
ForceZeroBlendUnion = false,
|
|
Brushes = aBrush,
|
|
NumBrushes = numBrushes,
|
|
MaterialsIn = aMaterial,
|
|
Tree = tree,
|
|
RootIndex = iRoot,
|
|
MaxSurfaceDistance = maxSurfaceDistance,
|
|
MaxSteps = maxSteps,
|
|
ComputeMaterials = computeMaterials,
|
|
SurfaceShift = surfaceShift,
|
|
SdfResults = s_resultDummy,
|
|
Contacts = results,
|
|
};
|
|
|
|
if (asyncMode == AsyncMode.AsyncInputCopied)
|
|
job.CopyInput();
|
|
|
|
if (asyncMode != AsyncMode.None)
|
|
{
|
|
return EvalJobHandle.New(job, asyncMode, renderer);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < samples.Length; ++i)
|
|
job.Execute(i);
|
|
job.Dispose();
|
|
return EvalJobHandle.Empty;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|