using UnityEngine;
using System.Linq;
using System.Collections.Generic;
namespace UnityEngine.ProBuilder
{
///
/// Utilities for working with smoothing groups. Smoothing groups are how ProBuilder defines hard and soft edges.
/// ProBuilder calculates vertex normals by first calculating the normal for every face, which in turn is applied to each
/// vertex that makes up the face. Afterwards, each vertex normal is averaged with coincident vertices belonging to the
/// same smoothing group.
///
public static class Smoothing
{
///
/// Faces with smoothingGroup = 0 are hard edges. Historically negative values were sometimes also written as hard edges.
///
internal const int smoothingGroupNone = 0;
///
/// Smoothing groups 1-24 are smooth.
///
internal const int smoothRangeMin = 1;
///
/// Smoothing groups 1-24 are smooth.
///
internal const int smoothRangeMax = 24;
///
/// Smoothing groups 25-42 are hard. Note that this is obsolete, and generally hard faces should be marked smoothingGroupNone.
///
internal const int hardRangeMin = 25;
///
/// Smoothing groups 25-42 are hard. Note that this is soon to be obsolete, and generally hard faces should be marked smoothingGroupNone.
///
internal const int hardRangeMax = 42;
///
/// Get the first available unused smoothing group.
///
/// The target mesh.
/// An unused smoothing group.
public static int GetUnusedSmoothingGroup(ProBuilderMesh mesh)
{
if (mesh == null)
throw new System.ArgumentNullException("mesh");
return GetNextUnusedSmoothingGroup(smoothRangeMin, new HashSet(mesh.facesInternal.Select(x => x.smoothingGroup)));
}
///
/// Get the first available smooth group after a specified index.
///
///
///
///
static int GetNextUnusedSmoothingGroup(int start, HashSet used)
{
while (used.Contains(start) && start < int.MaxValue - 1)
{
start++;
if (start > smoothRangeMax && start < hardRangeMax)
start = hardRangeMax + 1;
}
return start;
}
///
/// Is the smooth group value considered smooth?
///
/// The smoothing group to test.
/// True if the smoothing group value is smoothed, false if not.
public static bool IsSmooth(int index)
{
return (index > smoothingGroupNone && (index < hardRangeMin || index > hardRangeMax));
}
///
/// Generate smoothing groups for a set of faces by comparing adjacent faces with normal differences less than angleThreshold (in degrees).
///
/// The source mesh.
/// Faces to be considered for smoothing.
/// The maximum angle difference in degrees between adjacent face normals for the shared edge to be considered smooth.
public static void ApplySmoothingGroups(ProBuilderMesh mesh, IEnumerable faces, float angleThreshold)
{
ApplySmoothingGroups(mesh, faces, angleThreshold, null);
}
internal static void ApplySmoothingGroups(ProBuilderMesh mesh, IEnumerable faces, float angleThreshold, Vector3[] normals)
{
if (mesh == null || faces == null)
throw new System.ArgumentNullException("mesh");
// Reset the selected faces to no smoothing group
bool anySmoothed = false;
foreach (Face face in faces)
{
if (face.smoothingGroup != smoothingGroupNone)
anySmoothed = true;
face.smoothingGroup = Smoothing.smoothingGroupNone;
}
// if a set of normals was not supplied, get a new set of normals
// with no prior smoothing groups applied.
if (normals == null)
{
if (anySmoothed)
mesh.mesh.normals = null;
normals = mesh.GetNormals();
}
float threshold = Mathf.Abs(Mathf.Cos(Mathf.Clamp(angleThreshold, 0f, 89.999f) * Mathf.Deg2Rad));
HashSet used = new HashSet(mesh.facesInternal.Select(x => x.smoothingGroup));
int group = GetNextUnusedSmoothingGroup(1, used);
HashSet processed = new HashSet();
List wings = WingedEdge.GetWingedEdges(mesh, faces, true);
foreach (WingedEdge wing in wings)
{
// Already part of a group
if (!processed.Add(wing.face))
continue;
wing.face.smoothingGroup = group;
if (FindSoftEdgesRecursive(normals, wing, threshold, processed))
{
used.Add(group);
group = GetNextUnusedSmoothingGroup(group, used);
}
else
{
wing.face.smoothingGroup = Smoothing.smoothingGroupNone;
}
}
}
// Walk the perimiter of a wing looking for compatibly smooth connections. Returns true if any match was found, false if not.
static bool FindSoftEdgesRecursive(Vector3[] normals, WingedEdge wing, float angleThreshold, HashSet processed)
{
bool foundSmoothEdge = false;
using (var it = new WingedEdgeEnumerator(wing))
{
while (it.MoveNext())
{
var border = it.Current;
if (border.opposite == null)
continue;
if (border.opposite.face.smoothingGroup == Smoothing.smoothingGroupNone
&& IsSoftEdge(normals, border.edge, border.opposite.edge, angleThreshold))
{
if (processed.Add(border.opposite.face))
{
foundSmoothEdge = true;
border.opposite.face.smoothingGroup = wing.face.smoothingGroup;
FindSoftEdgesRecursive(normals, border.opposite, angleThreshold, processed);
}
}
}
}
return foundSmoothEdge;
}
static bool IsSoftEdge(Vector3[] normals, EdgeLookup left, EdgeLookup right, float threshold)
{
Vector3 lx = normals[left.local.a];
Vector3 ly = normals[left.local.b];
Vector3 rx = normals[right.common.a == left.common.a ? right.local.a : right.local.b];
Vector3 ry = normals[right.common.b == left.common.b ? right.local.b : right.local.a];
lx.Normalize();
ly.Normalize();
rx.Normalize();
ry.Normalize();
return Mathf.Abs(Vector3.Dot(lx, rx)) > threshold && Mathf.Abs(Vector3.Dot(ly, ry)) > threshold;
}
}
}