/// <summary> /// Returns the signed distance to the field as a whole. /// </summary> public float Map(Vector3 p) { float minDist = 10000000f; for (int i = 0; i < m_sdfData.Count; i++) { SDFGPUData data = m_sdfData[i]; if (data.IsOperation) { p = ElongateSpace(p, data.Data, data.Transform); } else { if (data.CombineType == 0) { minDist = SmoothUnion(minDist, SDF(p, data), data.Smoothing); } else if (data.CombineType == 1) { minDist = SmoothSubtract(SDF(p, data), minDist, data.Smoothing); } else { minDist = SmoothIntersect(SDF(p, data), minDist, data.Smoothing); } } } return(minDist); }
/// <summary> /// Get the signed distance to the object represented by the given data object. /// </summary> private float SDF(Vector3 p, SDFGPUData data) { if (data.IsMesh) { Vector3 vec = GetDirectionToMesh(p, data, out float distSign, out Vector3 transformedP); return(vec.magnitude * distSign * data.Flip); } else { p = data.Transform.MultiplyPoint(p); switch ((SDFPrimitiveType)(data.Type - 1)) { case SDFPrimitiveType.Sphere: return(MapSphere(p, data.Data.x) * data.Flip); case SDFPrimitiveType.Torus: return(MapTorus(p, data.Data) * data.Flip); case SDFPrimitiveType.Cuboid: return(MapRoundedBox(p, data.Data, data.Data.w) * data.Flip); case SDFPrimitiveType.Cylinder: return(MapCylinder(p, data.Data.x, data.Data.y) * data.Flip); default: return(MapBoxFrame(p, data.Data, data.Data.w) * data.Flip); } } }
private static Vector3 CellCoordinateToVertex(int x, int y, int z, SDFGPUData data) { float gridSize = data.Size - 1f; Vector3 minBounds = data.MinBounds; Vector3 maxBounds = data.MaxBounds; float xPos = Mathf.Lerp(minBounds.x, maxBounds.x, x / gridSize); float yPos = Mathf.Lerp(minBounds.y, maxBounds.y, y / gridSize); float zPos = Mathf.Lerp(minBounds.z, maxBounds.z, z / gridSize); return(new Vector3(xPos, yPos, zPos)); }
// clamp the input point to an axis aligned bounding cube of the given bounds // optionally can provide an offset which pushes the bounds in or out. // this is used to get the position on the bounding cube nearest the given point as // part of the sdf to mesh calculation. the additional push param is used to ensure we have enough // samples around our point that we can get a gradient private static Vector3 GetClosestPointToVolume(Vector3 p, SDFGPUData data, float boundsOffset = 0f) { Vector3 minBounds = data.MinBounds; Vector3 maxBounds = data.MaxBounds; return(new Vector3( Mathf.Clamp(p.x, minBounds.x + boundsOffset, maxBounds.x - boundsOffset), Mathf.Clamp(p.y, minBounds.y + boundsOffset, maxBounds.y - boundsOffset), Mathf.Clamp(p.z, minBounds.z + boundsOffset, maxBounds.z - boundsOffset) )); }
private Vector3 ComputeMeshGradient(Vector3 p, SDFGPUData data, float epsilon, float boundsOffset = 0f) { // sample the map 4 times to calculate the gradient at that point, then normalize it Vector2 e = new Vector2(epsilon, -epsilon); return(( XYY(e) * SampleAssetInterpolated(p + XYY(e), data, boundsOffset) + YYX(e) * SampleAssetInterpolated(p + YYX(e), data, boundsOffset) + YXY(e) * SampleAssetInterpolated(p + YXY(e), data, boundsOffset) + XXX(e) * SampleAssetInterpolated(p + XXX(e), data, boundsOffset)).normalized); }
// these functions are all specifically to do with SDFMesh objects only // given a point, return the coords of the cell it's in, and the fractional component for interpolation private static void GetNearestCoordinates(Vector3 p, SDFGPUData data, out Vector3 coords, out Vector3 fracs, float boundsOffset = 0f) { p = ClampAndNormalizeToVolume(p, data, boundsOffset); int cellsPerSide = data.Size - 1; // sometimes i'm not good at coming up with names :U Vector3 floored = Floor(p * cellsPerSide); coords = Min(floored, cellsPerSide - 1); fracs = Frac(p * cellsPerSide); }
// ensure the given point is inside the volume, and then smush into the the [0, 1] range private static Vector3 ClampAndNormalizeToVolume(Vector3 p, SDFGPUData data, float boundsOffset = 0f) { // clamp so we're inside the volume p = GetClosestPointToVolume(p, data, boundsOffset); Vector3 minBounds = data.MinBounds; Vector3 maxBounds = data.MaxBounds; return(new Vector3( Mathf.InverseLerp(minBounds.x + boundsOffset, maxBounds.x - boundsOffset, p.x), Mathf.InverseLerp(minBounds.y + boundsOffset, maxBounds.y - boundsOffset, p.y), Mathf.InverseLerp(minBounds.z + boundsOffset, maxBounds.z - boundsOffset, p.z) )); }
private float SampleAssetInterpolated(Vector3 p, SDFGPUData data, float boundsOffset = 0f) { GetNearestCoordinates(p, data, out Vector3 coords, out Vector3 fracs, boundsOffset); int x = (int)coords.x; int y = (int)coords.y; int z = (int)coords.z; float sampleA = GetMeshSignedDistance(x, y, z, data); float sampleB = GetMeshSignedDistance(x + 1, y, z, data); float sampleC = GetMeshSignedDistance(x, y + 1, z, data); float sampleD = GetMeshSignedDistance(x + 1, y + 1, z, data); float sampleE = GetMeshSignedDistance(x, y, z + 1, data); float sampleF = GetMeshSignedDistance(x + 1, y, z + 1, data); float sampleG = GetMeshSignedDistance(x, y + 1, z + 1, data); float sampleH = GetMeshSignedDistance(x + 1, y + 1, z + 1, data); return(Utils.TrilinearInterpolate(fracs, sampleA, sampleB, sampleC, sampleD, sampleE, sampleF, sampleG, sampleH)); }
// returns the vector pointing to the surface of the mesh representation, as well as the sign // (negative for inside, positive for outside) // this can be used to recreate a signed distance field private Vector3 GetDirectionToMesh(Vector3 p, SDFGPUData data, out float distSign, out Vector3 transformedP) { transformedP = data.Transform.MultiplyPoint(p); const float epsilon = 0.75f; const float pushIntoBounds = 0.04f; // get the distance either at p, or at the point on the bounds nearest p float sample = SampleAssetInterpolated(transformedP, data); Vector3 closestPoint = GetClosestPointToVolume(transformedP, data, pushIntoBounds); Vector3 vecInBounds = (-(ComputeMeshGradient(closestPoint, data, epsilon, pushIntoBounds)).normalized * sample); Vector3 vecToBounds = (closestPoint - transformedP); Vector3 finalVec = vecToBounds + vecInBounds; distSign = Mathf.Sign(sample); return(finalVec); }
private static int CellCoordinateToIndex(int x, int y, int z, SDFGPUData data) { int size = data.Size; return(data.SampleStartIndex + (x + y * size + z * size * size)); }
private float SDF_Colour(Vector3 p, SDFGPUData data, SDFMaterialGPU material, out Vector4 colour) { colour = material.Colour; return(SDF(p, data)); }
private float GetMeshSignedDistance(int x, int y, int z, SDFGPUData data) { int index = CellCoordinateToIndex(x, y, z, data); return(m_sdfMeshSamples[index]); }