// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Animation.Splines;
using UltimateXR.Core;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Extensions.Unity.Math;
using UnityEngine;
namespace UltimateXR.Extensions.Unity.Render
{
///
/// extensions.
///
public static partial class MeshExt
{
#region Public Methods
///
/// Creates a quad mesh .
///
/// Quad size (total width and height)
/// Quad
public static Mesh CreateQuad(float size)
{
Mesh quadMesh = new Mesh();
float halfSize = size * 0.5f;
quadMesh.vertices = new[] { new Vector3(halfSize, halfSize, 0.0f), new Vector3(halfSize, -halfSize, 0.0f), new Vector3(-halfSize, -halfSize, 0.0f), new Vector3(-halfSize, halfSize, 0.0f) };
quadMesh.uv = new[] { new Vector2(1.0f, 1.0f), new Vector2(1.0f, 0.0f), new Vector2(0.0f, 0.0f), new Vector2(0.0f, 1.0f) };
quadMesh.triangles = new[] { 0, 2, 1, 0, 3, 2 };
return quadMesh;
}
///
/// Creates a tessellating a
///
/// Spline to evaluate
/// Number of subdivisions along the spline axis
/// Number of subdivisions in the section
/// Section radius
/// with the tessellated
public static Mesh CreateSpline(UxrSpline spline, int subdivisions, int sides, float radius)
{
subdivisions = Mathf.Max(subdivisions, 2);
sides = Mathf.Max(sides, 3);
Mesh splineMesh = new Mesh();
// Create mesh
Vector3[] vertices = new Vector3[subdivisions * (sides + 1)];
Vector3[] normals = new Vector3[vertices.Length];
Vector2[] mapping = new Vector2[vertices.Length];
int[] indices = new int[(subdivisions - 1) * sides * 2 * 3];
for (int sub = 0; sub < subdivisions; ++sub)
{
float arcLength = sub / (subdivisions - 1.0f) * spline.ArcLength;
float normalizedArcLength = arcLength / spline.ArcLength;
spline.EvaluateUsingArcLength(arcLength, out Vector3 splinePosition, out Vector3 splineDirection);
Vector3 perpendicular = splineDirection.GetPerpendicularVector().normalized * radius;
Vector3 vertexStart = splinePosition + perpendicular;
for (int side = 0; side < sides + 1; ++side)
{
int vertexIndex = sub * (sides + 1) + side;
int faceBase = sub * sides * 2 * 3 + side * 2 * 3;
float rotation = side / (float)sides;
float degrees = 360.0f * rotation;
vertices[vertexIndex] = vertexStart.GetRotationAround(splinePosition, splineDirection, degrees);
mapping[vertexIndex] = new Vector2(rotation, normalizedArcLength);
normals[vertexIndex] = (vertices[vertexIndex] - splinePosition).normalized;
if (side < sides && sub < subdivisions - 1)
{
indices[faceBase + 0] = vertexIndex;
indices[faceBase + 1] = vertexIndex + 1;
indices[faceBase + 2] = vertexIndex + sides + 1;
indices[faceBase + 3] = vertexIndex + 1;
indices[faceBase + 4] = vertexIndex + sides + 2;
indices[faceBase + 5] = vertexIndex + sides + 1;
}
}
}
splineMesh.vertices = vertices;
splineMesh.uv = mapping;
splineMesh.normals = normals;
splineMesh.triangles = indices;
return splineMesh;
}
///
/// Creates a new mesh from a skinned mesh renderer based on a reference bone and an extract operation.
///
/// Skin to process
/// Reference bone
/// Which part of the skinned mesh to extract
/// Bone weight threshold above which the vertices will be extracted
/// New mesh
public static Mesh ExtractSubMesh(SkinnedMeshRenderer skin, Transform bone, ExtractSubMeshOperation extractOperation, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
{
Mesh newMesh = new Mesh();
// Create dictionary to check which bones belong to the hierarchy
Dictionary areHierarchyBones = new Dictionary();
Vector3[] vertices = skin.sharedMesh.vertices;
Vector3[] normals = skin.sharedMesh.normals;
Vector2[] uv = skin.sharedMesh.uv;
BoneWeight[] boneWeights = skin.sharedMesh.boneWeights;
Transform[] bones = skin.bones;
for (int i = 0; i < bones.Length; ++i)
{
areHierarchyBones.Add(i, bones[i].HasParent(bone));
}
// Create filtered mesh
List> newTriangles = new List>();
Dictionary old2New = new Dictionary();
List newVertices = new List();
List newNormals = new List();
List newUV = new List();
List newBoneWeights = new List();
bool VertexMeetsRequirement(bool isFromHierarchy)
{
switch (extractOperation)
{
case ExtractSubMeshOperation.BoneAndChildren: return isFromHierarchy;
case ExtractSubMeshOperation.NotFromBoneOrChildren: return !isFromHierarchy;
}
return false;
}
for (int submesh = 0; submesh < skin.sharedMesh.subMeshCount; ++submesh)
{
int[] submeshIndices = skin.sharedMesh.GetTriangles(submesh);
List newSubmeshIndices = new List();
for (int t = 0; t < submeshIndices.Length / 3; t++)
{
float totalWeight = 0.0f;
for (int v = 0; v < 3; v++)
{
BoneWeight boneWeight = boneWeights[submeshIndices[t * 3 + v]];
if (areHierarchyBones.TryGetValue(boneWeight.boneIndex0, out bool isFromHierarchy) && VertexMeetsRequirement(isFromHierarchy))
{
totalWeight += boneWeight.weight0;
}
if (areHierarchyBones.TryGetValue(boneWeight.boneIndex1, out isFromHierarchy) && VertexMeetsRequirement(isFromHierarchy))
{
totalWeight += boneWeight.weight1;
}
if (areHierarchyBones.TryGetValue(boneWeight.boneIndex2, out isFromHierarchy) && VertexMeetsRequirement(isFromHierarchy))
{
totalWeight += boneWeight.weight2;
}
if (areHierarchyBones.TryGetValue(boneWeight.boneIndex3, out isFromHierarchy) && VertexMeetsRequirement(isFromHierarchy))
{
totalWeight += boneWeight.weight3;
}
}
if (totalWeight > weightThreshold)
{
for (int v = 0; v < 3; v++)
{
int oldIndex = submeshIndices[t * 3 + v];
if (!old2New.ContainsKey(oldIndex))
{
old2New.Add(oldIndex, old2New.Count);
newVertices.Add(vertices[oldIndex]);
newNormals.Add(normals[oldIndex]);
newUV.Add(uv[oldIndex]);
newBoneWeights.Add(boneWeights[oldIndex]);
}
newSubmeshIndices.Add(old2New[oldIndex]);
}
}
}
newTriangles.Add(newSubmeshIndices);
}
// Create new mesh
newMesh.vertices = newVertices.ToArray();
newMesh.normals = newNormals.ToArray();
newMesh.uv = newUV.ToArray();
newMesh.boneWeights = newBoneWeights.ToArray();
// Create and assign new triangle list
newMesh.subMeshCount = newTriangles.Count;
for (int submesh = 0; submesh < newTriangles.Count; ++submesh)
{
newMesh.SetTriangles(newTriangles[submesh].ToArray(), submesh);
}
return newMesh;
}
///
/// Computes the number of vertices that a bone influences in a skinned mesh.
///
/// Skinned mesh
/// Bone to check
/// Weight above which will be considered significant influence
///
/// Number of vertices influenced by with a weight above
/// .
///
public static int GetBoneInfluenceVertexCount(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
{
Transform[] skinBones = skin.bones;
int boneIndex = skinBones.IndexOf(bone);
if (boneIndex == -1)
{
return 0;
}
BoneWeight[] boneWeights = skin.sharedMesh.boneWeights;
return boneWeights.Count(w => HasBoneInfluence(w, boneIndex, weightThreshold));
}
///
/// Computes the number of vertices that a bone influences in a skinned mesh.
///
/// Skinned mesh
/// Bone to check
/// Weight above which to consider significant influence
///
/// Number of vertices influenced by with a weight above
///
///
public static bool HasBoneInfluence(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
{
Transform[] skinBones = skin.bones;
int boneIndex = skinBones.IndexOf(bone);
if (boneIndex == -1)
{
return false;
}
BoneWeight[] boneWeights = skin.sharedMesh.boneWeights;
return boneWeights.Any(w => HasBoneInfluence(w, boneIndex, weightThreshold));
}
///
/// Checks whether a given bone index has influence on a skinned mesh vertex.
///
/// Vertex's bone weight information
/// Bone index
/// Weight above which will be considered significant influence
/// Whether the bone influences the vertex in a significant amount
public static bool HasBoneInfluence(in BoneWeight boneWeight, int boneIndex, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
{
if (boneWeight.boneIndex0 == boneIndex && boneWeight.weight0 > weightThreshold)
{
return true;
}
if (boneWeight.boneIndex1 == boneIndex && boneWeight.weight1 > weightThreshold)
{
return true;
}
if (boneWeight.boneIndex2 == boneIndex && boneWeight.weight2 > weightThreshold)
{
return true;
}
if (boneWeight.boneIndex3 == boneIndex && boneWeight.weight3 > weightThreshold)
{
return true;
}
return false;
}
///
/// Computes the bounding box that contains all the vertices that a bone has influence on in a skinned mesh. The
/// bounding box is computed in local bone space.
///
/// Skinned mesh
/// Bone to check
/// Weight above which to consider significant influence
///
/// Bounding box in local coordinates.
///
public static Bounds GetBoneInfluenceBounds(SkinnedMeshRenderer skin, Transform bone, float weightThreshold = UxrConstants.Geometry.SignificantBoneWeight)
{
Transform[] skinBones = skin.bones;
int boneIndex = skinBones.IndexOf(bone);
if (boneIndex == -1)
{
return new Bounds();
}
Vector3[] vertices = skin.sharedMesh.vertices;
BoneWeight[] boneWeights = skin.sharedMesh.boneWeights;
Transform[] bones = skin.bones;
Matrix4x4[] boneBindPoses = skin.sharedMesh.bindposes;
Vector3 min = Vector3.zero;
Vector3 max = Vector3.zero;
bool initialized = false;
for (int i = 0; i < boneWeights.Length; ++i)
{
if (HasBoneInfluence(boneWeights[i], boneIndex, weightThreshold))
{
Vector3 localVertex = bones[boneIndex].InverseTransformPoint(GetSkinnedWorldVertex(skin, boneWeights[i], vertices[i], bones, boneBindPoses));
if (!initialized)
{
initialized = true;
min = localVertex;
max = localVertex;
}
else
{
min = Vector3Ext.Min(localVertex, min);
max = Vector3Ext.Max(localVertex, max);
}
}
}
return new Bounds((min + max) * 0.5f, max - min);
}
///
/// Gets a skinned vertex in world coordinates.
///
/// Skin
/// Vertex bone weights info
/// Vertex in local skin coordinates when the skin is in the bind pose
/// Bone list
/// Bone bind poses
/// Vertex in world coordinates
public static Vector3 GetSkinnedWorldVertex(SkinnedMeshRenderer skin, BoneWeight boneWeight, Vector3 vertex, Transform[] bones, Matrix4x4[] boneBindPoses)
{
Vector3 result = Vector3.zero;
if (boneWeight.weight0 > UxrConstants.Geometry.SmallestBoneWeight)
{
result += bones[boneWeight.boneIndex0].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex0].MultiplyPoint(vertex)) * boneWeight.weight0;
}
if (boneWeight.weight1 > UxrConstants.Geometry.SmallestBoneWeight)
{
result += bones[boneWeight.boneIndex1].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex1].MultiplyPoint(vertex)) * boneWeight.weight1;
}
if (boneWeight.weight2 > UxrConstants.Geometry.SmallestBoneWeight)
{
result += bones[boneWeight.boneIndex2].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex2].MultiplyPoint(vertex)) * boneWeight.weight2;
}
if (boneWeight.weight3 > UxrConstants.Geometry.SmallestBoneWeight)
{
result += bones[boneWeight.boneIndex3].localToWorldMatrix.MultiplyPoint(boneBindPoses[boneWeight.boneIndex3].MultiplyPoint(vertex)) * boneWeight.weight3;
}
return result;
}
#endregion
}
}