From 84fbc36f1df2c2fa18bbe83ab579fe3b5fcc76ac Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 18 Sep 2023 20:59:14 +0900 Subject: [PATCH 01/27] feat: MeshPreviewController --- Editor/MeshPreviewController.cs | 71 ++++++++++++++++++++++++++++ Editor/MeshPreviewController.cs.meta | 3 ++ 2 files changed, 74 insertions(+) create mode 100644 Editor/MeshPreviewController.cs create mode 100644 Editor/MeshPreviewController.cs.meta diff --git a/Editor/MeshPreviewController.cs b/Editor/MeshPreviewController.cs new file mode 100644 index 000000000..6c5c30691 --- /dev/null +++ b/Editor/MeshPreviewController.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + class MeshPreviewController : ScriptableSingleton + { + private static readonly Type[] EditorTypes = { }; + + public GameObject targetGameObject; + public SkinnedMeshRenderer targetRenderer; + public bool previewing; + + private void UpdatePreviewing() + { + if (!previewing) return; + + bool ShouldStopPreview() + { + // target GameObject disappears + if (targetGameObject == null || targetRenderer == null) return true; + // animation mode externally exited + if (!AnimationMode.InAnimationMode()) return true; + // Showing Inspector changed + if (ActiveEditorTracker.sharedTracker.activeEditors[0].target != targetGameObject) return true; + // Preview Component Not Found + if (EditorTypes.All(t => targetGameObject.GetComponent(t) == null)) return true; + + return false; + } + + if (ShouldStopPreview()) + StopPreview(); + } + + private bool StartPreview(GameObject expectedGameObject = null) + { + // Already in AnimationMode of other object + if (AnimationMode.InAnimationMode()) return false; + // Previewing object + if (previewing) return false; + + var gameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; + if (gameObject == null) return false; + if (expectedGameObject != null && expectedGameObject != gameObject) return false; + var renderer = gameObject.GetComponent(); + if (renderer == null) return false; + // Editor Components does not exists + if (EditorTypes.All(t => gameObject.GetComponent(t) == null)) return false; + + AnimationMode.StartAnimationMode(); + targetGameObject = gameObject; + targetRenderer = renderer; + previewing = true; + EditorApplication.update -= UpdatePreviewing; + EditorApplication.update += UpdatePreviewing; + return true; + } + + private void StopPreview() + { + AnimationMode.StopAnimationMode(); + targetGameObject = null; + targetRenderer = null; + previewing = false; + EditorApplication.update -= UpdatePreviewing; + } + } +} \ No newline at end of file diff --git a/Editor/MeshPreviewController.cs.meta b/Editor/MeshPreviewController.cs.meta new file mode 100644 index 000000000..b9c1a14a5 --- /dev/null +++ b/Editor/MeshPreviewController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2a9aa96e10a54b7cb7c238f3656a6cdf +timeCreated: 1695026721 \ No newline at end of file From 5a1a2ea9f226b33de15e45fc570f3ea77641a284 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 18 Sep 2023 23:58:41 +0900 Subject: [PATCH 02/27] feat: add RemoveMeshWithBoxPreviewContext --- Editor/EditModePreview.meta | 3 + .../RemoveMeshWithBoxPreviewContext.cs | 232 ++++++++++++++++++ .../RemoveMeshWithBoxPreviewContext.cs.meta | 3 + ...m.anatawa12.avatar-optimizer.editor.asmdef | 3 +- 4 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 Editor/EditModePreview.meta create mode 100644 Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs create mode 100644 Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs.meta diff --git a/Editor/EditModePreview.meta b/Editor/EditModePreview.meta new file mode 100644 index 000000000..b2b130867 --- /dev/null +++ b/Editor/EditModePreview.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ebb5beda53504474adcc5086a8409ba7 +timeCreated: 1695047925 \ No newline at end of file diff --git a/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs b/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs new file mode 100644 index 000000000..637139d68 --- /dev/null +++ b/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.EditModePreview +{ + /// + /// Preview Context for RemoveMesh with Box. + /// RuntimePreview of Remove Mesh with Box needs BlendShape applied vertex position so + /// this class holds that information and update if nesessary + /// + class RemoveMeshWithBoxPreviewContext : IDisposable + { + private readonly int _vertexCount; + + public NativeArray Vertices => _vertices; + + // not transformed vertices + private NativeArray _originalVertices; + // this should be blendShape transformed + private NativeArray _vertices; + // blendshape vertex transforms. _blendShapeVertices[vertexIndex + frameIndex * vertexCount] + private NativeArray _blendShapeVertices; + // frame info. _blendShapeFrameInfo[blendShapeIndex][frameIndexInShape] + private readonly (float weight, int globalIndex)[][] _blendShapeFrameInfo; + // configured BlendShape weights + [NotNull] private readonly float[] _blendShapeWeights; + + public RemoveMeshWithBoxPreviewContext(Mesh originalMesh) + { + _vertexCount = originalMesh.vertexCount; + _originalVertices = new NativeArray(_vertexCount, Allocator.Persistent); + _originalVertices.CopyFrom(originalMesh.vertices); + + // initialize with original vertices + _vertices = new NativeArray(_originalVertices, Allocator.Persistent); + + // BlendShapes + var blendShapeFrameCount = 0; + for (var i = 0; i < originalMesh.blendShapeCount; i++) + blendShapeFrameCount += originalMesh.GetBlendShapeFrameCount(i); + + _blendShapeFrameInfo = new (float weight, int globalIndex)[originalMesh.blendShapeCount][]; + _blendShapeVertices = new NativeArray(_vertexCount * blendShapeFrameCount, Allocator.Persistent); + var frameIndex = 0; + var getterBuffer = new Vector3[_vertexCount]; + for (var i = 0; i < originalMesh.blendShapeCount; i++) + { + var frameCount = originalMesh.GetBlendShapeFrameCount(i); + var thisShapeInfo = _blendShapeFrameInfo[i] = new (float weight, int globalIndex)[frameCount]; + for (var j = 0; j < frameCount; j++) + { + originalMesh.GetBlendShapeFrameVertices(i, j, getterBuffer, null, null); + getterBuffer.AsSpan() + .CopyTo(_blendShapeVertices.AsSpan().Slice(frameIndex * _vertexCount, _vertexCount)); + thisShapeInfo[j] = (originalMesh.GetBlendShapeFrameWeight(i, j), frameIndex); + frameIndex++; + } + } + _blendShapeWeights = new float[originalMesh.blendShapeCount]; + } + + public void UpdateVertices(SkinnedMeshRenderer renderer) + { + var modified = false; + for (var i = 0; i < _blendShapeWeights.Length; i++) + { + var currentWeight = renderer.GetBlendShapeWeight(i); + if (Math.Abs(currentWeight - _blendShapeWeights[i]) > Mathf.Epsilon) + { + _blendShapeWeights[i] = currentWeight; + modified = true; + } + } + + if (!modified) return; + + var frameIndices = new List(); + var frameWeights = new List(); + + for (var i = 0; i < _blendShapeWeights.Length; i++) + GetBlendShape(i, _blendShapeWeights[i], frameIndices, frameWeights); + + if (frameWeights.Count == 0) + { + _vertices.CopyFrom(_originalVertices); + return; + } + + using (var blendShapeFrameIndices = new NativeArray(frameIndices.ToArray(), Allocator.TempJob)) + using (var blendShapeFrameWeights = new NativeArray(frameWeights.ToArray(), Allocator.TempJob)) + { + new ApplyBlendShapeJob + { + OriginalVertices = _originalVertices, + BlendShapeVertices = _blendShapeVertices, + BlendShapeFrameIndices = blendShapeFrameIndices, + BlendShapeFrameWeights = blendShapeFrameWeights, + ResultVertices = _vertices + }.Schedule(_vertexCount, 1).Complete(); + } + } + + [BurstCompile] + struct ApplyBlendShapeJob: IJobParallelFor + { + [ReadOnly] + public NativeArray OriginalVertices; + [ReadOnly] + public NativeArray BlendShapeVertices; + [ReadOnly] + public NativeArray BlendShapeFrameIndices; + [ReadOnly] + public NativeArray BlendShapeFrameWeights; + public NativeArray ResultVertices; + + public void Execute (int vertexIndex) + { + var vertexCount = OriginalVertices.Length; + var original = OriginalVertices[vertexIndex]; + + for (var indicesIndex = 0; indicesIndex < BlendShapeFrameIndices.Length; indicesIndex++) + { + var frameIndex = BlendShapeFrameIndices[indicesIndex]; + var weight = BlendShapeFrameWeights[indicesIndex]; + var delta = BlendShapeVertices[frameIndex * vertexCount + vertexIndex]; + original += delta * weight; + } + + ResultVertices[vertexIndex] = original; + } + } + + public void Dispose() + { + _originalVertices.Dispose(); + _vertices.Dispose(); + _blendShapeVertices.Dispose(); + } + + public void GetBlendShape(int shapeIndex, float weight, List frameIndices, List frameWeights) + { + const float blendShapeEpsilon = 0.0001f; + var frames = _blendShapeFrameInfo[shapeIndex]; + + if (Mathf.Abs(weight) <= blendShapeEpsilon && ZeroForWeightZero()) + { + // the blendShape is not active + return; + } + + bool ZeroForWeightZero() + { + if (frames.Length == 1) return true; + var first = frames.First(); + var end = frames.Last(); + + // both weight are same sign, zero for 0 weight + if (first.weight <= 0 && end.weight <= 0) return true; + if (first.weight >= 0 && end.weight >= 0) return true; + + return false; + } + + if (frames.Length == 1) + { + // simplest and likely + var frame = frames[0]; + frameIndices.Add(frame.globalIndex); + frameWeights.Add(weight / frame.weight); + } + else + { + // multi frame + + var firstFrame = frames[0]; + var lastFrame = frames.Last(); + + if (firstFrame.weight > 0 && weight < firstFrame.weight) + { + // if all weights are positive and the weight is less than first weight: lerp 0..first + frameIndices.Add(firstFrame.globalIndex); + frameWeights.Add(weight / firstFrame.weight); + } + + if (lastFrame.weight < 0 && weight > lastFrame.weight) + { + // if all weights are negative and the weight is more than last weight: lerp last..0 + frameIndices.Add(lastFrame.globalIndex); + frameWeights.Add(weight / lastFrame.weight); + } + + // otherwise, lerp between two surrounding frames OR nearest two frames + var (lessFrame, greaterFrame) = FindFrame(); + var weightDiff = greaterFrame.weight - lessFrame.weight; + var lessFrameWeight = (weight - lessFrame.weight) / weightDiff; + var graterFrameWeight = (greaterFrame.weight - weight) / weightDiff; + + if (!(Mathf.Abs(lessFrameWeight) < blendShapeEpsilon)) + { + frameIndices.Add(lessFrame.globalIndex); + frameWeights.Add(lessFrameWeight); + } + + if (!(Mathf.Abs(graterFrameWeight) < blendShapeEpsilon)) + { + frameIndices.Add(greaterFrame.globalIndex); + frameWeights.Add(graterFrameWeight); + } + } + + return; + + // TODO: merge this logic with it in MeshInfo2 + ((float weight, int globalIndex), (float weight, int globalIndex)) FindFrame() + { + for (var i = 1; i < frames.Length; i++) + { + if (weight <= frames[i].weight) + return (frames[i - 1], frames[i]); + } + + return (frames[frames.Length - 2], frames[frames.Length - 1]); + } + } + } +} \ No newline at end of file diff --git a/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs.meta b/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs.meta new file mode 100644 index 000000000..6e766024e --- /dev/null +++ b/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 50c50f2cd0fa4899a3dfe97e787c6e03 +timeCreated: 1695047935 \ No newline at end of file diff --git a/Editor/com.anatawa12.avatar-optimizer.editor.asmdef b/Editor/com.anatawa12.avatar-optimizer.editor.asmdef index 41396b371..004330a75 100644 --- a/Editor/com.anatawa12.avatar-optimizer.editor.asmdef +++ b/Editor/com.anatawa12.avatar-optimizer.editor.asmdef @@ -9,7 +9,8 @@ "GUID:f69eeb3e25674f4a9bd20e6d7e69e0e6", "GUID:2633ab9fa94544a69517fc9a1bc143c9", "GUID:b9880ca0b6584453a2627bd3c038759f", - "GUID:8542dbf824204440a818dbc2377cb4d6" + "GUID:8542dbf824204440a818dbc2377cb4d6", + "GUID:2665a8d13d1b3f18800f46e256720795" ], "includePlatforms": [ "Editor" From 0f1b7ee58aee7828f773a0e7ba49e352994ff4a6 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 00:38:26 +0900 Subject: [PATCH 03/27] chore: move MeshPreviewController --- Editor/{ => EditModePreview}/MeshPreviewController.cs | 2 +- Editor/{ => EditModePreview}/MeshPreviewController.cs.meta | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Editor/{ => EditModePreview}/MeshPreviewController.cs (97%) rename Editor/{ => EditModePreview}/MeshPreviewController.cs.meta (100%) diff --git a/Editor/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs similarity index 97% rename from Editor/MeshPreviewController.cs rename to Editor/EditModePreview/MeshPreviewController.cs index 6c5c30691..ea84f925f 100644 --- a/Editor/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -3,7 +3,7 @@ using UnityEditor; using UnityEngine; -namespace Anatawa12.AvatarOptimizer +namespace Anatawa12.AvatarOptimizer.EditModePreview { class MeshPreviewController : ScriptableSingleton { diff --git a/Editor/MeshPreviewController.cs.meta b/Editor/EditModePreview/MeshPreviewController.cs.meta similarity index 100% rename from Editor/MeshPreviewController.cs.meta rename to Editor/EditModePreview/MeshPreviewController.cs.meta From 529d22d36d797450f73bf27d98b65937710ce8b8 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 01:32:18 +0900 Subject: [PATCH 04/27] chore: extract BlendShapePreviewContext from RemoveMeshWithBoxPreviewContext --- .../BlendShapePreviewContext.cs | 205 ++++++++++++++++++ .../BlendShapePreviewContext.cs.meta | 3 + .../RemoveMeshWithBoxPreviewContext.cs | 184 +--------------- 3 files changed, 214 insertions(+), 178 deletions(-) create mode 100644 Editor/EditModePreview/BlendShapePreviewContext.cs create mode 100644 Editor/EditModePreview/BlendShapePreviewContext.cs.meta diff --git a/Editor/EditModePreview/BlendShapePreviewContext.cs b/Editor/EditModePreview/BlendShapePreviewContext.cs new file mode 100644 index 000000000..bb0af58dc --- /dev/null +++ b/Editor/EditModePreview/BlendShapePreviewContext.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.EditModePreview +{ + /// + /// Preview Context that holds information about BlendShapes + /// + class BlendShapePreviewContext : IDisposable + { + private readonly int _vertexCount; + // blendshape vertex transforms. _blendShapeVertices[vertexIndex + frameIndex * vertexCount] + private NativeArray _blendShapeVertices; + // frame info. _blendShapeFrameInfo[blendShapeIndex][frameIndexInShape] + private readonly (float weight, int globalIndex)[][] _blendShapeFrameInfo; + + public BlendShapePreviewContext(Mesh originalMesh) + { + _vertexCount = originalMesh.vertexCount; + + // BlendShapes + var blendShapeFrameCount = 0; + for (var i = 0; i < originalMesh.blendShapeCount; i++) + blendShapeFrameCount += originalMesh.GetBlendShapeFrameCount(i); + + _blendShapeFrameInfo = new (float weight, int globalIndex)[originalMesh.blendShapeCount][]; + _blendShapeVertices = new NativeArray(_vertexCount * blendShapeFrameCount, Allocator.Persistent); + var frameIndex = 0; + var getterBuffer = new Vector3[_vertexCount]; + for (var i = 0; i < originalMesh.blendShapeCount; i++) + { + var frameCount = originalMesh.GetBlendShapeFrameCount(i); + var thisShapeInfo = _blendShapeFrameInfo[i] = new (float weight, int globalIndex)[frameCount]; + for (var j = 0; j < frameCount; j++) + { + originalMesh.GetBlendShapeFrameVertices(i, j, getterBuffer, null, null); + getterBuffer.AsSpan() + .CopyTo(_blendShapeVertices.AsSpan().Slice(frameIndex * _vertexCount, _vertexCount)); + thisShapeInfo[j] = (originalMesh.GetBlendShapeFrameWeight(i, j), frameIndex); + frameIndex++; + } + } + } + + public void ComputeBlendShape( + float[] blendShapeWeights, + NativeArray originalVertices, + NativeArray outputVertices) + { + System.Diagnostics.Debug.Assert(originalVertices.Length == _vertexCount); + System.Diagnostics.Debug.Assert(outputVertices.Length == _vertexCount); + + var frameIndices = new List(); + var frameWeights = new List(); + + for (var i = 0; i < blendShapeWeights.Length; i++) + GetBlendShape(i, blendShapeWeights[i], frameIndices, frameWeights); + + if (frameWeights.Count == 0) + { + outputVertices.CopyFrom(originalVertices); + return; + } + + using (var blendShapeFrameIndices = new NativeArray(frameIndices.ToArray(), Allocator.TempJob)) + using (var blendShapeFrameWeights = new NativeArray(frameWeights.ToArray(), Allocator.TempJob)) + { + new ApplyBlendShapeJob + { + OriginalVertices = originalVertices, + BlendShapeVertices = _blendShapeVertices, + BlendShapeFrameIndices = blendShapeFrameIndices, + BlendShapeFrameWeights = blendShapeFrameWeights, + ResultVertices = outputVertices + }.Schedule(outputVertices.Length, 1).Complete(); + } + } + + [BurstCompile] + struct ApplyBlendShapeJob: IJobParallelFor + { + [ReadOnly] + public NativeArray OriginalVertices; + [ReadOnly] + public NativeArray BlendShapeVertices; + [ReadOnly] + public NativeArray BlendShapeFrameIndices; + [ReadOnly] + public NativeArray BlendShapeFrameWeights; + public NativeArray ResultVertices; + + public void Execute (int vertexIndex) + { + var vertexCount = OriginalVertices.Length; + var original = OriginalVertices[vertexIndex]; + + for (var indicesIndex = 0; indicesIndex < BlendShapeFrameIndices.Length; indicesIndex++) + { + var frameIndex = BlendShapeFrameIndices[indicesIndex]; + var weight = BlendShapeFrameWeights[indicesIndex]; + var delta = BlendShapeVertices[frameIndex * vertexCount + vertexIndex]; + original += delta * weight; + } + + ResultVertices[vertexIndex] = original; + } + } + + public void Dispose() + { + _blendShapeVertices.Dispose(); + } + + private void GetBlendShape(int shapeIndex, float weight, List frameIndices, List frameWeights) + { + const float blendShapeEpsilon = 0.0001f; + var frames = _blendShapeFrameInfo[shapeIndex]; + + if (Mathf.Abs(weight) <= blendShapeEpsilon && ZeroForWeightZero()) + { + // the blendShape is not active + return; + } + + bool ZeroForWeightZero() + { + if (frames.Length == 1) return true; + var first = frames.First(); + var end = frames.Last(); + + // both weight are same sign, zero for 0 weight + if (first.weight <= 0 && end.weight <= 0) return true; + if (first.weight >= 0 && end.weight >= 0) return true; + + return false; + } + + if (frames.Length == 1) + { + // simplest and likely + var frame = frames[0]; + frameIndices.Add(frame.globalIndex); + frameWeights.Add(weight / frame.weight); + } + else + { + // multi frame + + var firstFrame = frames[0]; + var lastFrame = frames.Last(); + + if (firstFrame.weight > 0 && weight < firstFrame.weight) + { + // if all weights are positive and the weight is less than first weight: lerp 0..first + frameIndices.Add(firstFrame.globalIndex); + frameWeights.Add(weight / firstFrame.weight); + } + + if (lastFrame.weight < 0 && weight > lastFrame.weight) + { + // if all weights are negative and the weight is more than last weight: lerp last..0 + frameIndices.Add(lastFrame.globalIndex); + frameWeights.Add(weight / lastFrame.weight); + } + + // otherwise, lerp between two surrounding frames OR nearest two frames + var (lessFrame, greaterFrame) = FindFrame(); + var weightDiff = greaterFrame.weight - lessFrame.weight; + var lessFrameWeight = (weight - lessFrame.weight) / weightDiff; + var graterFrameWeight = (greaterFrame.weight - weight) / weightDiff; + + if (!(Mathf.Abs(lessFrameWeight) < blendShapeEpsilon)) + { + frameIndices.Add(lessFrame.globalIndex); + frameWeights.Add(lessFrameWeight); + } + + if (!(Mathf.Abs(graterFrameWeight) < blendShapeEpsilon)) + { + frameIndices.Add(greaterFrame.globalIndex); + frameWeights.Add(graterFrameWeight); + } + } + + return; + + // TODO: merge this logic with it in MeshInfo2 + ((float weight, int globalIndex), (float weight, int globalIndex)) FindFrame() + { + for (var i = 1; i < frames.Length; i++) + { + if (weight <= frames[i].weight) + return (frames[i - 1], frames[i]); + } + + return (frames[frames.Length - 2], frames[frames.Length - 1]); + } + } + } +} \ No newline at end of file diff --git a/Editor/EditModePreview/BlendShapePreviewContext.cs.meta b/Editor/EditModePreview/BlendShapePreviewContext.cs.meta new file mode 100644 index 000000000..bae6bed02 --- /dev/null +++ b/Editor/EditModePreview/BlendShapePreviewContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e053c35a488c4e40ae440d1619782165 +timeCreated: 1695053866 \ No newline at end of file diff --git a/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs b/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs index 637139d68..055795829 100644 --- a/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs +++ b/Editor/EditModePreview/RemoveMeshWithBoxPreviewContext.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; -using Unity.Burst; using Unity.Collections; -using Unity.Jobs; using UnityEngine; namespace Anatawa12.AvatarOptimizer.EditModePreview @@ -16,56 +12,28 @@ namespace Anatawa12.AvatarOptimizer.EditModePreview /// class RemoveMeshWithBoxPreviewContext : IDisposable { - private readonly int _vertexCount; - public NativeArray Vertices => _vertices; + private readonly BlendShapePreviewContext _blendShapePreviewContext; // not transformed vertices private NativeArray _originalVertices; // this should be blendShape transformed private NativeArray _vertices; - // blendshape vertex transforms. _blendShapeVertices[vertexIndex + frameIndex * vertexCount] - private NativeArray _blendShapeVertices; - // frame info. _blendShapeFrameInfo[blendShapeIndex][frameIndexInShape] - private readonly (float weight, int globalIndex)[][] _blendShapeFrameInfo; // configured BlendShape weights [NotNull] private readonly float[] _blendShapeWeights; - public RemoveMeshWithBoxPreviewContext(Mesh originalMesh) + public RemoveMeshWithBoxPreviewContext(BlendShapePreviewContext blendShapePreviewContext, Mesh originalMesh) { - _vertexCount = originalMesh.vertexCount; - _originalVertices = new NativeArray(_vertexCount, Allocator.Persistent); - _originalVertices.CopyFrom(originalMesh.vertices); + _blendShapePreviewContext = blendShapePreviewContext; + _originalVertices = new NativeArray(originalMesh.vertices, Allocator.Persistent); // initialize with original vertices _vertices = new NativeArray(_originalVertices, Allocator.Persistent); - // BlendShapes - var blendShapeFrameCount = 0; - for (var i = 0; i < originalMesh.blendShapeCount; i++) - blendShapeFrameCount += originalMesh.GetBlendShapeFrameCount(i); - - _blendShapeFrameInfo = new (float weight, int globalIndex)[originalMesh.blendShapeCount][]; - _blendShapeVertices = new NativeArray(_vertexCount * blendShapeFrameCount, Allocator.Persistent); - var frameIndex = 0; - var getterBuffer = new Vector3[_vertexCount]; - for (var i = 0; i < originalMesh.blendShapeCount; i++) - { - var frameCount = originalMesh.GetBlendShapeFrameCount(i); - var thisShapeInfo = _blendShapeFrameInfo[i] = new (float weight, int globalIndex)[frameCount]; - for (var j = 0; j < frameCount; j++) - { - originalMesh.GetBlendShapeFrameVertices(i, j, getterBuffer, null, null); - getterBuffer.AsSpan() - .CopyTo(_blendShapeVertices.AsSpan().Slice(frameIndex * _vertexCount, _vertexCount)); - thisShapeInfo[j] = (originalMesh.GetBlendShapeFrameWeight(i, j), frameIndex); - frameIndex++; - } - } _blendShapeWeights = new float[originalMesh.blendShapeCount]; } - public void UpdateVertices(SkinnedMeshRenderer renderer) + public void OnUpdateSkinnedMeshRenderer(SkinnedMeshRenderer renderer) { var modified = false; for (var i = 0; i < _blendShapeWeights.Length; i++) @@ -80,153 +48,13 @@ public void UpdateVertices(SkinnedMeshRenderer renderer) if (!modified) return; - var frameIndices = new List(); - var frameWeights = new List(); - - for (var i = 0; i < _blendShapeWeights.Length; i++) - GetBlendShape(i, _blendShapeWeights[i], frameIndices, frameWeights); - - if (frameWeights.Count == 0) - { - _vertices.CopyFrom(_originalVertices); - return; - } - - using (var blendShapeFrameIndices = new NativeArray(frameIndices.ToArray(), Allocator.TempJob)) - using (var blendShapeFrameWeights = new NativeArray(frameWeights.ToArray(), Allocator.TempJob)) - { - new ApplyBlendShapeJob - { - OriginalVertices = _originalVertices, - BlendShapeVertices = _blendShapeVertices, - BlendShapeFrameIndices = blendShapeFrameIndices, - BlendShapeFrameWeights = blendShapeFrameWeights, - ResultVertices = _vertices - }.Schedule(_vertexCount, 1).Complete(); - } - } - - [BurstCompile] - struct ApplyBlendShapeJob: IJobParallelFor - { - [ReadOnly] - public NativeArray OriginalVertices; - [ReadOnly] - public NativeArray BlendShapeVertices; - [ReadOnly] - public NativeArray BlendShapeFrameIndices; - [ReadOnly] - public NativeArray BlendShapeFrameWeights; - public NativeArray ResultVertices; - - public void Execute (int vertexIndex) - { - var vertexCount = OriginalVertices.Length; - var original = OriginalVertices[vertexIndex]; - - for (var indicesIndex = 0; indicesIndex < BlendShapeFrameIndices.Length; indicesIndex++) - { - var frameIndex = BlendShapeFrameIndices[indicesIndex]; - var weight = BlendShapeFrameWeights[indicesIndex]; - var delta = BlendShapeVertices[frameIndex * vertexCount + vertexIndex]; - original += delta * weight; - } - - ResultVertices[vertexIndex] = original; - } + _blendShapePreviewContext.ComputeBlendShape(_blendShapeWeights, _originalVertices, _vertices); } public void Dispose() { _originalVertices.Dispose(); _vertices.Dispose(); - _blendShapeVertices.Dispose(); - } - - public void GetBlendShape(int shapeIndex, float weight, List frameIndices, List frameWeights) - { - const float blendShapeEpsilon = 0.0001f; - var frames = _blendShapeFrameInfo[shapeIndex]; - - if (Mathf.Abs(weight) <= blendShapeEpsilon && ZeroForWeightZero()) - { - // the blendShape is not active - return; - } - - bool ZeroForWeightZero() - { - if (frames.Length == 1) return true; - var first = frames.First(); - var end = frames.Last(); - - // both weight are same sign, zero for 0 weight - if (first.weight <= 0 && end.weight <= 0) return true; - if (first.weight >= 0 && end.weight >= 0) return true; - - return false; - } - - if (frames.Length == 1) - { - // simplest and likely - var frame = frames[0]; - frameIndices.Add(frame.globalIndex); - frameWeights.Add(weight / frame.weight); - } - else - { - // multi frame - - var firstFrame = frames[0]; - var lastFrame = frames.Last(); - - if (firstFrame.weight > 0 && weight < firstFrame.weight) - { - // if all weights are positive and the weight is less than first weight: lerp 0..first - frameIndices.Add(firstFrame.globalIndex); - frameWeights.Add(weight / firstFrame.weight); - } - - if (lastFrame.weight < 0 && weight > lastFrame.weight) - { - // if all weights are negative and the weight is more than last weight: lerp last..0 - frameIndices.Add(lastFrame.globalIndex); - frameWeights.Add(weight / lastFrame.weight); - } - - // otherwise, lerp between two surrounding frames OR nearest two frames - var (lessFrame, greaterFrame) = FindFrame(); - var weightDiff = greaterFrame.weight - lessFrame.weight; - var lessFrameWeight = (weight - lessFrame.weight) / weightDiff; - var graterFrameWeight = (greaterFrame.weight - weight) / weightDiff; - - if (!(Mathf.Abs(lessFrameWeight) < blendShapeEpsilon)) - { - frameIndices.Add(lessFrame.globalIndex); - frameWeights.Add(lessFrameWeight); - } - - if (!(Mathf.Abs(graterFrameWeight) < blendShapeEpsilon)) - { - frameIndices.Add(greaterFrame.globalIndex); - frameWeights.Add(graterFrameWeight); - } - } - - return; - - // TODO: merge this logic with it in MeshInfo2 - ((float weight, int globalIndex), (float weight, int globalIndex)) FindFrame() - { - for (var i = 1; i < frames.Length; i++) - { - if (weight <= frames[i].weight) - return (frames[i - 1], frames[i]); - } - - return (frames[frames.Length - 2], frames[frames.Length - 1]); - } } } } \ No newline at end of file From de31fa04268dac9447cb2fd608d6267f10f68fd7 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 01:39:25 +0900 Subject: [PATCH 05/27] chore: use NativeSlice instead of NativeArray --- Editor/EditModePreview/BlendShapePreviewContext.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Editor/EditModePreview/BlendShapePreviewContext.cs b/Editor/EditModePreview/BlendShapePreviewContext.cs index bb0af58dc..e54c78fc9 100644 --- a/Editor/EditModePreview/BlendShapePreviewContext.cs +++ b/Editor/EditModePreview/BlendShapePreviewContext.cs @@ -49,8 +49,8 @@ public BlendShapePreviewContext(Mesh originalMesh) public void ComputeBlendShape( float[] blendShapeWeights, - NativeArray originalVertices, - NativeArray outputVertices) + NativeSlice originalVertices, + NativeSlice outputVertices) { System.Diagnostics.Debug.Assert(originalVertices.Length == _vertexCount); System.Diagnostics.Debug.Assert(outputVertices.Length == _vertexCount); @@ -85,14 +85,14 @@ public void ComputeBlendShape( struct ApplyBlendShapeJob: IJobParallelFor { [ReadOnly] - public NativeArray OriginalVertices; + public NativeSlice OriginalVertices; [ReadOnly] public NativeArray BlendShapeVertices; [ReadOnly] public NativeArray BlendShapeFrameIndices; [ReadOnly] public NativeArray BlendShapeFrameWeights; - public NativeArray ResultVertices; + public NativeSlice ResultVertices; public void Execute (int vertexIndex) { From 37286bc511a7233a98b04f1afda00f9d716d7b45 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 02:30:13 +0900 Subject: [PATCH 06/27] chore: add RemoveMeshByBlendShapePreviewContext --- .../RemoveMeshByBlendShapePreviewContext.cs | 54 +++++++++++++++++++ ...moveMeshByBlendShapePreviewContext.cs.meta | 3 ++ 2 files changed, 57 insertions(+) create mode 100644 Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs create mode 100644 Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs.meta diff --git a/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs b/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs new file mode 100644 index 000000000..964b09a93 --- /dev/null +++ b/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.EditModePreview +{ + class RemoveMeshByBlendShapePreviewContext : IDisposable + { + // PerVertexBlendShapeRemoveFlags[vertexIndex / 32 + blendShapeIndex * _rowVertexCount / 32] & 1 << (vertexIndex % 32) + // blendshape vertex transforms. _blendShapeVertices[vertexIndex + blendShapeIndex * vertexCount] + public NativeArray BlendShapeMovements => _blendShapeMovements; + + private NativeArray _blendShapeMovements; + + private float _previousTolerance = float.NaN; + private HashSet _previousRemovingShapeKeys; + + public RemoveMeshByBlendShapePreviewContext(BlendShapePreviewContext blendShapePreviewContext, + Mesh originalMesh) + { + var vertexCount = originalMesh.vertexCount; + var blendShapeCount = originalMesh.blendShapeCount; + + _blendShapeMovements = new NativeArray(blendShapeCount * vertexCount, Allocator.Persistent); + + try + { + using (var zeros = new NativeArray(vertexCount, Allocator.Temp)) + { + var weights = new float[blendShapeCount]; + for (var i = 0; i < blendShapeCount; i++) + { + weights[i] = 100; + blendShapePreviewContext.ComputeBlendShape(weights, + zeros, + _blendShapeMovements.Slice(i * vertexCount, vertexCount) + ); + } + } + } + catch + { + _blendShapeMovements.Dispose(); + throw; + } + } + + public void Dispose() + { + _blendShapeMovements.Dispose(); + } + } +} \ No newline at end of file diff --git a/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs.meta b/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs.meta new file mode 100644 index 000000000..f7697ed1e --- /dev/null +++ b/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fb9b892b24954095aebd8a18ee194a04 +timeCreated: 1695053774 \ No newline at end of file From 702708c25aa9a4e2cd9fdba0cb89a61fe313dfa2 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 03:26:26 +0900 Subject: [PATCH 07/27] chore: make BoundingBox struct --- Runtime/RemoveMeshInBox.cs | 17 +++++++++++++---- ...om.anatawa12.avatar-optimizer.runtime.asmdef | 3 ++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Runtime/RemoveMeshInBox.cs b/Runtime/RemoveMeshInBox.cs index 649536200..898451d4e 100644 --- a/Runtime/RemoveMeshInBox.cs +++ b/Runtime/RemoveMeshInBox.cs @@ -1,5 +1,6 @@ using System; using CustomLocalization4EditorExtension; +using Unity.Burst; using UnityEngine; namespace Anatawa12.AvatarOptimizer @@ -14,19 +15,27 @@ internal class RemoveMeshInBox : EditSkinnedMeshComponent private void Reset() { - boxes = new[] { new BoundingBox() }; + boxes = new[] { BoundingBox.Default }; } [Serializable] - public class BoundingBox + public struct BoundingBox { [CL4EELocalized("RemoveMeshInBox:BoundingBox:prop:center")] public Vector3 center; [CL4EELocalized("RemoveMeshInBox:BoundingBox:prop:size")] - public Vector3 size = new Vector3(1, 1, 1); + public Vector3 size; [CL4EELocalized("RemoveMeshInBox:BoundingBox:prop:rotation")] - public Quaternion rotation = Quaternion.identity; + public Quaternion rotation; + public static BoundingBox Default = new BoundingBox + { + center = Vector3.zero, + size = new Vector3(1, 1, 1), + rotation = Quaternion.identity, + }; + + [BurstCompile] public bool ContainsVertex(Vector3 point) { var positionInBox = Quaternion.Inverse(rotation) * (point - center); diff --git a/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef b/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef index 9d9f9ecf3..05092ff79 100644 --- a/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef +++ b/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef @@ -3,7 +3,8 @@ "references": [ "GUID:b23bdfe8d18741a29e869995d47026d0", "GUID:8264e72376854221affe9980c91c2fff", - "GUID:8542dbf824204440a818dbc2377cb4d6" + "GUID:8542dbf824204440a818dbc2377cb4d6", + "GUID:2665a8d13d1b3f18800f46e256720795" ], "includePlatforms": [], "excludePlatforms": [], From e7c0c607897bfe898209d0b3f0e5cd2eaaa90403 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 03:42:50 +0900 Subject: [PATCH 08/27] feat: initial experimental implementation of editmode preview --- .../EditModePreview/MeshPreviewController.cs | 421 +++++++++++++++++- .../RemoveMeshByBlendShapePreviewContext.cs | 2 +- Editor/RemoveMeshByBlendShapeEditor.cs | 16 + 3 files changed, 423 insertions(+), 16 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index ea84f925f..f242e1f2d 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -1,7 +1,15 @@ using System; +using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; using UnityEditor; using UnityEngine; +using UnityEngine.Rendering; +using Debug = System.Diagnostics.Debug; +using Object = UnityEngine.Object; namespace Anatawa12.AvatarOptimizer.EditModePreview { @@ -10,9 +18,50 @@ class MeshPreviewController : ScriptableSingleton private static readonly Type[] EditorTypes = { }; public GameObject targetGameObject; + public Mesh originalMesh; + public Mesh previewMesh; + public SkinnedMeshRenderer targetRenderer; + public VersionCounter targetRendererVersion; + + public RemoveMeshInBox removeMeshInBox; + public VersionCounter removeMeshInBoxVersion; + + public RemoveMeshByBlendShape removeMeshByBlendShape; + public VersionCounter removeMeshByBlendShapeVersion; + public bool previewing; + // non serialized properties + private BlendShapePreviewContext _blendShapePreviewContext; + private int[] _subMeshTriangleEndIndices; + private NativeArray _triangles; + [CanBeNull] private RemoveMeshWithBoxPreviewContext _removeMeshWithBoxPreviewContext; + [CanBeNull] private RemoveMeshByBlendShapePreviewContext _removeMeshByBlendShapePreviewContext; + private string[] _blendShapeNames; + private List _indexBuffer; + + struct Triangle + { + public int First; + public int Second; + public int Third; + + public Triangle(int first, int second, int third) + { + First = first; + Second = second; + Third = third; + } + + public Triangle(int first, int second, int third, int baseIndex) : this(first + baseIndex, + second + baseIndex, third + baseIndex) + { + } + } + + public static bool Previewing => instance.previewing; + private void UpdatePreviewing() { if (!previewing) return; @@ -32,40 +81,382 @@ bool ShouldStopPreview() } if (ShouldStopPreview()) + { StopPreview(); + return; + } + + var modified = false; + + if (targetRendererVersion.Update(targetRenderer, () => null) != Changed.Nothing) + { + _removeMeshWithBoxPreviewContext?.OnUpdateSkinnedMeshRenderer(targetRenderer); + modified = true; + } + + switch (removeMeshInBoxVersion.Update(removeMeshInBox, + () => removeMeshInBox = targetGameObject.GetComponent())) + { + default: + case Changed.Updated: + modified = true; + break; + case Changed.Removed: + Debug.Assert(_removeMeshWithBoxPreviewContext != null, + nameof(_removeMeshWithBoxPreviewContext) + " != null"); + _removeMeshWithBoxPreviewContext.Dispose(); + _removeMeshWithBoxPreviewContext = null; + modified = true; + break; + case Changed.Created: + Debug.Assert(_removeMeshWithBoxPreviewContext == null, + nameof(_removeMeshWithBoxPreviewContext) + " == null"); + _removeMeshWithBoxPreviewContext = + new RemoveMeshWithBoxPreviewContext(_blendShapePreviewContext, originalMesh); + modified = true; + break; + case Changed.Nothing: + break; + } + + switch (removeMeshByBlendShapeVersion.Update(removeMeshByBlendShape, + () => removeMeshByBlendShape = targetGameObject.GetComponent())) + { + default: + case Changed.Updated: + modified = true; + break; + case Changed.Removed: + modified = true; + break; + case Changed.Created: + if (_removeMeshByBlendShapePreviewContext == null) + _removeMeshByBlendShapePreviewContext = + new RemoveMeshByBlendShapePreviewContext(_blendShapePreviewContext, originalMesh); + modified = true; + break; + case Changed.Nothing: + break; + } + + if (modified) + UpdatePreviewMesh(); } - private bool StartPreview(GameObject expectedGameObject = null) + private void InitPreviewMesh() + { + var subMeshes = new SubMeshDescriptor[originalMesh.subMeshCount]; + _subMeshTriangleEndIndices = new int[originalMesh.subMeshCount]; + var totalTriangles = 0; + for (var i = 0; i < subMeshes.Length; i++) + { + subMeshes[i] = originalMesh.GetSubMesh(i); + totalTriangles += subMeshes[i].indexCount / 3; + _subMeshTriangleEndIndices[i] = totalTriangles; + } + + _blendShapeNames = new string[originalMesh.blendShapeCount]; + for (var i = 0; i < originalMesh.blendShapeCount; i++) + _blendShapeNames[i] = originalMesh.GetBlendShapeName(i); + + var originalTriangles = originalMesh.triangles; + + _triangles = new NativeArray(totalTriangles, Allocator.Persistent); + _indexBuffer = new List(); + + var trianglesIndex = 0; + foreach (var subMeshDescriptor in subMeshes) + { + var indexStart = subMeshDescriptor.indexStart; + for (var i = 0; i < subMeshDescriptor.indexCount / 3; i++) + { + _triangles[trianglesIndex++] = new Triangle( + originalTriangles[indexStart + i * 3 + 0], + originalTriangles[indexStart + i * 3 + 1], + originalTriangles[indexStart + i * 3 + 2], + subMeshDescriptor.baseVertex); + } + } + + previewMesh = Instantiate(originalMesh); + previewMesh.name = originalMesh.name + " (AAO Preview)"; + previewMesh.indexFormat = IndexFormat.UInt32; + } + + private void SetPreviewMesh() + { + try + { + AnimationMode.BeginSampling(); + + AnimationMode.AddPropertyModification( + EditorCurveBinding.PPtrCurve("", typeof(SkinnedMeshRenderer), "m_Mesh"), + new PropertyModification + { + target = targetRenderer, + propertyPath = "m_Mesh", + objectReference = originalMesh, + }, + true); + + targetRenderer.sharedMesh = previewMesh; + } + finally + { + AnimationMode.EndSampling(); + } + } + + private void UpdatePreviewMesh() + { + var removeBlendShapeIndicesList = new List(); + if (removeMeshByBlendShape) + { + var blendShapes = removeMeshByBlendShape.RemovingShapeKeys; + + for (var i = 0; i < _blendShapeNames.Length; i++) + if (blendShapes.Contains(_blendShapeNames[i])) + removeBlendShapeIndicesList.Add(i); + } + + using (var flags = new NativeArray(_triangles.Length, Allocator.TempJob)) + { + using (var boxes = new NativeArray( + removeMeshInBox.boxes ?? Array.Empty(), Allocator.TempJob)) + using (var blendShapeIndices = + new NativeArray(removeBlendShapeIndicesList.ToArray(), Allocator.TempJob)) + { + var blendShapeAppliedVertices = _removeMeshWithBoxPreviewContext?.Vertices ?? default; + var blendShapeMovements = _removeMeshByBlendShapePreviewContext?.BlendShapeMovements ?? default; + + new FlagTrianglesJob + { + Triangles = _triangles, + RemoveFlags = flags, + VertexCount = originalMesh.vertexCount, + + Boxes = boxes, + BlendShapeAppliedVertices = blendShapeAppliedVertices, + + BlendShapeIndices = blendShapeIndices, + ToleranceSquared = (float)(removeMeshByBlendShape ? removeMeshByBlendShape.tolerance : 0), + BlendShapeMovements = blendShapeMovements, + }.Schedule(_triangles.Length, 1).Complete(); + } + + var subMeshes = new SubMeshDescriptor[_subMeshTriangleEndIndices.Length]; + var subMeshIdx = 0; + var subMeshIndexStart = 0; + + _indexBuffer.Clear(); + + for (var triIdx = 0; triIdx < _triangles.Length; triIdx++) + { + if (flags[triIdx]) + { + _indexBuffer.Add(_triangles[triIdx].First); + _indexBuffer.Add(_triangles[triIdx].Second); + _indexBuffer.Add(_triangles[triIdx].Third); + } + + while (_subMeshTriangleEndIndices[subMeshIdx] >= triIdx) + { + subMeshes[subMeshIdx] = new SubMeshDescriptor(subMeshIndexStart, _indexBuffer.Count - subMeshIndexStart); + subMeshIndexStart = _indexBuffer.Count; + subMeshIdx++; + } + } + + previewMesh.SetTriangles(_indexBuffer, subMeshes.Length); + + for (var i = 0; i < subMeshes.Length; i++) + previewMesh.SetSubMesh(i, subMeshes[i]); + } + } + + [BurstCompile] + struct FlagTrianglesJob : IJobParallelFor + { + [ReadOnly] + public NativeArray Triangles; + public int VertexCount; + public NativeArray RemoveFlags; + + // Remove Mesh in Box + [ReadOnly] + public NativeArray Boxes; + [ReadOnly] + public NativeArray BlendShapeAppliedVertices; + + // Remove Mesh by BlendShape + [ReadOnly] + public NativeArray BlendShapeIndices; + [ReadOnly] + public NativeArray BlendShapeMovements; + + public float ToleranceSquared { get; set; } + + public void Execute(int index) => RemoveFlags[index] = TestTriangle(Triangles[index]); + + private bool TestTriangle(Triangle triangle) + { + // return true if remove + if (BlendShapeIndices.Length != 0) + { + // for RemoveMesh by BlendShape, *any* of vertex is moved, remove the triangle + foreach (var blendShapeIndex in BlendShapeIndices) + { + var movementBase = blendShapeIndex * VertexCount; + + if (TestBlendShape(movementBase, triangle.First)) return true; + if (TestBlendShape(movementBase, triangle.Second)) return true; + if (TestBlendShape(movementBase, triangle.Third)) return true; + } + } + + if (Boxes.Length != 0) + { + foreach (var boundingBox in Boxes) + { + if (boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First]) + && boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First]) + && boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First])) + { + return true; + } + } + } + + return false; + } + + private bool TestBlendShape(int movementBase, int index) => + BlendShapeMovements[movementBase + index].sqrMagnitude > ToleranceSquared; + } + + public bool StartPreview(GameObject expectedGameObject = null) { // Already in AnimationMode of other object if (AnimationMode.InAnimationMode()) return false; // Previewing object if (previewing) return false; - var gameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; - if (gameObject == null) return false; - if (expectedGameObject != null && expectedGameObject != gameObject) return false; - var renderer = gameObject.GetComponent(); - if (renderer == null) return false; - // Editor Components does not exists - if (EditorTypes.All(t => gameObject.GetComponent(t) == null)) return false; + targetGameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; + if (targetGameObject == null) return false; + if (expectedGameObject != null && expectedGameObject != targetGameObject) return false; + targetRenderer = targetGameObject.GetComponent(); + if (targetRenderer == null) return false; + + originalMesh = targetRenderer.sharedMesh; + _blendShapePreviewContext = new BlendShapePreviewContext(originalMesh); + targetRendererVersion.Init(targetRenderer); + + // reset variables + removeMeshInBox = null; + _removeMeshWithBoxPreviewContext = null; + removeMeshByBlendShape = null; + _removeMeshByBlendShapePreviewContext = null; + removeMeshInBoxVersion = default; + removeMeshByBlendShapeVersion = default; + + removeMeshInBox = targetGameObject.GetComponent(); + if (removeMeshInBox) + { + removeMeshInBoxVersion.Init(removeMeshInBox); + _removeMeshWithBoxPreviewContext = + new RemoveMeshWithBoxPreviewContext(_blendShapePreviewContext, originalMesh); + _removeMeshWithBoxPreviewContext.OnUpdateSkinnedMeshRenderer(targetRenderer); + } + + removeMeshByBlendShape = targetGameObject.GetComponent(); + if (removeMeshByBlendShape) + { + removeMeshByBlendShapeVersion.Init(removeMeshByBlendShape); + _removeMeshByBlendShapePreviewContext = + new RemoveMeshByBlendShapePreviewContext(_blendShapePreviewContext, originalMesh); + } + + // modifier component not found + if (!(removeMeshInBox || removeMeshByBlendShape)) + { + StopPreview(); // for dispose invocation + return false; + } + + InitPreviewMesh(); AnimationMode.StartAnimationMode(); - targetGameObject = gameObject; - targetRenderer = renderer; previewing = true; EditorApplication.update -= UpdatePreviewing; EditorApplication.update += UpdatePreviewing; + SetPreviewMesh(); return true; } - private void StopPreview() + public void StopPreview() { - AnimationMode.StopAnimationMode(); - targetGameObject = null; - targetRenderer = null; + if (AnimationMode.InAnimationMode()) AnimationMode.StopAnimationMode(); previewing = false; EditorApplication.update -= UpdatePreviewing; + targetGameObject = null; + targetRenderer = null; + _triangles.Dispose(); + _removeMeshWithBoxPreviewContext?.Dispose(); + _removeMeshWithBoxPreviewContext = null; + _removeMeshByBlendShapePreviewContext?.Dispose(); + _removeMeshByBlendShapePreviewContext = null; + } + + [Serializable] + public struct VersionCounter + { + // preview version + 1 to make default value != EditorUtility.GetDirtyCount(new object) + public int previewVersion; + + public Changed Update(Object current, Func getObject) + { + if (!current) + { + current = getObject(); + if (current) + { + // newly created + previewVersion = EditorUtility.GetDirtyCount(current) + 1; + return Changed.Created; + } + else + { + if (previewVersion == 0) return Changed.Nothing; + + // it seem component is removed + previewVersion = 0; + return Changed.Removed; + } + } + else + { + var currentVersion = EditorUtility.GetDirtyCount(current) + 1; + if (previewVersion == currentVersion) + return Changed.Nothing; + + previewVersion = currentVersion; + return Changed.Updated; + } + + } + + public void Init(Object value) + { + if (value) previewVersion = EditorUtility.GetDirtyCount(value) + 1; + } + } + + internal enum Changed + { + Nothing, + Updated, + Removed, + Created, } } -} \ No newline at end of file +} diff --git a/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs b/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs index 964b09a93..00e296185 100644 --- a/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs +++ b/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs @@ -26,7 +26,7 @@ public RemoveMeshByBlendShapePreviewContext(BlendShapePreviewContext blendShapeP try { - using (var zeros = new NativeArray(vertexCount, Allocator.Temp)) + using (var zeros = new NativeArray(vertexCount, Allocator.TempJob)) { var weights = new float[blendShapeCount]; for (var i = 0; i < blendShapeCount; i++) diff --git a/Editor/RemoveMeshByBlendShapeEditor.cs b/Editor/RemoveMeshByBlendShapeEditor.cs index 3f67a2cff..66ed100b2 100644 --- a/Editor/RemoveMeshByBlendShapeEditor.cs +++ b/Editor/RemoveMeshByBlendShapeEditor.cs @@ -28,6 +28,22 @@ protected override void OnInspectorGUIInner() { var component = (RemoveMeshByBlendShape)target; + // TODO: replace with better GUI + if (EditModePreview.MeshPreviewController.Previewing) + { + if (GUILayout.Button("End Preview")) + { + EditModePreview.MeshPreviewController.instance.StopPreview(); + } + } + else + { + if (GUILayout.Button("Start Preview")) + { + EditModePreview.MeshPreviewController.instance.StartPreview(component.gameObject); + } + } + if (!_renderer) { EditorGUI.BeginDisabledGroup(true); From e58f6fe38cc91f85a4293eaeb74c2b22d59123c3 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 03:56:43 +0900 Subject: [PATCH 09/27] fix: edit component disappear detection --- Editor/EditModePreview/MeshPreviewController.cs | 9 ++++----- Editor/RemoveMeshByBlendShapeEditor.cs | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index f242e1f2d..5d6115bba 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using Unity.Burst; using Unity.Collections; @@ -15,8 +14,6 @@ namespace Anatawa12.AvatarOptimizer.EditModePreview { class MeshPreviewController : ScriptableSingleton { - private static readonly Type[] EditorTypes = { }; - public GameObject targetGameObject; public Mesh originalMesh; public Mesh previewMesh; @@ -74,8 +71,6 @@ bool ShouldStopPreview() if (!AnimationMode.InAnimationMode()) return true; // Showing Inspector changed if (ActiveEditorTracker.sharedTracker.activeEditors[0].target != targetGameObject) return true; - // Preview Component Not Found - if (EditorTypes.All(t => targetGameObject.GetComponent(t) == null)) return true; return false; } @@ -139,6 +134,10 @@ bool ShouldStopPreview() break; } + // modifier component not found + if (!(removeMeshInBox || removeMeshByBlendShape)) + StopPreview(); + if (modified) UpdatePreviewMesh(); } diff --git a/Editor/RemoveMeshByBlendShapeEditor.cs b/Editor/RemoveMeshByBlendShapeEditor.cs index 66ed100b2..3fb351933 100644 --- a/Editor/RemoveMeshByBlendShapeEditor.cs +++ b/Editor/RemoveMeshByBlendShapeEditor.cs @@ -1,4 +1,5 @@ using CustomLocalization4EditorExtension; +using Unity.Collections; using UnityEditor; using UnityEngine; @@ -14,6 +15,7 @@ internal class RemoveMeshByBlendShapeEditor : AvatarTagComponentEditorBase private void OnEnable() { + NativeLeakDetection.Mode = NativeLeakDetectionMode.EnabledWithStackTrace; _renderer = targets.Length == 1 ? ((Component)target).GetComponent() : null; var nestCount = PrefabSafeSet.PrefabSafeSetUtil.PrefabNestCount(serializedObject.targetObject); _shapeKeysSet = PrefabSafeSet.EditorUtil.Create( From fffc15ec1d666e587b713b80bdb599c9fbd4e94a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 03:59:33 +0900 Subject: [PATCH 10/27] fix: IndexOutOfExctpion --- Editor/EditModePreview/MeshPreviewController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index 5d6115bba..19c8475bf 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -258,9 +258,11 @@ private void UpdatePreviewMesh() _indexBuffer.Add(_triangles[triIdx].Third); } - while (_subMeshTriangleEndIndices[subMeshIdx] >= triIdx) + while (subMeshIdx < _subMeshTriangleEndIndices.Length && + _subMeshTriangleEndIndices[subMeshIdx] >= triIdx) { - subMeshes[subMeshIdx] = new SubMeshDescriptor(subMeshIndexStart, _indexBuffer.Count - subMeshIndexStart); + subMeshes[subMeshIdx] = + new SubMeshDescriptor(subMeshIndexStart, _indexBuffer.Count - subMeshIndexStart); subMeshIndexStart = _indexBuffer.Count; subMeshIdx++; } From 6135628505969418208f2669ab106d0a824336d8 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 04:00:32 +0900 Subject: [PATCH 11/27] fix: flag is remove flag but used as keep flag --- Editor/EditModePreview/MeshPreviewController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index 19c8475bf..04dc3c5ad 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -251,7 +251,7 @@ private void UpdatePreviewMesh() for (var triIdx = 0; triIdx < _triangles.Length; triIdx++) { - if (flags[triIdx]) + if (!flags[triIdx]) { _indexBuffer.Add(_triangles[triIdx].First); _indexBuffer.Add(_triangles[triIdx].Second); From b7d79227d2c809666c91f53da484f57e44094e35 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 04:04:09 +0900 Subject: [PATCH 12/27] fix: invalid SetTriangles usage --- Editor/EditModePreview/MeshPreviewController.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index 04dc3c5ad..99c781806 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -243,9 +243,7 @@ private void UpdatePreviewMesh() }.Schedule(_triangles.Length, 1).Complete(); } - var subMeshes = new SubMeshDescriptor[_subMeshTriangleEndIndices.Length]; var subMeshIdx = 0; - var subMeshIndexStart = 0; _indexBuffer.Clear(); @@ -261,17 +259,11 @@ private void UpdatePreviewMesh() while (subMeshIdx < _subMeshTriangleEndIndices.Length && _subMeshTriangleEndIndices[subMeshIdx] >= triIdx) { - subMeshes[subMeshIdx] = - new SubMeshDescriptor(subMeshIndexStart, _indexBuffer.Count - subMeshIndexStart); - subMeshIndexStart = _indexBuffer.Count; + previewMesh.SetTriangles(_indexBuffer, subMeshIdx); + _indexBuffer.Clear(); subMeshIdx++; } } - - previewMesh.SetTriangles(_indexBuffer, subMeshes.Length); - - for (var i = 0; i < subMeshes.Length; i++) - previewMesh.SetSubMesh(i, subMeshes[i]); } } From 46a1299b3eef6b21ebd6cbd0bac08c6a249eac80 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 04:29:04 +0900 Subject: [PATCH 13/27] chore: move everything about actual preview to RemoveMeshPreviewController --- .../EditModePreview/MeshPreviewController.cs | 421 +----------------- .../RemoveMeshPreviewController.cs | 407 +++++++++++++++++ 2 files changed, 418 insertions(+), 410 deletions(-) create mode 100644 Editor/EditModePreview/RemoveMeshPreviewController.cs diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index 99c781806..4579c5802 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -1,330 +1,24 @@ using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; using UnityEditor; using UnityEngine; -using UnityEngine.Rendering; -using Debug = System.Diagnostics.Debug; -using Object = UnityEngine.Object; namespace Anatawa12.AvatarOptimizer.EditModePreview { class MeshPreviewController : ScriptableSingleton { - public GameObject targetGameObject; - public Mesh originalMesh; - public Mesh previewMesh; - - public SkinnedMeshRenderer targetRenderer; - public VersionCounter targetRendererVersion; - - public RemoveMeshInBox removeMeshInBox; - public VersionCounter removeMeshInBoxVersion; - - public RemoveMeshByBlendShape removeMeshByBlendShape; - public VersionCounter removeMeshByBlendShapeVersion; - + public RemoveMeshPreviewController PreviewController; public bool previewing; - - // non serialized properties - private BlendShapePreviewContext _blendShapePreviewContext; - private int[] _subMeshTriangleEndIndices; - private NativeArray _triangles; - [CanBeNull] private RemoveMeshWithBoxPreviewContext _removeMeshWithBoxPreviewContext; - [CanBeNull] private RemoveMeshByBlendShapePreviewContext _removeMeshByBlendShapePreviewContext; - private string[] _blendShapeNames; - private List _indexBuffer; - - struct Triangle - { - public int First; - public int Second; - public int Third; - - public Triangle(int first, int second, int third) - { - First = first; - Second = second; - Third = third; - } - - public Triangle(int first, int second, int third, int baseIndex) : this(first + baseIndex, - second + baseIndex, third + baseIndex) - { - } - } - public static bool Previewing => instance.previewing; private void UpdatePreviewing() { - if (!previewing) return; - - bool ShouldStopPreview() + if (!previewing) { - // target GameObject disappears - if (targetGameObject == null || targetRenderer == null) return true; - // animation mode externally exited - if (!AnimationMode.InAnimationMode()) return true; - // Showing Inspector changed - if (ActiveEditorTracker.sharedTracker.activeEditors[0].target != targetGameObject) return true; - - return false; + EditorApplication.update -= UpdatePreviewing; } - if (ShouldStopPreview()) - { + if (PreviewController.UpdatePreviewing()) StopPreview(); - return; - } - - var modified = false; - - if (targetRendererVersion.Update(targetRenderer, () => null) != Changed.Nothing) - { - _removeMeshWithBoxPreviewContext?.OnUpdateSkinnedMeshRenderer(targetRenderer); - modified = true; - } - - switch (removeMeshInBoxVersion.Update(removeMeshInBox, - () => removeMeshInBox = targetGameObject.GetComponent())) - { - default: - case Changed.Updated: - modified = true; - break; - case Changed.Removed: - Debug.Assert(_removeMeshWithBoxPreviewContext != null, - nameof(_removeMeshWithBoxPreviewContext) + " != null"); - _removeMeshWithBoxPreviewContext.Dispose(); - _removeMeshWithBoxPreviewContext = null; - modified = true; - break; - case Changed.Created: - Debug.Assert(_removeMeshWithBoxPreviewContext == null, - nameof(_removeMeshWithBoxPreviewContext) + " == null"); - _removeMeshWithBoxPreviewContext = - new RemoveMeshWithBoxPreviewContext(_blendShapePreviewContext, originalMesh); - modified = true; - break; - case Changed.Nothing: - break; - } - - switch (removeMeshByBlendShapeVersion.Update(removeMeshByBlendShape, - () => removeMeshByBlendShape = targetGameObject.GetComponent())) - { - default: - case Changed.Updated: - modified = true; - break; - case Changed.Removed: - modified = true; - break; - case Changed.Created: - if (_removeMeshByBlendShapePreviewContext == null) - _removeMeshByBlendShapePreviewContext = - new RemoveMeshByBlendShapePreviewContext(_blendShapePreviewContext, originalMesh); - modified = true; - break; - case Changed.Nothing: - break; - } - - // modifier component not found - if (!(removeMeshInBox || removeMeshByBlendShape)) - StopPreview(); - - if (modified) - UpdatePreviewMesh(); - } - - private void InitPreviewMesh() - { - var subMeshes = new SubMeshDescriptor[originalMesh.subMeshCount]; - _subMeshTriangleEndIndices = new int[originalMesh.subMeshCount]; - var totalTriangles = 0; - for (var i = 0; i < subMeshes.Length; i++) - { - subMeshes[i] = originalMesh.GetSubMesh(i); - totalTriangles += subMeshes[i].indexCount / 3; - _subMeshTriangleEndIndices[i] = totalTriangles; - } - - _blendShapeNames = new string[originalMesh.blendShapeCount]; - for (var i = 0; i < originalMesh.blendShapeCount; i++) - _blendShapeNames[i] = originalMesh.GetBlendShapeName(i); - - var originalTriangles = originalMesh.triangles; - - _triangles = new NativeArray(totalTriangles, Allocator.Persistent); - _indexBuffer = new List(); - - var trianglesIndex = 0; - foreach (var subMeshDescriptor in subMeshes) - { - var indexStart = subMeshDescriptor.indexStart; - for (var i = 0; i < subMeshDescriptor.indexCount / 3; i++) - { - _triangles[trianglesIndex++] = new Triangle( - originalTriangles[indexStart + i * 3 + 0], - originalTriangles[indexStart + i * 3 + 1], - originalTriangles[indexStart + i * 3 + 2], - subMeshDescriptor.baseVertex); - } - } - - previewMesh = Instantiate(originalMesh); - previewMesh.name = originalMesh.name + " (AAO Preview)"; - previewMesh.indexFormat = IndexFormat.UInt32; - } - - private void SetPreviewMesh() - { - try - { - AnimationMode.BeginSampling(); - - AnimationMode.AddPropertyModification( - EditorCurveBinding.PPtrCurve("", typeof(SkinnedMeshRenderer), "m_Mesh"), - new PropertyModification - { - target = targetRenderer, - propertyPath = "m_Mesh", - objectReference = originalMesh, - }, - true); - - targetRenderer.sharedMesh = previewMesh; - } - finally - { - AnimationMode.EndSampling(); - } - } - - private void UpdatePreviewMesh() - { - var removeBlendShapeIndicesList = new List(); - if (removeMeshByBlendShape) - { - var blendShapes = removeMeshByBlendShape.RemovingShapeKeys; - - for (var i = 0; i < _blendShapeNames.Length; i++) - if (blendShapes.Contains(_blendShapeNames[i])) - removeBlendShapeIndicesList.Add(i); - } - - using (var flags = new NativeArray(_triangles.Length, Allocator.TempJob)) - { - using (var boxes = new NativeArray( - removeMeshInBox.boxes ?? Array.Empty(), Allocator.TempJob)) - using (var blendShapeIndices = - new NativeArray(removeBlendShapeIndicesList.ToArray(), Allocator.TempJob)) - { - var blendShapeAppliedVertices = _removeMeshWithBoxPreviewContext?.Vertices ?? default; - var blendShapeMovements = _removeMeshByBlendShapePreviewContext?.BlendShapeMovements ?? default; - - new FlagTrianglesJob - { - Triangles = _triangles, - RemoveFlags = flags, - VertexCount = originalMesh.vertexCount, - - Boxes = boxes, - BlendShapeAppliedVertices = blendShapeAppliedVertices, - - BlendShapeIndices = blendShapeIndices, - ToleranceSquared = (float)(removeMeshByBlendShape ? removeMeshByBlendShape.tolerance : 0), - BlendShapeMovements = blendShapeMovements, - }.Schedule(_triangles.Length, 1).Complete(); - } - - var subMeshIdx = 0; - - _indexBuffer.Clear(); - - for (var triIdx = 0; triIdx < _triangles.Length; triIdx++) - { - if (!flags[triIdx]) - { - _indexBuffer.Add(_triangles[triIdx].First); - _indexBuffer.Add(_triangles[triIdx].Second); - _indexBuffer.Add(_triangles[triIdx].Third); - } - - while (subMeshIdx < _subMeshTriangleEndIndices.Length && - _subMeshTriangleEndIndices[subMeshIdx] >= triIdx) - { - previewMesh.SetTriangles(_indexBuffer, subMeshIdx); - _indexBuffer.Clear(); - subMeshIdx++; - } - } - } - } - - [BurstCompile] - struct FlagTrianglesJob : IJobParallelFor - { - [ReadOnly] - public NativeArray Triangles; - public int VertexCount; - public NativeArray RemoveFlags; - - // Remove Mesh in Box - [ReadOnly] - public NativeArray Boxes; - [ReadOnly] - public NativeArray BlendShapeAppliedVertices; - - // Remove Mesh by BlendShape - [ReadOnly] - public NativeArray BlendShapeIndices; - [ReadOnly] - public NativeArray BlendShapeMovements; - - public float ToleranceSquared { get; set; } - - public void Execute(int index) => RemoveFlags[index] = TestTriangle(Triangles[index]); - - private bool TestTriangle(Triangle triangle) - { - // return true if remove - if (BlendShapeIndices.Length != 0) - { - // for RemoveMesh by BlendShape, *any* of vertex is moved, remove the triangle - foreach (var blendShapeIndex in BlendShapeIndices) - { - var movementBase = blendShapeIndex * VertexCount; - - if (TestBlendShape(movementBase, triangle.First)) return true; - if (TestBlendShape(movementBase, triangle.Second)) return true; - if (TestBlendShape(movementBase, triangle.Third)) return true; - } - } - - if (Boxes.Length != 0) - { - foreach (var boundingBox in Boxes) - { - if (boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First]) - && boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First]) - && boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First])) - { - return true; - } - } - } - - return false; - } - - private bool TestBlendShape(int movementBase, int index) => - BlendShapeMovements[movementBase + index].sqrMagnitude > ToleranceSquared; } public bool StartPreview(GameObject expectedGameObject = null) @@ -334,122 +28,29 @@ public bool StartPreview(GameObject expectedGameObject = null) // Previewing object if (previewing) return false; - targetGameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; - if (targetGameObject == null) return false; - if (expectedGameObject != null && expectedGameObject != targetGameObject) return false; - targetRenderer = targetGameObject.GetComponent(); - if (targetRenderer == null) return false; - - originalMesh = targetRenderer.sharedMesh; - _blendShapePreviewContext = new BlendShapePreviewContext(originalMesh); - targetRendererVersion.Init(targetRenderer); - - // reset variables - removeMeshInBox = null; - _removeMeshWithBoxPreviewContext = null; - removeMeshByBlendShape = null; - _removeMeshByBlendShapePreviewContext = null; - removeMeshInBoxVersion = default; - removeMeshByBlendShapeVersion = default; - - removeMeshInBox = targetGameObject.GetComponent(); - if (removeMeshInBox) - { - removeMeshInBoxVersion.Init(removeMeshInBox); - _removeMeshWithBoxPreviewContext = - new RemoveMeshWithBoxPreviewContext(_blendShapePreviewContext, originalMesh); - _removeMeshWithBoxPreviewContext.OnUpdateSkinnedMeshRenderer(targetRenderer); - } - - removeMeshByBlendShape = targetGameObject.GetComponent(); - if (removeMeshByBlendShape) + try { - removeMeshByBlendShapeVersion.Init(removeMeshByBlendShape); - _removeMeshByBlendShapePreviewContext = - new RemoveMeshByBlendShapePreviewContext(_blendShapePreviewContext, originalMesh); + PreviewController = new RemoveMeshPreviewController(expectedGameObject); } - - // modifier component not found - if (!(removeMeshInBox || removeMeshByBlendShape)) + catch (Exception e) { - StopPreview(); // for dispose invocation + Debug.LogException(e); return false; } - InitPreviewMesh(); - - AnimationMode.StartAnimationMode(); previewing = true; EditorApplication.update -= UpdatePreviewing; EditorApplication.update += UpdatePreviewing; - SetPreviewMesh(); + PreviewController.BeginPreview(); return true; } public void StopPreview() { - if (AnimationMode.InAnimationMode()) AnimationMode.StopAnimationMode(); previewing = false; + PreviewController.Dispose(); + PreviewController = null; EditorApplication.update -= UpdatePreviewing; - targetGameObject = null; - targetRenderer = null; - _triangles.Dispose(); - _removeMeshWithBoxPreviewContext?.Dispose(); - _removeMeshWithBoxPreviewContext = null; - _removeMeshByBlendShapePreviewContext?.Dispose(); - _removeMeshByBlendShapePreviewContext = null; - } - - [Serializable] - public struct VersionCounter - { - // preview version + 1 to make default value != EditorUtility.GetDirtyCount(new object) - public int previewVersion; - - public Changed Update(Object current, Func getObject) - { - if (!current) - { - current = getObject(); - if (current) - { - // newly created - previewVersion = EditorUtility.GetDirtyCount(current) + 1; - return Changed.Created; - } - else - { - if (previewVersion == 0) return Changed.Nothing; - - // it seem component is removed - previewVersion = 0; - return Changed.Removed; - } - } - else - { - var currentVersion = EditorUtility.GetDirtyCount(current) + 1; - if (previewVersion == currentVersion) - return Changed.Nothing; - - previewVersion = currentVersion; - return Changed.Updated; - } - - } - - public void Init(Object value) - { - if (value) previewVersion = EditorUtility.GetDirtyCount(value) + 1; - } - } - - internal enum Changed - { - Nothing, - Updated, - Removed, - Created, } } } diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs new file mode 100644 index 000000000..627a939b9 --- /dev/null +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; +using Object = UnityEngine.Object; + +namespace Anatawa12.AvatarOptimizer.EditModePreview +{ + class RemoveMeshPreviewController : IDisposable + { + public RemoveMeshPreviewController(GameObject expectedGameObject = null) + { + // Already in AnimationMode of other object + if (AnimationMode.InAnimationMode()) + throw new Exception("Already In Animation Mode"); + // Previewing object + + _targetGameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; + if (_targetGameObject == null) + throw new Exception("Already In Animation Mode"); + if (expectedGameObject != null && expectedGameObject != _targetGameObject) + throw new Exception("Unexpected GameObject"); + _targetRenderer = new ComponentHolder(_targetGameObject.GetComponent()); + if (_targetRenderer.Value == null) + throw new Exception("Renderer Not Found"); + + OriginalMesh = _targetRenderer.Value.sharedMesh; + _blendShapePreviewContext = new BlendShapePreviewContext(OriginalMesh); + + _removeMeshInBox = default; + _removeMeshByBlendShape = default; + + var subMeshes = new SubMeshDescriptor[OriginalMesh.subMeshCount]; + _subMeshTriangleEndIndices = new int[OriginalMesh.subMeshCount]; + var totalTriangles = 0; + for (var i = 0; i < subMeshes.Length; i++) + { + subMeshes[i] = OriginalMesh.GetSubMesh(i); + totalTriangles += subMeshes[i].indexCount / 3; + _subMeshTriangleEndIndices[i] = totalTriangles; + } + + _blendShapeNames = new string[OriginalMesh.blendShapeCount]; + for (var i = 0; i < OriginalMesh.blendShapeCount; i++) + _blendShapeNames[i] = OriginalMesh.GetBlendShapeName(i); + + var originalTriangles = OriginalMesh.triangles; + + _triangles = new NativeArray(totalTriangles, Allocator.Persistent); + _indexBuffer = new List(); + + var trianglesIndex = 0; + foreach (var subMeshDescriptor in subMeshes) + { + var indexStart = subMeshDescriptor.indexStart; + for (var i = 0; i < subMeshDescriptor.indexCount / 3; i++) + { + _triangles[trianglesIndex++] = new Triangle( + originalTriangles[indexStart + i * 3 + 0], + originalTriangles[indexStart + i * 3 + 1], + originalTriangles[indexStart + i * 3 + 2], + subMeshDescriptor.baseVertex); + } + } + + PreviewMesh = Object.Instantiate(OriginalMesh); + PreviewMesh.name = OriginalMesh.name + " (AAO Preview)"; + PreviewMesh.indexFormat = IndexFormat.UInt32; + } + + public void BeginPreview() + { + AnimationMode.StartAnimationMode(); + SetPreviewMesh(); + } + + private readonly GameObject _targetGameObject; + public readonly Mesh OriginalMesh; + public readonly Mesh PreviewMesh; + + private ComponentHolder _targetRenderer; + + private ComponentHolder _removeMeshInBox; + + private ComponentHolder _removeMeshByBlendShape; + + // non serialized properties + private BlendShapePreviewContext _blendShapePreviewContext; + private int[] _subMeshTriangleEndIndices; + private NativeArray _triangles; + [CanBeNull] private RemoveMeshWithBoxPreviewContext _removeMeshWithBoxPreviewContext; + [CanBeNull] private RemoveMeshByBlendShapePreviewContext _removeMeshByBlendShapePreviewContext; + private string[] _blendShapeNames; + private List _indexBuffer; + + struct Triangle + { + public int First; + public int Second; + public int Third; + + public Triangle(int first, int second, int third) + { + First = first; + Second = second; + Third = third; + } + + public Triangle(int first, int second, int third, int baseIndex) : this(first + baseIndex, + second + baseIndex, third + baseIndex) + { + } + } + + /// True if this is no longer valid + public bool UpdatePreviewing() + { + bool ShouldStopPreview() + { + // target GameObject disappears + if (_targetGameObject == null || _targetRenderer.Value == null) return true; + // animation mode externally exited + if (!AnimationMode.InAnimationMode()) return true; + // Showing Inspector changed + if (ActiveEditorTracker.sharedTracker.activeEditors[0].target != _targetGameObject) return true; + + return false; + } + + if (ShouldStopPreview()) return true; + + var modified = false; + + if (_targetRenderer.Update(null) != Changed.Nothing) + { + _removeMeshWithBoxPreviewContext?.OnUpdateSkinnedMeshRenderer(_targetRenderer.Value); + modified = true; + } + + switch (_removeMeshInBox.Update(_targetGameObject)) + { + default: + case Changed.Updated: + modified = true; + break; + case Changed.Removed: + Debug.Assert(_removeMeshWithBoxPreviewContext != null, + nameof(_removeMeshWithBoxPreviewContext) + " != null"); + _removeMeshWithBoxPreviewContext.Dispose(); + _removeMeshWithBoxPreviewContext = null; + modified = true; + break; + case Changed.Created: + Debug.Assert(_removeMeshWithBoxPreviewContext == null, + nameof(_removeMeshWithBoxPreviewContext) + " == null"); + _removeMeshWithBoxPreviewContext = + new RemoveMeshWithBoxPreviewContext(_blendShapePreviewContext, OriginalMesh); + modified = true; + break; + case Changed.Nothing: + break; + } + + switch (_removeMeshByBlendShape.Update(_targetGameObject)) + { + default: + case Changed.Updated: + modified = true; + break; + case Changed.Removed: + modified = true; + break; + case Changed.Created: + if (_removeMeshByBlendShapePreviewContext == null) + _removeMeshByBlendShapePreviewContext = + new RemoveMeshByBlendShapePreviewContext(_blendShapePreviewContext, OriginalMesh); + modified = true; + break; + case Changed.Nothing: + break; + } + + if (modified) + UpdatePreviewMesh(); + + // modifier component not found + if (!(_removeMeshInBox.Value || _removeMeshByBlendShape.Value)) return false; + + return false; + } + + private void SetPreviewMesh() + { + try + { + AnimationMode.BeginSampling(); + + AnimationMode.AddPropertyModification( + EditorCurveBinding.PPtrCurve("", typeof(SkinnedMeshRenderer), "m_Mesh"), + new PropertyModification + { + target = _targetRenderer.Value, + propertyPath = "m_Mesh", + objectReference = OriginalMesh, + }, + true); + + _targetRenderer.Value.sharedMesh = PreviewMesh; + } + finally + { + AnimationMode.EndSampling(); + } + } + + private void UpdatePreviewMesh() + { + var removeBlendShapeIndicesList = new List(); + if (_removeMeshByBlendShape.Value) + { + var blendShapes = _removeMeshByBlendShape.Value.RemovingShapeKeys; + + for (var i = 0; i < _blendShapeNames.Length; i++) + if (blendShapes.Contains(_blendShapeNames[i])) + removeBlendShapeIndicesList.Add(i); + } + + using (var flags = new NativeArray(_triangles.Length, Allocator.TempJob)) + { + using (var boxes = new NativeArray( + _removeMeshInBox.Value.boxes ?? Array.Empty(), Allocator.TempJob)) + using (var blendShapeIndices = + new NativeArray(removeBlendShapeIndicesList.ToArray(), Allocator.TempJob)) + { + var blendShapeAppliedVertices = _removeMeshWithBoxPreviewContext?.Vertices ?? default; + var blendShapeMovements = _removeMeshByBlendShapePreviewContext?.BlendShapeMovements ?? default; + var tolerance = + (float)(_removeMeshByBlendShape.Value ? _removeMeshByBlendShape.Value.tolerance : 0); + + new FlagTrianglesJob + { + Triangles = _triangles, + RemoveFlags = flags, + VertexCount = OriginalMesh.vertexCount, + + Boxes = boxes, + BlendShapeAppliedVertices = blendShapeAppliedVertices, + + BlendShapeIndices = blendShapeIndices, + ToleranceSquared = tolerance * tolerance, + BlendShapeMovements = blendShapeMovements, + }.Schedule(_triangles.Length, 1).Complete(); + } + + var subMeshIdx = 0; + + _indexBuffer.Clear(); + + for (var triIdx = 0; triIdx < _triangles.Length; triIdx++) + { + if (!flags[triIdx]) + { + _indexBuffer.Add(_triangles[triIdx].First); + _indexBuffer.Add(_triangles[triIdx].Second); + _indexBuffer.Add(_triangles[triIdx].Third); + } + + while (subMeshIdx < _subMeshTriangleEndIndices.Length && + _subMeshTriangleEndIndices[subMeshIdx] >= triIdx) + { + PreviewMesh.SetTriangles(_indexBuffer, subMeshIdx); + _indexBuffer.Clear(); + subMeshIdx++; + } + } + } + } + + [BurstCompile] + struct FlagTrianglesJob : IJobParallelFor + { + [ReadOnly] + public NativeArray Triangles; + public int VertexCount; + public NativeArray RemoveFlags; + + // Remove Mesh in Box + [ReadOnly] + public NativeArray Boxes; + [ReadOnly] + public NativeArray BlendShapeAppliedVertices; + + // Remove Mesh by BlendShape + [ReadOnly] + public NativeArray BlendShapeIndices; + [ReadOnly] + public NativeArray BlendShapeMovements; + + public float ToleranceSquared { get; set; } + + public void Execute(int index) => RemoveFlags[index] = TestTriangle(Triangles[index]); + + private bool TestTriangle(Triangle triangle) + { + // return true if remove + if (BlendShapeIndices.Length != 0) + { + // for RemoveMesh by BlendShape, *any* of vertex is moved, remove the triangle + foreach (var blendShapeIndex in BlendShapeIndices) + { + var movementBase = blendShapeIndex * VertexCount; + + if (TestBlendShape(movementBase, triangle.First)) return true; + if (TestBlendShape(movementBase, triangle.Second)) return true; + if (TestBlendShape(movementBase, triangle.Third)) return true; + } + } + + if (Boxes.Length != 0) + { + foreach (var boundingBox in Boxes) + { + if (boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First]) + && boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First]) + && boundingBox.ContainsVertex(BlendShapeAppliedVertices[triangle.First])) + { + return true; + } + } + } + + return false; + } + + private bool TestBlendShape(int movementBase, int index) => + BlendShapeMovements[movementBase + index].sqrMagnitude > ToleranceSquared; + } + + public void Dispose() + { + if (AnimationMode.InAnimationMode()) AnimationMode.StopAnimationMode(); + _triangles.Dispose(); + _removeMeshWithBoxPreviewContext?.Dispose(); + _removeMeshByBlendShapePreviewContext?.Dispose(); + } + + public struct ComponentHolder where T : Component + { + public T Value => _value; + // preview version + 1 to make default value != EditorUtility.GetDirtyCount(new object) + private int _previousVersion; + private T _value; + + public ComponentHolder(T value) + { + _value = value; + if (value) _previousVersion = EditorUtility.GetDirtyCount(value) + 1; + else _previousVersion = 0; + } + + public Changed Update(GameObject gameObject) + { + if (!_value) + { + _value = gameObject ? gameObject.GetComponent() : null; + if (_value) + { + // newly created + _previousVersion = EditorUtility.GetDirtyCount(_value) + 1; + return Changed.Created; + } + else + { + if (_previousVersion == 0) return Changed.Nothing; + + // it seem component is removed + _previousVersion = 0; + return Changed.Removed; + } + } + else + { + var currentVersion = EditorUtility.GetDirtyCount(_value) + 1; + if (_previousVersion == currentVersion) + return Changed.Nothing; + + _previousVersion = currentVersion; + return Changed.Updated; + } + + } + } + + internal enum Changed + { + Nothing, + Updated, + Removed, + Created, + } + } +} \ No newline at end of file From 590e4d0df17162a5b23ff7d0a523dcfcaf741683 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 04:34:52 +0900 Subject: [PATCH 14/27] fix: missing file --- .../RemoveMeshPreviewController.cs.meta | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Editor/EditModePreview/RemoveMeshPreviewController.cs.meta diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs.meta b/Editor/EditModePreview/RemoveMeshPreviewController.cs.meta new file mode 100644 index 000000000..425bb36b5 --- /dev/null +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e07879318a84723897e9f7b39e3ad73 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From cc93420e88a90ddd2848828925a3539ee27aaab5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 04:37:58 +0900 Subject: [PATCH 15/27] fix: BlendShapePreviewContext is not disposed --- Editor/EditModePreview/RemoveMeshPreviewController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs index 627a939b9..e2a7f09ff 100644 --- a/Editor/EditModePreview/RemoveMeshPreviewController.cs +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -347,6 +347,7 @@ public void Dispose() _triangles.Dispose(); _removeMeshWithBoxPreviewContext?.Dispose(); _removeMeshByBlendShapePreviewContext?.Dispose(); + _blendShapePreviewContext?.Dispose(); } public struct ComponentHolder where T : Component From 0f916e3995d7015e0041795562462715773d2032 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 04:38:43 +0900 Subject: [PATCH 16/27] chore: make more fields read only --- Editor/EditModePreview/RemoveMeshPreviewController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs index e2a7f09ff..cd58d7fa1 100644 --- a/Editor/EditModePreview/RemoveMeshPreviewController.cs +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -90,13 +90,13 @@ public void BeginPreview() private ComponentHolder _removeMeshByBlendShape; // non serialized properties - private BlendShapePreviewContext _blendShapePreviewContext; - private int[] _subMeshTriangleEndIndices; + private readonly BlendShapePreviewContext _blendShapePreviewContext; + private readonly int[] _subMeshTriangleEndIndices; private NativeArray _triangles; [CanBeNull] private RemoveMeshWithBoxPreviewContext _removeMeshWithBoxPreviewContext; [CanBeNull] private RemoveMeshByBlendShapePreviewContext _removeMeshByBlendShapePreviewContext; - private string[] _blendShapeNames; - private List _indexBuffer; + private readonly string[] _blendShapeNames; + private readonly List _indexBuffer; struct Triangle { From cf24ae5ae1fd2a4a1a1883a45aa005eacfd4b81f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 04:52:58 +0900 Subject: [PATCH 17/27] fix: error with some component missing --- Editor/EditModePreview/RemoveMeshPreviewController.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs index cd58d7fa1..180f48642 100644 --- a/Editor/EditModePreview/RemoveMeshPreviewController.cs +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -233,12 +233,15 @@ private void UpdatePreviewMesh() using (var flags = new NativeArray(_triangles.Length, Allocator.TempJob)) { using (var boxes = new NativeArray( - _removeMeshInBox.Value.boxes ?? Array.Empty(), Allocator.TempJob)) + _removeMeshInBox.Value != null + ? _removeMeshInBox.Value.boxes + : Array.Empty(), Allocator.TempJob)) using (var blendShapeIndices = new NativeArray(removeBlendShapeIndicesList.ToArray(), Allocator.TempJob)) + using (var empty = new NativeArray(0, Allocator.TempJob)) { - var blendShapeAppliedVertices = _removeMeshWithBoxPreviewContext?.Vertices ?? default; - var blendShapeMovements = _removeMeshByBlendShapePreviewContext?.BlendShapeMovements ?? default; + var blendShapeAppliedVertices = _removeMeshWithBoxPreviewContext?.Vertices ?? empty; + var blendShapeMovements = _removeMeshByBlendShapePreviewContext?.BlendShapeMovements ?? empty; var tolerance = (float)(_removeMeshByBlendShape.Value ? _removeMeshByBlendShape.Value.tolerance : 0); From 4febfa3b6b36dfabfde549d90add86da4f59743f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 05:19:53 +0900 Subject: [PATCH 18/27] fix: SubMesh generation is not correct --- Editor/EditModePreview/RemoveMeshPreviewController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs index 180f48642..3b55e47e8 100644 --- a/Editor/EditModePreview/RemoveMeshPreviewController.cs +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -274,7 +274,7 @@ private void UpdatePreviewMesh() } while (subMeshIdx < _subMeshTriangleEndIndices.Length && - _subMeshTriangleEndIndices[subMeshIdx] >= triIdx) + triIdx + 1 == _subMeshTriangleEndIndices[subMeshIdx]) { PreviewMesh.SetTriangles(_indexBuffer, subMeshIdx); _indexBuffer.Clear(); From 73bb95d7691e9a3070bb0e9877cbfa88c3c7b2f0 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 05:35:15 +0900 Subject: [PATCH 19/27] fix: blendShapeMovements is not correct --- Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs b/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs index 00e296185..44fe836ef 100644 --- a/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs +++ b/Editor/EditModePreview/RemoveMeshByBlendShapePreviewContext.cs @@ -36,6 +36,7 @@ public RemoveMeshByBlendShapePreviewContext(BlendShapePreviewContext blendShapeP zeros, _blendShapeMovements.Slice(i * vertexCount, vertexCount) ); + weights[i] = 0; } } } From b9ece001fe87d651b84ddfd166c43d0a7d8d2305 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 05:57:27 +0900 Subject: [PATCH 20/27] chore: support Reload Domain --- .../EditModePreview/MeshPreviewController.cs | 23 +++++++++++ .../RemoveMeshPreviewController.cs | 39 ++++++++++++------- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index 4579c5802..47eb5559e 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -10,6 +10,25 @@ class MeshPreviewController : ScriptableSingleton public bool previewing; public static bool Previewing => instance.previewing; + public Mesh previewMesh; + public Mesh originalMesh; + public GameObject gameObject; + + protected MeshPreviewController() + { + EditorApplication.delayCall += Initialize; + } + + private void Initialize() + { + if (previewing) + { + PreviewController = new RemoveMeshPreviewController(gameObject, originalMesh, previewMesh); + EditorApplication.update -= UpdatePreviewing; + EditorApplication.update += UpdatePreviewing; + } + } + private void UpdatePreviewing() { if (!previewing) @@ -38,6 +57,10 @@ public bool StartPreview(GameObject expectedGameObject = null) return false; } + gameObject = PreviewController.TargetGameObject; + previewMesh = PreviewController.PreviewMesh; + originalMesh = PreviewController.OriginalMesh; + previewing = true; EditorApplication.update -= UpdatePreviewing; EditorApplication.update += UpdatePreviewing; diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs index 3b55e47e8..206c746ff 100644 --- a/Editor/EditModePreview/RemoveMeshPreviewController.cs +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -13,23 +13,24 @@ namespace Anatawa12.AvatarOptimizer.EditModePreview { class RemoveMeshPreviewController : IDisposable { - public RemoveMeshPreviewController(GameObject expectedGameObject = null) + public RemoveMeshPreviewController(GameObject expectedGameObject = null, Mesh originalMesh = null, Mesh previewMesh = null) { // Already in AnimationMode of other object if (AnimationMode.InAnimationMode()) throw new Exception("Already In Animation Mode"); - // Previewing object - _targetGameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; - if (_targetGameObject == null) + // Previewing object + TargetGameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; + if (TargetGameObject == null) throw new Exception("Already In Animation Mode"); - if (expectedGameObject != null && expectedGameObject != _targetGameObject) + if (expectedGameObject != null && expectedGameObject != TargetGameObject) throw new Exception("Unexpected GameObject"); - _targetRenderer = new ComponentHolder(_targetGameObject.GetComponent()); + _targetRenderer = + new ComponentHolder(TargetGameObject.GetComponent()); if (_targetRenderer.Value == null) throw new Exception("Renderer Not Found"); - OriginalMesh = _targetRenderer.Value.sharedMesh; + OriginalMesh = originalMesh ? originalMesh : _targetRenderer.Value.sharedMesh; _blendShapePreviewContext = new BlendShapePreviewContext(OriginalMesh); _removeMeshInBox = default; @@ -51,6 +52,7 @@ public RemoveMeshPreviewController(GameObject expectedGameObject = null) var originalTriangles = OriginalMesh.triangles; + _triangles = new NativeArray(totalTriangles, Allocator.Persistent); _indexBuffer = new List(); @@ -68,9 +70,16 @@ public RemoveMeshPreviewController(GameObject expectedGameObject = null) } } - PreviewMesh = Object.Instantiate(OriginalMesh); - PreviewMesh.name = OriginalMesh.name + " (AAO Preview)"; - PreviewMesh.indexFormat = IndexFormat.UInt32; + if (previewMesh) + { + PreviewMesh = previewMesh; + } + else + { + PreviewMesh = Object.Instantiate(OriginalMesh); + PreviewMesh.name = OriginalMesh.name + " (AAO Preview)"; + PreviewMesh.indexFormat = IndexFormat.UInt32; + } } public void BeginPreview() @@ -79,7 +88,7 @@ public void BeginPreview() SetPreviewMesh(); } - private readonly GameObject _targetGameObject; + public readonly GameObject TargetGameObject; public readonly Mesh OriginalMesh; public readonly Mesh PreviewMesh; @@ -123,11 +132,11 @@ public bool UpdatePreviewing() bool ShouldStopPreview() { // target GameObject disappears - if (_targetGameObject == null || _targetRenderer.Value == null) return true; + if (TargetGameObject == null || _targetRenderer.Value == null) return true; // animation mode externally exited if (!AnimationMode.InAnimationMode()) return true; // Showing Inspector changed - if (ActiveEditorTracker.sharedTracker.activeEditors[0].target != _targetGameObject) return true; + if (ActiveEditorTracker.sharedTracker.activeEditors[0].target != TargetGameObject) return true; return false; } @@ -142,7 +151,7 @@ bool ShouldStopPreview() modified = true; } - switch (_removeMeshInBox.Update(_targetGameObject)) + switch (_removeMeshInBox.Update(TargetGameObject)) { default: case Changed.Updated: @@ -166,7 +175,7 @@ bool ShouldStopPreview() break; } - switch (_removeMeshByBlendShape.Update(_targetGameObject)) + switch (_removeMeshByBlendShape.Update(TargetGameObject)) { default: case Changed.Updated: From c19cff7c0af7ff747ef46c1b45e47e295bdc30fc Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 06:19:41 +0900 Subject: [PATCH 21/27] chore: disable preview when assembly reload --- Editor/EditModePreview/MeshPreviewController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index 47eb5559e..de16245c2 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -68,6 +68,11 @@ public bool StartPreview(GameObject expectedGameObject = null) return true; } + private void OnDisable() + { + StopPreview(); + } + public void StopPreview() { previewing = false; From 140b3e0b3182c90c79a6b4311900d3da740902a5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 16:35:03 +0900 Subject: [PATCH 22/27] chore: remove responsibility about AnimationMode from RemoveMeshPreviewController Do that in MeshPreviewController --- .../EditModePreview/MeshPreviewController.cs | 38 +++++++++++++- .../RemoveMeshPreviewController.cs | 49 ++----------------- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index de16245c2..eb7dcef6a 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -36,6 +36,13 @@ private void UpdatePreviewing() EditorApplication.update -= UpdatePreviewing; } + // Showing Inspector changed + if (ActiveEditorTracker.sharedTracker.activeEditors[0].target != gameObject) + { + StopPreview(); + return; + } + if (PreviewController.UpdatePreviewing()) StopPreview(); } @@ -49,7 +56,13 @@ public bool StartPreview(GameObject expectedGameObject = null) try { - PreviewController = new RemoveMeshPreviewController(expectedGameObject); + var targetGameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; + if (targetGameObject == null) + throw new Exception("Already In Animation Mode"); + if (expectedGameObject != null && expectedGameObject != targetGameObject) + throw new Exception("Unexpected GameObject"); + + PreviewController = new RemoveMeshPreviewController(targetGameObject); } catch (Exception e) { @@ -64,7 +77,27 @@ public bool StartPreview(GameObject expectedGameObject = null) previewing = true; EditorApplication.update -= UpdatePreviewing; EditorApplication.update += UpdatePreviewing; - PreviewController.BeginPreview(); + AnimationMode.StartAnimationMode(); + try + { + AnimationMode.BeginSampling(); + + AnimationMode.AddPropertyModification( + EditorCurveBinding.PPtrCurve("", typeof(SkinnedMeshRenderer), "m_Mesh"), + new PropertyModification + { + target = PreviewController.TargetRenderer, + propertyPath = "m_Mesh", + objectReference = PreviewController.OriginalMesh, + }, + true); + + PreviewController.TargetRenderer.sharedMesh = PreviewController.PreviewMesh; + } + finally + { + AnimationMode.EndSampling(); + } return true; } @@ -76,6 +109,7 @@ private void OnDisable() public void StopPreview() { previewing = false; + AnimationMode.StopAnimationMode(); PreviewController.Dispose(); PreviewController = null; EditorApplication.update -= UpdatePreviewing; diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs index 206c746ff..441739cf4 100644 --- a/Editor/EditModePreview/RemoveMeshPreviewController.cs +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -13,22 +13,16 @@ namespace Anatawa12.AvatarOptimizer.EditModePreview { class RemoveMeshPreviewController : IDisposable { - public RemoveMeshPreviewController(GameObject expectedGameObject = null, Mesh originalMesh = null, Mesh previewMesh = null) + public RemoveMeshPreviewController([NotNull] GameObject targetGameObject, Mesh originalMesh = null, Mesh previewMesh = null) { - // Already in AnimationMode of other object - if (AnimationMode.InAnimationMode()) - throw new Exception("Already In Animation Mode"); + if (targetGameObject == null) throw new ArgumentNullException(nameof(targetGameObject)); // Previewing object - TargetGameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; - if (TargetGameObject == null) - throw new Exception("Already In Animation Mode"); - if (expectedGameObject != null && expectedGameObject != TargetGameObject) - throw new Exception("Unexpected GameObject"); + TargetGameObject = targetGameObject; _targetRenderer = new ComponentHolder(TargetGameObject.GetComponent()); if (_targetRenderer.Value == null) - throw new Exception("Renderer Not Found"); + throw new ArgumentException("Renderer Not Found", nameof(targetGameObject)); OriginalMesh = originalMesh ? originalMesh : _targetRenderer.Value.sharedMesh; _blendShapePreviewContext = new BlendShapePreviewContext(OriginalMesh); @@ -82,23 +76,15 @@ public RemoveMeshPreviewController(GameObject expectedGameObject = null, Mesh or } } - public void BeginPreview() - { - AnimationMode.StartAnimationMode(); - SetPreviewMesh(); - } - public readonly GameObject TargetGameObject; public readonly Mesh OriginalMesh; public readonly Mesh PreviewMesh; + public SkinnedMeshRenderer TargetRenderer => _targetRenderer.Value; private ComponentHolder _targetRenderer; - private ComponentHolder _removeMeshInBox; - private ComponentHolder _removeMeshByBlendShape; - // non serialized properties private readonly BlendShapePreviewContext _blendShapePreviewContext; private readonly int[] _subMeshTriangleEndIndices; private NativeArray _triangles; @@ -203,30 +189,6 @@ bool ShouldStopPreview() return false; } - private void SetPreviewMesh() - { - try - { - AnimationMode.BeginSampling(); - - AnimationMode.AddPropertyModification( - EditorCurveBinding.PPtrCurve("", typeof(SkinnedMeshRenderer), "m_Mesh"), - new PropertyModification - { - target = _targetRenderer.Value, - propertyPath = "m_Mesh", - objectReference = OriginalMesh, - }, - true); - - _targetRenderer.Value.sharedMesh = PreviewMesh; - } - finally - { - AnimationMode.EndSampling(); - } - } - private void UpdatePreviewMesh() { var removeBlendShapeIndicesList = new List(); @@ -355,7 +317,6 @@ private bool TestBlendShape(int movementBase, int index) => public void Dispose() { - if (AnimationMode.InAnimationMode()) AnimationMode.StopAnimationMode(); _triangles.Dispose(); _removeMeshWithBoxPreviewContext?.Dispose(); _removeMeshByBlendShapePreviewContext?.Dispose(); From e206796734146a534e8fb05abb958e0bdc6fa576 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 17:45:06 +0900 Subject: [PATCH 23/27] chore: use shared AnimationModeDriver for AnimationMode --- .../EditModePreview/MeshPreviewController.cs | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index eb7dcef6a..413f1a6cf 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -1,6 +1,8 @@ using System; +using System.Reflection; using UnityEditor; using UnityEngine; +using Object = UnityEngine.Object; namespace Anatawa12.AvatarOptimizer.EditModePreview { @@ -10,11 +12,14 @@ class MeshPreviewController : ScriptableSingleton public bool previewing; public static bool Previewing => instance.previewing; - public Mesh previewMesh; - public Mesh originalMesh; - public GameObject gameObject; + [SerializeField] private Mesh previewMesh; + [SerializeField] private Mesh originalMesh; + [SerializeField] private GameObject gameObject; + [SerializeField] private Object driverCached; - protected MeshPreviewController() + private Object DriverCached => driverCached ? driverCached : driverCached = AnimationMode.CreateDriver(); + + private void OnEnable() { EditorApplication.delayCall += Initialize; } @@ -29,6 +34,11 @@ private void Initialize() } } + private void OnDisable() + { + PreviewController?.Dispose(); + } + private void UpdatePreviewing() { if (!previewing) @@ -77,7 +87,7 @@ public bool StartPreview(GameObject expectedGameObject = null) previewing = true; EditorApplication.update -= UpdatePreviewing; EditorApplication.update += UpdatePreviewing; - AnimationMode.StartAnimationMode(); + AnimationMode.StartAnimationMode(DriverCached); try { AnimationMode.BeginSampling(); @@ -101,18 +111,42 @@ public bool StartPreview(GameObject expectedGameObject = null) return true; } - private void OnDisable() - { - StopPreview(); - } - public void StopPreview() { previewing = false; - AnimationMode.StopAnimationMode(); + AnimationMode.StopAnimationMode(DriverCached); PreviewController.Dispose(); PreviewController = null; EditorApplication.update -= UpdatePreviewing; } + + // TODO: in Unity 2022, this class must be removed and replaced with UnityEditor.AnimationMode + public static class AnimationMode + { + public static void BeginSampling() => UnityEditor.AnimationMode.BeginSampling(); + public static void EndSampling() => UnityEditor.AnimationMode.EndSampling(); + public static bool InAnimationMode() => UnityEditor.AnimationMode.InAnimationMode(); + public static void StartAnimationMode(Object o) => StartAnimationMode("StartAnimationMode", o); + public static void StopAnimationMode(Object o) => StartAnimationMode("StopAnimationMode", o); + + public static void AddPropertyModification(EditorCurveBinding binding, PropertyModification modification, + bool keepPrefabOverride) => + UnityEditor.AnimationMode.AddPropertyModification(binding, modification, keepPrefabOverride); + + public static Object CreateDriver() => + ScriptableObject.CreateInstance( + typeof(UnityEditor.AnimationMode).Assembly.GetType("UnityEditor.AnimationModeDriver")); + + private static void StartAnimationMode(string name, Object o) + { + var method = typeof(UnityEditor.AnimationMode).GetMethod(name, + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, + null, + new[] { typeof(Object) }, + null); + System.Diagnostics.Debug.Assert(method != null, nameof(method) + " != null"); + method.Invoke(null, new object[] { o }); + } + } } } From e81de7e44cc3dcdc76190a6af80de593f03b689f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 17:57:28 +0900 Subject: [PATCH 24/27] chore: try to support 2020.1 or newer --- .../EditModePreview/MeshPreviewController.cs | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index 413f1a6cf..dd93292a2 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -2,7 +2,9 @@ using System.Reflection; using UnityEditor; using UnityEngine; -using Object = UnityEngine.Object; +#if !UNITY_2020_1_OR_NEWER +using AnimationModeDriver = UnityEngine.Object; +#endif namespace Anatawa12.AvatarOptimizer.EditModePreview { @@ -15,9 +17,9 @@ class MeshPreviewController : ScriptableSingleton [SerializeField] private Mesh previewMesh; [SerializeField] private Mesh originalMesh; [SerializeField] private GameObject gameObject; - [SerializeField] private Object driverCached; + [SerializeField] private AnimationModeDriver driverCached; - private Object DriverCached => driverCached ? driverCached : driverCached = AnimationMode.CreateDriver(); + private AnimationModeDriver DriverCached => driverCached ? driverCached : driverCached = CreateDriver(); private void OnEnable() { @@ -120,33 +122,38 @@ public void StopPreview() EditorApplication.update -= UpdatePreviewing; } - // TODO: in Unity 2022, this class must be removed and replaced with UnityEditor.AnimationMode +#if !UNITY_2020_1_OR_NEWER + private static AnimationModeDriver CreateDriver() => + ScriptableObject.CreateInstance( + typeof(UnityEditor.AnimationMode).Assembly.GetType("UnityEditor.AnimationModeDriver")); +#else + private static AnimationModeDriver CreateDriver() => ScriptableObject.CreateInstance(); +#endif + +#if !UNITY_2020_1_OR_NEWER public static class AnimationMode { public static void BeginSampling() => UnityEditor.AnimationMode.BeginSampling(); public static void EndSampling() => UnityEditor.AnimationMode.EndSampling(); public static bool InAnimationMode() => UnityEditor.AnimationMode.InAnimationMode(); - public static void StartAnimationMode(Object o) => StartAnimationMode("StartAnimationMode", o); - public static void StopAnimationMode(Object o) => StartAnimationMode("StopAnimationMode", o); + public static void StartAnimationMode(AnimationModeDriver o) => StartAnimationMode("StartAnimationMode", o); + public static void StopAnimationMode(AnimationModeDriver o) => StartAnimationMode("StopAnimationMode", o); public static void AddPropertyModification(EditorCurveBinding binding, PropertyModification modification, bool keepPrefabOverride) => UnityEditor.AnimationMode.AddPropertyModification(binding, modification, keepPrefabOverride); - public static Object CreateDriver() => - ScriptableObject.CreateInstance( - typeof(UnityEditor.AnimationMode).Assembly.GetType("UnityEditor.AnimationModeDriver")); - - private static void StartAnimationMode(string name, Object o) + private static void StartAnimationMode(string name, AnimationModeDriver o) { var method = typeof(UnityEditor.AnimationMode).GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, - new[] { typeof(Object) }, + new[] { typeof(AnimationModeDriver) }, null); System.Diagnostics.Debug.Assert(method != null, nameof(method) + " != null"); method.Invoke(null, new object[] { o }); } } +#endif } } From 15f4c82782d8f89fa36ed2df714e8bf9791c402d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 18:09:10 +0900 Subject: [PATCH 25/27] fix: error with nothing selected --- Editor/EditModePreview/MeshPreviewController.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index dd93292a2..a8cb6a768 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -2,6 +2,7 @@ using System.Reflection; using UnityEditor; using UnityEngine; +using Object = UnityEngine.Object; #if !UNITY_2020_1_OR_NEWER using AnimationModeDriver = UnityEngine.Object; #endif @@ -41,6 +42,12 @@ private void OnDisable() PreviewController?.Dispose(); } + private Object ActiveEditor() + { + var editors = ActiveEditorTracker.sharedTracker.activeEditors; + return editors.Length == 0 ? null : editors[0].target; + } + private void UpdatePreviewing() { if (!previewing) @@ -49,7 +56,7 @@ private void UpdatePreviewing() } // Showing Inspector changed - if (ActiveEditorTracker.sharedTracker.activeEditors[0].target != gameObject) + if (ActiveEditor() != gameObject) { StopPreview(); return; @@ -68,7 +75,7 @@ public bool StartPreview(GameObject expectedGameObject = null) try { - var targetGameObject = ActiveEditorTracker.sharedTracker.activeEditors[0].target as GameObject; + var targetGameObject = ActiveEditor() as GameObject; if (targetGameObject == null) throw new Exception("Already In Animation Mode"); if (expectedGameObject != null && expectedGameObject != targetGameObject) From c6bca1cda8769cb9d723370e9e8180e897e59e82 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 19 Sep 2023 18:23:29 +0900 Subject: [PATCH 26/27] chore: pass SkinnedMeshRenderer in RemoveMeshPreviewController --- .../EditModePreview/MeshPreviewController.cs | 96 ++++++++----------- .../RemoveMeshPreviewController.cs | 11 +-- 2 files changed, 42 insertions(+), 65 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index a8cb6a768..ce7373aee 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using JetBrains.Annotations; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; @@ -11,35 +12,27 @@ namespace Anatawa12.AvatarOptimizer.EditModePreview { class MeshPreviewController : ScriptableSingleton { - public RemoveMeshPreviewController PreviewController; - public bool previewing; + [CanBeNull] private RemoveMeshPreviewController _previewController; public static bool Previewing => instance.previewing; + [SerializeField] private bool previewing; [SerializeField] private Mesh previewMesh; [SerializeField] private Mesh originalMesh; - [SerializeField] private GameObject gameObject; + [SerializeField] private SkinnedMeshRenderer targetRenderer; [SerializeField] private AnimationModeDriver driverCached; private AnimationModeDriver DriverCached => driverCached ? driverCached : driverCached = CreateDriver(); private void OnEnable() { - EditorApplication.delayCall += Initialize; - } - - private void Initialize() - { - if (previewing) - { - PreviewController = new RemoveMeshPreviewController(gameObject, originalMesh, previewMesh); - EditorApplication.update -= UpdatePreviewing; - EditorApplication.update += UpdatePreviewing; - } + EditorApplication.update -= Update; + EditorApplication.update += Update; } private void OnDisable() { - PreviewController?.Dispose(); + _previewController?.Dispose(); + EditorApplication.update -= Update; } private Object ActiveEditor() @@ -48,54 +41,43 @@ private Object ActiveEditor() return editors.Length == 0 ? null : editors[0].target; } - private void UpdatePreviewing() + private void Update() { - if (!previewing) + if (previewing) { - EditorApplication.update -= UpdatePreviewing; - } + if (_previewController == null) + _previewController = new RemoveMeshPreviewController(targetRenderer, originalMesh, previewMesh); - // Showing Inspector changed - if (ActiveEditor() != gameObject) - { - StopPreview(); - return; - } + if (targetRenderer == null || ActiveEditor() != targetRenderer.gameObject) + { + StopPreview(); + return; + } - if (PreviewController.UpdatePreviewing()) - StopPreview(); + if (_previewController.UpdatePreviewing()) + StopPreview(); + } } - public bool StartPreview(GameObject expectedGameObject = null) + public void StartPreview(GameObject expectedGameObject = null) { // Already in AnimationMode of other object - if (AnimationMode.InAnimationMode()) return false; - // Previewing object - if (previewing) return false; - - try - { - var targetGameObject = ActiveEditor() as GameObject; - if (targetGameObject == null) - throw new Exception("Already In Animation Mode"); - if (expectedGameObject != null && expectedGameObject != targetGameObject) - throw new Exception("Unexpected GameObject"); + if (AnimationMode.InAnimationMode()) + throw new Exception("Already in Animation Mode"); - PreviewController = new RemoveMeshPreviewController(targetGameObject); - } - catch (Exception e) - { - Debug.LogException(e); - return false; - } + var targetGameObject = ActiveEditor() as GameObject; + if (targetGameObject == null) + throw new Exception("Active Editor is not GameObject"); + if (expectedGameObject != null && expectedGameObject != targetGameObject) + throw new Exception("Unexpected GameObject"); - gameObject = PreviewController.TargetGameObject; - previewMesh = PreviewController.PreviewMesh; - originalMesh = PreviewController.OriginalMesh; + targetRenderer = targetGameObject.GetComponent(); + _previewController = new RemoveMeshPreviewController(targetRenderer); + targetRenderer = _previewController.TargetRenderer; + previewMesh = _previewController.PreviewMesh; + originalMesh = _previewController.OriginalMesh; previewing = true; - EditorApplication.update -= UpdatePreviewing; - EditorApplication.update += UpdatePreviewing; AnimationMode.StartAnimationMode(DriverCached); try { @@ -105,28 +87,26 @@ public bool StartPreview(GameObject expectedGameObject = null) EditorCurveBinding.PPtrCurve("", typeof(SkinnedMeshRenderer), "m_Mesh"), new PropertyModification { - target = PreviewController.TargetRenderer, + target = _previewController.TargetRenderer, propertyPath = "m_Mesh", - objectReference = PreviewController.OriginalMesh, + objectReference = _previewController.OriginalMesh, }, true); - PreviewController.TargetRenderer.sharedMesh = PreviewController.PreviewMesh; + _previewController.TargetRenderer.sharedMesh = _previewController.PreviewMesh; } finally { AnimationMode.EndSampling(); } - return true; } public void StopPreview() { previewing = false; AnimationMode.StopAnimationMode(DriverCached); - PreviewController.Dispose(); - PreviewController = null; - EditorApplication.update -= UpdatePreviewing; + _previewController?.Dispose(); + _previewController = null; } #if !UNITY_2020_1_OR_NEWER diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs index 441739cf4..d70a38ebe 100644 --- a/Editor/EditModePreview/RemoveMeshPreviewController.cs +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -13,16 +13,13 @@ namespace Anatawa12.AvatarOptimizer.EditModePreview { class RemoveMeshPreviewController : IDisposable { - public RemoveMeshPreviewController([NotNull] GameObject targetGameObject, Mesh originalMesh = null, Mesh previewMesh = null) + public RemoveMeshPreviewController([NotNull] SkinnedMeshRenderer targetRenderer, Mesh originalMesh = null, Mesh previewMesh = null) { - if (targetGameObject == null) throw new ArgumentNullException(nameof(targetGameObject)); + if (targetRenderer == null) throw new ArgumentNullException(nameof(targetRenderer)); // Previewing object - TargetGameObject = targetGameObject; - _targetRenderer = - new ComponentHolder(TargetGameObject.GetComponent()); - if (_targetRenderer.Value == null) - throw new ArgumentException("Renderer Not Found", nameof(targetGameObject)); + TargetGameObject = targetRenderer.gameObject; + _targetRenderer = new ComponentHolder(targetRenderer); OriginalMesh = originalMesh ? originalMesh : _targetRenderer.Value.sharedMesh; _blendShapePreviewContext = new BlendShapePreviewContext(OriginalMesh); From a9c0859a3d5efec7f602d10a3a68b1d29e516738 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 21 Sep 2023 04:23:26 +0900 Subject: [PATCH 27/27] chore: preview control --- .../EditModePreview/MeshPreviewController.cs | 81 +++++++++++++++++++ .../RemoveMeshPreviewController.cs | 6 ++ Editor/RemoveMeshByBlendShapeEditor.cs | 16 +--- Editor/RemoveMeshInBoxEditor.cs | 2 + 4 files changed, 90 insertions(+), 15 deletions(-) diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index ce7373aee..245ba1e4a 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reflection; using JetBrains.Annotations; using UnityEditor; @@ -23,6 +24,12 @@ class MeshPreviewController : ScriptableSingleton private AnimationModeDriver DriverCached => driverCached ? driverCached : driverCached = CreateDriver(); + public bool Enabled + { + get => EditorPrefs.GetBool("com.anatawa12.avatar-optimizer.mesh-preview.enabled", true); + set => EditorPrefs.SetBool("com.anatawa12.avatar-optimizer.mesh-preview.enabled", value); + } + private void OnEnable() { EditorApplication.update -= Update; @@ -57,6 +64,80 @@ private void Update() if (_previewController.UpdatePreviewing()) StopPreview(); } + else + { + if (Enabled && StateForImpl(null) == PreviewState.PreviewAble) + { + var editorObj = ActiveEditor(); + if (editorObj is GameObject go && + RemoveMeshPreviewController.EditorTypes.Any(t => go.GetComponent(t))) + { + StartPreview(go); + } + } + } + } + + public enum PreviewState + { + PreviewAble, + PreviewingThat, + + PreviewingOther, + ActiveEditorMismatch, + } + + public static PreviewState StateFor([CanBeNull] Component component) => instance.StateForImpl(component); + + private PreviewState StateForImpl([CanBeNull] Component component) + { + var gameObject = component ? component.gameObject : null; + + if (previewing && targetRenderer && targetRenderer.gameObject == gameObject) + return PreviewState.PreviewingThat; + + if (AnimationMode.InAnimationMode()) + return PreviewState.PreviewingOther; + + if (gameObject && ActiveEditor() as GameObject != gameObject) + return PreviewState.ActiveEditorMismatch; + + return PreviewState.PreviewAble; + } + + public static void ShowPreviewControl(Component component) => instance.ShowPreviewControlImpl(component); + + private void ShowPreviewControlImpl(Component component) + { + switch (StateForImpl(component)) + { + case PreviewState.PreviewAble: + if (GUILayout.Button("Preview")) + { + Enabled = true; + StartPreview(); + } + break; + case PreviewState.PreviewingThat: + if (GUILayout.Button("Stop Preview")) + { + StopPreview(); + Enabled = false; + } + break; + case PreviewState.PreviewingOther: + EditorGUI.BeginDisabledGroup(true); + GUILayout.Button("Preview (other Previewing)"); + EditorGUI.EndDisabledGroup(); + break; + case PreviewState.ActiveEditorMismatch: + EditorGUI.BeginDisabledGroup(true); + GUILayout.Button("Preview (not the active object)"); + EditorGUI.EndDisabledGroup(); + break; + default: + throw new ArgumentOutOfRangeException(); + } } public void StartPreview(GameObject expectedGameObject = null) diff --git a/Editor/EditModePreview/RemoveMeshPreviewController.cs b/Editor/EditModePreview/RemoveMeshPreviewController.cs index d70a38ebe..e574ef11f 100644 --- a/Editor/EditModePreview/RemoveMeshPreviewController.cs +++ b/Editor/EditModePreview/RemoveMeshPreviewController.cs @@ -13,6 +13,12 @@ namespace Anatawa12.AvatarOptimizer.EditModePreview { class RemoveMeshPreviewController : IDisposable { + public static Type[] EditorTypes = + { + typeof(RemoveMeshByBlendShape), + typeof(RemoveMeshInBox), + }; + public RemoveMeshPreviewController([NotNull] SkinnedMeshRenderer targetRenderer, Mesh originalMesh = null, Mesh previewMesh = null) { if (targetRenderer == null) throw new ArgumentNullException(nameof(targetRenderer)); diff --git a/Editor/RemoveMeshByBlendShapeEditor.cs b/Editor/RemoveMeshByBlendShapeEditor.cs index 3fb351933..49c7f1caa 100644 --- a/Editor/RemoveMeshByBlendShapeEditor.cs +++ b/Editor/RemoveMeshByBlendShapeEditor.cs @@ -30,21 +30,7 @@ protected override void OnInspectorGUIInner() { var component = (RemoveMeshByBlendShape)target; - // TODO: replace with better GUI - if (EditModePreview.MeshPreviewController.Previewing) - { - if (GUILayout.Button("End Preview")) - { - EditModePreview.MeshPreviewController.instance.StopPreview(); - } - } - else - { - if (GUILayout.Button("Start Preview")) - { - EditModePreview.MeshPreviewController.instance.StartPreview(component.gameObject); - } - } + EditModePreview.MeshPreviewController.ShowPreviewControl(component); if (!_renderer) { diff --git a/Editor/RemoveMeshInBoxEditor.cs b/Editor/RemoveMeshInBoxEditor.cs index 676c46cc7..f99e634e2 100644 --- a/Editor/RemoveMeshInBoxEditor.cs +++ b/Editor/RemoveMeshInBoxEditor.cs @@ -22,6 +22,8 @@ private void OnEnable() protected override void OnInspectorGUIInner() { + EditModePreview.MeshPreviewController.ShowPreviewControl((Component)target); + // size prop _boxes.isExpanded = true; using (new BoundingBoxEditor.EditorScope(this))