diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index f792afb1e..a8871b890 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -19,6 +19,30 @@ The format is based on [Keep a Changelog]. ### Security +## [1.5.6-beta.2] - 2023-10-16 +### Changed +- Make no-op as possible if no AAO component attached for your avatar [`#603`](https://github.com/anatawa12/AvatarOptimizer/pull/603) +- Error Report window is refreshed after exiting play mode [`#606`](https://github.com/anatawa12/AvatarOptimizer/pull/606) + +### Fixed +- Update notice may show incorrect version [`#602`](https://github.com/anatawa12/AvatarOptimizer/pull/602) +- `Preview` button is not disabled even if mesh is none [`#605`](https://github.com/anatawa12/AvatarOptimizer/pull/605) + +## [1.5.6-beta.1] - 2023-10-16 +### Fixed +- Multi-frame BlendShape can be broken [`#601`](https://github.com/anatawa12/AvatarOptimizer/pull/601) + +## [1.5.5] - 2023-10-15 +## [1.5.5-rc.1] - 2023-10-15 +### Fixed +- BlendShape can be broken with MergeBone Optimization [`#599`](https://github.com/anatawa12/AvatarOptimizer/pull/599) + +## [1.5.5-beta.1] - 2023-10-15 +### Fixed +- Constraints and Animations can be broken with Automatic MergeBone [`#594`](https://github.com/anatawa12/AvatarOptimizer/pull/594) +- NRE with SMR with None with preview system [`#596`](https://github.com/anatawa12/AvatarOptimizer/pull/596) +- Some Multi-Frame BlendShape broken [`#597`](https://github.com/anatawa12/AvatarOptimizer/pull/597) + ## [1.5.4] - 2023-10-14 ### Added - Add compatibility for Satania's KiseteneEx [`#584`](https://github.com/anatawa12/AvatarOptimizer/pull/584) @@ -887,7 +911,12 @@ This release is mistake. - Merge Bone - Clear Endpoint Position -[Unreleased]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.4...HEAD +[Unreleased]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.6-beta.2...HEAD +[1.5.6-beta.2]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.6-beta.1...v1.5.6-beta.2 +[1.5.6-beta.1]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.5...v1.5.6-beta.1 +[1.5.5]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.5-rc.1...v1.5.5 +[1.5.5-rc.1]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.5-beta.1...v1.5.5-rc.1 +[1.5.5-beta.1]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.4...v1.5.5-beta.1 [1.5.4]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.3...v1.5.4 [1.5.3]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.3-beta.1...v1.5.3 [1.5.3-beta.1]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.2...v1.5.3-beta.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index bc85299c2..1c5c20cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,27 @@ The format is based on [Keep a Changelog]. ### Added ### Changed +- Make no-op as possible if no AAO component attached for your avatar `#603` +- Error Report window is refreshed after exiting play mode `#606` ### Deprecated ### Removed ### Fixed +- Multi-frame BlendShape can be broken `#601` +- Update notice may show incorrect version `#602` +- `Preview` button is not disabled even if mesh is none `#605` ### Security +## [1.5.5] - 2023-10-15 +### Fixed +- Constraints and Animations can be broken with Automatic MergeBone [`#594`](https://github.com/anatawa12/AvatarOptimizer/pull/594) +- NRE with SMR with None with preview system [`#596`](https://github.com/anatawa12/AvatarOptimizer/pull/596) +- Some Multi-Frame BlendShape broken [`#597`](https://github.com/anatawa12/AvatarOptimizer/pull/597) +- BlendShape can be broken with MergeBone Optimization [`#599`](https://github.com/anatawa12/AvatarOptimizer/pull/599) + ## [1.5.4] - 2023-10-14 ### Added - Add compatibility for Satania's KiseteneEx [`#584`](https://github.com/anatawa12/AvatarOptimizer/pull/584) @@ -587,7 +599,8 @@ The format is based on [Keep a Changelog]. - Merge Bone - Clear Endpoint Position -[Unreleased]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.4...HEAD +[Unreleased]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.5...HEAD +[1.5.5]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.4...v1.5.5 [1.5.4]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.3...v1.5.4 [1.5.3]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.2...v1.5.3 [1.5.2]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.1...v1.5.2 diff --git a/Editor/CheckForUpdate.cs b/Editor/CheckForUpdate.cs index 03382593a..bb6b41856 100644 --- a/Editor/CheckForUpdate.cs +++ b/Editor/CheckForUpdate.cs @@ -85,15 +85,14 @@ static async Task GetLatestVersion(bool beta, string version) beta ? "https://vpm.anatawa12.com/avatar-optimizer/beta/latest.txt" : "https://vpm.anatawa12.com/avatar-optimizer/latest.txt"; - - var updatedAtKey = - beta - ? "com.anatawa12.avatar-optimizer.beta.latest.updated" - : "com.anatawa12.avatar-optimizer.latest.updated"; - var latestVersionKey = + + var keyPrefix = beta - ? "com.anatawa12.avatar-optimizer.beta.latest.value" - : "com.anatawa12.avatar-optimizer.latest.value"; + ? "com.anatawa12.avatar-optimizer.beta.latest" + : "com.anatawa12.avatar-optimizer.latest"; + var updatedAtKey = $"{keyPrefix}.updated"; + var checkedWithKey = $"{keyPrefix}.checked-with"; + var latestVersionKey = $"{keyPrefix}.value"; // fetch cached version var cachedVersion = EditorPrefs.GetString(latestVersionKey); @@ -101,6 +100,7 @@ static async Task GetLatestVersion(bool beta, string version) cachedVersion = null; if (cachedVersion != null + && EditorPrefs.GetString(checkedWithKey, "") == CurrentVersionName && DateTime.TryParse(EditorPrefs.GetString(updatedAtKey, ""), out var updatedAt) && updatedAt >= DateTime.UtcNow - TimeSpan.FromHours(1)) { @@ -135,6 +135,7 @@ static async Task GetLatestVersion(bool beta, string version) { // we successfully fetched latest version! EditorPrefs.SetString(latestVersionKey, fetchedLatestVersion); + EditorPrefs.SetString(checkedWithKey, CurrentVersionName); EditorPrefs.SetString(updatedAtKey, DateTime.UtcNow.ToString("O")); return fetchedLatestVersion; } diff --git a/Editor/EditModePreview/MeshPreviewController.cs b/Editor/EditModePreview/MeshPreviewController.cs index 245ba1e4a..5df181b39 100644 --- a/Editor/EditModePreview/MeshPreviewController.cs +++ b/Editor/EditModePreview/MeshPreviewController.cs @@ -70,6 +70,7 @@ private void Update() { var editorObj = ActiveEditor(); if (editorObj is GameObject go && + go.GetComponent().sharedMesh && RemoveMeshPreviewController.EditorTypes.Any(t => go.GetComponent(t))) { StartPreview(go); @@ -83,6 +84,7 @@ public enum PreviewState PreviewAble, PreviewingThat, + NoMesh, PreviewingOther, ActiveEditorMismatch, } @@ -99,8 +101,15 @@ private PreviewState StateForImpl([CanBeNull] Component component) if (AnimationMode.InAnimationMode()) return PreviewState.PreviewingOther; - if (gameObject && ActiveEditor() as GameObject != gameObject) - return PreviewState.ActiveEditorMismatch; + if (gameObject) + { + if (ActiveEditor() as GameObject != gameObject) + return PreviewState.ActiveEditorMismatch; + + var renderer = gameObject.GetComponent(); + if (!renderer || !renderer.sharedMesh) + return PreviewState.NoMesh; + } return PreviewState.PreviewAble; } @@ -125,6 +134,11 @@ private void ShowPreviewControlImpl(Component component) Enabled = false; } break; + case PreviewState.NoMesh: + EditorGUI.BeginDisabledGroup(true); + GUILayout.Button("Preview (no Mesh)"); + EditorGUI.EndDisabledGroup(); + break; case PreviewState.PreviewingOther: EditorGUI.BeginDisabledGroup(true); GUILayout.Button("Preview (other Previewing)"); diff --git a/Editor/OptimizerPlugin.cs b/Editor/OptimizerPlugin.cs index 9ab822eb4..be7e6591a 100644 --- a/Editor/OptimizerPlugin.cs +++ b/Editor/OptimizerPlugin.cs @@ -20,9 +20,11 @@ protected override void Configure() InPhase(BuildPhase.Resolving) .WithRequiredExtensions(new [] {typeof(BuildReportContext)}, seq => { - seq.Run("Info if AAO is Out of Date", _ => + seq.Run("Info if AAO is Out of Date", ctx => { - if (CheckForUpdate.OutOfDate) + // we skip check for update + var components = ctx.AvatarRootObject.GetComponentInChildren(true); + if (components && CheckForUpdate.OutOfDate) BuildReport.LogInfo("CheckForUpdate:out-of-date", CheckForUpdate.LatestVersionName, CheckForUpdate.CurrentVersionName); }) diff --git a/Editor/Processors/MergeBoneProcessor.cs b/Editor/Processors/MergeBoneProcessor.cs index 07d330ea0..14a8acdd4 100644 --- a/Editor/Processors/MergeBoneProcessor.cs +++ b/Editor/Processors/MergeBoneProcessor.cs @@ -65,6 +65,8 @@ protected override void Execute(BuildContext context) // normalize map mergeMapping.FlattenMapping(); + if (mergeMapping.Count == 0) return; + BuildReport.ReportingObjects(context.GetComponents(), renderer => { var meshInfo2 = context.GetMeshInfoFor(renderer); @@ -160,12 +162,12 @@ private void DoBoneMap2(MeshInfo2 meshInfo2, Dictionary me vertex.Tangent = new Vector4(tangentVec3.x, tangentVec3.y, tangentVec3.z, vertex.Tangent.w); foreach (var frames in vertex.BlendShapes.Values) { - for (var i = 0; i < frames.Count; i++) + for (var i = 0; i < frames.Length; i++) { var frame = frames[i]; frames[i] = new Vertex.BlendShapeFrame( weight: frame.Weight, - position: transBindPose.MultiplyPoint3x4(frame.Position), + position: transBindPose.MultiplyPoint3x3(frame.Position), normal: transBindPose.MultiplyPoint3x3(frame.Normal), tangent: transBindPose.MultiplyPoint3x3(frame.Tangent) ); diff --git a/Editor/Processors/MeshInfo2Holder.cs b/Editor/Processors/MeshInfo2Holder.cs index c8352f6ea..365711d5c 100644 --- a/Editor/Processors/MeshInfo2Holder.cs +++ b/Editor/Processors/MeshInfo2Holder.cs @@ -35,6 +35,8 @@ internal class MeshInfo2Holder public MeshInfo2Holder(GameObject rootObject) { + var avatarTagComponent = rootObject.GetComponentInChildren(true); + if (avatarTagComponent == null) return; foreach (var renderer in rootObject.GetComponentsInChildren(true)) { Profiler.BeginSample($"Read Skinned Mesh {renderer.name}"); diff --git a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs index 870b8970b..67f1756ba 100644 --- a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs +++ b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs @@ -168,27 +168,44 @@ public void ReadSkinnedMesh([NotNull] Mesh mesh) BlendShapes.Add((shapeName, 0.0f)); - var shapes = new List[Vertices.Count]; + var frameCount = mesh.GetBlendShapeFrameCount(i); - for (int frame = 0; frame < mesh.GetBlendShapeFrameCount(i); frame++) + var shapes = new Vertex.BlendShapeFrame[Vertices.Count][]; + for (var vertex = 0; vertex < shapes.Length; vertex++) + shapes[vertex] = new Vertex.BlendShapeFrame[frameCount]; + + for (var frame = 0; frame < frameCount; frame++) { mesh.GetBlendShapeFrameVertices(i, frame, deltaVertices, deltaNormals, deltaTangents); var weight = mesh.GetBlendShapeFrameWeight(i, frame); for (var vertex = 0; vertex < deltaNormals.Length; vertex++) { - if (deltaVertices[vertex] == Vector3.zero && deltaNormals[vertex] == Vector3.zero && deltaTangents[vertex] == Vector3.zero) - continue; - if (shapes[vertex] == null) - shapes[vertex] = new List(); - shapes[vertex].Add(new Vertex.BlendShapeFrame(weight, deltaVertices[vertex], - deltaNormals[vertex], deltaTangents[vertex])); - } + var deltaVertex = deltaVertices[vertex]; + var deltaNormal = deltaNormals[vertex]; + var deltaTangent = deltaTangents[vertex]; + shapes[vertex][frame] = + new Vertex.BlendShapeFrame(weight, deltaVertex, deltaNormal, deltaTangent); + } } for (var vertex = 0; vertex < shapes.Length; vertex++) - if (shapes[vertex] is List shapeFrames) - Vertices[vertex].BlendShapes[shapeName] = shapeFrames; + { + if (IsMeaningful(shapes[vertex])) + Vertices[vertex].BlendShapes[shapeName] = shapes[vertex]; + } + } + + bool IsMeaningful(Vertex.BlendShapeFrame[] frames) + { + foreach (var (_, position, normal, tangent) in frames) + { + if (position != Vector3.zero) return true; + if (normal != Vector3.zero) return true; + if (tangent != Vector3.zero) return true; + } + + return false; } Profiler.EndSample(); Profiler.EndSample(); @@ -490,8 +507,9 @@ public void WriteToMesh(Mesh destMesh) { var vertex = Vertices[vertexI]; - vertex.TryGetBlendShape(shapeName, weight, out var position, out var normal, - out var tangent); + vertex.TryGetBlendShape(shapeName, weight, + out var position, out var normal, out var tangent, + getDefined: true); positions[vertexI] = position; normals[vertexI] = normal; tangents[vertexI] = tangent; @@ -570,8 +588,8 @@ internal class Vertex public List<(Bone bone, float weight)> BoneWeights = new List<(Bone, float)>(); // Each frame must sorted increasingly - public readonly Dictionary> BlendShapes = - new Dictionary>(); + public readonly Dictionary BlendShapes = + new Dictionary(); public readonly struct BlendShapeFrame { @@ -633,7 +651,8 @@ public void SetTexCoord(int index, Vector4 value) } } - public bool TryGetBlendShape(string name, float weight, out Vector3 position, out Vector3 normal, out Vector3 tangent) + public bool TryGetBlendShape(string name, float weight, out Vector3 position, out Vector3 normal, + out Vector3 tangent, bool getDefined = false) { if (!BlendShapes.TryGetValue(name, out var frames)) { @@ -643,7 +662,7 @@ public bool TryGetBlendShape(string name, float weight, out Vector3 position, ou return false; } - if (frames.Count == 0) + if (frames.Length == 0) { position = default; normal = default; @@ -651,7 +670,7 @@ public bool TryGetBlendShape(string name, float weight, out Vector3 position, ou return false; } - if (Mathf.Abs(weight) <= 0.0001f && ZeroForWeightZero()) + if (!getDefined && Mathf.Abs(weight) <= 0.0001f && ZeroForWeightZero()) { position = Vector3.zero; normal = Vector3.zero; @@ -661,7 +680,7 @@ public bool TryGetBlendShape(string name, float weight, out Vector3 position, ou bool ZeroForWeightZero() { - if (frames.Count == 1) return true; + if (frames.Length == 1) return true; var first = frames.First(); var end = frames.Last(); @@ -672,7 +691,7 @@ bool ZeroForWeightZero() return false; } - if (frames.Count == 1) + if (frames.Length == 1) { // simplest and likely var frame = frames[0]; @@ -713,13 +732,13 @@ bool ZeroForWeightZero() // otherwise, lerp between two surrounding frames OR nearest two frames - for (var i = 1; i < frames.Count; i++) + for (var i = 1; i < frames.Length; i++) { if (weight <= frames[i].Weight) return (frames[i - 1], frames[i]); } - return (frames[frames.Count - 2], frames[frames.Count - 1]); + return (frames[frames.Length - 2], frames[frames.Length - 1]); } float InverseLerpUnclamped(float a, float b, float value) => (value - a) / (b - a); @@ -744,7 +763,7 @@ private Vertex(Vertex vertex) TexCoord7 = vertex.TexCoord7; Color = vertex.Color; BoneWeights = vertex.BoneWeights.ToList(); - BlendShapes = new Dictionary>(vertex.BlendShapes); + BlendShapes = new Dictionary(vertex.BlendShapes); } public Vertex Clone() => new Vertex(this); diff --git a/Editor/Processors/TraceAndOptimize/FindUnusedObjectsProcessor.cs b/Editor/Processors/TraceAndOptimize/FindUnusedObjectsProcessor.cs index 29e49b4b7..724efbb5d 100644 --- a/Editor/Processors/TraceAndOptimize/FindUnusedObjectsProcessor.cs +++ b/Editor/Processors/TraceAndOptimize/FindUnusedObjectsProcessor.cs @@ -368,31 +368,49 @@ private void ConfigureMergeBone() { ConfigureRecursive(this, _context.AvatarRootTransform, _modifications); - // returns true if merged - bool ConfigureRecursive(in FindUnusedObjectsProcessor processor, Transform transform, + // returns (original mergedChildren, list of merged children if merged, and null if not merged) + //[CanBeNull] + (bool, List) ConfigureRecursive(in FindUnusedObjectsProcessor processor, Transform transform, ImmutableModificationsContainer modifications) { var mergedChildren = true; + var afterChildren = new List(); foreach (var child in transform.DirectChildrenEnumerable()) - mergedChildren &= ConfigureRecursive(processor, child, modifications); + { + var (newMergedChildren, newChildren) = ConfigureRecursive(processor, child, modifications); + if (newChildren == null) + { + mergedChildren = false; + afterChildren.Add(child); + } + else + { + mergedChildren &= newMergedChildren; + afterChildren.AddRange(newChildren); + } + } const ComponentDependencyCollector.DependencyType AllowedUsages = ComponentDependencyCollector.DependencyType.Bone | ComponentDependencyCollector.DependencyType.Parent | ComponentDependencyCollector.DependencyType.ComponentToTransform; + // functions for make it easier to know meaning of result + (bool, List) YesMerge() => (mergedChildren, afterChildren); + (bool, List) NotMerged() => (mergedChildren, null); + // Already Merged - if (transform.GetComponent()) return true; + if (transform.GetComponent()) return YesMerge(); // Components must be Transform Only - if (transform.GetComponents().Length != 1) return false; + if (transform.GetComponents().Length != 1) return NotMerged(); // The bone cannot be used generally - if ((processor._marked[transform] & ~AllowedUsages) != 0) return false; + if ((processor._marked[transform] & ~AllowedUsages) != 0) return NotMerged(); // must not be animated - if (TransformAnimated(transform, modifications)) return false; + if (TransformAnimated(transform, modifications)) return NotMerged(); if (!mergedChildren) { - if (GameObjectAnimated(transform, modifications)) return false; + if (GameObjectAnimated(transform, modifications)) return NotMerged(); var localScale = transform.localScale; var identityTransform = localScale == Vector3.one && transform.localPosition == Vector3.zero && @@ -400,22 +418,21 @@ bool ConfigureRecursive(in FindUnusedObjectsProcessor processor, Transform trans if (!identityTransform) { - var childrenTransformAnimated = - transform.DirectChildrenEnumerable().Any(x => TransformAnimated(x, modifications)); + var childrenTransformAnimated = afterChildren.Any(x => TransformAnimated(x, modifications)); if (childrenTransformAnimated) // if this is not identity transform, animating children is not good - return false; + return NotMerged(); if (!MergeBoneProcessor.ScaledEvenly(localScale)) // non even scaling is not possible to reproduce in children - return false; + return NotMerged(); } } if (!transform.gameObject.GetComponent()) transform.gameObject.AddComponent().avoidNameConflict = true; - return true; + return YesMerge(); } bool TransformAnimated(Transform transform, ImmutableModificationsContainer modifications) diff --git a/Internal/ErrorReporter/Editor/ErrorReportUI.cs b/Internal/ErrorReporter/Editor/ErrorReportUI.cs index 51df82c59..1fb84cd7b 100644 --- a/Internal/ErrorReporter/Editor/ErrorReportUI.cs +++ b/Internal/ErrorReporter/Editor/ErrorReportUI.cs @@ -47,6 +47,7 @@ private void OnEnable() ReloadErrorReportEvent += RenderContent; Selection.selectionChanged += ScheduleRender; + EditorApplication.playModeStateChanged += PlayModeStateChanged; EditorApplication.hierarchyChanged += ScheduleRender; ErrorReporterRuntime.OnChangeAction += ScheduleRender; //Localization.OnLangChange += RenderContent; @@ -66,6 +67,21 @@ private void OnDisable() [CanBeNull] private HeaderBox _header; [CanBeNull] private GameObject _defaultAvatarObject; + private void PlayModeStateChanged(PlayModeStateChange obj) + { + switch (obj) + { + case PlayModeStateChange.EnteredEditMode: + _header?.RedrawList(); + break; + case PlayModeStateChange.ExitingEditMode: + case PlayModeStateChange.EnteredPlayMode: + case PlayModeStateChange.ExitingPlayMode: + default: + break; + } + } + private void ScheduleRender() { if (_delayTimer == null) @@ -215,6 +231,14 @@ [CanBeNull] public AvatarReport Value public event Action SelectionChanged; + public void RedrawList() + { + if (_field != null) + { + SelectionChanged?.Invoke(_field.value); + } + } + public HeaderBox(AvatarReport defaultValue) { AddToClassList("avatarHeader");