Rasagar/Library/PackageCache/com.unity.probuilder/Runtime/MeshOperations/UV/UVEditing.cs
2024-08-26 23:07:20 +03:00

415 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.ProBuilder.MeshOperations
{
/// <summary>
/// UV actions.
/// </summary>
static partial class UVEditing
{
/// <summary>
/// Get a reference to the mesh UV array at index.
/// </summary>
/// <param name="mesh"></param>
/// <param name="channel">The zero-indexed UV channel.</param>
/// <returns></returns>
internal static Vector2[] GetUVs(ProBuilderMesh mesh, int channel)
{
switch (channel)
{
case 1:
{
Mesh m = mesh.mesh;
if (m == null)
return null;
return mesh.mesh.uv2;
}
case 2:
case 3:
{
if (channel == 2 ? mesh.HasArrays(MeshArrays.Texture2) : mesh.HasArrays(MeshArrays.Texture3))
{
List<Vector4> uvs = new List<Vector4>();
mesh.GetUVs(channel, uvs);
return uvs.Select(x => (Vector2)x).ToArray();
}
return null;
}
default:
return mesh.texturesInternal;
}
}
/// <summary>
/// Sets an array to the appropriate UV channel, but don't refresh the Mesh.
/// </summary>
internal static void ApplyUVs(ProBuilderMesh mesh, Vector2[] uvs, int channel, bool applyToMesh = true)
{
switch (channel)
{
case 0:
mesh.texturesInternal = uvs;
if (applyToMesh && mesh.mesh != null)
mesh.mesh.uv = uvs;
break;
case 1:
if (applyToMesh && mesh.mesh != null)
mesh.mesh.uv2 = uvs;
break;
case 2:
case 3:
int vc = mesh.vertexCount;
if (vc != uvs.Length)
throw new IndexOutOfRangeException("uvs");
List<Vector4> list = new List<Vector4>(vc);
for (int i = 0; i < vc; i++)
list.Add(uvs[i]);
mesh.SetUVs(channel, list);
if (applyToMesh && mesh.mesh != null)
mesh.mesh.SetUVs(channel, list);
break;
}
}
/// <summary>
/// Sews (welds) a UV seam using delta to determine which UVs are close enough to be merged.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
/// <param name="delta"></param>
/// <returns></returns>
public static void SewUVs(this ProBuilderMesh mesh, int[] indexes, float delta)
{
Vector2[] uvs = mesh.texturesInternal;
if (uvs == null || uvs.Length != mesh.vertexCount)
uvs = new Vector2[mesh.vertexCount];
var lookup = mesh.sharedTextureLookup;
for (int i = 0; i < indexes.Length - 1; i++)
{
for (int n = i + 1; n < indexes.Length; n++)
{
int a, b;
if (!lookup.TryGetValue(indexes[i], out a))
lookup.Add(indexes[i], a = lookup.Count);
if (!lookup.TryGetValue(indexes[n], out b))
lookup.Add(indexes[n], b = lookup.Count);
if (a == b)
continue;
if (Vector2.Distance(uvs[indexes[i]], uvs[indexes[n]]) < delta)
{
Vector3 cen = (uvs[indexes[i]] + uvs[indexes[n]]) / 2f;
uvs[indexes[i]] = cen;
uvs[indexes[n]] = cen;
// ToArray prevents delayed execution of linq actions, which cause trouble when modifying the
// dictionary values
var merge = lookup.Where(x => x.Value == b).Select(y => y.Key).ToArray();
foreach (var key in merge)
lookup[key] = a;
}
}
}
mesh.SetSharedTextures(lookup);
}
/// <summary>
/// Similar to Sew, except Collapse just flattens all UVs to the center point no matter the distance.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
public static void CollapseUVs(this ProBuilderMesh mesh, int[] indexes)
{
Vector2[] uvs = mesh.texturesInternal;
// set the shared indexes cache to a unique non-used index
Vector2 cen = Math.Average(ArrayUtility.ValuesWithIndexes(uvs, indexes));
foreach (int i in indexes)
uvs[i] = cen;
mesh.SetTexturesCoincident(indexes);
}
/// <summary>
/// Creates separate entries in shared indexes cache for all passed indexes. If indexes are not present in pb_IntArray[], don't do anything with them.
/// </summary>
/// <param name="mesh"></param>
/// <param name="indexes"></param>
public static void SplitUVs(this ProBuilderMesh mesh, IEnumerable<int> indexes)
{
var lookup = mesh.sharedTextureLookup;
var index = lookup.Count;
foreach (var vertex in indexes)
{
int a;
if (lookup.TryGetValue(vertex, out a))
lookup[vertex] = index++;
}
mesh.SetSharedTextures(lookup);
}
/// <summary>
/// Creates separate entries in shared indexes cache for all passed indexes.
/// </summary>
internal static void SplitUVs(ProBuilderMesh mesh, IEnumerable<Face> faces)
{
var lookup = mesh.sharedTextureLookup;
var index = lookup.Count;
foreach(var face in faces)
{
foreach (var vertex in face.distinctIndexesInternal)
{
int a;
if (lookup.TryGetValue(vertex, out a))
lookup[vertex] = index++;
}
}
mesh.SetSharedTextures(lookup);
}
/// <summary>
/// Projects UVs on all passed faces, automatically updating the sharedIndexesUV table as required (only associates
/// vertices that share a seam).
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
/// <param name="channel"></param>
internal static void ProjectFacesAuto(ProBuilderMesh mesh, Face[] faces, int channel)
{
if (faces.Length < 1)
return;
int[] ind = faces.SelectMany(x => x.distinctIndexesInternal).ToArray();
// Get a projection direction by averaging the normals of all selected faces
var projectionDirection = Vector3.zero;
foreach (var face in faces)
{
var nrm = Math.Normal(mesh, face);
projectionDirection += nrm;
}
projectionDirection /= (float) faces.Length;
// project uv coordinates
Vector2[] uvs = Projection.PlanarProject(mesh.positionsInternal, ind, projectionDirection);
// re-assign new projected coords back into full uv array
Vector2[] rebuiltUVs = GetUVs(mesh, channel);
for (int i = 0; i < ind.Length; i++)
rebuiltUVs[ind[i]] = uvs[i];
// and set the msh uv array using the new coordintaes
ApplyUVs(mesh, rebuiltUVs, channel);
// now go trhough and set all adjacent face groups to use matching element groups
foreach (Face f in faces)
{
f.elementGroup = -1;
SplitUVs(mesh, f.distinctIndexesInternal);
}
mesh.SewUVs(faces.SelectMany(x => x.distinctIndexesInternal).ToArray(), .001f);
}
/// <summary>
/// Projects UVs for each face using the closest normal on a box.
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
/// <param name="channel"></param>
public static void ProjectFacesBox(ProBuilderMesh mesh, Face[] faces, int channel = 0)
{
Vector2[] uv = GetUVs(mesh, channel);
Dictionary<ProjectionAxis, List<Face>> sorted = new Dictionary<ProjectionAxis, List<Face>>();
for (int i = 0; i < faces.Length; i++)
{
Vector3 nrm = Math.Normal(mesh, faces[i]);
ProjectionAxis axis = Projection.VectorToProjectionAxis(nrm);
if (sorted.ContainsKey(axis))
sorted[axis].Add(faces[i]);
else
sorted.Add(axis, new List<Face>() { faces[i] });
// clean up UV stuff - no shared UV indexes and remove element group
faces[i].elementGroup = -1;
faces[i].manualUV = true;
}
foreach (KeyValuePair<ProjectionAxis, List<Face>> kvp in sorted)
{
int[] distinct = kvp.Value.SelectMany(x => x.distinctIndexesInternal).ToArray();
Vector2[] uvs = Projection.PlanarProject(mesh.positionsInternal, distinct, Projection.ProjectionAxisToVector(kvp.Key));
for (int n = 0; n < distinct.Length; n++)
uv[distinct[n]] = uvs[n];
SplitUVs(mesh, distinct);
}
/* and set the msh uv array using the new coordintaes */
ApplyUVs(mesh, uv, channel);
}
/// <summary>
/// Finds the minimal U and V coordinate of a set of an array of UVs
/// </summary>
internal static Vector2 FindMinimalUV(Vector2[] uvs, int[] indices = null, float xMin = 0f, float yMin = 0f)
{
int nbElements = (indices == null ? uvs.Length : indices.Length);
bool first = (xMin == 0f && yMin == 0f);
for (int i = 0; i < nbElements; ++i)
{
int currentIndex = (indices == null ? i : indices[i]);
if (first)
{
xMin = uvs[currentIndex].x;
yMin = uvs[currentIndex].y;
first = false;
}
else
{
if (uvs[currentIndex].x < xMin)
{
xMin = uvs[currentIndex].x;
}
if (uvs[currentIndex].y < yMin)
{
yMin = uvs[currentIndex].y;
}
}
}
return new Vector2(xMin, yMin);
}
/// <summary>
/// Projects UVs for each face using the closest normal on a box and then place the lower left coordinate at the anchor position.
/// </summary>
/// <param name="mesh"></param>
/// <param name="faces"></param>
/// <param name="lowerLeftAnchor"></param>
/// <param name="channel"></param>
public static void ProjectFacesBox(ProBuilderMesh mesh, Face[] faces, Vector2 lowerLeftAnchor, int channel = 0)
{
Vector2[] uv = GetUVs(mesh, channel);
Dictionary<ProjectionAxis, List<Face>> sorted = new Dictionary<ProjectionAxis, List<Face>>();
for (int i = 0; i < faces.Length; i++)
{
Vector3 nrm = Math.Normal(mesh, faces[i]);
ProjectionAxis axis = Projection.VectorToProjectionAxis(nrm);
if (sorted.ContainsKey(axis))
sorted[axis].Add(faces[i]);
else
sorted.Add(axis, new List<Face>() { faces[i] });
// clean up UV stuff - no shared UV indexes and remove element group
faces[i].elementGroup = -1;
faces[i].manualUV = true;
}
foreach (KeyValuePair<ProjectionAxis, List<Face>> kvp in sorted)
{
int[] distinct = kvp.Value.SelectMany(x => x.distinctIndexesInternal).ToArray();
Vector2[] uvs = Projection.PlanarProject(mesh.positionsInternal, distinct, Projection.ProjectionAxisToVector(kvp.Key));
Vector2 minimalUV = FindMinimalUV(uvs);
for (int n = 0; n < distinct.Length; n++)
uv[distinct[n]] = uvs[n] - minimalUV;
SplitUVs(mesh, distinct);
}
/* and set the msh uv array using the new coordintaes */
ApplyUVs(mesh, uv, channel);
}
/// <summary>
/// Projects UVs for each face using the closest normal on a sphere.
/// </summary>
/// <param name="pb"></param>
/// <param name="indexes"></param>
/// <param name="channel"></param>
public static void ProjectFacesSphere(ProBuilderMesh pb, int[] indexes, int channel = 0)
{
foreach (Face f in pb.facesInternal)
{
if (ArrayUtility.ContainsMatch<int>(f.distinctIndexesInternal, indexes))
{
f.elementGroup = -1;
f.manualUV = true;
}
}
SplitUVs(pb, indexes);
Vector2[] projected = Projection.SphericalProject(pb.positionsInternal, indexes);
Vector2[] uv = GetUVs(pb, channel);
for (int i = 0; i < indexes.Length; i++)
uv[indexes[i]] = projected[i];
/* and set the msh uv array using the new coordintaes */
ApplyUVs(pb, uv, channel);
}
/*
* Returns normalized UV values for a mesh uvs (0,0) - (1,1)
*/
public static Vector2[] FitUVs(Vector2[] uvs)
{
// shift UVs to zeroed coordinates
Vector2 smallestVector2 = Math.SmallestVector2(uvs);
int i;
for (i = 0; i < uvs.Length; i++)
uvs[i] -= smallestVector2;
float scale = Math.MakeNonZero(Math.LargestValue(Math.LargestVector2(uvs)));
for (i = 0; i < uvs.Length; i++)
uvs[i] /= scale;
return uvs;
}
}
}