-
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #168 from anatawa12/fix-component-at-same-place
Reimplemented Animation Mapping System Completely
- Loading branch information
Showing
16 changed files
with
454 additions
and
550 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string, MappedGameObjectInfo> _pathsCache = | ||
new Dictionary<string, MappedGameObjectInfo>(); | ||
|
||
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; | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<int, BeforeGameObjectTree> _beforeTree; | ||
private readonly IReadOnlyDictionary<int, ComponentInfo> _componentMapping; | ||
|
||
public ObjectMapping( | ||
IReadOnlyDictionary<int, BeforeGameObjectTree> beforeTree, | ||
IReadOnlyDictionary<int, ComponentInfo> 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<Type, int> 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<Component>(); | ||
ComponentInstanceIds = components.Select(x => x.GetInstanceID()).ToArray(); | ||
|
||
var componentByType = new Dictionary<Type, int>(); | ||
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<string, string> PropertyMapping; | ||
|
||
public ComponentInfo(int instanceId, int mergedInto, Type type, IReadOnlyDictionary<string, string> 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; | ||
} | ||
} | ||
} |
File renamed without changes.
Oops, something went wrong.