From 12391b927f510a2fa9faa19abb1cb5cc6091dac7 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 20 May 2023 13:46:30 +0900 Subject: [PATCH 1/2] chore: completely reimplement Object Mapping System --- Editor/ObjectMapping.cs | 480 ------------------ Editor/ObjectMapping.meta | 3 + Editor/ObjectMapping/AnimationObjectMapper.cs | 125 +++++ .../AnimationObjectMapper.cs.meta | 3 + Editor/ObjectMapping/ObjectMapping.cs | 102 ++++ .../{ => ObjectMapping}/ObjectMapping.cs.meta | 0 Editor/ObjectMapping/ObjectMappingBuilder.cs | 149 ++++++ .../ObjectMappingBuilder.cs.meta | 3 + Editor/Processors/ApplyObjectMapping.cs | 27 +- Editor/Processors/MakeChildrenProcessor.cs | 1 - Editor/Processors/MergeBoneProcessor.cs | 4 - Editor/Processors/MergePhysBoneProcessor.cs | 12 +- .../MergeSkinnedMeshProcessor.cs | 2 +- Test~/ObjectMappingTest.cs | 89 ++-- 14 files changed, 450 insertions(+), 550 deletions(-) delete mode 100644 Editor/ObjectMapping.cs create mode 100644 Editor/ObjectMapping.meta create mode 100644 Editor/ObjectMapping/AnimationObjectMapper.cs create mode 100644 Editor/ObjectMapping/AnimationObjectMapper.cs.meta create mode 100644 Editor/ObjectMapping/ObjectMapping.cs rename Editor/{ => ObjectMapping}/ObjectMapping.cs.meta (100%) create mode 100644 Editor/ObjectMapping/ObjectMappingBuilder.cs create mode 100644 Editor/ObjectMapping/ObjectMappingBuilder.cs.meta diff --git a/Editor/ObjectMapping.cs b/Editor/ObjectMapping.cs deleted file mode 100644 index 807a23b03..000000000 --- a/Editor/ObjectMapping.cs +++ /dev/null @@ -1,480 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using UnityEditor; -using UnityEngine; - -namespace Anatawa12.AvatarOptimizer -{ - /// - /// The class manages Object location mapping - /// - internal class ObjectMappingBuilder - { - // Those may not be an actual GameObject, may be a hierarchy root 'null' - private readonly VGameObject _tree = new VGameObject(null, ""); - private readonly GameObject _rootObject; - - public ObjectMappingBuilder([NotNull] GameObject rootObject) - { - _rootObject = rootObject ? rootObject : throw new ArgumentNullException(nameof(rootObject)); - RegisterAll(_rootObject, _rootObject.GetComponentsInChildren(typeof(Component), true)); - } - - private void RegisterAll(GameObject rootObject, Component[] components) - { - foreach (var component in components) - { - var path = Utils.RelativePath(rootObject.transform, component.transform); - System.Diagnostics.Debug.Assert(path != null, nameof(path) + " != null"); - _tree.GetGameObject(path).GetComponents(component.GetType(), component.GetInstanceID()); - } - } - - public void RecordMoveObject(GameObject from, GameObject newParent) - { - var oldPath = Utils.RelativePath(_rootObject.transform, from.transform) - ?? throw new ArgumentException("not of root GameObject", nameof(from)); - var newParentPath = Utils.RelativePath(_rootObject.transform, newParent.transform) - ?? throw new ArgumentException("not of root GameObject", nameof(newParent)); - - var oldVGameObject = _tree.GetGameObject(oldPath); - var newParentVGameObject = _tree.GetGameObject(newParentPath); - - oldVGameObject.MoveTo(newParentVGameObject); - } - - public void RecordRemoveGameObject(GameObject component) - { - var oldPath = Utils.RelativePath(_rootObject.transform, component.transform) - ?? throw new ArgumentException("not of root GameObject", nameof(component)); - _tree.GetGameObject(oldPath).Remove(); - } - - - public void RecordMoveComponent(Component from, GameObject newGameObject) - { - var oldPath = Utils.RelativePath(_rootObject.transform, from.transform) - ?? throw new ArgumentException("not of root GameObject", nameof(from)); - var newParentPath = Utils.RelativePath(_rootObject.transform, newGameObject.transform) - ?? throw new ArgumentException("not of root GameObject", nameof(newGameObject)); - - var components = _tree.GetGameObject(oldPath).GetComponents(from.GetType(), from.GetInstanceID()); - var newParentVGameObject = _tree.GetGameObject(newParentPath); - - foreach (var component in components.ToArray()) - component.MoveTo(newParentVGameObject); - } - - public void RecordRemoveComponent(Component component) - { - var oldPath = Utils.RelativePath(_rootObject.transform, component.transform) - ?? throw new ArgumentException("not of root GameObject", nameof(component)); - foreach (var vComponent in _tree.GetGameObject(oldPath) - .GetComponents(component.GetType(), component.GetInstanceID()).ToArray()) - vComponent.Remove(); - } - - public void RecordMoveProperty(Component from, string oldProp, string newProp) - { - var path = Utils.RelativePath(_rootObject.transform, from.transform) - ?? throw new ArgumentException("not of root GameObject", nameof(from)); - foreach (var component in _tree.GetGameObject(path).GetComponents(from.GetType(), from.GetInstanceID())) - component.MoveProperty(oldProp, newProp); - } - - public void RecordRemoveProperty(Component from, string oldProp) - { - var path = Utils.RelativePath(_rootObject.transform, from.transform) - ?? throw new ArgumentException("not of root GameObject", nameof(from)); - foreach (var component in _tree.GetGameObject(path).GetComponents(from.GetType(), from.GetInstanceID())) - component.RemoveProperty(oldProp); - - } - - public ObjectMapping BuildObjectMapping() - { - var goNewPath = _tree.BuildNewPathMapping(); - var goOldPath = _tree.BuildOldPathMapping(); - var goMapping = goOldPath.ToDictionary(x => x.Value, - x => - { - goNewPath.TryGetValue(x.Key, out var newPath); - return newPath; - }); - - var componentMapping = new Dictionary(); - var instanceIdToComponent = new Dictionary(); - var newGameObjectCache = new Dictionary(); - - foreach (var component in _tree.GetAllComponents()) - { - var oldPath = goOldPath[component.OriginalGameObject]; - if (component.NewGameObject != null && goNewPath.TryGetValue(component.NewGameObject, out var newPath)) - { - var propertyMapping = new Dictionary(); - var newMapping = component.NewProperties.ToDictionary(kvp => kvp.Value, kvp => kvp.Key); - foreach (var kvp in component.OriginalProperties) - { - newMapping.TryGetValue(kvp.Value, out var newPropName); - if (kvp.Key != newPropName) - propertyMapping[kvp.Key] = newPropName; - } - - // if nothing is changed - if (propertyMapping.Count == 0 && oldPath == newPath) continue; - - var mapped = new ObjectMapping.MappedComponent(newPath, propertyMapping); - componentMapping[new ObjectMapping.ComponentKey(oldPath, component.Type)] = mapped; - - if (!newGameObjectCache.TryGetValue(newPath, out var newGameObject)) - newGameObject = newGameObjectCache[newPath] = - Utils.GetGameObjectRelative(_rootObject, newPath); - var actualComponent = newGameObject.GetComponent(component.Type); - - // if nothing is changed - if (propertyMapping.Count == 0 && actualComponent.GetInstanceID() == component.InstanceId) continue; - instanceIdToComponent[component.InstanceId] = (actualComponent, mapped); - } - else - { - // GameObject or Component is removed - componentMapping[new ObjectMapping.ComponentKey(oldPath, component.Type)] = - ObjectMapping.MappedComponent.Removed; - instanceIdToComponent[component.InstanceId] = (null, null); - } - } - - return new ObjectMapping(goMapping, componentMapping, instanceIdToComponent); - } - - /// Represents a GameObject in Hierarchy - class VGameObject - { - private readonly VGameObject _originalParent; - private readonly string _originalName; - private VGameObject _newParent; - private string _newName; - - private readonly Dictionary _originalChildren = new Dictionary(); - - private readonly Dictionary> _newChildren = - new Dictionary>(); - - private readonly Dictionary _originalComponents = new Dictionary(); - - private readonly Dictionary> _newComponents = - new Dictionary>(); - - public VGameObject(VGameObject parent, string name) - { - _originalParent = _newParent = parent; - _originalName = _newName = name; - } - - private List GetComponents(Type type) => - _newComponents.TryGetValue(type, out var newList) - ? newList - : _newComponents[type] = new List(); - - public IEnumerable GetComponents(Type type, int instanceId) - { - var list = GetComponents(type); - if (list.All(x => x.InstanceId != instanceId)) - { - var newComponent = new VComponent(this, type, instanceId); - list.Add(newComponent); - if (!_originalComponents.ContainsKey(type)) - _originalComponents.Add(type, newComponent); - } - return list; - } - - public void MoveComponentTo(VComponent component, VGameObject newGameObject) - { - if (component.NewGameObject != this) - throw new ArgumentException("bad newGameObject", nameof(component)); - var components = GetComponents(component.Type); - Debug.Assert(components.Remove(component)); - newGameObject.GetComponents(component.Type).Add(component); - component.NewGameObject = newGameObject; - } - - public void RemoveComponent(VComponent component) - { - if (component.NewGameObject != this) - throw new ArgumentException("bad newGameObject", nameof(component)); - Debug.Assert(GetComponents(component.Type).Remove(component)); - component.NewGameObject = null; - } - - public VGameObject GetGameObject([NotNull] string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - if (path == "") return this; - - var cursor = this; - foreach (var pathComponent in path.Split('/')) - { - var children = cursor.ChildListWithName(pathComponent); - - if (children.Count == 0) - { - var child = new VGameObject(cursor, pathComponent); - cursor._originalChildren[pathComponent] = child; - children.Add(child); - } - - cursor = children[0]; - } - - return cursor; - } - - private List ChildListWithName(string name) - { - if (name == "") throw new ArgumentException("name must not null"); - return _newChildren.TryGetValue(name, out var newList) - ? newList - : _newChildren[name] = new List(); - } - - public void MoveTo(VGameObject newParent) - { - if (_newParent == newParent) return; // nothing to move - var currentList = _newParent.ChildListWithName(_newName); - var newList = newParent.ChildListWithName(_newName); - System.Diagnostics.Debug.Assert(currentList.Remove(this)); - newList.Add(this); - _newParent = newParent; - } - - public void Remove() - { - System.Diagnostics.Debug.Assert(_newParent.ChildListWithName(_newName).Remove(this)); - _newParent = null; - } - - public Dictionary BuildNewPathMapping() => BuildMapping(x => x - ._newChildren.Select(pair => new KeyValuePair>(pair.Key, pair.Value))); - public Dictionary BuildOldPathMapping() => BuildMapping(x => x - ._originalChildren.Select(pair => new KeyValuePair>(pair.Key, new []{pair.Value}))); - - private Dictionary BuildMapping(Func>>> mappingGetter) - { - var result = new Dictionary(); - var queue = new Queue<(string, VGameObject)>(); - - result.Add(this, ""); - foreach (var keyValuePair in mappingGetter(this)) - foreach (var gameObject in keyValuePair.Value) - queue.Enqueue((keyValuePair.Key, gameObject)); - - while (queue.Count != 0) - { - var (name, go) = queue.Dequeue(); - result.Add(go, name); - - - foreach (var keyValuePair in mappingGetter(go)) - foreach (var gameObject in keyValuePair.Value) - queue.Enqueue(($"{name}/{keyValuePair.Key}", gameObject)); - } - - return result; - } - - public IEnumerable GetAllComponents() - { - var queue = new Queue(); - queue.Enqueue(this); - - while (queue.Count != 0) - { - var go = queue.Dequeue(); - foreach (var component in go._originalComponents.Values) - yield return component; - - foreach (var gameObject in go._originalChildren.Values) - queue.Enqueue(gameObject); - } - } - } - - /// Represents a component - class VComponent - { - public readonly VGameObject OriginalGameObject; - public VGameObject NewGameObject; - public readonly Type Type; - public readonly Dictionary OriginalProperties = new Dictionary(); - public readonly Dictionary NewProperties = new Dictionary(); - public int InstanceId { get; } - - public VComponent(VGameObject gameObject, Type type, int instanceId) - { - OriginalGameObject = NewGameObject = gameObject; - InstanceId = instanceId; - Type = type; - } - - public void MoveTo(VGameObject newGameObject) => NewGameObject.MoveComponentTo(this, newGameObject); - - public void Remove() - { - NewGameObject.RemoveComponent(this); - } - - public void MoveProperty(string oldProp, string newProp) - { - if (!NewProperties.TryGetValue(oldProp, out var prop)) - prop = NewProperties[oldProp] = OriginalProperties[oldProp] = new VProperty(); - - NewProperties.Remove(oldProp); - NewProperties[newProp] = prop; - } - - public void RemoveProperty(string oldProp) - { - if (!NewProperties.TryGetValue(oldProp, out var prop)) - prop = NewProperties[oldProp] = OriginalProperties[oldProp] = new VProperty(); - - NewProperties.Remove(oldProp); - } - } - - /// Represents a property - class VProperty - { - } - } - - internal class ObjectMapping - { - public ObjectMapping(Dictionary goMapping, - Dictionary componentMapping, - Dictionary instanceIdToComponent) - { - GameObjectPathMapping = goMapping; - ComponentMapping = componentMapping; - InstanceIdToComponent = instanceIdToComponent; - } - - public Dictionary ComponentMapping { get; } - public Dictionary GameObjectPathMapping { get; } - public Dictionary InstanceIdToComponent { get; } - - public EditorCurveBinding MapPath(string rootPath, EditorCurveBinding binding) - { - string Join(string a, string b) => a == "" ? b : b == "" ? a : $"{a}/{b}"; - string StripPrefixPath(string parent, string path, char sep) - { - if (path == null) return null; - if (parent == path) return ""; - if (parent == "") return path; - if (path.StartsWith($"{parent}{sep}", StringComparison.Ordinal)) - return path.Substring(parent.Length + 1); - return null; - } - - var oldPath = Join(rootPath, binding.path); - - // try as component first - if (ComponentMapping.TryGetValue(new ComponentKey(oldPath, binding.type), out var mapped)) - { - var newPath = StripPrefixPath(rootPath, mapped.MappedGameObjectPath, '/'); - if (newPath == null || mapped.PropertyMapping == null) return default; - - binding.path = newPath; - - foreach (var (prop, rest) in Utils.FindSubPaths(binding.propertyName, '.')) - { - if (mapped.PropertyMapping.TryGetValue(prop, out var newProp)) - { - if (newProp == null) return default; - var newFullProp = newProp + rest; - binding.propertyName = newFullProp; - break; - } - } - return binding; - } - - // then, try as GameObject - foreach (var (path, rest) in Utils.FindSubPaths(oldPath, '/')) - { - if (GameObjectPathMapping.TryGetValue(path, out var newPath)) - { - newPath = StripPrefixPath(rootPath, newPath, '/'); - if (newPath == null) return default; - binding.path = newPath + rest; - return binding; - } - } - - return binding; - } - - public readonly struct ComponentKey - { - public readonly string GameObjectPath; - public readonly Type Type; - - public ComponentKey(string gameObjectPath, Type type) - { - GameObjectPath = gameObjectPath; - Type = type; - } - - public bool Equals(ComponentKey other) - { - return GameObjectPath == other.GameObjectPath && Type == other.Type; - } - - public override bool Equals(object obj) - { - return obj is ComponentKey other && Equals(other); - } - - public override int GetHashCode() - { - unchecked - { - return ((GameObjectPath != null ? GameObjectPath.GetHashCode() : 0) * 397) ^ - (Type != null ? Type.GetHashCode() : 0); - } - } - } - - public class MappedComponent - { - public readonly string MappedGameObjectPath; - public readonly IReadOnlyDictionary PropertyMapping; - - public MappedComponent(string mappedGameObjectPath, IReadOnlyDictionary propertyMapping) - { - MappedGameObjectPath = mappedGameObjectPath; - PropertyMapping = propertyMapping; - } - - public static MappedComponent Removed = new MappedComponent(null, null); - } - } - - static class VProp - { - private const string EXTRA_PROP = "AvatarOptimizerExtraProps"; - public static string BlendShapeIndex(int index) => $"{EXTRA_PROP}.BlendShapeIndex.{index}"; - - public static int ParseBlendShapeIndex(string prop) - { - if (!prop.StartsWith($"{EXTRA_PROP}.BlendShapeIndex.", StringComparison.Ordinal)) - throw new ArgumentException($"The property {prop} is not BlendShapeIndex", nameof(prop)); - var indexStr = prop.Substring($"{EXTRA_PROP}.BlendShapeIndex.".Length); - if (!int.TryParse(indexStr, out var index)) - throw new ArgumentException($"The property {prop} is not BlendShapeIndex", nameof(prop)); - return index; - } - } -} diff --git a/Editor/ObjectMapping.meta b/Editor/ObjectMapping.meta new file mode 100644 index 000000000..2b24f957e --- /dev/null +++ b/Editor/ObjectMapping.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b967f4b9cc114864b5539435a44f475a +timeCreated: 1684495215 \ No newline at end of file diff --git a/Editor/ObjectMapping/AnimationObjectMapper.cs b/Editor/ObjectMapping/AnimationObjectMapper.cs new file mode 100644 index 000000000..cfcadf4a3 --- /dev/null +++ b/Editor/ObjectMapping/AnimationObjectMapper.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using UnityEditor; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + internal class AnimationObjectMapper + { + readonly GameObject _rootGameObject; + readonly BeforeGameObjectTree _beforeGameObjectTree; + readonly ObjectMapping _objectMapping; + + private readonly Dictionary _pathsCache = + new Dictionary(); + + public AnimationObjectMapper(GameObject rootGameObject, BeforeGameObjectTree beforeGameObjectTree, + ObjectMapping objectMapping) + { + _rootGameObject = rootGameObject; + _beforeGameObjectTree = beforeGameObjectTree; + _objectMapping = objectMapping; + } + + // null means nothing to map + [CanBeNull] + private MappedGameObjectInfo GetGameObjectInfo(string path) + { + if (_pathsCache.TryGetValue(path, out var info)) return info; + + var tree = _beforeGameObjectTree; + + if (path != "") + { + foreach (var pathSegment in path.Split('/')) + { + tree = tree.Children.FirstOrDefault(x => x.Name == pathSegment); + if (tree == null) break; + } + } + + if (tree == null) + { + info = null; + } + else + { + var foundGameObject = EditorUtility.InstanceIDToObject(tree.InstanceId) as GameObject; + var newPath = foundGameObject + ? Utils.RelativePath(_rootGameObject.transform, foundGameObject.transform) + : null; + + info = new MappedGameObjectInfo(_objectMapping, newPath, tree); + } + + _pathsCache.Add(path, info); + return info; + } + + class MappedGameObjectInfo + { + private ObjectMapping _objectMapping; + + readonly BeforeGameObjectTree _tree; + + // null means removed gameObject + [CanBeNull] public readonly string NewPath; + + public MappedGameObjectInfo(ObjectMapping objectMapping, string newPath, + BeforeGameObjectTree tree) + { + _objectMapping = objectMapping; + NewPath = newPath; + _tree = tree; + } + + public (int instanceId, ComponentInfo) GetComponentByType(Type type) + { + if (!_tree.ComponentInstanceIdByType.TryGetValue(type, out var instanceId)) + return (instanceId, null); // Nothing to map + return (instanceId, _objectMapping.GetComponentMapping(instanceId)); + } + } + + public EditorCurveBinding MapBinding(EditorCurveBinding binding) + { + var gameObjectInfo = GetGameObjectInfo(binding.path); + if (gameObjectInfo == null) return binding; + var (instanceId, componentInfo) = gameObjectInfo.GetComponentByType(binding.type); + + if (componentInfo != null) + { + var component = EditorUtility.InstanceIDToObject(componentInfo.MergedInto) as Component; + // there's mapping about component. + // this means the component is merged or some prop has mapping + if (!component) return default; // this means removed. + + var newPath = Utils.RelativePath(_rootGameObject.transform, component.transform); + if (newPath == null) return default; // this means moved to out of the animator scope + + binding.path = newPath; + + if (componentInfo.PropertyMapping.TryGetValue(binding.propertyName, out var newProp)) + binding.propertyName = newProp; + } + else + { + // The component is not merged & no prop mapping so process GameObject mapping + + if (binding.type != typeof(GameObject)) + { + var component = EditorUtility.InstanceIDToObject(instanceId) as Component; + if (!component) return default; // this means removed + } + + if (gameObjectInfo.NewPath == null) return default; + binding.path = gameObjectInfo.NewPath; + } + + return binding; + } + } +} diff --git a/Editor/ObjectMapping/AnimationObjectMapper.cs.meta b/Editor/ObjectMapping/AnimationObjectMapper.cs.meta new file mode 100644 index 000000000..e7f732c66 --- /dev/null +++ b/Editor/ObjectMapping/AnimationObjectMapper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c5b99acfbb66432f840027523cb1842f +timeCreated: 1684557308 \ No newline at end of file diff --git a/Editor/ObjectMapping/ObjectMapping.cs b/Editor/ObjectMapping/ObjectMapping.cs new file mode 100644 index 000000000..aaf3ff194 --- /dev/null +++ b/Editor/ObjectMapping/ObjectMapping.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using UnityEditor; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + internal class ObjectMapping + { + private readonly IReadOnlyDictionary _beforeTree; + private readonly IReadOnlyDictionary _componentMapping; + + public ObjectMapping( + IReadOnlyDictionary beforeTree, + IReadOnlyDictionary componentMapping) + { + _beforeTree = beforeTree; + _componentMapping = componentMapping; + } + + public bool MapComponentInstance(int instanceId, out Component component) + { + var mergedInto = _componentMapping.TryGetValue(instanceId, out var info) ? info.MergedInto : instanceId; + + component = EditorUtility.InstanceIDToObject(mergedInto) as Component; + return instanceId != mergedInto || component == null; + } + + // null means nothing to map + [CanBeNull] + public ComponentInfo GetComponentMapping(int instanceId) => + _componentMapping.TryGetValue(instanceId, out var info) ? info : null; + + [CanBeNull] + public AnimationObjectMapper CreateAnimationMapper(GameObject rootGameObject) + { + if (!_beforeTree.TryGetValue(rootGameObject.GetInstanceID(), out var beforeTree)) return null; + return new AnimationObjectMapper(rootGameObject, beforeTree, this); + } + } + class BeforeGameObjectTree + { + public readonly int InstanceId; + public readonly int ParentInstanceId; + [NotNull] public readonly string Name; + [NotNull] public readonly IReadOnlyDictionary ComponentInstanceIdByType; + [NotNull] public readonly int[] ComponentInstanceIds; + [NotNull] public readonly BeforeGameObjectTree[] Children; + + public BeforeGameObjectTree(GameObject gameObject) + { + var parentTransform = gameObject.transform.parent; + InstanceId = gameObject.GetInstanceID(); + Name = gameObject.name; + ParentInstanceId = parentTransform ? parentTransform.gameObject.GetInstanceID() : 0; + Children = new BeforeGameObjectTree[gameObject.transform.childCount]; + + var components = gameObject.GetComponents(); + ComponentInstanceIds = components.Select(x => x.GetInstanceID()).ToArray(); + + var componentByType = new Dictionary(); + foreach (var component in components) + if (!componentByType.ContainsKey(component.GetType())) + componentByType.Add(component.GetType(), component.GetInstanceID()); + ComponentInstanceIdByType = componentByType; + } + } + + class ComponentInfo + { + public readonly int InstanceId; + public readonly int MergedInto; + public readonly Type Type; + public readonly IReadOnlyDictionary PropertyMapping; + + public ComponentInfo(int instanceId, int mergedInto, Type type, IReadOnlyDictionary propertyMapping) + { + InstanceId = instanceId; + MergedInto = mergedInto; + Type = type; + PropertyMapping = propertyMapping; + } + } + + static class VProp + { + private const string ExtraProps = "AvatarOptimizerExtraProps"; + public static string BlendShapeIndex(int index) => $"{ExtraProps}.BlendShapeIndex.{index}"; + + public static int ParseBlendShapeIndex(string prop) + { + if (!prop.StartsWith($"{ExtraProps}.BlendShapeIndex.", StringComparison.Ordinal)) + throw new ArgumentException($"The property {prop} is not BlendShapeIndex", nameof(prop)); + var indexStr = prop.Substring($"{ExtraProps}.BlendShapeIndex.".Length); + if (!int.TryParse(indexStr, out var index)) + throw new ArgumentException($"The property {prop} is not BlendShapeIndex", nameof(prop)); + return index; + } + } +} diff --git a/Editor/ObjectMapping.cs.meta b/Editor/ObjectMapping/ObjectMapping.cs.meta similarity index 100% rename from Editor/ObjectMapping.cs.meta rename to Editor/ObjectMapping/ObjectMapping.cs.meta diff --git a/Editor/ObjectMapping/ObjectMappingBuilder.cs b/Editor/ObjectMapping/ObjectMappingBuilder.cs new file mode 100644 index 000000000..a565bb0d0 --- /dev/null +++ b/Editor/ObjectMapping/ObjectMappingBuilder.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + /// + /// The class manages Object location mapping + /// + internal class ObjectMappingBuilder + { + // Responsibility of this class can be classified to the following parts + // - Track moving GameObjects + // - Track renaming Properties in Component + // - Track merging Components + + private readonly IReadOnlyDictionary _beforeGameObjectInfos; + + // key: instanceId + private readonly Dictionary _componentInfos = new Dictionary(); + + public ObjectMappingBuilder([NotNull] GameObject rootObject) + { + if (!rootObject) throw new ArgumentNullException(nameof(rootObject)); + var transforms = rootObject.GetComponentsInChildren(true); + + _beforeGameObjectInfos = transforms + .ToDictionary(t => t.gameObject.GetInstanceID(), t => new BeforeGameObjectTree(t.gameObject)); + + foreach (var transform in transforms) + { + if (!transform.parent) continue; + if (!_beforeGameObjectInfos.TryGetValue(transform.parent.gameObject.GetInstanceID(), + out var parentInfo)) continue; + var selfInfo = _beforeGameObjectInfos[transform.gameObject.GetInstanceID()]; + parentInfo.Children[transform.GetSiblingIndex()] = selfInfo; + } + +#if DEBUG + // assertion + foreach (var info in _beforeGameObjectInfos.Values) + System.Diagnostics.Debug.Assert(info.Children.All(x => x != null), "info.Children.All(x => x != null)"); +#endif + } + + public void RecordMergeComponent(Component from, Component mergeTo) => + GetComponentInfo(from).MergedTo(GetComponentInfo(mergeTo)); + + public void RecordMoveProperty(Component from, string oldProp, string newProp) => + GetComponentInfo(from).MoveProperty(oldProp, newProp); + + public void RecordRemoveProperty(Component from, string oldProp) => + GetComponentInfo(from).RemoveProperty(oldProp); + + private BuildingComponentInfo GetComponentInfo(Component component) + { + if (!_componentInfos.TryGetValue(component.GetInstanceID(), out var info)) + _componentInfos.Add(component.GetInstanceID(), info = new BuildingComponentInfo(component)); + return info; + } + + public ObjectMapping BuildObjectMapping() + { + return new ObjectMapping( + _beforeGameObjectInfos, + _componentInfos.ToDictionary(p => p.Key, p => p.Value.Build())); + } + + class BuildingComponentInfo + { + private readonly int _instanceId; + private readonly Type _type; + private readonly List MergeSources = new List(); + + // id in this -> id in merged + private BuildingComponentInfo _mergedInto; + + // renaming property tracker + private int _nextPropertyId = 1; + private readonly Dictionary _beforePropertyIds = new Dictionary(); + private readonly Dictionary _afterPropertyIds = new Dictionary(); + + public BuildingComponentInfo(Component component) + { + _instanceId = component.GetInstanceID(); + _type = component.GetType(); + } + + public void MergedTo([NotNull] BuildingComponentInfo mergeTo) + { + if (mergeTo == null) throw new ArgumentNullException(nameof(mergeTo)); + if (_mergedInto != null) throw new InvalidOperationException("Already merged"); + mergeTo.MergeSources.Add(this); + _mergedInto = mergeTo; + } + + public void MoveProperty(string oldProp, string newProp) + { + foreach (var mergeSource in MergeSources) mergeSource.MoveProperty(oldProp, newProp); + + if (_afterPropertyIds.TryGetValue(oldProp, out var propId)) + { + _afterPropertyIds.Remove(oldProp); + _afterPropertyIds[newProp] = propId; + if (!_beforePropertyIds.ContainsKey(oldProp)) + _beforePropertyIds.Add(newProp, propId); + } + else + { + if (!_beforePropertyIds.ContainsKey(oldProp)) + { + if (_afterPropertyIds.ContainsKey(newProp)) + throw new InvalidOperationException("Merging property"); + _beforePropertyIds.Add(oldProp, propId = _nextPropertyId++); + _afterPropertyIds.Add(newProp, propId); + } + } + } + + public void RemoveProperty(string oldProp) + { + foreach (var mergeSource in MergeSources) mergeSource.RemoveProperty(oldProp); + // if (_afterPropertyIds.ContainsKey(oldProp)) + // _afterPropertyIds.Remove(oldProp); + // else + // if (!_beforePropertyIds.ContainsKey(oldProp)) + // _beforePropertyIds.Add(oldProp, _nextPropertyId++); + if (!_afterPropertyIds.Remove(oldProp)) + if (!_beforePropertyIds.ContainsKey(oldProp)) + _beforePropertyIds.Add(oldProp, _nextPropertyId++); + } + + public ComponentInfo Build() + { + var mergedInfo = this; + while (mergedInfo._mergedInto != null) + mergedInfo = mergedInfo._mergedInto; + + var idToAfterName = _afterPropertyIds.ToDictionary(p => p.Value, p => p.Key); + var propertyMapping = _beforePropertyIds.ToDictionary(p => p.Key, + p => idToAfterName.TryGetValue(p.Value, out var name) ? name : null); + + return new ComponentInfo(_instanceId, mergedInfo._instanceId, _type, propertyMapping); + } + } + } +} diff --git a/Editor/ObjectMapping/ObjectMappingBuilder.cs.meta b/Editor/ObjectMapping/ObjectMappingBuilder.cs.meta new file mode 100644 index 000000000..87e29304b --- /dev/null +++ b/Editor/ObjectMapping/ObjectMappingBuilder.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8cc46b198b754e24bcce2f451d4a1f7e +timeCreated: 1684506419 \ No newline at end of file diff --git a/Editor/Processors/ApplyObjectMapping.cs b/Editor/Processors/ApplyObjectMapping.cs index 26e825ec4..032dcfeba 100644 --- a/Editor/Processors/ApplyObjectMapping.cs +++ b/Editor/Processors/ApplyObjectMapping.cs @@ -28,14 +28,13 @@ public void Apply(OptimizerSession session) { if (p.propertyType == SerializedPropertyType.ObjectReference) { - if (mapping.InstanceIdToComponent.TryGetValue(p.objectReferenceInstanceIDValue, - out var mappedComponent)) - p.objectReferenceValue = mappedComponent.component; + if (mapping.MapComponentInstance(p.objectReferenceInstanceIDValue, out var mappedComponent)) + p.objectReferenceValue = mappedComponent; if (p.objectReferenceValue is AnimatorController controller) { if (mapper == null) - mapper = new AnimatorControllerMapper(mapping, + mapper = new AnimatorControllerMapper(mapping.CreateAnimationMapper(component.gameObject), session.RelativePath(component.transform), session); // ReSharper disable once AccessToModifiedClosure @@ -69,19 +68,15 @@ private static void VRCAvatarDescriptor(SerializedObject serialized, var eyelidsBlendshapes = serialized.FindProperty("customEyeLookSettings.eyelidsBlendshapes"); if (eyelidsSkinnedMesh == null || eyelidsBlendshapes == null) return; - if (!mapping.InstanceIdToComponent.TryGetValue( - eyelidsSkinnedMesh.objectReferenceInstanceIDValue, - out var mappedComponent)) - return; - if (mappedComponent.component == null || mappedComponent.mapping == null) - return; + var info = mapping.GetComponentMapping(eyelidsSkinnedMesh.objectReferenceInstanceIDValue); + if (info == null) return; - eyelidsSkinnedMesh.objectReferenceValue = mappedComponent.component; + eyelidsSkinnedMesh.objectReferenceValue = EditorUtility.InstanceIDToObject(info.MergedInto); for (var i = 0; i < eyelidsBlendshapes.arraySize; i++) { var indexProp = eyelidsBlendshapes.GetArrayElementAtIndex(i); - if (mappedComponent.mapping.PropertyMapping.TryGetValue( + if (info.PropertyMapping.TryGetValue( VProp.BlendShapeIndex(indexProp.intValue), out var mappedPropName)) { @@ -93,13 +88,13 @@ private static void VRCAvatarDescriptor(SerializedObject serialized, internal class AnimatorControllerMapper { - private readonly ObjectMapping _mapping; + private readonly AnimationObjectMapper _mapping; private readonly Dictionary _cache = new Dictionary(); private readonly OptimizerSession _session; private readonly string _rootPath; private bool _mapped = false; - public AnimatorControllerMapper(ObjectMapping mapping, string rootPath, OptimizerSession session) + public AnimatorControllerMapper(AnimationObjectMapper mapping, string rootPath, OptimizerSession session) { _session = session; _mapping = mapping; @@ -148,7 +143,7 @@ private Object CustomClone(Object o) foreach (var binding in AnimationUtility.GetCurveBindings(clip)) { - var newBinding = _mapping.MapPath(_rootPath, binding); + var newBinding = _mapping.MapBinding(binding); _mapped |= newBinding != binding; if (newBinding.type == null) continue; newClip.SetCurve(newBinding.path, newBinding.type, newBinding.propertyName, @@ -157,7 +152,7 @@ private Object CustomClone(Object o) foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)) { - var newBinding = _mapping.MapPath(_rootPath, binding); + var newBinding = _mapping.MapBinding(binding); _mapped |= newBinding != binding; if (newBinding.type == null) continue; AnimationUtility.SetObjectReferenceCurve(newClip, newBinding, diff --git a/Editor/Processors/MakeChildrenProcessor.cs b/Editor/Processors/MakeChildrenProcessor.cs index bfa44d82c..e2e95e396 100644 --- a/Editor/Processors/MakeChildrenProcessor.cs +++ b/Editor/Processors/MakeChildrenProcessor.cs @@ -11,7 +11,6 @@ public void Process(OptimizerSession session) { foreach (var makeChildrenChild in makeChildren.children.GetAsSet().Where(x => x)) { - session.MappingBuilder.RecordMoveObject(makeChildrenChild.gameObject, makeChildren.gameObject); makeChildrenChild.parent = makeChildren.transform; } }); diff --git a/Editor/Processors/MergeBoneProcessor.cs b/Editor/Processors/MergeBoneProcessor.cs index 71683b0d5..b4cac532f 100644 --- a/Editor/Processors/MergeBoneProcessor.cs +++ b/Editor/Processors/MergeBoneProcessor.cs @@ -36,13 +36,9 @@ public void Process(OptimizerSession session) var mapping = pair.Key; var mapped = pair.Value; foreach (var child in mapping.DirectChildrenEnumerable()) - { - session.MappingBuilder.RecordMoveObject(child.gameObject, mapped.gameObject); child.parent = mapped; - } session.Destroy(mapping.gameObject); - session.MappingBuilder.RecordRemoveGameObject(mapping.gameObject); } } diff --git a/Editor/Processors/MergePhysBoneProcessor.cs b/Editor/Processors/MergePhysBoneProcessor.cs index 633244e86..4f7c9a55f 100644 --- a/Editor/Processors/MergePhysBoneProcessor.cs +++ b/Editor/Processors/MergePhysBoneProcessor.cs @@ -38,11 +38,7 @@ internal static void DoMerge(MergePhysBone merge, OptimizerSession session) return; // error reported by validator foreach (var physBone in sourceComponents) - { - var pbTarget = physBone.GetTarget(); - session.MappingBuilder.RecordMoveObject(pbTarget.gameObject, root.gameObject); - pbTarget.parent = root; - } + physBone.GetTarget().parent = root; } else { @@ -63,11 +59,7 @@ internal static void DoMerge(MergePhysBone merge, OptimizerSession session) root = Utils.NewGameObject($"PhysBoneRoot-{Guid.NewGuid()}", pb.GetTarget().parent).transform; foreach (var physBone in sourceComponents) - { - var pbTarget = physBone.GetTarget(); - session.MappingBuilder.RecordMoveObject(pbTarget.gameObject, root.gameObject); - pbTarget.parent = root; - } + physBone.GetTarget().parent = root; } } diff --git a/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs b/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs index 420955edd..4b1f319d2 100644 --- a/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs +++ b/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs @@ -72,7 +72,7 @@ TexCoordStatus TexCoordStatusMax(TexCoordStatus x, TexCoordStatus y) => foreach (var renderer in Component.renderersSet.GetAsSet()) { - session.MappingBuilder.RecordMoveComponent(renderer, Component.gameObject); + session.MappingBuilder.RecordMergeComponent(renderer, Component); session.Destroy(renderer); // process removeEmptyRendererObject diff --git a/Test~/ObjectMappingTest.cs b/Test~/ObjectMappingTest.cs index 63b1b902c..109f4e19b 100644 --- a/Test~/ObjectMappingTest.cs +++ b/Test~/ObjectMappingTest.cs @@ -14,24 +14,26 @@ public void MoveObjectTest() var root = new GameObject(); var child1 = Utils.NewGameObject("child1", root.transform); var child11 = Utils.NewGameObject("child11", child1.transform); + var child111 = Utils.NewGameObject("child111", child11.transform); var child2 = Utils.NewGameObject("child2", root.transform); var builder = new ObjectMappingBuilder(root); - builder.RecordMoveObject(child11, child2); child11.transform.parent = child2.transform; var built = builder.BuildObjectMapping(); + var rootMapper = built.CreateAnimationMapper(root); Assert.That( - built.MapPath("", B("child1/child11", typeof(GameObject), "m_Enabled")), + rootMapper.MapBinding(B("child1/child11", typeof(GameObject), "m_Enabled")), Is.EqualTo(B("child2/child11", typeof(GameObject), "m_Enabled"))); Assert.That( - built.MapPath("", B("child1/child11/child111", typeof(GameObject), "m_Enabled")), + rootMapper.MapBinding(B("child1/child11/child111", typeof(GameObject), "m_Enabled")), Is.EqualTo(B("child2/child11/child111", typeof(GameObject), "m_Enabled"))); + var child1Mapper = built.CreateAnimationMapper(child1); Assert.That( - built.MapPath("child1", B("child11", typeof(GameObject), "m_Enabled")), + child1Mapper.MapBinding(B("child11", typeof(GameObject), "m_Enabled")), Is.EqualTo(Default)); } @@ -41,27 +43,29 @@ public void RecordRemoveGameObject() var root = new GameObject(); var child1 = Utils.NewGameObject("child1", root.transform); var child11 = Utils.NewGameObject("child11", child1.transform); + var child111 = Utils.NewGameObject("child111", child11.transform); var builder = new ObjectMappingBuilder(root); - builder.RecordRemoveGameObject(child11); Object.DestroyImmediate(child11); var built = builder.BuildObjectMapping(); + var rootMapper = built.CreateAnimationMapper(root); Assert.That( - built.MapPath("", B("child1/child11", typeof(GameObject), "m_Enabled")), + rootMapper.MapBinding(B("child1/child11", typeof(GameObject), "m_Enabled")), Is.EqualTo(Default)); Assert.That( - built.MapPath("", B("child1", typeof(GameObject), "m_Enabled")), + rootMapper.MapBinding(B("child1", typeof(GameObject), "m_Enabled")), Is.EqualTo(B("child1", typeof(GameObject), "m_Enabled"))); Assert.That( - built.MapPath("", B("child1/child11/child111", typeof(GameObject), "m_Enabled")), + rootMapper.MapBinding(B("child1/child11/child111", typeof(GameObject), "m_Enabled")), Is.EqualTo(Default)); + var child1Mapper = built.CreateAnimationMapper(child1); Assert.That( - built.MapPath("child1", B("child11", typeof(GameObject), "m_Enabled")), + child1Mapper.MapBinding(B("child11", typeof(GameObject), "m_Enabled")), Is.EqualTo(Default)); } @@ -75,24 +79,26 @@ public void RecordMoveComponentTest() var child2Component = child2.AddComponent(); var builder = new ObjectMappingBuilder(root); - builder.RecordMoveComponent(child1Component, child2); + builder.RecordMergeComponent(child1Component, child2Component); Object.DestroyImmediate(child1Component); var child1ComponentId = child1Component.GetInstanceID(); var built = builder.BuildObjectMapping(); + var rootMapper = built.CreateAnimationMapper(root); // should not affect to GameObject Assert.That( - built.MapPath("", B("child1", typeof(GameObject), "m_Enabled")), + rootMapper.MapBinding(B("child1", typeof(GameObject), "m_Enabled")), Is.EqualTo(B("child1", typeof(GameObject), "m_Enabled"))); // but should affect to component Assert.That( - built.MapPath("", B("child1", typeof(SkinnedMeshRenderer), "blendShapes.test")), + rootMapper.MapBinding(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.test")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.test"))); // check for component replication - Assert.That(built.InstanceIdToComponent[child1ComponentId].component, Is.SameAs(child2Component)); + Assert.That(built.MapComponentInstance(child1ComponentId, out var component), Is.True); + Assert.That(component, Is.SameAs(child2Component)); } [Test] @@ -103,24 +109,26 @@ public void RecordRemoveComponentTest() var child1Component = child1.AddComponent(); var builder = new ObjectMappingBuilder(root); - builder.RecordRemoveComponent(child1Component); Object.DestroyImmediate(child1Component); var child1ComponentId = child1Component.GetInstanceID(); var built = builder.BuildObjectMapping(); + var rootMapper = built.CreateAnimationMapper(root); + // should not affect to GameObject itself Assert.That( - built.MapPath("", B("child1", typeof(GameObject), "m_Enabled")), + rootMapper.MapBinding(B("child1", typeof(GameObject), "m_Enabled")), Is.EqualTo(B("child1", typeof(GameObject), "m_Enabled"))); // but should affect to component Assert.That( - built.MapPath("", B("child1", typeof(SkinnedMeshRenderer), "blendShapes.test")), + rootMapper.MapBinding(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.test")), Is.EqualTo(Default)); // check for component replication - Assert.That(built.InstanceIdToComponent[child1ComponentId].component, Is.SameAs(null)); + Assert.That(built.MapComponentInstance(child1ComponentId, out var component), Is.True); + Assert.That(component, Is.SameAs(null)); } [Test] @@ -132,18 +140,19 @@ public void RecordMovePropertyTest() var builder = new ObjectMappingBuilder(root); builder.RecordMoveProperty(child1Component, "blendShapes.test", "blendShapes.changed"); - Object.DestroyImmediate(child1Component); var built = builder.BuildObjectMapping(); + var rootMapper = built.CreateAnimationMapper(root); + // should not affect to other component Assert.That( - built.MapPath("", B("child2", typeof(SkinnedMeshRenderer), "blendShapes.test")), + rootMapper.MapBinding(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.test")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.test"))); // but should affect to component Assert.That( - built.MapPath("", B("child1", typeof(SkinnedMeshRenderer), "blendShapes.test")), + rootMapper.MapBinding(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.test")), Is.EqualTo(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.changed"))); } @@ -160,14 +169,16 @@ public void RecordRemovePropertyTest() var built = builder.BuildObjectMapping(); + var rootMapper = built.CreateAnimationMapper(root); + // should not affect to other component Assert.That( - built.MapPath("", B("child2", typeof(SkinnedMeshRenderer), "blendShapes.test")), + rootMapper.MapBinding(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.test")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.test"))); // but should affect to component Assert.That( - built.MapPath("", B("child1", typeof(SkinnedMeshRenderer), "blendShapes.test")), + rootMapper.MapBinding(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.test")), Is.EqualTo(Default)); } @@ -183,35 +194,37 @@ public void RecordMovePropertyThenComponentThenPropertyTest() var builder = new ObjectMappingBuilder(root); builder.RecordMoveProperty(child2Component, "blendShapes.child2", "blendShapes.child2Changed"); builder.RecordMoveProperty(child1Component, "blendShapes.child1", "blendShapes.child1Changed"); - builder.RecordMoveComponent(child1Component, child2); + builder.RecordMergeComponent(child1Component, child2Component); builder.RecordMoveProperty(child2Component, "blendShapes.moved", "blendShapes.movedChanged"); var built = builder.BuildObjectMapping(); + + var rootMapper = built.CreateAnimationMapper(root); Assert.That( - built.MapPath("", B("child1", typeof(SkinnedMeshRenderer), "blendShapes.child1")), + rootMapper.MapBinding(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.child1")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child1Changed"))); Assert.That( - built.MapPath("", B("child1", typeof(SkinnedMeshRenderer), "blendShapes.child2")), + rootMapper.MapBinding(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.child2")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child2"))); Assert.That( - built.MapPath("", B("child1", typeof(SkinnedMeshRenderer), "blendShapes.child1Other")), + rootMapper.MapBinding(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.child1Other")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child1Other"))); Assert.That( - built.MapPath("", B("child1", typeof(SkinnedMeshRenderer), "blendShapes.moved")), + rootMapper.MapBinding(B("child1", typeof(SkinnedMeshRenderer), "blendShapes.moved")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.movedChanged"))); Assert.That( - built.MapPath("", B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child1")), + rootMapper.MapBinding(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child1")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child1"))); Assert.That( - built.MapPath("", B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child2")), + rootMapper.MapBinding(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child2")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child2Changed"))); Assert.That( - built.MapPath("", B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child2Other")), + rootMapper.MapBinding(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child2Other")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.child2Other"))); Assert.That( - built.MapPath("", B("child2", typeof(SkinnedMeshRenderer), "blendShapes.moved")), + rootMapper.MapBinding(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.moved")), Is.EqualTo(B("child2", typeof(SkinnedMeshRenderer), "blendShapes.movedChanged"))); } @@ -226,20 +239,21 @@ public void RecordMovePropertyThenGameObjectThenPropertyTest() var builder = new ObjectMappingBuilder(root); builder.RecordMoveProperty(child11Component, "blendShapes.child11", "blendShapes.child11Changed"); - builder.RecordMoveObject(child11, child2); child11.transform.parent = child2.transform; builder.RecordMoveProperty(child11Component, "blendShapes.moved", "blendShapes.movedChanged"); var built = builder.BuildObjectMapping(); + + var rootMapper = built.CreateAnimationMapper(root); Assert.That( - built.MapPath("", B("child1/child11", typeof(SkinnedMeshRenderer), "blendShapes.child11")), + rootMapper.MapBinding(B("child1/child11", typeof(SkinnedMeshRenderer), "blendShapes.child11")), Is.EqualTo(B("child2/child11", typeof(SkinnedMeshRenderer), "blendShapes.child11Changed"))); Assert.That( - built.MapPath("", B("child1/child11", typeof(SkinnedMeshRenderer), "blendShapes.moved")), + rootMapper.MapBinding(B("child1/child11", typeof(SkinnedMeshRenderer), "blendShapes.moved")), Is.EqualTo(B("child2/child11", typeof(SkinnedMeshRenderer), "blendShapes.movedChanged"))); - Assert.That(built.InstanceIdToComponent[child11Component.GetInstanceID()].component, Is.SameAs(child11Component)); + Assert.That(built.MapComponentInstance(child11Component.GetInstanceID(), out var component), Is.False); } @@ -249,10 +263,9 @@ public void RecordMovePropertyThenGameObjectThenPropertyTest() static class ObjectMappingTestUtils { - public static (string, Type, string) MapPath(this ObjectMapping mapping, string rootPath, (string, Type, string) binding) + public static (string, Type, string) MapBinding(this AnimationObjectMapper mapping, (string, Type, string) binding) { - var result = mapping.MapPath(rootPath, - EditorCurveBinding.PPtrCurve(binding.Item1, binding.Item2, binding.Item3)); + var result = mapping.MapBinding(EditorCurveBinding.PPtrCurve(binding.Item1, binding.Item2, binding.Item3)); return (result.path, result.type, result.propertyName); } From 93a7f662ca51c5f32713fa87664d5c812d57bc1b Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 20 May 2023 15:29:48 +0900 Subject: [PATCH 2/2] docs(changelog): add Reimplemented Animation Mapping System Completely --- CHANGELOG-PRERELEASE.md | 2 ++ CHANGELOG.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index a12d767e1..68bd4b2f8 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog]. ### Added ### Changed +- Reimplemented Animation Mapping System Completely `#168` + - This should fixes problem with objects/components at same place. ### Deprecated diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b8b3a3a..a2fea92a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog]. ### Added ### Changed +- Reimplemented Animation Mapping System Completely `#168` + - This should fixes problem with objects/components at same place. ### Deprecated