// -------------------------------------------------------------------------------------------------------------------- // // 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 } }