From 96f35265c0b4cdebc38ed634e58cd1a5490ec5a5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 26 Jan 2023 22:13:04 +0900 Subject: [PATCH] feat: remove polygons --- Editor/EditSkinnedMeshComponentUtil.cs | 1 + Editor/EulerQuaternionAttributeDrawer.cs | 35 ++++ Editor/EulerQuaternionAttributeDrawer.cs.meta | 3 + Editor/Processors/SkinnedMeshes/MeshInfo.cs | 15 +- .../SkinnedMeshes/RemoveMeshInBoxProcessor.cs | 189 ++++++++++++++++++ .../RemoveMeshInBoxProcessor.cs.meta | 3 + Editor/RemoveMeshInBoxEditor.cs | 69 +++++++ Editor/RemoveMeshInBoxEditor.cs.meta | 3 + Runtime/EulerQuaternionAttribute.cs | 9 + Runtime/EulerQuaternionAttribute.cs.meta | 3 + Runtime/RemoveMeshInBox.cs | 31 +++ Runtime/RemoveMeshInBox.cs.meta | 3 + 12 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 Editor/EulerQuaternionAttributeDrawer.cs create mode 100644 Editor/EulerQuaternionAttributeDrawer.cs.meta create mode 100644 Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs create mode 100644 Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs.meta create mode 100644 Editor/RemoveMeshInBoxEditor.cs create mode 100644 Editor/RemoveMeshInBoxEditor.cs.meta create mode 100644 Runtime/EulerQuaternionAttribute.cs create mode 100644 Runtime/EulerQuaternionAttribute.cs.meta create mode 100644 Runtime/RemoveMeshInBox.cs create mode 100644 Runtime/RemoveMeshInBox.cs.meta diff --git a/Editor/EditSkinnedMeshComponentUtil.cs b/Editor/EditSkinnedMeshComponentUtil.cs index 999d75f62..a472dba45 100644 --- a/Editor/EditSkinnedMeshComponentUtil.cs +++ b/Editor/EditSkinnedMeshComponentUtil.cs @@ -146,6 +146,7 @@ public Material[] GetMaterials(EditSkinnedMeshComponent before = null, bool fast [typeof(MergeSkinnedMesh)] = x => new MergeSkinnedMeshProcessor((MergeSkinnedMesh)x), [typeof(FreezeBlendShape)] = x => new FreezeBlendShapeProcessor((FreezeBlendShape)x), [typeof(MergeToonLitMaterial)] = x => new MergeToonLitMaterialProcessor((MergeToonLitMaterial)x), + [typeof(RemoveMeshInBox)] = x => new RemoveMeshInBoxProcessor((RemoveMeshInBox)x), }; private static IEditSkinnedMeshProcessor CreateProcessor(EditSkinnedMeshComponent mergePhysBone) => diff --git a/Editor/EulerQuaternionAttributeDrawer.cs b/Editor/EulerQuaternionAttributeDrawer.cs new file mode 100644 index 000000000..17d4fda3a --- /dev/null +++ b/Editor/EulerQuaternionAttributeDrawer.cs @@ -0,0 +1,35 @@ +using UnityEditor; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + [CustomPropertyDrawer(typeof(EulerQuaternionAttribute))] + public class EulerQuaternionAttributeDrawer : PropertyDrawer + { + /// + /// Override this method to make your own IMGUI based GUI for the property. + /// + /// Rectangle on the screen to use for the property GUI. + /// The SerializedProperty to make the custom GUI for. + /// The label of this property. + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + if (fieldInfo.FieldType != typeof(Quaternion)) + { + EditorGUI.LabelField(position, label.text, "Use EulerQuaternion with Quaternion."); + return; + } + + label = EditorGUI.BeginProperty(position, label, property); + + EditorGUI.BeginChangeCheck(); + var changedEuler = EditorGUI.Vector3Field(position, label, property.quaternionValue.eulerAngles); + if (EditorGUI.EndChangeCheck()) + property.quaternionValue = Quaternion.Euler(changedEuler); + + EditorGUI.EndProperty(); + } + + public override bool CanCacheInspectorGUI(SerializedProperty property) => false; + } +} diff --git a/Editor/EulerQuaternionAttributeDrawer.cs.meta b/Editor/EulerQuaternionAttributeDrawer.cs.meta new file mode 100644 index 000000000..a565a8683 --- /dev/null +++ b/Editor/EulerQuaternionAttributeDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0d13a04834f44cb3b5b0e5cf6206d865 +timeCreated: 1674737626 \ No newline at end of file diff --git a/Editor/Processors/SkinnedMeshes/MeshInfo.cs b/Editor/Processors/SkinnedMeshes/MeshInfo.cs index 679a0567f..c6a33590d 100644 --- a/Editor/Processors/SkinnedMeshes/MeshInfo.cs +++ b/Editor/Processors/SkinnedMeshes/MeshInfo.cs @@ -38,6 +38,17 @@ public readonly (string name, (Vector3[] vertices, Vector3[] normals, Vector3[] public readonly Matrix4x4[] bindposes; public readonly Transform[] bones; + + public int uvCount => + uv8 != null ? 8 : + uv7 != null ? 7 : + uv6 != null ? 6 : + uv5 != null ? 5 : + uv4 != null ? 4 : + uv3 != null ? 3 : + uv2 != null ? 2 : + uv != null ? 1 : + 0; // ReSharper restore InconsistentNaming public MeshInfo( @@ -179,6 +190,7 @@ public MeshInfo(MeshRenderer renderer) public void WriteToMesh(Mesh destMesh) { + destMesh.Clear(); destMesh.vertices = vertices; destMesh.normals = normals; destMesh.tangents = tangents; @@ -194,7 +206,8 @@ public void WriteToMesh(Mesh destMesh) destMesh.triangles = Triangles; destMesh.bindposes = bindposes; destMesh.triangles = Triangles; - destMesh.SetBoneWeights(BonesPerVertex, AllBoneWeights); + if (AllBoneWeights.Length != 0) + destMesh.SetBoneWeights(BonesPerVertex, AllBoneWeights); destMesh.ClearBlendShapes(); foreach (var (name, (vertice, normal, tangent, _)) in BlendShapes) diff --git a/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs b/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs new file mode 100644 index 000000000..a169c53d4 --- /dev/null +++ b/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs @@ -0,0 +1,189 @@ +using System.Collections; +using System.Linq; +using Unity.Collections; +using UnityEngine; +using UnityEngine.Assertions; +using UnityEngine.Rendering; + +namespace Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes +{ + internal class RemoveMeshInBoxProcessor : EditSkinnedMeshProcessor + { + public RemoveMeshInBoxProcessor(RemoveMeshInBox component) : base(component) + { + } + + public override int ProcessOrder => -10000; + + public override void Process(OptimizerSession session) + { + var srcMesh = new MeshInfo(Target); + var vertices = srcMesh.vertices; + var inBoxFlag = ComputeInBoxVertices(vertices); + if (inBoxFlag.CountTrue() == 0) return; + var (destTriangles, indexMapping) = SweepTrianglesInBox(inBoxFlag, srcMesh.Triangles); + var usingVertices = CollectUsingVertices(vertices.Length, destTriangles); + var usingVerticesCount = usingVertices.CountTrue(); + + //var mesh = session.MayInstantiate(Target.sharedMesh); + + var destMesh = new MeshInfo( + bounds: srcMesh.Bounds, + trianglesCount: destTriangles.Length, + vertexCount: usingVerticesCount, + uvCount: srcMesh.uvCount, + withColors: srcMesh.colors32 != null && srcMesh.colors32.Length == 0, + subMeshCount: srcMesh.SubMeshes.Length, + bonesCount: srcMesh.bones.Length, + blendShapes: srcMesh.BlendShapes.Select(x => (x.name, x.Item2.weight)).ToArray() + ); + + // sweep vertices + SweepUnusedVertices(usingVertices, destMesh.vertices, srcMesh.vertices); + SweepUnusedVertices(usingVertices, destMesh.normals, srcMesh.normals); + SweepUnusedVertices(usingVertices, destMesh.tangents, srcMesh.tangents); + SweepUnusedVertices(usingVertices, destMesh.uv, srcMesh.uv); + SweepUnusedVertices(usingVertices, destMesh.uv2, srcMesh.uv2); + SweepUnusedVertices(usingVertices, destMesh.uv3, srcMesh.uv3); + SweepUnusedVertices(usingVertices, destMesh.uv4, srcMesh.uv4); + SweepUnusedVertices(usingVertices, destMesh.uv5, srcMesh.uv5); + SweepUnusedVertices(usingVertices, destMesh.uv6, srcMesh.uv6); + SweepUnusedVertices(usingVertices, destMesh.uv7, srcMesh.uv7); + SweepUnusedVertices(usingVertices, destMesh.uv8, srcMesh.uv8); + SweepUnusedVertices(usingVertices, destMesh.colors32, srcMesh.colors32); + for (var i = 0; i < srcMesh.BlendShapes.Length; i++) + { + SweepUnusedVertices(usingVertices, destMesh.BlendShapes[i].Item2.vertices, + srcMesh.BlendShapes[i].Item2.vertices); + SweepUnusedVertices(usingVertices, destMesh.BlendShapes[i].Item2.normals, + srcMesh.BlendShapes[i].Item2.normals); + SweepUnusedVertices(usingVertices, destMesh.BlendShapes[i].Item2.tangents, + srcMesh.BlendShapes[i].Item2.tangents); + } + + var vertexIndexMapping = CreateVertexIndexMapping(usingVertices); + for (var i = 0; i < destTriangles.Length; i++) + destMesh.Triangles[i] = vertexIndexMapping[destTriangles[i]]; + + // Bone Weights + //* + var boneWeightsCount = srcMesh.BonesPerVertex.Where((_, i) => usingVertices[i]).Sum(x => x); + destMesh.AllBoneWeights = new NativeArray(boneWeightsCount, Allocator.Temp); + for (int srcI = 0, dstI = 0, srcBoneWeightBase = 0, dstBoneWeightBase = 0; + srcI < srcMesh.BonesPerVertex.Length; + srcI++) + { + if (usingVertices[srcI]) + { + int bones = destMesh.BonesPerVertex[dstI] = srcMesh.BonesPerVertex[srcI]; + srcMesh.AllBoneWeights.AsReadOnlySpan().Slice(srcBoneWeightBase, bones) + .CopyTo(destMesh.AllBoneWeights.AsSpan().Slice(dstBoneWeightBase, bones)); + dstBoneWeightBase += bones; + dstI++; + } + + srcBoneWeightBase += srcMesh.BonesPerVertex[srcI]; + } + // */ + + for (var i = 0; i < destMesh.SubMeshes.Length; i++) + { + var srcSubMesh = srcMesh.SubMeshes[i]; + Assert.AreEqual(MeshTopology.Triangles, srcSubMesh.topology); + var indexStart = srcSubMesh.indexStart; + while (indexMapping[indexStart] == -1) + indexStart++; + var indexEnd = srcSubMesh.indexStart + srcSubMesh.indexCount; + while (indexMapping[indexEnd] == -1 && indexStart < indexEnd) + indexEnd--; + destMesh.SubMeshes[i] = new SubMeshDescriptor(indexMapping[indexStart], + indexMapping[indexEnd] - indexMapping[indexStart]); + } + + srcMesh.bindposes.CopyTo(destMesh.bindposes, 0); + + var mesh = session.MayInstantiate(Target.sharedMesh); + destMesh.WriteToMesh(mesh); + Target.sharedMesh = mesh; + } + + private BitArray ComputeInBoxVertices(Vector3[] vertices) + { + var inBox = new BitArray(vertices.Length); + + for (var i = 0; i < vertices.Length; i++) + inBox[i] = Component.boxes.Any(x => x.ContainsVertex(vertices[i])); + + return inBox; + } + + private (int[] newTriangles, int[] triangleIndexMapping) SweepTrianglesInBox(BitArray inBoxVertices, + int[] triangles) + { + // process triangles + // -1 means removed triangle + var triangleMapping = new int[triangles.Length + 1]; + int srcI = 0, dstI = 0; + for (; srcI < triangles.Length; srcI += 3) + { + var remove = + inBoxVertices[triangles[srcI + 0]] + && inBoxVertices[triangles[srcI + 1]] + && inBoxVertices[triangles[srcI + 2]]; + if (remove) + { + triangleMapping[srcI + 0] = triangleMapping[srcI + 1] = triangleMapping[srcI + 2] = -1; + } + else + { + triangleMapping[srcI + 0] = dstI + 0; + triangleMapping[srcI + 1] = dstI + 1; + triangleMapping[srcI + 2] = dstI + 2; + triangles[dstI + 0] = triangles[srcI + 0]; + triangles[dstI + 1] = triangles[srcI + 1]; + triangles[dstI + 2] = triangles[srcI + 2]; + dstI += 3; + } + } + + triangleMapping[srcI] = dstI; + + return (triangles.Take(dstI).ToArray(), triangleMapping); + } + + private BitArray CollectUsingVertices(int verticesCount, int[] triangles) + { + var usingVertices = new BitArray(verticesCount); + foreach (var vertexIndex in triangles) + usingVertices[vertexIndex] = true; + return usingVertices; + } + + private void SweepUnusedVertices(BitArray usingVertices, T[] result, T[] vertexAttribute) + { + if (vertexAttribute == null || result == null) return; + + for (int srcI = 0, dstI = 0; srcI < vertexAttribute.Length; srcI++) + if (usingVertices[srcI]) + result[dstI++] = vertexAttribute[srcI]; + } + + private int[] CreateVertexIndexMapping(BitArray usingVertices) + { + var result = new int[usingVertices.Length]; + for (int srcI = 0, dstI = 0; srcI < result.Length; srcI++) + result[srcI] = usingVertices[srcI] ? dstI++ : -1; + return result; + } + + public override IMeshInfoComputer GetComputer(IMeshInfoComputer upstream) => new MeshInfoComputer(this, upstream); + + class MeshInfoComputer : AbstractMeshInfoComputer + { + private readonly RemoveMeshInBoxProcessor _processor; + + public MeshInfoComputer(RemoveMeshInBoxProcessor processor, IMeshInfoComputer upstream) : base(upstream) + => _processor = processor; + } + } +} diff --git a/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs.meta b/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs.meta new file mode 100644 index 000000000..a3028fce8 --- /dev/null +++ b/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cb55dfcab1704eb481a7ab8e30d3a16d +timeCreated: 1674715755 \ No newline at end of file diff --git a/Editor/RemoveMeshInBoxEditor.cs b/Editor/RemoveMeshInBoxEditor.cs new file mode 100644 index 000000000..592028244 --- /dev/null +++ b/Editor/RemoveMeshInBoxEditor.cs @@ -0,0 +1,69 @@ +using UnityEditor; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + [CustomEditor(typeof(RemoveMeshInBox))] + internal class RemoveMeshInBoxEditor : Editor + { + public override void OnInspectorGUI() + { + // TODO: implement custom editor + base.OnInspectorGUI(); + } + + // ReSharper disable BitwiseOperatorOnEnumWithoutFlags + [DrawGizmo(GizmoType.Selected | GizmoType.Active)] + // ReSharper restore BitwiseOperatorOnEnumWithoutFlags + public static void DrawGizmoActive(RemoveMeshInBox script, GizmoType gizmoType) + { + // ReSharper disable BitwiseOperatorOnEnumWithoutFlags + var selecting = (gizmoType & (GizmoType.InSelectionHierarchy | GizmoType.Selected)) != 0; + // ReSharper restore BitwiseOperatorOnEnumWithoutFlags + + var matrixPrev = Handles.matrix; + var colorPrev = Handles.color; + try + { + Handles.matrix = script.transform.localToWorldMatrix; + Handles.color = Color.red; + + foreach (var boundingBox in script.boxes) + { + var halfSize = boundingBox.size / 2; + var x = boundingBox.rotation * new Vector3(halfSize.x, 0, 0); + var y = boundingBox.rotation * new Vector3(0, halfSize.y, 0); + var z = boundingBox.rotation * new Vector3(0, 0, halfSize.z); + var center = boundingBox.center; + + var points = new Vector3[8] + { + center + x + y + z, + center + x + y - z, + center + x - y + z, + center + x - y - z, + center - x + y + z, + center - x + y - z, + center - x - y + z, + center - x - y - z, + }; + + var indices = new int[12 * 2] + { + 0, 1, 0, 2, 0, 4, + 3, 1, 3, 2, 3, 7, + 5, 1, 5, 4, 5, 7, + 6, 2, 6, 4, 6, 7, + }; + + Handles.DrawLines(points, indices); + } + } + finally + { + Handles.matrix = matrixPrev; + Handles.color = colorPrev; + } + } + } +} diff --git a/Editor/RemoveMeshInBoxEditor.cs.meta b/Editor/RemoveMeshInBoxEditor.cs.meta new file mode 100644 index 000000000..a19d24664 --- /dev/null +++ b/Editor/RemoveMeshInBoxEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6f45e8fab6e04ffebaf31011ae763654 +timeCreated: 1674716389 \ No newline at end of file diff --git a/Runtime/EulerQuaternionAttribute.cs b/Runtime/EulerQuaternionAttribute.cs new file mode 100644 index 000000000..1b525d0a6 --- /dev/null +++ b/Runtime/EulerQuaternionAttribute.cs @@ -0,0 +1,9 @@ +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + /// + /// Show EulerAngle in inspector + /// + internal class EulerQuaternionAttribute : PropertyAttribute { } +} diff --git a/Runtime/EulerQuaternionAttribute.cs.meta b/Runtime/EulerQuaternionAttribute.cs.meta new file mode 100644 index 000000000..9667d47c7 --- /dev/null +++ b/Runtime/EulerQuaternionAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c9cd084ea586450da36c70959c73c4e5 +timeCreated: 1674737446 \ No newline at end of file diff --git a/Runtime/RemoveMeshInBox.cs b/Runtime/RemoveMeshInBox.cs new file mode 100644 index 000000000..b1c4631e8 --- /dev/null +++ b/Runtime/RemoveMeshInBox.cs @@ -0,0 +1,31 @@ +using System; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + [AddComponentMenu("Optimizer/Remove Mesh in Box")] + [RequireComponent(typeof(SkinnedMeshRenderer))] + [DisallowMultipleComponent] + internal class RemoveMeshInBox : EditSkinnedMeshComponent + { + public BoundingBox[] boxes = Array.Empty(); + + [Serializable] + public class BoundingBox + { + public Vector3 center; + public Vector3 size = new Vector3(1, 1, 1); + [EulerQuaternion] + public Quaternion rotation = Quaternion.identity; + + public bool ContainsVertex(Vector3 point) + { + var positionInBox = Quaternion.Inverse(rotation) * (point - center); + var halfSize = size / 2; + return (-halfSize.x <= positionInBox.x && positionInBox.x <= halfSize.x) + && (-halfSize.y <= positionInBox.y && positionInBox.y <= halfSize.y) + && (-halfSize.z <= positionInBox.z && positionInBox.z <= halfSize.z); + } + } + } +} diff --git a/Runtime/RemoveMeshInBox.cs.meta b/Runtime/RemoveMeshInBox.cs.meta new file mode 100644 index 000000000..346004e69 --- /dev/null +++ b/Runtime/RemoveMeshInBox.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a9fd0617dd174314b0a375fb2188510c +timeCreated: 1674712731 \ No newline at end of file