using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Serialization;
using System.Collections.ObjectModel;
namespace UnityEngine.ProBuilder
{
///
/// A face is composed of a set of triangles, and a material.
///
/// Triangle indices may point to the same vertex index as long as the vertices are unique to the face. That is,
/// every vertex that a face references should only be used by that face's indices. To associate vertices that
/// share common attributes (usually position), use the sharedIndexes property.
///
/// ProBuilder automatically manages condensing common vertices in the
/// EditorMeshUtility.Optimize function.
///
[Serializable]
public sealed class Face
{
[FormerlySerializedAs("_indices")]
[SerializeField]
int[] m_Indexes;
///
/// Stores the ID of the smoothing group that this Face is part of. ProBuilder averages the edge
/// normals for all faces that share a [Smoothing Group](../manual/workflow-edit-smoothing.md).
///
[SerializeField]
[FormerlySerializedAs("_smoothingGroup")]
int m_SmoothingGroup;
///
/// Determines how ProBuilder projects this face's vertices 2D space when is false.
///
[SerializeField]
[FormerlySerializedAs("_uv")]
AutoUnwrapSettings m_Uv;
///
/// Stores the material for this face to use.
///
[SerializeField]
[FormerlySerializedAs("_mat")]
Material m_Material;
[SerializeField]
int m_SubmeshIndex;
[SerializeField]
[FormerlySerializedAs("manualUV")]
bool m_ManualUV;
///
/// Gets or sets whether to map this face's UV coordinates manually or automatically. See
/// [Mapping Textures with UVs](../manual/workflow-uvs.html) for an overview of the differences.
///
///
/// True to set UV coordinates manually; false to use .
///
public bool manualUV
{
get { return m_ManualUV; }
set { m_ManualUV = value; }
}
///
/// UV element group. Used by the UV editor to group faces.
///
[SerializeField]
internal int elementGroup;
[SerializeField]
int m_TextureGroup;
///
/// Gets or sets which texture group this face belongs to. ProBuilder uses texture groups when
/// projecting Auto UVs. See [Continuous tiling](../manual/workflow-uvs.html#continuous-tiling).
///
///
/// ID of the texture group for this face.
///
public int textureGroup
{
get { return m_TextureGroup;}
set { m_TextureGroup = value; }
}
///
/// Gets or sets a reference to the array of triangle indices that make up this face.
///
internal int[] indexesInternal
{
get { return m_Indexes; }
set
{
if (m_Indexes == null)
throw new ArgumentNullException("value");
if (m_Indexes.Length % 3 != 0)
throw new ArgumentException("Face indexes must be a multiple of 3.");
m_Indexes = value;
InvalidateCache();
}
}
///
/// Gets the triangle indices that compose this face.
///
///
/// The array of indices representing this face's triangles.
///
public ReadOnlyCollection indexes
{
get { return new ReadOnlyCollection(m_Indexes); }
}
///
/// Sets the triangles that compose this face.
///
/// The new triangle array.
public void SetIndexes(IEnumerable indices)
{
if (indices == null)
throw new ArgumentNullException("indices");
var array = indices.ToArray();
int len = array.Length;
if (len % 3 != 0)
throw new ArgumentException("Face indexes must be a multiple of 3.");
m_Indexes = array;
InvalidateCache();
}
[NonSerialized]
int[] m_DistinctIndexes;
[NonSerialized]
Edge[] m_Edges;
///
/// Returns a reference to the cached distinct indexes (each vertex index is only referenced once in m_DistinctIndexes).
///
internal int[] distinctIndexesInternal
{
get { return m_DistinctIndexes == null ? CacheDistinctIndexes() : m_DistinctIndexes; }
}
///
/// Gets a collection of the vertex indexes that the indexes array references, made distinct.
///
///
/// A unique collection of vertices.
///
public ReadOnlyCollection distinctIndexes
{
get { return new ReadOnlyCollection(distinctIndexesInternal); }
}
internal Edge[] edgesInternal
{
get { return m_Edges == null ? CacheEdges() : m_Edges; }
}
///
/// Gets the perimeter edges that compose this face.
///
///
/// The collection of edges on this face.
///
public ReadOnlyCollection edges
{
get { return new ReadOnlyCollection(edgesInternal); }
}
///
/// Gets or sets which smoothing group this face belongs to, if any. This is used to calculate vertex normals.
///
///
/// The ID of this smoothing group as an integer.
///
public int smoothingGroup
{
get { return m_SmoothingGroup; }
set { m_SmoothingGroup = value; }
}
///
/// Gets the material that this face uses.
///
[Obsolete("Face.material is deprecated. Please use submeshIndex instead.")]
public Material material
{
get { return m_Material; }
set { m_Material = value; }
}
///
/// Gets or sets the index of the submesh that this face belongs to.
///
///
/// The ID of the submesh as an integer.
///
public int submeshIndex
{
get { return m_SubmeshIndex; }
set { m_SubmeshIndex = value; }
}
///
/// Gets or sets a reference to the [Auto UV](../manual/workflow-uvs.html#auto-uv-mode-features) mapping parameters.
///
///
/// The ID of this submesh as an integer.
///
public AutoUnwrapSettings uv
{
get { return m_Uv; }
set { m_Uv = value; }
}
///
/// Gets the index for the specified triangle in this face's array of triangle indices.
///
/// The triangle to access
///
/// The index of the specified triangle.
///
public int this[int i]
{
get { return indexesInternal[i]; }
}
///
/// Creates a Face with an empty triangles array.
///
public Face()
{
m_SubmeshIndex = 0;
}
///
/// Creates a face with default values and the specified set of triangles.
///
/// The new triangles array.
public Face(IEnumerable indices)
{
SetIndexes(indices);
m_Uv = AutoUnwrapSettings.tile;
m_Material = BuiltinMaterials.defaultMaterial;
m_SmoothingGroup = Smoothing.smoothingGroupNone;
m_SubmeshIndex = 0;
textureGroup = -1;
elementGroup = 0;
}
[Obsolete("Face.material is deprecated. Please use \"submeshIndex\" instead.")]
internal Face(int[] triangles, Material m, AutoUnwrapSettings u, int smoothing, int texture, int element, bool manualUVs)
{
SetIndexes(triangles);
m_Uv = new AutoUnwrapSettings(u);
m_Material = m;
m_SmoothingGroup = smoothing;
textureGroup = texture;
elementGroup = element;
manualUV = manualUVs;
m_SubmeshIndex = 0;
}
internal Face(IEnumerable triangles, int submeshIndex, AutoUnwrapSettings u, int smoothing, int texture, int element, bool manualUVs)
{
SetIndexes(triangles);
m_Uv = new AutoUnwrapSettings(u);
m_SmoothingGroup = smoothing;
textureGroup = texture;
elementGroup = element;
manualUV = manualUVs;
m_SubmeshIndex = submeshIndex;
}
///
/// Creates a new Face as a copy of another face.
///
/// The Face from which to copy properties and triangles.
public Face(Face other)
{
CopyFrom(other);
}
///
/// Copies properties and triangles from the specified face to this face.
///
/// The Face from which to copy properties and triangles.
public void CopyFrom(Face other)
{
if (other == null)
throw new ArgumentNullException("other");
int len = other.indexesInternal.Length;
m_Indexes = new int[len];
Array.Copy(other.indexesInternal, m_Indexes, len);
m_SmoothingGroup = other.smoothingGroup;
m_Uv = new AutoUnwrapSettings(other.uv);
#pragma warning disable 618
m_Material = other.material;
#pragma warning restore 618
manualUV = other.manualUV;
m_TextureGroup = other.textureGroup;
elementGroup = other.elementGroup;
m_SubmeshIndex = other.m_SubmeshIndex;
InvalidateCache();
}
internal void InvalidateCache()
{
m_Edges = null;
m_DistinctIndexes = null;
}
Edge[] CacheEdges()
{
if (m_Indexes == null)
return null;
HashSet dist = new HashSet();
List dup = new List();
for (int i = 0; i < indexesInternal.Length; i += 3)
{
Edge a = new Edge(indexesInternal[i + 0], indexesInternal[i + 1]);
Edge b = new Edge(indexesInternal[i + 1], indexesInternal[i + 2]);
Edge c = new Edge(indexesInternal[i + 2], indexesInternal[i + 0]);
if (!dist.Add(a)) dup.Add(a);
if (!dist.Add(b)) dup.Add(b);
if (!dist.Add(c)) dup.Add(c);
}
dist.ExceptWith(dup);
m_Edges = dist.ToArray();
return m_Edges;
}
int[] CacheDistinctIndexes()
{
if (m_Indexes == null)
return null;
m_DistinctIndexes = m_Indexes.Distinct().ToArray();
return distinctIndexesInternal;
}
///
/// Tests whether a triangle matches one of the triangles of this face.
///
/// First index in the triangle
/// Second index in the triangle
/// Third index in the triangle
/// True if {a,b,c} is found in this face's list of triangles; otherwise false.
public bool Contains(int a, int b, int c)
{
for (int i = 0, cnt = indexesInternal.Length; i < cnt; i += 3)
{
if (a == indexesInternal[i + 0]
&& b == indexesInternal[i + 1]
&& c == indexesInternal[i + 2])
return true;
}
return false;
}
///
/// Returns whether this face can be converted to a quad (a polygon with four sides).
///
/// True if this face is divisible by 4; false otherwise.
public bool IsQuad()
{
return edgesInternal != null && edgesInternal.Length == 4;
}
///
/// Converts a two-triangle face to a quad representation.
///
/// A quad (an array of four indices); or null if indices are not able to be represented as a quad.
public int[] ToQuad()
{
if (!IsQuad())
throw new InvalidOperationException("Face is not representable as a quad. Use Face.IsQuad to check for validity.");
int[] quad = new int[4] { edgesInternal[0].a, edgesInternal[0].b, -1, -1 };
if (edgesInternal[1].a == quad[1])
quad[2] = edgesInternal[1].b;
else if (edgesInternal[2].a == quad[1])
quad[2] = edgesInternal[2].b;
else if (edgesInternal[3].a == quad[1])
quad[2] = edgesInternal[3].b;
if (edgesInternal[1].a == quad[2])
quad[3] = edgesInternal[1].b;
else if (edgesInternal[2].a == quad[2])
quad[3] = edgesInternal[2].b;
else if (edgesInternal[3].a == quad[2])
quad[3] = edgesInternal[3].b;
return quad;
}
///
/// Returns a string representation of the face.
///
/// String formatted as `[a, b, c], ...`.
public override string ToString()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int i = 0; i < indexesInternal.Length; i += 3)
{
sb.Append("[");
sb.Append(indexesInternal[i]);
sb.Append(", ");
sb.Append(indexesInternal[i + 1]);
sb.Append(", ");
sb.Append(indexesInternal[i + 2]);
sb.Append("]");
if (i < indexesInternal.Length - 3)
sb.Append(", ");
}
return sb.ToString();
}
///
/// Adds an offset to each value in the indices array.
///
/// The value to add to each index.
public void ShiftIndexes(int offset)
{
for (int i = 0, c = m_Indexes.Length; i < c; i++)
m_Indexes[i] += offset;
InvalidateCache();
}
///
/// Finds the smallest value in the triangles array.
///
/// The smallest value in the indices array.
int SmallestIndexValue()
{
int smallest = m_Indexes[0];
for (int i = 1; i < m_Indexes.Length; i++)
{
if (m_Indexes[i] < smallest)
smallest = m_Indexes[i];
}
return smallest;
}
///
/// Finds the smallest value in the indices array, then offsets by subtracting that value from each index.
///
///
/// ```
/// // sets the indexes array to `{0, 1, 2}`.
/// new Face(3,4,5).ShiftIndexesToZero();
/// ```
///
public void ShiftIndexesToZero()
{
int offset = SmallestIndexValue();
for (int i = 0; i < m_Indexes.Length; i++)
m_Indexes[i] -= offset;
InvalidateCache();
}
///
/// Reverses the order of the triangle array. This has the effect of reversing the direction that this face renders.
///
public void Reverse()
{
Array.Reverse(m_Indexes);
InvalidateCache();
}
internal static void GetIndices(IEnumerable faces, List indices)
{
indices.Clear();
foreach (var face in faces)
{
for (int i = 0, c = face.indexesInternal.Length; i < c; ++i)
indices.Add(face.indexesInternal[i]);
}
}
internal static void GetDistinctIndices(IEnumerable faces, List indices)
{
indices.Clear();
foreach (var face in faces)
{
for (int i = 0, c = face.distinctIndexesInternal.Length; i < c; ++i)
indices.Add(face.distinctIndexesInternal[i]);
}
}
///
/// Advances to the next connected edge given a source edge and the index connect.
///
internal bool TryGetNextEdge(Edge source, int index, ref Edge nextEdge, ref int nextIndex)
{
for (int i = 0, c = edgesInternal.Length; i < c; i++)
{
if (edgesInternal[i] == source)
continue;
nextEdge = edgesInternal[i];
if (nextEdge.Contains(index))
{
nextIndex = nextEdge.a == index ? nextEdge.b : nextEdge.a;
return true;
}
}
return false;
}
}
}