947 lines
35 KiB
C#
947 lines
35 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System;
|
|
using UnityEngine.ProBuilder.Shapes;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace UnityEngine.ProBuilder
|
|
{
|
|
#if UNITY_EDITOR
|
|
public sealed partial class ProBuilderMesh : ISerializationCallbackReceiver
|
|
#else
|
|
public sealed partial class ProBuilderMesh
|
|
#endif
|
|
{
|
|
static HashSet<int> s_CachedHashSet = new HashSet<int>();
|
|
|
|
#if UNITY_EDITOR
|
|
public void OnBeforeSerialize() {}
|
|
|
|
public void OnAfterDeserialize()
|
|
{
|
|
InvalidateCaches();
|
|
}
|
|
|
|
#if ENABLE_DRIVEN_PROPERTIES
|
|
// Using the internal callbacks here to avoid registering this component as "enable-able"
|
|
void OnEnableINTERNAL()
|
|
{
|
|
ApplyDrivenProperties();
|
|
}
|
|
|
|
void OnDisableINTERNAL()
|
|
{
|
|
// Don't call DrivenPropertyManager.Unregister in OnDestroy. At that point GameObject::m_ActivationState is
|
|
// already set to kDestroying, and DrivenPropertyManager.Unregister will try to revert the driven values to
|
|
// their previous state (which will assert that the object is _not_ being destroyed)
|
|
ClearDrivenProperties();
|
|
}
|
|
|
|
internal void ApplyDrivenProperties()
|
|
{
|
|
SerializationUtility.RegisterDrivenProperty(this, this, "m_Mesh");
|
|
if(gameObject != null && gameObject.TryGetComponent(out MeshCollider meshCollider))
|
|
SerializationUtility.RegisterDrivenProperty(this, meshCollider, "m_Mesh");
|
|
}
|
|
|
|
internal void ClearDrivenProperties()
|
|
{
|
|
SerializationUtility.UnregisterDrivenProperty(this, this, "m_Mesh");
|
|
if(gameObject != null && gameObject.TryGetComponent(out MeshCollider meshCollider))
|
|
SerializationUtility.UnregisterDrivenProperty(this, meshCollider, "m_Mesh");
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
void Awake()
|
|
{
|
|
EnsureMeshFilterIsAssigned();
|
|
EnsureMeshColliderIsAssigned();
|
|
//Ensure no element is selected at awake
|
|
ClearSelection();
|
|
|
|
if(vertexCount > 0
|
|
&& faceCount > 0
|
|
&& meshSyncState == MeshSyncState.Null)
|
|
{
|
|
using (new NonVersionedEditScope(this))
|
|
{
|
|
Rebuild();
|
|
meshWasInitialized?.Invoke(this);
|
|
}
|
|
|
|
// only sync instance version index when a new mesh is created
|
|
m_InstanceVersionIndex = m_VersionIndex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rebuilds the mesh positions and submeshes, and then recalculates the normals, collisions,
|
|
/// UVs, tangents, and colors.
|
|
/// </summary>
|
|
/// <seealso cref="ToMesh"/>
|
|
/// <seealso cref="Refresh"/>
|
|
void Reset()
|
|
{
|
|
if (meshSyncState != MeshSyncState.Null)
|
|
{
|
|
Rebuild();
|
|
if (componentHasBeenReset != null)
|
|
componentHasBeenReset(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up when the ProBuilderMesh component is removed (that is, when a ProBuilder mesh
|
|
/// is converted to a standard Unity mesh).
|
|
/// </summary>
|
|
/// <seealso cref="meshWillBeDestroyed" />
|
|
/// <seealso cref="preserveMeshAssetOnDestroy"/>
|
|
void OnDestroy()
|
|
{
|
|
// Always re-enable the MeshFilter when the ProBuilderMesh component is removed
|
|
if (m_MeshFilter != null || this.TryGetComponent(out m_MeshFilter))
|
|
m_MeshFilter.hideFlags = HideFlags.None;
|
|
|
|
if (componentWillBeDestroyed != null)
|
|
componentWillBeDestroyed(this);
|
|
|
|
// Time.frameCount is zero when loading scenes in the Editor. It's the only way I could figure to
|
|
// differentiate between OnDestroy invoked from user delete & editor scene loading.
|
|
if (!preserveMeshAssetOnDestroy &&
|
|
Application.isEditor &&
|
|
!Application.isPlaying &&
|
|
Time.frameCount > 0)
|
|
{
|
|
DestroyUnityMesh();
|
|
}
|
|
}
|
|
|
|
internal void DestroyUnityMesh()
|
|
{
|
|
if (meshWillBeDestroyed != null)
|
|
meshWillBeDestroyed(this);
|
|
else
|
|
DestroyImmediate(gameObject.GetComponent<MeshFilter>().sharedMesh, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increments the mesh version index. This helps ProBuilder track
|
|
/// when the mesh changes.
|
|
/// </summary>
|
|
void IncrementVersionIndex()
|
|
{
|
|
// it doesn't matter if the version index wraps. the important thing is that it is changed.
|
|
unchecked
|
|
{
|
|
if (++m_VersionIndex == 0)
|
|
m_VersionIndex = 1;
|
|
m_InstanceVersionIndex = m_VersionIndex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets (empties) all the attribute arrays on this object and clears any selected elements.
|
|
/// The attribute arrays include faces, positions, texture UVs, tangents, shared vertices,
|
|
/// shared textures, and vertex colors.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
// various editor tools expect faces & vertices to always be valid.
|
|
// ideally we'd null everything here, but that would break a lot of existing code.
|
|
m_Faces = new Face[0];
|
|
m_Positions = new Vector3[0];
|
|
m_Textures0 = new Vector2[0];
|
|
m_Textures2 = null;
|
|
m_Textures3 = null;
|
|
m_Tangents = null;
|
|
m_SharedVertices = new SharedVertex[0];
|
|
m_SharedTextures = new SharedVertex[0];
|
|
InvalidateSharedVertexLookup();
|
|
InvalidateSharedTextureLookup();
|
|
m_Colors = null;
|
|
m_MeshFormatVersion = k_MeshFormatVersion;
|
|
IncrementVersionIndex();
|
|
ClearSelection();
|
|
}
|
|
|
|
internal void EnsureMeshFilterIsAssigned()
|
|
{
|
|
if (filter == null)
|
|
m_MeshFilter = gameObject.AddComponent<MeshFilter>();
|
|
|
|
#if UNITY_EDITOR
|
|
m_MeshFilter.hideFlags = k_MeshFilterHideFlags;
|
|
#endif
|
|
|
|
if (!renderer.isPartOfStaticBatch && filter.sharedMesh != m_Mesh)
|
|
filter.sharedMesh = m_Mesh;
|
|
}
|
|
|
|
internal static ProBuilderMesh CreateInstanceWithPoints(Vector3[] positions)
|
|
{
|
|
if (positions.Length % 4 != 0)
|
|
{
|
|
Log.Warning("Invalid Geometry. Make sure vertices in are pairs of 4 (faces).");
|
|
return null;
|
|
}
|
|
|
|
GameObject go = new GameObject();
|
|
go.name = "ProBuilder Mesh";
|
|
ProBuilderMesh pb = go.AddComponent<ProBuilderMesh>();
|
|
pb.m_MeshFormatVersion = k_MeshFormatVersion;
|
|
pb.GeometryWithPoints(positions);
|
|
|
|
return pb;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new GameObject with a ProBuilderMesh, <see cref="UnityEngine.MeshFilter" />,
|
|
/// and <see cref="UnityEngine.MeshRenderer" /> component but leaves the position and face
|
|
/// information empty.
|
|
/// </summary>
|
|
/// <returns>A reference to the new ProBuilderMesh component.</returns>
|
|
public static ProBuilderMesh Create()
|
|
{
|
|
var go = new GameObject();
|
|
var pb = go.AddComponent<ProBuilderMesh>();
|
|
pb.m_MeshFormatVersion = k_MeshFormatVersion;
|
|
pb.Clear();
|
|
return pb;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new GameObject with a ProBuilderMesh, <see cref="UnityEngine.MeshFilter" />, and <see cref="UnityEngine.MeshRenderer" /> component.
|
|
/// Then it initializes the ProBuilderMesh with the specified sets of positions and faces.
|
|
/// </summary>
|
|
/// <param name="positions">Vertex positions array.</param>
|
|
/// <param name="faces">Faces array.</param>
|
|
/// <returns>A reference to the new ProBuilderMesh component.</returns>
|
|
public static ProBuilderMesh Create(IEnumerable<Vector3> positions, IEnumerable<Face> faces)
|
|
{
|
|
GameObject go = new GameObject();
|
|
ProBuilderMesh pb = go.AddComponent<ProBuilderMesh>();
|
|
go.name = "ProBuilder Mesh";
|
|
pb.m_MeshFormatVersion = k_MeshFormatVersion;
|
|
pb.RebuildWithPositionsAndFaces(positions, faces);
|
|
return pb;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new GameObject with a ProBuilderMesh, <see cref="UnityEngine.MeshFilter" />, and
|
|
/// <see cref="UnityEngine.MeshRenderer" /> component. Then it initializes the ProBuilderMesh
|
|
/// with the specified sets of positions and faces, and if specified, coincident vertices,
|
|
/// texture coordinates, and materials.
|
|
/// </summary>
|
|
/// <param name="vertices">Array of vertex positions to use.</param>
|
|
/// <param name="faces">Array of faces to use.</param>
|
|
/// <param name="sharedVertices">Optional <see cref="SharedVertex" /> array to define the coincident vertices.</param>
|
|
/// <param name="sharedTextures">Optional <see cref="SharedVertex" /> array to define the coincident texture coordinates (UV0).</param>
|
|
/// <param name="materials">Optional array of materials to be assigned to the <see cref="UnityEngine.MeshRenderer" />.</param>
|
|
/// <returns>A reference to the new ProBuilderMesh component.</returns>
|
|
public static ProBuilderMesh Create(
|
|
IList<Vertex> vertices,
|
|
IList<Face> faces,
|
|
IList<SharedVertex> sharedVertices = null,
|
|
IList<SharedVertex> sharedTextures = null,
|
|
IList<Material> materials = null)
|
|
{
|
|
var go = new GameObject();
|
|
go.name = "ProBuilder Mesh";
|
|
var mesh = go.AddComponent<ProBuilderMesh>();
|
|
if (materials != null)
|
|
mesh.renderer.sharedMaterials = materials.ToArray();
|
|
mesh.m_MeshFormatVersion = k_MeshFormatVersion;
|
|
mesh.SetVertices(vertices);
|
|
mesh.faces = faces;
|
|
mesh.sharedVertices = sharedVertices;
|
|
mesh.sharedTextures = sharedTextures != null ? sharedTextures.ToArray() : null;
|
|
mesh.ToMesh();
|
|
mesh.Refresh();
|
|
return mesh;
|
|
}
|
|
|
|
internal void GeometryWithPoints(Vector3[] points)
|
|
{
|
|
// Wrap in faces
|
|
Face[] f = new Face[points.Length / 4];
|
|
|
|
for (int i = 0; i < points.Length; i += 4)
|
|
{
|
|
f[i / 4] = new Face(new int[6]
|
|
{
|
|
i + 0, i + 1, i + 2,
|
|
i + 1, i + 3, i + 2
|
|
},
|
|
0,
|
|
AutoUnwrapSettings.tile,
|
|
0,
|
|
-1,
|
|
-1,
|
|
false);
|
|
}
|
|
|
|
Clear();
|
|
positions = points;
|
|
m_Faces = f;
|
|
m_SharedVertices = SharedVertex.GetSharedVerticesWithPositions(points);
|
|
InvalidateCaches();
|
|
ToMesh();
|
|
Refresh();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all mesh attributes and reinitializes the mesh with new positions and face collections.
|
|
/// </summary>
|
|
/// <param name="vertices">New vertex positions array to use.</param>
|
|
/// <param name="faces">New faces array to use.</param>
|
|
public void RebuildWithPositionsAndFaces(IEnumerable<Vector3> vertices, IEnumerable<Face> faces)
|
|
{
|
|
if (vertices == null)
|
|
throw new ArgumentNullException("vertices");
|
|
|
|
Clear();
|
|
m_Positions = vertices.ToArray();
|
|
m_Faces = faces.ToArray();
|
|
m_SharedVertices = SharedVertex.GetSharedVerticesWithPositions(m_Positions);
|
|
InvalidateSharedVertexLookup();
|
|
InvalidateSharedTextureLookup();
|
|
ToMesh();
|
|
Refresh();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wraps <see cref="ToMesh"/> and <see cref="Refresh"/>.
|
|
/// </summary>
|
|
internal void Rebuild()
|
|
{
|
|
ToMesh();
|
|
Refresh();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rebuilds the mesh positions and submeshes.
|
|
///
|
|
/// If the vertex count matches the new positions array, the existing attributes are kept
|
|
/// (except for UV2s, which are always cleared). Otherwise, the mesh is cleared.
|
|
/// </summary>
|
|
/// <param name="preferredTopology">You can specify MeshTopology.Quads if you don't want to use the default MeshTopology.Triangles. </param>
|
|
public void ToMesh(MeshTopology preferredTopology = MeshTopology.Triangles)
|
|
{
|
|
bool usedInParticleSystem = false;
|
|
|
|
// if the mesh vertex count hasn't been modified, we can keep most of the mesh elements around
|
|
if (mesh == null)
|
|
{
|
|
#if ENABLE_DRIVEN_PROPERTIES
|
|
SerializationUtility.RegisterDrivenProperty(this, this, "m_Mesh");
|
|
#endif
|
|
mesh = new Mesh() { name = $"pb_Mesh{GetInstanceID()}" };
|
|
}
|
|
else if (mesh.vertexCount != vertexCount)
|
|
{
|
|
usedInParticleSystem = MeshUtility.IsUsedInParticleSystem(this);
|
|
mesh.Clear();
|
|
}
|
|
|
|
mesh.indexFormat = vertexCount > ushort.MaxValue ? Rendering.IndexFormat.UInt32 : Rendering.IndexFormat.UInt16;
|
|
mesh.vertices = m_Positions;
|
|
mesh.uv2 = null;
|
|
|
|
if (m_MeshFormatVersion < k_MeshFormatVersion)
|
|
{
|
|
if (m_MeshFormatVersion < k_MeshFormatVersionSubmeshMaterialRefactor)
|
|
Submesh.MapFaceMaterialsToSubmeshIndex(this);
|
|
if (m_MeshFormatVersion < k_MeshFormatVersionAutoUVScaleOffset)
|
|
UvUnwrapping.UpgradeAutoUVScaleOffset(this);
|
|
m_MeshFormatVersion = k_MeshFormatVersion;
|
|
}
|
|
|
|
m_MeshFormatVersion = k_MeshFormatVersion;
|
|
|
|
int materialCount = MaterialUtility.GetMaterialCount(renderer);
|
|
|
|
Submesh[] submeshes = Submesh.GetSubmeshes(facesInternal, materialCount, preferredTopology);
|
|
|
|
mesh.subMeshCount = submeshes.Length;
|
|
|
|
var currentSubmeshIndex = 0;
|
|
var shouldReassignMaterials = false;
|
|
for (int i = 0; i < mesh.subMeshCount; i++)
|
|
{
|
|
#if DEVELOPER_MODE
|
|
if (i >= materialCount)
|
|
Log.Warning("Submesh index " + i + " is out of bounds of the MeshRenderer materials array.");
|
|
if (submeshes[i] == null)
|
|
throw new Exception("Attempting to assign a null submesh. " + i + "/" + materialCount);
|
|
#endif
|
|
if (submeshes[i].m_Indexes.Length == 0)
|
|
{
|
|
if (!shouldReassignMaterials)
|
|
{
|
|
MaterialUtility.s_MaterialArray.Clear();
|
|
renderer.GetSharedMaterials(MaterialUtility.s_MaterialArray);
|
|
shouldReassignMaterials = true;
|
|
}
|
|
|
|
submeshes[i].submeshIndex = -1;
|
|
MaterialUtility.s_MaterialArray.RemoveAt(currentSubmeshIndex);
|
|
|
|
foreach (var face in facesInternal)
|
|
{
|
|
if (currentSubmeshIndex < face.submeshIndex)
|
|
face.submeshIndex -= 1;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
submeshes[i].submeshIndex = currentSubmeshIndex;
|
|
mesh.SetIndices(submeshes[i].m_Indexes, submeshes[i].m_Topology, submeshes[i].submeshIndex, false);
|
|
currentSubmeshIndex++;
|
|
}
|
|
|
|
if (mesh.subMeshCount < materialCount)
|
|
{
|
|
var delta = materialCount - mesh.subMeshCount;
|
|
var start = MaterialUtility.s_MaterialArray.Count - delta;
|
|
MaterialUtility.s_MaterialArray.RemoveRange(start, delta);
|
|
|
|
shouldReassignMaterials = true;
|
|
}
|
|
|
|
if (shouldReassignMaterials)
|
|
renderer.sharedMaterials = MaterialUtility.s_MaterialArray.ToArray();
|
|
|
|
EnsureMeshFilterIsAssigned();
|
|
|
|
if(usedInParticleSystem)
|
|
MeshUtility.RestoreParticleSystem(this);
|
|
|
|
IncrementVersionIndex();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that the UnityEngine.Mesh associated with this object is unique. When instantiating a ProBuilderMesh,
|
|
/// the mesh asset will reference the original instance. If you are making a copy to edit, you must call
|
|
/// MakeUnique to avoid modifying a shared mesh asset.
|
|
/// </summary>
|
|
public void MakeUnique()
|
|
{
|
|
mesh = mesh != null
|
|
? Instantiate(mesh)
|
|
: new Mesh() { name = $"pb_Mesh{GetInstanceID()}" };
|
|
|
|
if (meshSyncState == MeshSyncState.InSync)
|
|
{
|
|
filter.mesh = mesh;
|
|
return;
|
|
}
|
|
|
|
ToMesh();
|
|
Refresh();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the mesh data from another mesh to this one.
|
|
/// </summary>
|
|
/// <param name="other">The mesh to copy from.</param>
|
|
public void CopyFrom(ProBuilderMesh other)
|
|
{
|
|
if (other == null)
|
|
throw new ArgumentNullException(nameof(other));
|
|
|
|
Clear();
|
|
positions = other.positions;
|
|
sharedVertices = other.sharedVerticesInternal;
|
|
SetSharedTextures(other.sharedTextureLookup);
|
|
facesInternal = other.faces.Select(x => new Face(x)).ToArray();
|
|
|
|
List<Vector4> uvs = new List<Vector4>();
|
|
|
|
for (var i = 0; i < k_UVChannelCount; i++)
|
|
{
|
|
other.GetUVs(i, uvs);
|
|
SetUVs(i, uvs);
|
|
}
|
|
|
|
tangents = other.tangents;
|
|
colors = other.colors;
|
|
userCollisions = other.userCollisions;
|
|
selectable = other.selectable;
|
|
unwrapParameters = new UnwrapParameters(other.unwrapParameters);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculates mesh attributes: normals, collisions, UVs, tangents, and colors.
|
|
/// </summary>
|
|
/// <param name="mask">
|
|
/// Optional. Specify a RefreshMask to indicate which components to update. Use this when you want to
|
|
/// wait until later to rebuild some components in order to save processing power, since UVs and
|
|
/// collisions are expensive to rebuild and can usually be deferred until the task finishes.
|
|
/// </param>
|
|
public void Refresh(RefreshMask mask = RefreshMask.All)
|
|
{
|
|
// Mesh
|
|
if ((mask & RefreshMask.UV) > 0)
|
|
RefreshUV(facesInternal);
|
|
|
|
if ((mask & RefreshMask.Colors) > 0)
|
|
RefreshColors();
|
|
|
|
if ((mask & RefreshMask.Normals) > 0)
|
|
RefreshNormals();
|
|
|
|
if ((mask & RefreshMask.Tangents) > 0)
|
|
RefreshTangents();
|
|
|
|
if ((mask & RefreshMask.Collisions) > 0)
|
|
EnsureMeshColliderIsAssigned();
|
|
|
|
if ((mask & RefreshMask.Bounds) > 0 && mesh != null)
|
|
mesh.RecalculateBounds();
|
|
|
|
IncrementVersionIndex();
|
|
}
|
|
|
|
internal void EnsureMeshColliderIsAssigned()
|
|
{
|
|
if(gameObject.TryGetComponent<MeshCollider>(out MeshCollider collider))
|
|
{
|
|
#if ENABLE_DRIVEN_PROPERTIES
|
|
SerializationUtility.RegisterDrivenProperty(this, collider, "m_Mesh");
|
|
#endif
|
|
collider.sharedMesh = (mesh != null && mesh.vertexCount > 0) ? mesh : null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new unused texture group ID.
|
|
/// </summary>
|
|
/// <param name="i">Optional value specifying the 'last' used ID. Defaults to 1.</param>
|
|
/// <returns>
|
|
/// An integer greater than or equal to the specified value `i`.
|
|
/// </returns>
|
|
internal int GetUnusedTextureGroup(int i = 1)
|
|
{
|
|
while (Array.Exists(facesInternal, element => element.textureGroup == i))
|
|
i++;
|
|
|
|
return i;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests whether the specified texture group ID is valid.
|
|
/// </summary>
|
|
/// <param name="group">ID of the texture group to check.</param>
|
|
/// <returns>
|
|
/// True if the specified group is greater than 0; false otherwise.
|
|
/// </returns>
|
|
static bool IsValidTextureGroup(int group)
|
|
{
|
|
return group > 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new unused element group.
|
|
/// </summary>
|
|
/// <param name="i">Optional value specifying the 'last' used group. Defaults to 1.</param>
|
|
/// <returns>
|
|
/// An integer greater than or equal to the specified value `i`.
|
|
/// </returns>
|
|
internal int UnusedElementGroup(int i = 1)
|
|
{
|
|
while (Array.Exists(facesInternal, element => element.elementGroup == i))
|
|
i++;
|
|
|
|
return i;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rebuilds the UV arrays on the specified faces.
|
|
///
|
|
/// This usually applies only to faces set to use Auto UVs. However, if ProBuilder can't detect
|
|
/// any valid UV arrays, it resets the faces from Manual to Auto before rebuilding them.
|
|
/// </summary>
|
|
/// <param name="facesToRefresh">The set of faces to process.</param>
|
|
/// <seealso cref="Refresh" />
|
|
public void RefreshUV(IEnumerable<Face> facesToRefresh)
|
|
{
|
|
// If the UV array has gone out of sync with the positions array, reset all faces to Auto UV so that we can
|
|
// correct the texture array.
|
|
if (!HasArrays(MeshArrays.Texture0))
|
|
{
|
|
m_Textures0 = new Vector2[vertexCount];
|
|
foreach (Face f in facesInternal)
|
|
f.manualUV = false;
|
|
facesToRefresh = facesInternal;
|
|
}
|
|
|
|
s_CachedHashSet.Clear();
|
|
|
|
foreach (var face in facesToRefresh)
|
|
{
|
|
if (face.manualUV || face.indexesInternal?.Length < 3)
|
|
continue;
|
|
|
|
int textureGroup = face.textureGroup;
|
|
|
|
if (!IsValidTextureGroup(textureGroup))
|
|
UvUnwrapping.Unwrap(this, face);
|
|
else if (s_CachedHashSet.Add(textureGroup))
|
|
UvUnwrapping.ProjectTextureGroup(this, textureGroup, face.uv);
|
|
}
|
|
|
|
mesh.uv = m_Textures0;
|
|
|
|
if (HasArrays(MeshArrays.Texture2))
|
|
mesh.SetUVs(2, m_Textures2);
|
|
if (HasArrays(MeshArrays.Texture3))
|
|
mesh.SetUVs(3, m_Textures3);
|
|
|
|
IncrementVersionIndex();
|
|
}
|
|
|
|
internal void SetGroupUV(AutoUnwrapSettings settings, int group)
|
|
{
|
|
if (!IsValidTextureGroup(group))
|
|
return;
|
|
|
|
foreach (var face in facesInternal)
|
|
{
|
|
if (face.textureGroup != group)
|
|
continue;
|
|
|
|
face.uv = settings;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reapplies the vertex colors for this mesh.
|
|
/// </summary>
|
|
/// <seealso cref="Refresh" />
|
|
void RefreshColors()
|
|
{
|
|
Mesh m = filter.sharedMesh;
|
|
m.colors = m_Colors;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies a [vertex color](../manual/workflow-vertexcolors.html) to the specified <see cref="Face" />.
|
|
/// </summary>
|
|
/// <param name="face">The target face to apply the colors to.</param>
|
|
/// <param name="color">The color to apply to this face's referenced vertices.</param>
|
|
public void SetFaceColor(Face face, Color color)
|
|
{
|
|
if (face == null)
|
|
throw new ArgumentNullException("face");
|
|
|
|
if (!HasArrays(MeshArrays.Color))
|
|
m_Colors = ArrayUtility.Fill(Color.white, vertexCount);
|
|
|
|
foreach (int i in face.distinctIndexes)
|
|
m_Colors[i] = color;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a specific material on a collection of faces.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// To apply the changes to the <see cref="UnityEngine.Mesh" />, call
|
|
/// <see cref="ToMesh" /> and <see cref="Refresh" />.
|
|
/// </remarks>
|
|
/// <param name="faces">The faces to apply the material to.</param>
|
|
/// <param name="material">The material to apply.</param>
|
|
public void SetMaterial(IEnumerable<Face> faces, Material material)
|
|
{
|
|
var materials = renderer.sharedMaterials;
|
|
var submeshCount = materials.Length;
|
|
var index = -1;
|
|
|
|
for (int i = 0; i < submeshCount && index < 0; i++)
|
|
{
|
|
if (materials[i] == material)
|
|
index = i;
|
|
}
|
|
|
|
if (index < 0)
|
|
{
|
|
// Material doesn't exist in MeshRenderer.sharedMaterials, now check if there is an unused
|
|
// submeshIndex that we can replace with this value instead of creating a new entry.
|
|
var submeshIndexes = new bool[submeshCount];
|
|
|
|
foreach (var face in m_Faces)
|
|
submeshIndexes[Math.Clamp(face.submeshIndex, 0, submeshCount - 1)] = true;
|
|
|
|
index = Array.IndexOf(submeshIndexes, false);
|
|
|
|
// Found an unused submeshIndex, replace it with the material.
|
|
if (index > -1)
|
|
{
|
|
materials[index] = material;
|
|
renderer.sharedMaterials = materials;
|
|
}
|
|
else
|
|
{
|
|
// There were no unused submesh indices, append another submesh and material.
|
|
index = materials.Length;
|
|
var copy = new Material[index + 1];
|
|
Array.Copy(materials, copy, index);
|
|
copy[index] = material;
|
|
renderer.sharedMaterials = copy;
|
|
}
|
|
}
|
|
|
|
foreach (var face in faces)
|
|
face.submeshIndex = index;
|
|
|
|
IncrementVersionIndex();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculates the normals for this mesh.
|
|
/// </summary>
|
|
/// <seealso cref="Refresh" />
|
|
void RefreshNormals()
|
|
{
|
|
Normals.CalculateNormals(this);
|
|
mesh.normals = m_Normals;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recalculates the tangents on this mesh.
|
|
/// </summary>
|
|
/// <seealso cref="Refresh" />
|
|
void RefreshTangents()
|
|
{
|
|
Normals.CalculateTangents(this);
|
|
mesh.tangents = m_Tangents;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the index of a vertex index (triangle) in an array of vertices.
|
|
/// The index returned is called the common index, or shared index.
|
|
/// </summary>
|
|
/// <remarks>Aids in removing duplicate vertex indexes.</remarks>
|
|
/// <param name="vertex">The vertex to find.</param>
|
|
/// <returns>The common (or shared) index.</returns>
|
|
internal int GetSharedVertexHandle(int vertex)
|
|
{
|
|
int res;
|
|
|
|
if (m_SharedVertexLookup.TryGetValue(vertex, out res))
|
|
return res;
|
|
|
|
for (int i = 0; i < m_SharedVertices.Length; i++)
|
|
{
|
|
for (int n = 0, c = m_SharedVertices[i].Count; n < c; n++)
|
|
if (m_SharedVertices[i][n] == vertex)
|
|
return i;
|
|
}
|
|
|
|
throw new ArgumentOutOfRangeException("vertex");
|
|
}
|
|
|
|
internal HashSet<int> GetSharedVertexHandles(IEnumerable<int> vertices)
|
|
{
|
|
var lookup = sharedVertexLookup;
|
|
HashSet<int> common = new HashSet<int>();
|
|
foreach (var i in vertices)
|
|
common.Add(lookup[i]);
|
|
return common;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a list of vertices that are coincident to any of the specified vertices.
|
|
/// </summary>
|
|
/// <param name="vertices">A collection of indices relative to the mesh positions.</param>
|
|
/// <returns>A list of all vertices that share a position with any of the specified vertices.</returns>
|
|
/// <exception cref="ArgumentNullException">The vertices parameter may not be null.</exception>
|
|
public List<int> GetCoincidentVertices(IEnumerable<int> vertices)
|
|
{
|
|
if (vertices == null)
|
|
throw new ArgumentNullException("vertices");
|
|
|
|
List<int> shared = new List<int>();
|
|
GetCoincidentVertices(vertices, shared);
|
|
return shared;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates a list of vertices that are coincident to any of the specified vertices.
|
|
/// </summary>
|
|
/// <param name="faces">A collection of faces to gather vertices from.</param>
|
|
/// <param name="coincident">The list to clear and populate with any vertices that are coincident.</param>
|
|
/// <exception cref="ArgumentNullException">The vertices and coincident parameters may not be null.</exception>
|
|
public void GetCoincidentVertices(IEnumerable<Face> faces, List<int> coincident)
|
|
{
|
|
if (faces == null)
|
|
throw new ArgumentNullException("faces");
|
|
|
|
if (coincident == null)
|
|
throw new ArgumentNullException("coincident");
|
|
|
|
coincident.Clear();
|
|
s_CachedHashSet.Clear();
|
|
var lookup = sharedVertexLookup;
|
|
|
|
foreach (var face in faces)
|
|
{
|
|
foreach (var v in face.distinctIndexesInternal)
|
|
{
|
|
var common = lookup[v];
|
|
|
|
if (s_CachedHashSet.Add(common))
|
|
{
|
|
var indices = m_SharedVertices[common];
|
|
|
|
for (int i = 0, c = indices.Count; i < c; i++)
|
|
coincident.Add(indices[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates a list of vertices that are coincident to any of the specified vertices.
|
|
/// </summary>
|
|
/// <param name="edges">A collection of edges to gather vertices from.</param>
|
|
/// <param name="coincident">The list to clear and populate with any vertices that are coincident.</param>
|
|
/// <exception cref="ArgumentNullException">The vertices and coincident parameters may not be null.</exception>
|
|
public void GetCoincidentVertices(IEnumerable<Edge> edges, List<int> coincident)
|
|
{
|
|
if (faces == null)
|
|
throw new ArgumentNullException("edges");
|
|
|
|
if (coincident == null)
|
|
throw new ArgumentNullException("coincident");
|
|
|
|
coincident.Clear();
|
|
s_CachedHashSet.Clear();
|
|
var lookup = sharedVertexLookup;
|
|
|
|
foreach (var edge in edges)
|
|
{
|
|
var common = lookup[edge.a];
|
|
|
|
if (s_CachedHashSet.Add(common))
|
|
{
|
|
var indices = m_SharedVertices[common];
|
|
|
|
for (int i = 0, c = indices.Count; i < c; i++)
|
|
coincident.Add(indices[i]);
|
|
}
|
|
|
|
common = lookup[edge.b];
|
|
|
|
if (s_CachedHashSet.Add(common))
|
|
{
|
|
var indices = m_SharedVertices[common];
|
|
|
|
for (int i = 0, c = indices.Count; i < c; i++)
|
|
coincident.Add(indices[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates a list of vertices that are coincident to any of the specified vertices.
|
|
/// </summary>
|
|
/// <param name="vertices">A collection of indices relative to the mesh positions.</param>
|
|
/// <param name="coincident">The list to clear and populate with any vertices that are coincident.</param>
|
|
/// <exception cref="ArgumentNullException">The vertices and coincident parameters may not be null.</exception>
|
|
public void GetCoincidentVertices(IEnumerable<int> vertices, List<int> coincident)
|
|
{
|
|
if (vertices == null)
|
|
throw new ArgumentNullException("vertices");
|
|
|
|
if (coincident == null)
|
|
throw new ArgumentNullException("coincident");
|
|
|
|
coincident.Clear();
|
|
s_CachedHashSet.Clear();
|
|
var lookup = sharedVertexLookup;
|
|
|
|
foreach (var v in vertices)
|
|
{
|
|
var common = lookup[v];
|
|
|
|
if (s_CachedHashSet.Add(common))
|
|
{
|
|
var indices = m_SharedVertices[common];
|
|
|
|
for (int i = 0, c = indices.Count; i < c; i++)
|
|
coincident.Add(indices[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates a list with all the vertices that are coincident to the specified vertex.
|
|
/// </summary>
|
|
/// <param name="vertex">An index relative to a positions array.</param>
|
|
/// <param name="coincident">The list to clear and populate with any vertices that are coincident.</param>
|
|
/// <exception cref="ArgumentNullException">The coincident list may not be null.</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException">The SharedVertex[] does not contain an entry for the requested vertex.</exception>
|
|
public void GetCoincidentVertices(int vertex, List<int> coincident)
|
|
{
|
|
if (coincident == null)
|
|
throw new ArgumentNullException("coincident");
|
|
|
|
int common;
|
|
|
|
if (!sharedVertexLookup.TryGetValue(vertex, out common))
|
|
throw new ArgumentOutOfRangeException("vertex");
|
|
|
|
var indices = m_SharedVertices[common];
|
|
|
|
for (int i = 0, c = indices.Count; i < c; i++)
|
|
coincident.Add(indices[i]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks the specified vertices as coincident on this mesh.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Note that it is up to the caller to ensure that the specified vertices are indeed sharing a position.
|
|
/// </remarks>
|
|
/// <param name="vertices">The list of vertices to be marked as coincident.</param>
|
|
public void SetVerticesCoincident(IEnumerable<int> vertices)
|
|
{
|
|
var lookup = sharedVertexLookup;
|
|
List<int> coincident = new List<int>();
|
|
GetCoincidentVertices(vertices, coincident);
|
|
SharedVertex.SetCoincident(ref lookup, coincident);
|
|
SetSharedVertices(lookup);
|
|
}
|
|
|
|
internal void SetTexturesCoincident(IEnumerable<int> vertices)
|
|
{
|
|
var lookup = sharedTextureLookup;
|
|
SharedVertex.SetCoincident(ref lookup, vertices);
|
|
SetSharedTextures(lookup);
|
|
}
|
|
|
|
internal void AddToSharedVertex(int sharedVertexHandle, int vertex)
|
|
{
|
|
if (sharedVertexHandle < 0 || sharedVertexHandle >= m_SharedVertices.Length)
|
|
throw new ArgumentOutOfRangeException("sharedVertexHandle");
|
|
|
|
m_SharedVertices[sharedVertexHandle].Add(vertex);
|
|
InvalidateSharedVertexLookup();
|
|
}
|
|
|
|
internal void AddSharedVertex(SharedVertex vertex)
|
|
{
|
|
if (vertex == null)
|
|
throw new ArgumentNullException("vertex");
|
|
|
|
m_SharedVertices = m_SharedVertices.Add(vertex);
|
|
InvalidateSharedVertexLookup();
|
|
}
|
|
}
|
|
}
|