Skip to content

Commit

Permalink
feat: remove polygons
Browse files Browse the repository at this point in the history
  • Loading branch information
anatawa12 committed Jan 26, 2023
1 parent 7467c09 commit 96f3526
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 1 deletion.
1 change: 1 addition & 0 deletions Editor/EditSkinnedMeshComponentUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ private IMeshInfoComputer[] GetComputers()
[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) =>
Expand Down
35 changes: 35 additions & 0 deletions Editor/EulerQuaternionAttributeDrawer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using UnityEditor;
using UnityEngine;

namespace Anatawa12.AvatarOptimizer
{
[CustomPropertyDrawer(typeof(EulerQuaternionAttribute))]
public class EulerQuaternionAttributeDrawer : PropertyDrawer
{
/// <summary>
/// <para>Override this method to make your own IMGUI based GUI for the property.</para>
/// </summary>
/// <param name="position">Rectangle on the screen to use for the property GUI.</param>
/// <param name="property">The SerializedProperty to make the custom GUI for.</param>
/// <param name="label">The label of this property.</param>
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;
}
}
3 changes: 3 additions & 0 deletions Editor/EulerQuaternionAttributeDrawer.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion Editor/Processors/SkinnedMeshes/MeshInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ internal class MeshInfo
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(
Expand Down Expand Up @@ -179,6 +190,7 @@ public MeshInfo(MeshRenderer renderer)

public void WriteToMesh(Mesh destMesh)
{
destMesh.Clear();
destMesh.vertices = vertices;
destMesh.normals = normals;
destMesh.tangents = tangents;
Expand All @@ -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)
Expand Down
189 changes: 189 additions & 0 deletions Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs
Original file line number Diff line number Diff line change
@@ -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<RemoveMeshInBox>
{
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<BoneWeight1>(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<T>(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;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions Editor/RemoveMeshInBoxEditor.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
3 changes: 3 additions & 0 deletions Editor/RemoveMeshInBoxEditor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Runtime/EulerQuaternionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using UnityEngine;

namespace Anatawa12.AvatarOptimizer
{
/// <summary>
/// Show EulerAngle in inspector
/// </summary>
internal class EulerQuaternionAttribute : PropertyAttribute { }
}
3 changes: 3 additions & 0 deletions Runtime/EulerQuaternionAttribute.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 96f3526

Please sign in to comment.