导入角色动画,和增加角色控制
This commit is contained in:
8
Assets/KINEMATION.meta
Normal file
8
Assets/KINEMATION.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b263a1e4a208c2f4ea2d8033c87fd158
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
6
Assets/KINEMATION/Join our Discord!.url
Normal file
6
Assets/KINEMATION/Join our Discord!.url
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[InternetShortcut]
|
||||||
|
URL=https://discord.gg/kinemation-1027338787958816860
|
||||||
|
IDList=
|
||||||
|
HotKey=0
|
||||||
|
[{000214A0-0000-0000-C000-000000000046}]
|
||||||
|
Prop3=19,11
|
||||||
7
Assets/KINEMATION/Join our Discord!.url.meta
Normal file
7
Assets/KINEMATION/Join our Discord!.url.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1c76f699feab13d48b502cea4355d361
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
3
Assets/KINEMATION/KAnimationCore.meta
Normal file
3
Assets/KINEMATION/KAnimationCore.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2a5fd998e8be412390025a255064c5c7
|
||||||
|
timeCreated: 1698338900
|
||||||
8
Assets/KINEMATION/KAnimationCore/Editor.meta
Normal file
8
Assets/KINEMATION/KAnimationCore/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f23f15c5bacdf7943b62dba14bba8c08
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/KINEMATION/KAnimationCore/Editor/Attributes.meta
Normal file
8
Assets/KINEMATION/KAnimationCore/Editor/Attributes.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d2023e663c3139c489b202b2e97564eb
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
// Designed by KINEMATION, 2025.
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Attributes;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Input;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.Animations;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Attributes
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(CurveSelectorAttribute))]
|
||||||
|
public class CurveSelectorDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
private void AddAnimatorNames(ref List<string> options, RuntimeAnimatorController controller)
|
||||||
|
{
|
||||||
|
if (controller == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatorController animatorController = controller as AnimatorController;
|
||||||
|
if (animatorController != null)
|
||||||
|
{
|
||||||
|
var parameters = animatorController.parameters;
|
||||||
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
if (parameters[i].type != AnimatorControllerParameterType.Float)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.Add($"{parameters[i].name} (Animator)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddInputNames(ref List<string> options, UserInputConfig config)
|
||||||
|
{
|
||||||
|
if (config == null) return;
|
||||||
|
|
||||||
|
foreach (var property in config.floatProperties)
|
||||||
|
{
|
||||||
|
options.Add($"{property.name} (Input)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
CurveSelectorAttribute curveAttribute = attribute as CurveSelectorAttribute;
|
||||||
|
if (curveAttribute == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KRig rig = (property.serializedObject.targetObject as IRigUser)?.GetRigAsset();
|
||||||
|
if (rig == null)
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(position, property, label, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializedProperty name = property.FindPropertyRelative("name");
|
||||||
|
SerializedProperty source = property.FindPropertyRelative("source");
|
||||||
|
SerializedProperty mode = property.FindPropertyRelative("mode");
|
||||||
|
SerializedProperty clampMin = property.FindPropertyRelative("clampMin");
|
||||||
|
|
||||||
|
List<string> options = new List<string>();
|
||||||
|
options.Add("None");
|
||||||
|
|
||||||
|
if (rig != null)
|
||||||
|
{
|
||||||
|
if (curveAttribute.useAnimator)
|
||||||
|
{
|
||||||
|
// Add the Animator curves.
|
||||||
|
AddAnimatorNames(ref options, rig.targetAnimator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curveAttribute.usePlayables)
|
||||||
|
{
|
||||||
|
// Add the Playables curves.
|
||||||
|
foreach (var curve in rig.rigCurves)
|
||||||
|
{
|
||||||
|
options.Add($"{curve} (Playables)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the Input parameters.
|
||||||
|
if (curveAttribute.useInput)
|
||||||
|
{
|
||||||
|
AddInputNames(ref options, rig.inputConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name ??= property;
|
||||||
|
|
||||||
|
int index = options.IndexOf(name.stringValue);
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = options.ToList().IndexOf(name.stringValue + " (Animator)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = options.ToList().IndexOf(name.stringValue + " (Playables)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = options.ToList().IndexOf(name.stringValue + " (Input)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect propertyRect = position;
|
||||||
|
propertyRect.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
|
||||||
|
index = EditorGUI.Popup(propertyRect, label.text, index, options.ToArray());
|
||||||
|
string selection = index >= 0 ? options[index] : "None";
|
||||||
|
|
||||||
|
if (source != null)
|
||||||
|
{
|
||||||
|
if (selection.EndsWith("(Animator)"))
|
||||||
|
{
|
||||||
|
source.intValue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.EndsWith("(Playables)"))
|
||||||
|
{
|
||||||
|
source.intValue = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.EndsWith("(Input)"))
|
||||||
|
{
|
||||||
|
source.intValue = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selection = selection.Replace(" (Playables)", "");
|
||||||
|
selection = selection.Replace(" (Animator)", "");
|
||||||
|
selection = selection.Replace(" (Input)", "");
|
||||||
|
|
||||||
|
name.stringValue = selection;
|
||||||
|
|
||||||
|
if (mode != null)
|
||||||
|
{
|
||||||
|
propertyRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
EditorGUI.PropertyField(propertyRect, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clampMin != null)
|
||||||
|
{
|
||||||
|
propertyRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
EditorGUI.PropertyField(propertyRect, clampMin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
SerializedProperty name = property.FindPropertyRelative("name");
|
||||||
|
SerializedProperty mode = property.FindPropertyRelative("mode");
|
||||||
|
SerializedProperty clampMin = property.FindPropertyRelative("clampMin");
|
||||||
|
|
||||||
|
if (name == null || mode == null || clampMin == null)
|
||||||
|
{
|
||||||
|
return base.GetPropertyHeight(property, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EditorGUIUtility.singleLineHeight * 3f + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 291f05330bc74df98860957b47e438f2
|
||||||
|
timeCreated: 1704268108
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// Designed by KINEMATION, 2025.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using KINEMATION.KAnimationCore.Editor.Rig;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Attributes;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Attributes
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(KRigElementChain))]
|
||||||
|
public class ElementChainDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
private CustomElementChainDrawerAttribute GetCustomChainAttribute()
|
||||||
|
{
|
||||||
|
CustomElementChainDrawerAttribute attr = null;
|
||||||
|
|
||||||
|
var attributes = fieldInfo.GetCustomAttributes(true);
|
||||||
|
foreach (var customAttribute in attributes)
|
||||||
|
{
|
||||||
|
attr = customAttribute as CustomElementChainDrawerAttribute;
|
||||||
|
if (attr != null) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginProperty(position, label, property);
|
||||||
|
|
||||||
|
IRigProvider rig = RigEditorUtility.TryGetRigProvider(fieldInfo, property);
|
||||||
|
|
||||||
|
SerializedProperty elementChain = property.FindPropertyRelative("elementChain");
|
||||||
|
SerializedProperty chainName = property.FindPropertyRelative("chainName");
|
||||||
|
|
||||||
|
if (rig != null)
|
||||||
|
{
|
||||||
|
float labelWidth = EditorGUIUtility.labelWidth;
|
||||||
|
var customChain = GetCustomChainAttribute();
|
||||||
|
|
||||||
|
Rect labelRect = new Rect(position.x, position.y, labelWidth, EditorGUIUtility.singleLineHeight);
|
||||||
|
Rect buttonRect = position;
|
||||||
|
|
||||||
|
string buttonText = $"Edit {chainName.stringValue}";
|
||||||
|
|
||||||
|
if (customChain is {drawLabel: true})
|
||||||
|
{
|
||||||
|
EditorGUI.PrefixLabel(labelRect, label);
|
||||||
|
labelRect.x += labelRect.width;
|
||||||
|
labelRect.width = (position.width - labelWidth) / 2f;
|
||||||
|
|
||||||
|
buttonRect.x = labelRect.x;
|
||||||
|
buttonRect.width = position.width - labelWidth;
|
||||||
|
|
||||||
|
buttonText = $"Edit {label.text}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customChain is {drawTextField: true})
|
||||||
|
{
|
||||||
|
chainName.stringValue = EditorGUI.TextField(labelRect, chainName.stringValue);
|
||||||
|
|
||||||
|
buttonRect.width = position.width - labelRect.width - (labelRect.x - position.x);
|
||||||
|
buttonRect.x = labelRect.x + labelRect.width;
|
||||||
|
|
||||||
|
buttonText = "Edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUI.Button(buttonRect, buttonText))
|
||||||
|
{
|
||||||
|
var hierarchy = rig.GetHierarchy();
|
||||||
|
if (hierarchy != null)
|
||||||
|
{
|
||||||
|
List<int> selectedIds = null;
|
||||||
|
|
||||||
|
// Get the active element indexes.
|
||||||
|
int arraySize = elementChain.arraySize;
|
||||||
|
|
||||||
|
if (arraySize > 0)
|
||||||
|
{
|
||||||
|
selectedIds = new List<int>();
|
||||||
|
|
||||||
|
for (int i = 0; i < arraySize; i++)
|
||||||
|
{
|
||||||
|
var boneName
|
||||||
|
= elementChain.GetArrayElementAtIndex(i).FindPropertyRelative("name").stringValue;
|
||||||
|
selectedIds.Add(Array.FindIndex(hierarchy,
|
||||||
|
element => element.name.Equals(boneName)) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RigWindow.ShowWindow(hierarchy,
|
||||||
|
(selectedElement) => { },
|
||||||
|
items =>
|
||||||
|
{
|
||||||
|
elementChain.ClearArray();
|
||||||
|
|
||||||
|
foreach (var selection in items)
|
||||||
|
{
|
||||||
|
elementChain.arraySize++;
|
||||||
|
int lastIndex = elementChain.arraySize - 1;
|
||||||
|
|
||||||
|
var element = elementChain.GetArrayElementAtIndex(lastIndex);
|
||||||
|
var name = element.FindPropertyRelative("name");
|
||||||
|
var index = element.FindPropertyRelative("index");
|
||||||
|
|
||||||
|
name.stringValue = selection.name;
|
||||||
|
index.intValue = selection.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
property.serializedObject.ApplyModifiedProperties();
|
||||||
|
},
|
||||||
|
true, selectedIds, "Element Chain Selection"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUI.enabled = false;
|
||||||
|
EditorGUI.PropertyField(position, property, label, true);
|
||||||
|
GUI.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1a14bd657f5c4df59493632603d3441f
|
||||||
|
timeCreated: 1711355637
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Attributes;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Attributes
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(ElementChainSelectorAttribute))]
|
||||||
|
public class ElementChainSelectorDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
ElementChainSelectorAttribute chainSelectorAttribute = attribute as ElementChainSelectorAttribute;
|
||||||
|
|
||||||
|
if (chainSelectorAttribute == null)
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(position, property, label, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KRig rig = (property.serializedObject.targetObject as IRigUser)?.GetRigAsset();
|
||||||
|
SerializedProperty assetProp = property.serializedObject.FindProperty(chainSelectorAttribute.assetName);
|
||||||
|
|
||||||
|
if (rig == null || assetProp != null)
|
||||||
|
{
|
||||||
|
if (assetProp == null) return;
|
||||||
|
rig = assetProp.objectReferenceValue as KRig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rig == null)
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(position, property, label, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> options = new List<string> {"None"};
|
||||||
|
var chainNames = rig.rigElementChains.Select(chain => chain.chainName).ToArray();
|
||||||
|
options.AddRange(chainNames);
|
||||||
|
|
||||||
|
int currentIndex = options.IndexOf(property.stringValue);
|
||||||
|
currentIndex = EditorGUI.Popup(position, label.text, currentIndex, options.ToArray());
|
||||||
|
string selection = currentIndex >= 0 ? options[currentIndex] : "None";
|
||||||
|
|
||||||
|
property.stringValue = selection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: df661746193b43b98e92a78be79cd084
|
||||||
|
timeCreated: 1710236013
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Attributes;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Input;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Attributes
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(InputProperty))]
|
||||||
|
public class InputPropertyDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
MonoBehaviour component = property.serializedObject.targetObject as MonoBehaviour;
|
||||||
|
UserInputConfig config = null;
|
||||||
|
|
||||||
|
if (component != null)
|
||||||
|
{
|
||||||
|
var root = component.transform.root;
|
||||||
|
UserInputController controller = root.gameObject.GetComponentInChildren<UserInputController>();
|
||||||
|
config = controller == null ? null : controller.inputConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
config = (property.serializedObject.targetObject as IRigUser)?.GetRigAsset().inputConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(position, property, label, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int selectedIndex = -1;
|
||||||
|
int indexOffset = 0;
|
||||||
|
|
||||||
|
List<string> properties = new List<string>();
|
||||||
|
List<string> options = new List<string>();
|
||||||
|
|
||||||
|
int count = config.boolProperties.Count;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var inputProperty = config.boolProperties[i];
|
||||||
|
properties.Add(inputProperty.name);
|
||||||
|
options.Add($"{inputProperty.name} (bool)");
|
||||||
|
if (property.stringValue.Equals(inputProperty.name))
|
||||||
|
{
|
||||||
|
selectedIndex = i + indexOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indexOffset += count;
|
||||||
|
|
||||||
|
count = config.intProperties.Count;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var inputProperty = config.intProperties[i];
|
||||||
|
properties.Add(inputProperty.name);
|
||||||
|
options.Add($"{inputProperty.name} (int)");
|
||||||
|
if (property.stringValue.Equals(inputProperty.name))
|
||||||
|
{
|
||||||
|
selectedIndex = i + indexOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indexOffset += count;
|
||||||
|
|
||||||
|
count = config.floatProperties.Count;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var inputProperty = config.floatProperties[i];
|
||||||
|
properties.Add(inputProperty.name);
|
||||||
|
options.Add($"{inputProperty.name} (float)");
|
||||||
|
if (property.stringValue.Equals(inputProperty.name))
|
||||||
|
{
|
||||||
|
selectedIndex = i + indexOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indexOffset += count;
|
||||||
|
|
||||||
|
count = config.vectorProperties.Count;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var inputProperty = config.vectorProperties[i];
|
||||||
|
properties.Add(inputProperty.name);
|
||||||
|
options.Add($"{inputProperty.name} (Vector4)");
|
||||||
|
if (property.stringValue.Equals(inputProperty.name))
|
||||||
|
{
|
||||||
|
selectedIndex = i + indexOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedIndex = EditorGUI.Popup(position, label.text, selectedIndex,
|
||||||
|
options.ToArray());
|
||||||
|
|
||||||
|
property.stringValue = selectedIndex == -1 ? "None" : properties[selectedIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d71581c42592488289ee2189885f2deb
|
||||||
|
timeCreated: 1711297253
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Designed by KINEMATION, 2024
|
||||||
|
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Attributes
|
||||||
|
{
|
||||||
|
public class KAttributesEditor
|
||||||
|
{
|
||||||
|
public static T GetComponent<T>(SerializedProperty property) where T : class
|
||||||
|
{
|
||||||
|
Object targetObject = property.serializedObject.targetObject;
|
||||||
|
|
||||||
|
Component targetComponent = targetObject as Component;
|
||||||
|
if (targetComponent != null)
|
||||||
|
{
|
||||||
|
return targetComponent.GetComponentInChildren<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3d63cd2c08ebe834286c4a63636ea284
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Attributes;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Attributes
|
||||||
|
{
|
||||||
|
public class RigEditorUtility
|
||||||
|
{
|
||||||
|
public static IRigProvider TryGetRigProvider(FieldInfo fieldInfo, SerializedProperty property)
|
||||||
|
{
|
||||||
|
IRigProvider provider = null;
|
||||||
|
|
||||||
|
RigAssetSelectorAttribute assetAttribute = null;
|
||||||
|
foreach (var customAttribute in fieldInfo.GetCustomAttributes(false))
|
||||||
|
{
|
||||||
|
if (customAttribute is RigAssetSelectorAttribute)
|
||||||
|
{
|
||||||
|
assetAttribute = customAttribute as RigAssetSelectorAttribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetAttribute != null && !string.IsNullOrEmpty(assetAttribute.assetName))
|
||||||
|
{
|
||||||
|
if (property.serializedObject.FindProperty(assetAttribute.assetName) is var prop)
|
||||||
|
{
|
||||||
|
provider = prop.objectReferenceValue as IRigProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider == null)
|
||||||
|
{
|
||||||
|
provider = property.serializedObject.targetObject as IRigProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider == null && property.serializedObject.targetObject is MonoBehaviour component)
|
||||||
|
{
|
||||||
|
provider = component.GetComponentInChildren<IRigProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fa2c3b5e5c3a4334a4d0ac853266c22f
|
||||||
|
timeCreated: 1722195056
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
using KINEMATION.KAnimationCore.Editor.Rig;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Attributes
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(KRigElement))]
|
||||||
|
public class RigElementDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
private void DrawRigElement(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
IRigProvider rig = RigEditorUtility.TryGetRigProvider(fieldInfo, property);
|
||||||
|
|
||||||
|
SerializedProperty name = property.FindPropertyRelative("name");
|
||||||
|
SerializedProperty index = property.FindPropertyRelative("index");
|
||||||
|
|
||||||
|
if (rig == null)
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(position, name, label, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate label width
|
||||||
|
float labelWidth = EditorGUIUtility.labelWidth;
|
||||||
|
float indentLevel = EditorGUI.indentLevel;
|
||||||
|
|
||||||
|
// Calculate button width and property field width
|
||||||
|
float totalWidth = position.width - indentLevel - labelWidth;
|
||||||
|
|
||||||
|
// Display the default property field
|
||||||
|
Rect propertyFieldRect = new Rect(position.x + indentLevel, position.y,
|
||||||
|
labelWidth, position.height);
|
||||||
|
|
||||||
|
EditorGUI.LabelField(propertyFieldRect, label.text);
|
||||||
|
|
||||||
|
// Display the bone selection button
|
||||||
|
Rect buttonRect = new Rect(position.x + indentLevel + labelWidth, position.y,
|
||||||
|
totalWidth, EditorGUIUtility.singleLineHeight);
|
||||||
|
|
||||||
|
string currentName = string.IsNullOrEmpty(name.stringValue) ? "None" : name.stringValue;
|
||||||
|
|
||||||
|
if (GUI.Button(buttonRect, currentName))
|
||||||
|
{
|
||||||
|
var hierarchy = rig.GetHierarchy();
|
||||||
|
if (hierarchy == null) return;
|
||||||
|
|
||||||
|
List<int> selection = null;
|
||||||
|
if (index.intValue > -1 || !string.IsNullOrEmpty(name.stringValue))
|
||||||
|
{
|
||||||
|
int foundIndex = ArrayUtility.FindIndex(hierarchy,
|
||||||
|
element => element.name.Equals(name.stringValue));
|
||||||
|
if(foundIndex >= 0) selection = new List<int>() { foundIndex + 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
RigWindow.ShowWindow(hierarchy, (selectedElement) =>
|
||||||
|
{
|
||||||
|
name.stringValue = selectedElement.name;
|
||||||
|
index.intValue = selectedElement.index;
|
||||||
|
name.serializedObject.ApplyModifiedProperties();
|
||||||
|
},
|
||||||
|
items => { },
|
||||||
|
false, selection, "Rig Element Selection"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginProperty(position, label, property);
|
||||||
|
|
||||||
|
DrawRigElement(position, property, label);
|
||||||
|
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8de53271a60e4843b40829a26b606311
|
||||||
|
timeCreated: 1704268272
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
using KINEMATION.KAnimationCore.Runtime.Attributes;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Attributes
|
||||||
|
{
|
||||||
|
[CustomPropertyDrawer(typeof(UnfoldAttribute))]
|
||||||
|
public class UnfoldDrawer : PropertyDrawer
|
||||||
|
{
|
||||||
|
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginProperty(position, label, property);
|
||||||
|
|
||||||
|
if (property.propertyType == SerializedPropertyType.Generic && !property.isArray)
|
||||||
|
{
|
||||||
|
SerializedProperty iterator = property.Copy();
|
||||||
|
bool enterChildren = true;
|
||||||
|
|
||||||
|
// Calculate the initial position rect for the first property
|
||||||
|
Rect propertyPosition = position;
|
||||||
|
propertyPosition.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
|
||||||
|
while (iterator.NextVisible(enterChildren))
|
||||||
|
{
|
||||||
|
if (SerializedProperty.EqualContents(iterator, property.GetEndProperty()))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
enterChildren = false;
|
||||||
|
|
||||||
|
EditorGUI.PropertyField(propertyPosition, iterator, new GUIContent(iterator.displayName), true);
|
||||||
|
|
||||||
|
// Update the position for the next property
|
||||||
|
propertyPosition.y +=
|
||||||
|
EditorGUI.GetPropertyHeight(iterator, new GUIContent(iterator.displayName), true) +
|
||||||
|
EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUI.PropertyField(position, property, GUIContent.none, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.EndProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||||
|
{
|
||||||
|
if (property.propertyType == SerializedPropertyType.Generic && !property.isArray)
|
||||||
|
{
|
||||||
|
float totalHeight = 0;
|
||||||
|
SerializedProperty iterator = property.Copy();
|
||||||
|
bool enterChildren = true;
|
||||||
|
while (iterator.NextVisible(enterChildren))
|
||||||
|
{
|
||||||
|
if (SerializedProperty.EqualContents(iterator, property.GetEndProperty()))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
enterChildren = false;
|
||||||
|
totalHeight +=
|
||||||
|
EditorGUI.GetPropertyHeight(iterator, new GUIContent(iterator.displayName), true) +
|
||||||
|
EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EditorGUI.GetPropertyHeight(property, GUIContent.none, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a0cfc4757c394ac39bff808454cc13bc
|
||||||
|
timeCreated: 1708710029
|
||||||
3
Assets/KINEMATION/KAnimationCore/Editor/Input.meta
Normal file
3
Assets/KINEMATION/KAnimationCore/Editor/Input.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 20621b83ddfc45a1bbbc606cca5fce17
|
||||||
|
timeCreated: 1710504821
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Editor.Misc;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Input;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Input
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(UserInputController), true)]
|
||||||
|
public class UserInputControllerInspector : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private UserInputController _controller;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_controller = (UserInputController) target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsInspectorFocused()
|
||||||
|
{
|
||||||
|
var focusedWindow = EditorWindow.focusedWindow;
|
||||||
|
|
||||||
|
if (focusedWindow != null && focusedWindow.GetType().Name == "InspectorWindow")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
base.OnInspectorGUI();
|
||||||
|
|
||||||
|
var properties = _controller.GetPropertyBindings();
|
||||||
|
|
||||||
|
if (properties == null) return;
|
||||||
|
|
||||||
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
string label = property.Item1;
|
||||||
|
object value = property.Item2;
|
||||||
|
|
||||||
|
if (value is bool)
|
||||||
|
{
|
||||||
|
bool toggle = EditorGUILayout.Toggle(label, (bool) value);
|
||||||
|
if(toggle != (bool) value) _controller.SetValue(label, toggle);
|
||||||
|
}
|
||||||
|
else if (value is int)
|
||||||
|
{
|
||||||
|
int integer = EditorGUILayout.IntField(label, (int) value);
|
||||||
|
if(integer != (int) value) _controller.SetValue(label, integer);
|
||||||
|
}
|
||||||
|
else if (value is float)
|
||||||
|
{
|
||||||
|
float floatVal = EditorGUILayout.FloatField(label, (float) value);
|
||||||
|
if(!Mathf.Approximately(floatVal, (float) value)) _controller.SetValue(label, floatVal);
|
||||||
|
}
|
||||||
|
else if (value is Vector4)
|
||||||
|
{
|
||||||
|
Vector4 vector4 = EditorGUILayout.Vector4Field(label, (Vector4) value);
|
||||||
|
if(!vector4.Equals(((Vector4) value))) _controller.SetValue(label, vector4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
if(!IsInspectorFocused()) Repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b96946dd2fc94f4ea898d02e62160f8d
|
||||||
|
timeCreated: 1710504830
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "KAnimationCore.Editor",
|
||||||
|
"rootNamespace": "",
|
||||||
|
"references": [
|
||||||
|
"GUID:d8548a9d25a091541a1fcde53694c91a"
|
||||||
|
],
|
||||||
|
"includePlatforms": [
|
||||||
|
"Editor"
|
||||||
|
],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3b9c1e777897a454ab8f17fb7264c2af
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
84
Assets/KINEMATION/KAnimationCore/Editor/KEditorUtility.cs
Normal file
84
Assets/KINEMATION/KAnimationCore/Editor/KEditorUtility.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// Designed by KINEMATION, 2025.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor
|
||||||
|
{
|
||||||
|
public class KEditorUtility
|
||||||
|
{
|
||||||
|
public const string EditorToolsPath = "Tools/KINEMATION";
|
||||||
|
|
||||||
|
public static GUIStyle boldLabel = new GUIStyle(EditorStyles.label)
|
||||||
|
{
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
richText = true,
|
||||||
|
wordWrap = false
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string GetProjectActiveFolder()
|
||||||
|
{
|
||||||
|
Type projectWindowUtilType = typeof(ProjectWindowUtil);
|
||||||
|
|
||||||
|
MethodInfo getActiveFolderPathMethod = projectWindowUtilType.GetMethod("GetActiveFolderPath",
|
||||||
|
BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
|
||||||
|
if (getActiveFolderPathMethod != null)
|
||||||
|
{
|
||||||
|
object result = getActiveFolderPathMethod.Invoke(null, null);
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return result.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "No folder is currently opened.";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveAsset(Object asset, string directory, string nameWithExtension)
|
||||||
|
{
|
||||||
|
string filePath = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(directory, nameWithExtension));
|
||||||
|
|
||||||
|
AssetDatabase.CreateAsset(asset, filePath);
|
||||||
|
EditorUtility.SetDirty(asset);
|
||||||
|
AssetDatabase.SaveAssetIfDirty(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSubAsset(Object asset)
|
||||||
|
{
|
||||||
|
if (asset == null) return false;
|
||||||
|
|
||||||
|
string path = AssetDatabase.GetAssetPath(asset);
|
||||||
|
if (string.IsNullOrEmpty(path)) return false;
|
||||||
|
|
||||||
|
Object mainAsset = AssetDatabase.LoadMainAssetAtPath(path);
|
||||||
|
return asset != mainAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AnimationClip GetAnimationClipFromSelection()
|
||||||
|
{
|
||||||
|
Object selected = Selection.activeObject;
|
||||||
|
if (selected == null) return null;
|
||||||
|
|
||||||
|
AnimationClip clip = selected as AnimationClip;
|
||||||
|
string path = AssetDatabase.GetAssetPath(selected);
|
||||||
|
|
||||||
|
// Try to find a clip in an FBX file.
|
||||||
|
if (clip == null && Path.GetExtension(path).ToLower() == ".fbx")
|
||||||
|
{
|
||||||
|
Object[] assets = AssetDatabase.LoadAllAssetsAtPath(path);
|
||||||
|
foreach (Object asset in assets)
|
||||||
|
{
|
||||||
|
clip = asset as AnimationClip;
|
||||||
|
if (clip != null && (clip.hideFlags & HideFlags.HideInHierarchy) == 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4c58ead6ef1d43e89c592bf4946771a3
|
||||||
|
timeCreated: 1722413573
|
||||||
3
Assets/KINEMATION/KAnimationCore/Editor/Rig.meta
Normal file
3
Assets/KINEMATION/KAnimationCore/Editor/Rig.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c942a0fe433842d08e13cd1ec3fc7981
|
||||||
|
timeCreated: 1704100963
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Rig
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(KRigComponent), true)]
|
||||||
|
public class KRigComponentEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private KRigComponent _rigComponent;
|
||||||
|
private int _boneCount = 0;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_rigComponent = (KRigComponent) target;
|
||||||
|
_boneCount = _rigComponent.GetRigTransforms().Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("Total bones: " + _boneCount);
|
||||||
|
if (GUILayout.Button("Refresh Hierarchy"))
|
||||||
|
{
|
||||||
|
_rigComponent.RefreshHierarchy();
|
||||||
|
_boneCount = _rigComponent.GetRigTransforms().Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 69ff5379d20945a39a83cf6611fd2fa3
|
||||||
|
timeCreated: 1704612600
|
||||||
97
Assets/KINEMATION/KAnimationCore/Editor/Rig/KRigEditor.cs
Normal file
97
Assets/KINEMATION/KAnimationCore/Editor/Rig/KRigEditor.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using KINEMATION.KAnimationCore.Editor.Misc;
|
||||||
|
using KINEMATION.KAnimationCore.Editor.Tools;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Input;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Rig
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(KRig), true)]
|
||||||
|
public class KRigEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private KRig _rigAsset;
|
||||||
|
private KRigComponent _rigComponent;
|
||||||
|
|
||||||
|
private SerializedProperty _rigElementChains;
|
||||||
|
private SerializedProperty _rigCurves;
|
||||||
|
|
||||||
|
private KToolbarWidget _kToolbarWidget;
|
||||||
|
private RigTreeWidget _rigTreeWidget;
|
||||||
|
|
||||||
|
private void RenderHierarchy()
|
||||||
|
{
|
||||||
|
_rigTreeWidget.Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderElementChains()
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(_rigElementChains);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderCurves()
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(_rigCurves);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_rigAsset = (KRig) target;
|
||||||
|
|
||||||
|
_rigElementChains = serializedObject.FindProperty("rigElementChains");
|
||||||
|
_rigCurves = serializedObject.FindProperty("rigCurves");
|
||||||
|
|
||||||
|
_kToolbarWidget = new KToolbarWidget(new KToolbarTab[]
|
||||||
|
{
|
||||||
|
new KToolbarTab()
|
||||||
|
{
|
||||||
|
name = "Hierarchy",
|
||||||
|
onTabRendered = RenderHierarchy
|
||||||
|
},
|
||||||
|
new KToolbarTab()
|
||||||
|
{
|
||||||
|
name = "Element Chains",
|
||||||
|
onTabRendered = RenderElementChains
|
||||||
|
},
|
||||||
|
new KToolbarTab()
|
||||||
|
{
|
||||||
|
name = "Curves",
|
||||||
|
onTabRendered = RenderCurves
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_rigTreeWidget = new RigTreeWidget();
|
||||||
|
_rigTreeWidget.Refresh(_rigAsset.GetHierarchy());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ImportRig()
|
||||||
|
{
|
||||||
|
_rigAsset.ImportRig(_rigComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
_rigComponent = (KRigComponent) EditorGUILayout.ObjectField("Rig Component",
|
||||||
|
_rigComponent, typeof(KRigComponent), true);
|
||||||
|
|
||||||
|
_rigAsset.targetAnimator = (RuntimeAnimatorController) EditorGUILayout.ObjectField("Animator",
|
||||||
|
_rigAsset.targetAnimator, typeof(RuntimeAnimatorController), true);
|
||||||
|
|
||||||
|
_rigAsset.inputConfig = (UserInputConfig) EditorGUILayout.ObjectField("Input Config",
|
||||||
|
_rigAsset.inputConfig, typeof(UserInputConfig), true);
|
||||||
|
|
||||||
|
if (_rigComponent == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Rig Component not specified", MessageType.Warning);
|
||||||
|
}
|
||||||
|
else if (GUILayout.Button("Import Rig"))
|
||||||
|
{
|
||||||
|
ImportRig();
|
||||||
|
_rigTreeWidget.Refresh(_rigAsset.GetHierarchy());
|
||||||
|
}
|
||||||
|
|
||||||
|
_kToolbarWidget.Render();
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: af8d20a2cb744d08ba69726161fe4a40
|
||||||
|
timeCreated: 1704100975
|
||||||
208
Assets/KINEMATION/KAnimationCore/Editor/Rig/RigMappingMenu.cs
Normal file
208
Assets/KINEMATION/KAnimationCore/Editor/Rig/RigMappingMenu.cs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using KINEMATION.KAnimationCore.Editor.Misc;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditorInternal;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Rig
|
||||||
|
{
|
||||||
|
public class StringListWidget
|
||||||
|
{
|
||||||
|
public List<string> list = new List<string>();
|
||||||
|
private ReorderableList _reorderableOptionsList;
|
||||||
|
|
||||||
|
public void Initialize(string listName)
|
||||||
|
{
|
||||||
|
_reorderableOptionsList = new ReorderableList(list, typeof(string), true,
|
||||||
|
true, true, true);
|
||||||
|
|
||||||
|
_reorderableOptionsList.drawHeaderCallback = (Rect rect) =>
|
||||||
|
{
|
||||||
|
EditorGUI.LabelField(rect, listName);
|
||||||
|
};
|
||||||
|
|
||||||
|
_reorderableOptionsList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
|
||||||
|
{
|
||||||
|
rect.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
list[index] = EditorGUI.TextField(rect, list[index]);
|
||||||
|
};
|
||||||
|
|
||||||
|
_reorderableOptionsList.onAddCallback = (ReorderableList list) =>
|
||||||
|
{
|
||||||
|
this.list.Add("");
|
||||||
|
};
|
||||||
|
|
||||||
|
_reorderableOptionsList.onRemoveCallback = (ReorderableList list) =>
|
||||||
|
{
|
||||||
|
ReorderableList.defaultBehaviours.DoRemoveButton(list);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render()
|
||||||
|
{
|
||||||
|
_reorderableOptionsList.DoLayoutList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RigMappingWindow : EditorWindow
|
||||||
|
{
|
||||||
|
private KRig _rigAsset;
|
||||||
|
private GameObject _root;
|
||||||
|
private StringListWidget _entriesToAvoid;
|
||||||
|
|
||||||
|
public static RigMappingWindow CreateWindow()
|
||||||
|
{
|
||||||
|
var window = GetWindow<RigMappingWindow>(false, "Rig Mapping", true);
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TraverseHierarchy(Transform root, ref KRigElementChain chain, KRig rig)
|
||||||
|
{
|
||||||
|
KRigElement element = rig.rigHierarchy.Find(item => item.name.Equals(root.name));
|
||||||
|
chain.elementChain.Add(element);
|
||||||
|
|
||||||
|
// Filter child bones from corrections, twists and IKs.
|
||||||
|
List<int> fkBoneIndexes = new List<int>();
|
||||||
|
for (int i = 0; i < root.childCount; i++)
|
||||||
|
{
|
||||||
|
string childName = root.GetChild(i).name.ToLower();
|
||||||
|
|
||||||
|
bool bSkipIteration = false;
|
||||||
|
foreach (var entry in _entriesToAvoid.list)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrEmpty(entry)) continue;
|
||||||
|
if (childName.Contains(entry.ToLower()))
|
||||||
|
{
|
||||||
|
bSkipIteration = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bSkipIteration)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fkBoneIndexes.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no extra branches, traverse the old chain.
|
||||||
|
if (fkBoneIndexes.Count == 1)
|
||||||
|
{
|
||||||
|
TraverseHierarchy(root.GetChild(fkBoneIndexes[0]), ref chain, rig);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have branches, create new chains and start traversing them.
|
||||||
|
foreach (var boneIndex in fkBoneIndexes)
|
||||||
|
{
|
||||||
|
string chainName = root.GetChild(boneIndex).name;
|
||||||
|
KRigElementChain newChain = new KRigElementChain()
|
||||||
|
{
|
||||||
|
chainName = chainName,
|
||||||
|
elementChain = new List<KRigElement>()
|
||||||
|
};
|
||||||
|
|
||||||
|
rig.rigElementChains.Add(newChain);
|
||||||
|
TraverseHierarchy(root.GetChild(boneIndex), ref newChain, rig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MapRigChains(GameObject root)
|
||||||
|
{
|
||||||
|
if (root == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("RigMappingWindow: Selected GameObject is NULL!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KRigComponent rigComponent = root.GetComponent<KRigComponent>();
|
||||||
|
if (rigComponent == null)
|
||||||
|
{
|
||||||
|
rigComponent = root.AddComponent<KRigComponent>();
|
||||||
|
rigComponent.RefreshHierarchy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_rigAsset == null)
|
||||||
|
{
|
||||||
|
_rigAsset = ScriptableObject.CreateInstance<KRig>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rigAsset.rigElementChains.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_rigAsset.ImportRig(rigComponent);
|
||||||
|
|
||||||
|
KRigElementChain newChain = new KRigElementChain()
|
||||||
|
{
|
||||||
|
chainName = root.name,
|
||||||
|
elementChain = new List<KRigElement>()
|
||||||
|
};
|
||||||
|
|
||||||
|
_rigAsset.rigElementChains.Add(newChain);
|
||||||
|
TraverseHierarchy(root.transform, ref newChain, _rigAsset);
|
||||||
|
|
||||||
|
if (EditorUtility.IsPersistent(_rigAsset))
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(_rigAsset);
|
||||||
|
AssetDatabase.SaveAssetIfDirty(_rigAsset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Undo.RegisterCreatedObjectUndo(_rigAsset, "Create Rig Asset");
|
||||||
|
string path = $"{KEditorUtility.GetProjectActiveFolder()}/Rig_{root.transform.root.name}.asset";
|
||||||
|
AssetDatabase.CreateAsset(_rigAsset, AssetDatabase.GenerateUniqueAssetPath(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_entriesToAvoid = new StringListWidget();
|
||||||
|
_entriesToAvoid.Initialize("Bone Names to Avoid");
|
||||||
|
|
||||||
|
_entriesToAvoid.list.Add("twist");
|
||||||
|
_entriesToAvoid.list.Add("correct");
|
||||||
|
|
||||||
|
_root = Selection.activeObject as GameObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGUI()
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("If empty, a new asset will be created.", MessageType.Info);
|
||||||
|
_rigAsset = (KRig) EditorGUILayout.ObjectField("Rig Asset", _rigAsset, typeof(KRig),
|
||||||
|
false);
|
||||||
|
|
||||||
|
_root = (GameObject) EditorGUILayout.ObjectField("Root Bone", _root, typeof(GameObject),
|
||||||
|
true);
|
||||||
|
|
||||||
|
_entriesToAvoid.Render();
|
||||||
|
|
||||||
|
if (GUILayout.Button("Create Rig Mapping"))
|
||||||
|
{
|
||||||
|
MapRigChains(_root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RigMappingMenu
|
||||||
|
{
|
||||||
|
private const string RigItemName = "GameObject/Auto Rig Mapping";
|
||||||
|
|
||||||
|
[MenuItem(RigItemName, true)]
|
||||||
|
private static bool ValidateCreateRigMapping()
|
||||||
|
{
|
||||||
|
return Selection.activeObject is GameObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem(RigItemName)]
|
||||||
|
private static void CreateRigMapping()
|
||||||
|
{
|
||||||
|
var window = RigMappingWindow.CreateWindow();
|
||||||
|
window.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 34bf35b5a2cd4145b620c133c2a54346
|
||||||
|
timeCreated: 1721131260
|
||||||
200
Assets/KINEMATION/KAnimationCore/Editor/Rig/RigTreeView.cs
Normal file
200
Assets/KINEMATION/KAnimationCore/Editor/Rig/RigTreeView.cs
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Editor.Misc;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Rig
|
||||||
|
{
|
||||||
|
public delegate void OnItemClicked(KRigElement selection);
|
||||||
|
public delegate void OnSelectionChanged(KRigElement[] selectedItems);
|
||||||
|
|
||||||
|
public class RigTreeView : TreeView
|
||||||
|
{
|
||||||
|
public OnItemClicked onItemClicked;
|
||||||
|
|
||||||
|
public float singleRowHeight = 0f;
|
||||||
|
public bool useToggle;
|
||||||
|
|
||||||
|
private List<TreeViewItem> _treeItems;
|
||||||
|
private KRigElement[] _originalItems;
|
||||||
|
private bool[] _selectedItems;
|
||||||
|
|
||||||
|
public RigTreeView(TreeViewState state) : base(state)
|
||||||
|
{
|
||||||
|
_treeItems = new List<TreeViewItem>();
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public KRigElement[] GetToggledItems()
|
||||||
|
{
|
||||||
|
List<KRigElement> toggledItems = new List<KRigElement>();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
foreach (var element in _originalItems)
|
||||||
|
{
|
||||||
|
if (_selectedItems[index])
|
||||||
|
{
|
||||||
|
var newElement = element;
|
||||||
|
newElement.index = index;
|
||||||
|
toggledItems.Add(newElement);
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toggledItems.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeTreeItems(KRigElement[] hierarchy)
|
||||||
|
{
|
||||||
|
_treeItems.Clear();
|
||||||
|
|
||||||
|
int count = hierarchy.Length;
|
||||||
|
_originalItems = new KRigElement[count];
|
||||||
|
|
||||||
|
int depthOffset = useToggle ? 1 : 0;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
_treeItems.Add(new TreeViewItem(i + 1, hierarchy[i].depth + depthOffset, hierarchy[i].name));
|
||||||
|
}
|
||||||
|
|
||||||
|
hierarchy.CopyTo(_originalItems, 0);
|
||||||
|
|
||||||
|
_selectedItems = new bool[count];
|
||||||
|
var selection = GetSelection();
|
||||||
|
|
||||||
|
foreach (var index in selection)
|
||||||
|
{
|
||||||
|
_selectedItems[index - 1] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Filter(string query)
|
||||||
|
{
|
||||||
|
int depthOffset = useToggle ? 1 : 0;
|
||||||
|
|
||||||
|
_treeItems.Clear();
|
||||||
|
query = query.ToLower().Trim();
|
||||||
|
|
||||||
|
int count = _originalItems.Length;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(query))
|
||||||
|
{
|
||||||
|
_treeItems.Add(new TreeViewItem(i + 1, _originalItems[i].depth + depthOffset,
|
||||||
|
_originalItems[i].name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_originalItems[i].name.ToLower().Trim().Contains(query)) continue;
|
||||||
|
|
||||||
|
_treeItems.Add(new TreeViewItem(i + 1, depthOffset, _originalItems[i].name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TreeViewItem BuildRoot()
|
||||||
|
{
|
||||||
|
// 0 is the root ID, -1 means the root has no parent
|
||||||
|
var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
|
||||||
|
|
||||||
|
// Utility method to setup the parent/children relationship
|
||||||
|
SetupParentsAndChildrenFromDepths(root, _treeItems);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void RowGUI(RowGUIArgs args)
|
||||||
|
{
|
||||||
|
Color darkGrey = new Color(0.2f, 0.2f, 0.2f);
|
||||||
|
Color lightGrey = new Color(0.25f, 0.25f, 0.25f);
|
||||||
|
Color blue = new Color(115f / 255f, 147f / 255f, 179f / 255f, 0.25f);
|
||||||
|
|
||||||
|
bool isSelected = args.selected;
|
||||||
|
if (args.rowRect.Contains(Event.current.mousePosition)) isSelected = true;
|
||||||
|
|
||||||
|
var color = isSelected ? blue : args.row % 2 == 0 ? lightGrey : darkGrey;
|
||||||
|
EditorGUI.DrawRect(args.rowRect, color);
|
||||||
|
|
||||||
|
if (useToggle)
|
||||||
|
{
|
||||||
|
var rect = args.rowRect;
|
||||||
|
rect.width = rect.height;
|
||||||
|
|
||||||
|
bool prevToggle = _selectedItems[args.item.id - 1];
|
||||||
|
bool toggle = EditorGUI.Toggle(rect, prevToggle);
|
||||||
|
|
||||||
|
if (toggle != prevToggle)
|
||||||
|
{
|
||||||
|
// If this item is a part of a larger selection, update the status globally.
|
||||||
|
if (IsSelected(args.item.id))
|
||||||
|
{
|
||||||
|
var selection = GetSelection();
|
||||||
|
foreach (var selectedId in selection) _selectedItems[selectedId - 1] = toggle;
|
||||||
|
} // Otherwise, change this toggle only.
|
||||||
|
else _selectedItems[args.item.id - 1] = toggle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
singleRowHeight = rowHeight;
|
||||||
|
|
||||||
|
if (!useToggle)
|
||||||
|
{
|
||||||
|
Rect buttonRect = args.rowRect;
|
||||||
|
float indent = GetContentIndent(args.item);
|
||||||
|
buttonRect.x += indent;
|
||||||
|
|
||||||
|
if (GUI.Button(buttonRect, args.item.displayName, EditorStyles.label))
|
||||||
|
{
|
||||||
|
var element = _originalItems[args.item.id - 1];
|
||||||
|
element.index = args.item.id - 1;
|
||||||
|
onItemClicked?.Invoke(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.RowGUI(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RigTreeWidget
|
||||||
|
{
|
||||||
|
public RigTreeView rigTreeView = new RigTreeView(new TreeViewState());
|
||||||
|
|
||||||
|
public void Refresh(KRigElement[] hierarchy)
|
||||||
|
{
|
||||||
|
rigTreeView.InitializeTreeItems(hierarchy);
|
||||||
|
rigTreeView.Reload();
|
||||||
|
rigTreeView.ExpandAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render()
|
||||||
|
{
|
||||||
|
float maxHeight = rigTreeView.singleRowHeight + rigTreeView.totalHeight;
|
||||||
|
float height = Mathf.Max(rigTreeView.singleRowHeight * 2f, maxHeight);
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
Rect parentRect = GUILayoutUtility.GetRect(0f, 0f, 0f, height);
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
float padding = 7f;
|
||||||
|
|
||||||
|
GUI.Box(parentRect, "", EditorStyles.helpBox);
|
||||||
|
|
||||||
|
parentRect.x += padding;
|
||||||
|
parentRect.y += padding;
|
||||||
|
|
||||||
|
parentRect.width -= 2f * padding;
|
||||||
|
parentRect.height -= 2f * padding;
|
||||||
|
|
||||||
|
rigTreeView.OnGUI(parentRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e0c126b8e1357c94ebe0fe4b77d0cdfa
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
69
Assets/KINEMATION/KAnimationCore/Editor/Rig/RigWindow.cs
Normal file
69
Assets/KINEMATION/KAnimationCore/Editor/Rig/RigWindow.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Rig
|
||||||
|
{
|
||||||
|
public class RigWindow : EditorWindow
|
||||||
|
{
|
||||||
|
private OnItemClicked _onClicked;
|
||||||
|
private OnSelectionChanged _onSelectionChanged;
|
||||||
|
|
||||||
|
private Vector2 _scrollPosition;
|
||||||
|
private string _searchEntry = string.Empty;
|
||||||
|
|
||||||
|
private RigTreeWidget _rigTreeWidget;
|
||||||
|
private bool _useSelection = false;
|
||||||
|
|
||||||
|
public static void ShowWindow(KRigElement[] hierarchy, OnItemClicked onClicked,
|
||||||
|
OnSelectionChanged onSelectionChanged, bool useSelection, List<int> selection = null, string title = "Selection")
|
||||||
|
{
|
||||||
|
RigWindow window = CreateInstance<RigWindow>();
|
||||||
|
|
||||||
|
window._useSelection = useSelection;
|
||||||
|
window._onClicked = onClicked;
|
||||||
|
window._onSelectionChanged = onSelectionChanged;
|
||||||
|
window.titleContent = new GUIContent(title);
|
||||||
|
|
||||||
|
window._rigTreeWidget = new RigTreeWidget
|
||||||
|
{
|
||||||
|
rigTreeView =
|
||||||
|
{
|
||||||
|
useToggle = useSelection,
|
||||||
|
onItemClicked = window.OnItemClicked
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selection != null)
|
||||||
|
{
|
||||||
|
window._rigTreeWidget.rigTreeView.SetSelection(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
window._rigTreeWidget.Refresh(hierarchy);
|
||||||
|
window.minSize = new Vector2(450f, 550f);
|
||||||
|
window.ShowAuxWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnItemClicked(KRigElement selection)
|
||||||
|
{
|
||||||
|
_onClicked.Invoke(selection);
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginHorizontal(GUI.skin.FindStyle("Toolbar"));
|
||||||
|
_searchEntry = EditorGUILayout.TextField(_searchEntry, EditorStyles.toolbarSearchField);
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
_rigTreeWidget.rigTreeView.Filter(_searchEntry);
|
||||||
|
_rigTreeWidget.Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (_useSelection) _onSelectionChanged?.Invoke(_rigTreeWidget.rigTreeView.GetToggledItems());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ebe01bfda1354888806c73d35b2e8af0
|
||||||
|
timeCreated: 1704267364
|
||||||
3
Assets/KINEMATION/KAnimationCore/Editor/Tools.meta
Normal file
3
Assets/KINEMATION/KAnimationCore/Editor/Tools.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9718b071330346578b345a2c61308296
|
||||||
|
timeCreated: 1756377341
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Tools
|
||||||
|
{
|
||||||
|
public class AvatarMaskTool : IEditorTool
|
||||||
|
{
|
||||||
|
private Transform _root;
|
||||||
|
private Transform _boneToAdd;
|
||||||
|
private AvatarMask _maskToModify;
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render()
|
||||||
|
{
|
||||||
|
_root = EditorGUILayout.ObjectField("Root", _root, typeof(Transform), true)
|
||||||
|
as Transform;
|
||||||
|
|
||||||
|
_boneToAdd =
|
||||||
|
EditorGUILayout.ObjectField("Bone To Add", _boneToAdd, typeof(Transform), true)
|
||||||
|
as Transform;
|
||||||
|
|
||||||
|
_maskToModify =
|
||||||
|
EditorGUILayout.ObjectField("Upper Body Mask", _maskToModify, typeof(AvatarMask), true)
|
||||||
|
as AvatarMask;
|
||||||
|
|
||||||
|
if (_boneToAdd == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Select the Bone transform", MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_maskToModify == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Select the Avatar Mask", MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Add Bone"))
|
||||||
|
{
|
||||||
|
for (int i = _maskToModify.transformCount - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (_maskToModify.GetTransformPath(i).EndsWith(_boneToAdd.name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_maskToModify.AddTransformPath(_boneToAdd, false);
|
||||||
|
|
||||||
|
if (_root == null) return;
|
||||||
|
|
||||||
|
string path = _maskToModify.GetTransformPath(_maskToModify.transformCount - 1);
|
||||||
|
string[] array = path.Split("/");
|
||||||
|
int rootIndex = Array.IndexOf(array, _root.name);
|
||||||
|
|
||||||
|
if (rootIndex == -1 || rootIndex == array.Length - 1) return;
|
||||||
|
|
||||||
|
path = String.Join("/", array, rootIndex + 1, array.Length - rootIndex - 1);
|
||||||
|
|
||||||
|
_maskToModify.SetTransformPath(_maskToModify.transformCount - 1, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetToolCategory()
|
||||||
|
{
|
||||||
|
return "Animation";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetToolName()
|
||||||
|
{
|
||||||
|
return "Modify Avatar Mask";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDocsURL()
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetToolDescription()
|
||||||
|
{
|
||||||
|
return "This tool lets you add custom bones and Game Objects to an Avatar Mask.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bdad9af0d0f248378d9dc58e2c401b2c
|
||||||
|
timeCreated: 1757309137
|
||||||
240
Assets/KINEMATION/KAnimationCore/Editor/Tools/CopyBoneTool.cs
Normal file
240
Assets/KINEMATION/KAnimationCore/Editor/Tools/CopyBoneTool.cs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Tools
|
||||||
|
{
|
||||||
|
public class CopyBoneTool : IEditorTool
|
||||||
|
{
|
||||||
|
private Transform _root;
|
||||||
|
private Transform _extractFrom;
|
||||||
|
private Transform _extractTo;
|
||||||
|
|
||||||
|
private AnimationClip _clip;
|
||||||
|
private AnimationClip _refClip;
|
||||||
|
|
||||||
|
private Vector3 _rotationOffset;
|
||||||
|
private bool _isAdditive;
|
||||||
|
|
||||||
|
private static Vector3 GetVectorValue(AnimationClip clip, EditorCurveBinding[] bindings, float time)
|
||||||
|
{
|
||||||
|
float tX = AnimationUtility.GetEditorCurve(clip, bindings[0]).Evaluate(time);
|
||||||
|
float tY = AnimationUtility.GetEditorCurve(clip, bindings[1]).Evaluate(time);
|
||||||
|
float tZ = AnimationUtility.GetEditorCurve(clip, bindings[2]).Evaluate(time);
|
||||||
|
|
||||||
|
return new Vector3(tX, tY, tZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Quaternion GetQuatValue(AnimationClip clip, EditorCurveBinding[] bindings, float time)
|
||||||
|
{
|
||||||
|
float tX = AnimationUtility.GetEditorCurve(clip, bindings[0]).Evaluate(time);
|
||||||
|
float tY = AnimationUtility.GetEditorCurve(clip, bindings[1]).Evaluate(time);
|
||||||
|
float tZ = AnimationUtility.GetEditorCurve(clip, bindings[2]).Evaluate(time);
|
||||||
|
float tW = AnimationUtility.GetEditorCurve(clip, bindings[3]).Evaluate(time);
|
||||||
|
|
||||||
|
return new Quaternion(tX, tY, tZ, tW);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetBonePath(Transform targetBone, Transform root)
|
||||||
|
{
|
||||||
|
if (targetBone == null || root == null) return "";
|
||||||
|
|
||||||
|
string path = targetBone.name;
|
||||||
|
Transform current = targetBone.parent;
|
||||||
|
|
||||||
|
while (current != null && current != root)
|
||||||
|
{
|
||||||
|
path = current.name + "/" + path;
|
||||||
|
current = current.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (current == root) ? path : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractAndSetAnimationData()
|
||||||
|
{
|
||||||
|
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(_clip);
|
||||||
|
|
||||||
|
EditorCurveBinding[] tBindings = new EditorCurveBinding[3];
|
||||||
|
EditorCurveBinding[] rBindings = new EditorCurveBinding[4];
|
||||||
|
|
||||||
|
foreach (var binding in bindings)
|
||||||
|
{
|
||||||
|
if (!binding.path.EndsWith(_extractFrom.name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.propertyName.ToLower().Contains("m_localposition.x"))
|
||||||
|
{
|
||||||
|
tBindings[0] = binding;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.propertyName.ToLower().Contains("m_localposition.y"))
|
||||||
|
{
|
||||||
|
tBindings[1] = binding;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.propertyName.ToLower().Contains("m_localposition.z"))
|
||||||
|
{
|
||||||
|
tBindings[2] = binding;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.propertyName.ToLower().Contains("m_localrotation.x"))
|
||||||
|
{
|
||||||
|
rBindings[0] = binding;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.propertyName.ToLower().Contains("m_localrotation.y"))
|
||||||
|
{
|
||||||
|
rBindings[1] = binding;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.propertyName.ToLower().Contains("m_localrotation.z"))
|
||||||
|
{
|
||||||
|
rBindings[2] = binding;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.propertyName.ToLower().Contains("m_localrotation.w"))
|
||||||
|
{
|
||||||
|
rBindings[3] = binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 refTranslation = Vector3.zero;
|
||||||
|
Quaternion refRotation = Quaternion.identity;
|
||||||
|
|
||||||
|
if (_refClip != null && _isAdditive)
|
||||||
|
{
|
||||||
|
refTranslation = GetVectorValue(_refClip, tBindings, 0f);
|
||||||
|
refRotation = GetQuatValue(_refClip, rBindings, 0f) * Quaternion.Euler(_rotationOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationCurve tX = new AnimationCurve();
|
||||||
|
AnimationCurve tY = new AnimationCurve();
|
||||||
|
AnimationCurve tZ = new AnimationCurve();
|
||||||
|
|
||||||
|
AnimationCurve rX = new AnimationCurve();
|
||||||
|
AnimationCurve rY = new AnimationCurve();
|
||||||
|
AnimationCurve rZ = new AnimationCurve();
|
||||||
|
AnimationCurve rW = new AnimationCurve();
|
||||||
|
|
||||||
|
float playLength = _clip.length;
|
||||||
|
float frameRate = 1f / _clip.frameRate;
|
||||||
|
float playBack = 0f;
|
||||||
|
|
||||||
|
while (playBack <= playLength)
|
||||||
|
{
|
||||||
|
Vector3 translation = GetVectorValue(_clip, tBindings, playBack);
|
||||||
|
Quaternion rotation = GetQuatValue(_clip, rBindings, playBack) * Quaternion.Euler(_rotationOffset);
|
||||||
|
|
||||||
|
Vector3 deltaT = translation - refTranslation;
|
||||||
|
Quaternion deltaR = Quaternion.Inverse(refRotation) * rotation;
|
||||||
|
|
||||||
|
tX.AddKey(playBack, deltaT.x);
|
||||||
|
tY.AddKey(playBack, deltaT.y);
|
||||||
|
tZ.AddKey(playBack, deltaT.z);
|
||||||
|
|
||||||
|
rX.AddKey(playBack, deltaR.x);
|
||||||
|
rY.AddKey(playBack, deltaR.y);
|
||||||
|
rZ.AddKey(playBack, deltaR.z);
|
||||||
|
rW.AddKey(playBack, deltaR.w);
|
||||||
|
|
||||||
|
playBack += frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = GetBonePath(_extractTo, _root);
|
||||||
|
|
||||||
|
_clip.SetCurve(path, typeof(Transform), tBindings[0].propertyName, tX);
|
||||||
|
_clip.SetCurve(path, typeof(Transform), tBindings[1].propertyName, tY);
|
||||||
|
_clip.SetCurve(path, typeof(Transform), tBindings[2].propertyName, tZ);
|
||||||
|
|
||||||
|
_clip.SetCurve(path, typeof(Transform), rBindings[0].propertyName, rX);
|
||||||
|
_clip.SetCurve(path, typeof(Transform), rBindings[1].propertyName, rY);
|
||||||
|
_clip.SetCurve(path, typeof(Transform), rBindings[2].propertyName, rZ);
|
||||||
|
_clip.SetCurve(path, typeof(Transform), rBindings[3].propertyName, rW);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render()
|
||||||
|
{
|
||||||
|
if (!EditorGUIUtility.wideMode)
|
||||||
|
{
|
||||||
|
EditorGUIUtility.wideMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Label("Settings", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
_clip =
|
||||||
|
EditorGUILayout.ObjectField("Target Animation", _clip, typeof(AnimationClip), true)
|
||||||
|
as AnimationClip;
|
||||||
|
|
||||||
|
_refClip =
|
||||||
|
EditorGUILayout.ObjectField("Reference Animation", _refClip,
|
||||||
|
typeof(AnimationClip), true) as AnimationClip;
|
||||||
|
|
||||||
|
_root = EditorGUILayout.ObjectField("Root", _root, typeof(Transform), true)
|
||||||
|
as Transform;
|
||||||
|
|
||||||
|
_extractFrom = EditorGUILayout.ObjectField("From", _extractFrom, typeof(Transform), true)
|
||||||
|
as Transform;
|
||||||
|
|
||||||
|
_extractTo = EditorGUILayout.ObjectField("To", _extractTo, typeof(Transform), true)
|
||||||
|
as Transform;
|
||||||
|
|
||||||
|
_rotationOffset = EditorGUILayout.Vector3Field("Rotation Offset", _rotationOffset);
|
||||||
|
_isAdditive = EditorGUILayout.Toggle("Is Additive", _isAdditive);
|
||||||
|
|
||||||
|
if (_clip == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Please, specify the Target Animation!", MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_refClip == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Please, specify the Reference Animation!", MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_root == null || _extractFrom == null || _extractTo == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Please, specify the bones!", MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Extract"))
|
||||||
|
{
|
||||||
|
ExtractAndSetAnimationData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetToolCategory()
|
||||||
|
{
|
||||||
|
return "Animation/";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetToolName()
|
||||||
|
{
|
||||||
|
return "Copy Bone";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDocsURL()
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetToolDescription()
|
||||||
|
{
|
||||||
|
return "Copy curves from one bone to another with this tool.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 807725ac2fcc430da4e1b5de9ab3f9f3
|
||||||
|
timeCreated: 1757669501
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Tools
|
||||||
|
{
|
||||||
|
public struct ContentLicense
|
||||||
|
{
|
||||||
|
public string contentAuthor;
|
||||||
|
public string contentName;
|
||||||
|
public List<Tag> tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Tag
|
||||||
|
{
|
||||||
|
public string text;
|
||||||
|
public string tooltip;
|
||||||
|
public string url;
|
||||||
|
|
||||||
|
public Tag(string text, string tooltip = "", string url = "")
|
||||||
|
{
|
||||||
|
this.text = text;
|
||||||
|
this.tooltip = tooltip;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ContentLicenseWidget
|
||||||
|
{
|
||||||
|
private static GUIStyle _labelStyle;
|
||||||
|
private static GUIStyle _chipStyle;
|
||||||
|
|
||||||
|
private static void InitStyles()
|
||||||
|
{
|
||||||
|
if (_labelStyle != null) return;
|
||||||
|
|
||||||
|
_labelStyle = new GUIStyle(EditorStyles.label)
|
||||||
|
{
|
||||||
|
fontSize = 12,
|
||||||
|
alignment = TextAnchor.MiddleLeft,
|
||||||
|
clipping = TextClipping.Clip,
|
||||||
|
wordWrap = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_chipStyle = new GUIStyle(EditorStyles.miniButton)
|
||||||
|
{
|
||||||
|
alignment = TextAnchor.MiddleCenter,
|
||||||
|
fontSize = 11,
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
stretchWidth = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawRow(ContentLicense contentLicense)
|
||||||
|
{
|
||||||
|
InitStyles();
|
||||||
|
|
||||||
|
// Begin padded horizontal row — respects the vertical group padding!
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
|
||||||
|
string rowString = $"{contentLicense.contentAuthor} • {contentLicense.contentName}";
|
||||||
|
|
||||||
|
// Draw label (auto expands)
|
||||||
|
GUILayout.Label(new GUIContent(rowString, contentLicense.contentName), _labelStyle);
|
||||||
|
|
||||||
|
// Draw chips (right side but attached to label, no snapping)
|
||||||
|
if (contentLicense.tags != null)
|
||||||
|
{
|
||||||
|
Color originalColor = GUI.backgroundColor;
|
||||||
|
Color chipColor = EditorGUIUtility.isProSkin
|
||||||
|
? new Color(1f, 0.89f, 0.4f)
|
||||||
|
: new Color(1f, 0.86f, 0.34f);
|
||||||
|
|
||||||
|
GUI.backgroundColor = chipColor;
|
||||||
|
|
||||||
|
foreach (var chip in contentLicense.tags)
|
||||||
|
{
|
||||||
|
var chipContent = new GUIContent(chip.text, chip.tooltip);
|
||||||
|
|
||||||
|
if (GUILayout.Button(chipContent, _chipStyle))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(chip.url)) Application.OpenURL(chip.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.backgroundColor = originalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class DemoDownloaderTool : IEditorTool
|
||||||
|
{
|
||||||
|
private static readonly string PackageDirectory = Path.Combine("Library", "KINEMATION");
|
||||||
|
|
||||||
|
private WebClient _webClient;
|
||||||
|
private float _downloadProgress = 0f;
|
||||||
|
private string _progressLabel = "";
|
||||||
|
private bool _isDownloading = false;
|
||||||
|
private bool _cancelledDownload = false;
|
||||||
|
|
||||||
|
private string _fullPackagePath;
|
||||||
|
private List<ContentLicense> _contentLicences;
|
||||||
|
|
||||||
|
protected virtual string GetPackageUrl()
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual string GetPackageFileName()
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual List<ContentLicense> GetContentLicenses()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Init()
|
||||||
|
{
|
||||||
|
_contentLicences = GetContentLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Render()
|
||||||
|
{
|
||||||
|
if (_contentLicences != null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("This demo has third-party content:", MessageType.Warning);
|
||||||
|
foreach (var contentLicense in _contentLicences) ContentLicenseWidget.DrawRow(contentLicense);
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.enabled = !_isDownloading;
|
||||||
|
if (GUILayout.Button("Download and Import"))
|
||||||
|
{
|
||||||
|
StartDownload();
|
||||||
|
}
|
||||||
|
GUI.enabled = true;
|
||||||
|
|
||||||
|
if (!_isDownloading) return;
|
||||||
|
|
||||||
|
EditorGUILayout.Space(1f);
|
||||||
|
|
||||||
|
Rect rect = EditorGUILayout.GetControlRect(false);
|
||||||
|
Rect barRect = rect;
|
||||||
|
barRect.width *= 0.7f;
|
||||||
|
|
||||||
|
float padding = 5f;
|
||||||
|
|
||||||
|
Rect buttonRect = rect;
|
||||||
|
buttonRect.width *= 0.3f;
|
||||||
|
buttonRect.width -= padding;
|
||||||
|
buttonRect.x += barRect.width + padding;
|
||||||
|
|
||||||
|
EditorGUI.ProgressBar(barRect, _downloadProgress, _progressLabel);
|
||||||
|
if (GUI.Button(buttonRect, "Stop", EditorStyles.miniButton))
|
||||||
|
{
|
||||||
|
_webClient.CancelAsync();
|
||||||
|
_cancelledDownload = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetToolCategory()
|
||||||
|
{
|
||||||
|
return "Demo Content/";
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetToolName()
|
||||||
|
{
|
||||||
|
return "Demo Downloader";
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetDocsURL()
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string GetToolDescription()
|
||||||
|
{
|
||||||
|
return "Download demo projects for KINEMATION assets with this tool.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDownload()
|
||||||
|
{
|
||||||
|
string fullDirPath = Path.Combine(Application.dataPath, "..", PackageDirectory);
|
||||||
|
_fullPackagePath = Path.Combine(fullDirPath, GetPackageFileName() + ".unitypackage");
|
||||||
|
|
||||||
|
if (!Directory.Exists(fullDirPath)) Directory.CreateDirectory(fullDirPath);
|
||||||
|
|
||||||
|
_webClient = new WebClient();
|
||||||
|
_webClient.DownloadProgressChanged += OnDownloadProgressChanged;
|
||||||
|
_webClient.DownloadFileCompleted += OnDownloadFileCompleted;
|
||||||
|
|
||||||
|
_isDownloading = true;
|
||||||
|
_cancelledDownload = false;
|
||||||
|
_downloadProgress = 0f;
|
||||||
|
_progressLabel = "Starting download...";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_webClient.DownloadFileAsync(new Uri(GetPackageUrl()), _fullPackagePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError("Download error: " + ex.Message);
|
||||||
|
_isDownloading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
|
||||||
|
{
|
||||||
|
_downloadProgress = e.ProgressPercentage / 100f;
|
||||||
|
_progressLabel = $"Downloaded {e.BytesReceived / 1024} KB of {e.TotalBytesToReceive / 1024} KB";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
|
||||||
|
{
|
||||||
|
_isDownloading = false;
|
||||||
|
_downloadProgress = 1f;
|
||||||
|
_progressLabel = "Download complete.";
|
||||||
|
|
||||||
|
if (!_cancelledDownload)
|
||||||
|
{
|
||||||
|
if (e.Error != null)
|
||||||
|
{
|
||||||
|
Debug.LogError("Download error: " + e.Error.Message);
|
||||||
|
}
|
||||||
|
else if (File.Exists(_fullPackagePath))
|
||||||
|
{
|
||||||
|
AssetDatabase.ImportPackage(_fullPackagePath, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError("Package file not found after download.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_webClient.Dispose();
|
||||||
|
_webClient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e30400ea37b62834a9d0dbd05bf76eb1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
12
Assets/KINEMATION/KAnimationCore/Editor/Tools/IEditorTool.cs
Normal file
12
Assets/KINEMATION/KAnimationCore/Editor/Tools/IEditorTool.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace KINEMATION.KAnimationCore.Editor.Tools
|
||||||
|
{
|
||||||
|
public interface IEditorTool
|
||||||
|
{
|
||||||
|
public void Init();
|
||||||
|
public void Render();
|
||||||
|
public string GetToolCategory();
|
||||||
|
public string GetToolName();
|
||||||
|
public string GetDocsURL();
|
||||||
|
public string GetToolDescription();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 203b1c50a8184a859a06ffb418dc3fcf
|
||||||
|
timeCreated: 1704274917
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using KINEMATION.KAnimationCore.Editor.Widgets;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Tools
|
||||||
|
{
|
||||||
|
public class KEditorToolWindow : EditorWindow
|
||||||
|
{
|
||||||
|
private static Vector2 MinSize = new Vector2(640f, 360f);
|
||||||
|
|
||||||
|
private KSplitterWidget _splitterWidget;
|
||||||
|
private List<IEditorTool> _tools;
|
||||||
|
private readonly List<DisplayCategoryNode> _categories = new List<DisplayCategoryNode>();
|
||||||
|
private IEditorTool _selectedTool;
|
||||||
|
|
||||||
|
private const float RowHeight = 18f;
|
||||||
|
private const float Indent = 16f;
|
||||||
|
private const float HoverPad = 4f;
|
||||||
|
|
||||||
|
private GUIStyle _toolWindowStyle;
|
||||||
|
private GUIStyle _toolNameStyle;
|
||||||
|
private GUIStyle _docsLinkStyle;
|
||||||
|
|
||||||
|
[SerializeField] private string selectedToolTypeName;
|
||||||
|
|
||||||
|
[MenuItem(KEditorUtility.EditorToolsPath)]
|
||||||
|
public static void CreateWindow()
|
||||||
|
{
|
||||||
|
var window = GetWindow<KEditorToolWindow>("KINEMATION Tools");
|
||||||
|
window.minSize = MinSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_toolWindowStyle = new GUIStyle()
|
||||||
|
{
|
||||||
|
padding = new RectOffset(6, 0, 6, 6)
|
||||||
|
};
|
||||||
|
|
||||||
|
_tools = new List<IEditorTool>();
|
||||||
|
var toolTypes = TypeCache.GetTypesDerivedFrom<IEditorTool>().ToArray();
|
||||||
|
|
||||||
|
foreach (var toolType in toolTypes)
|
||||||
|
{
|
||||||
|
if (toolType.IsAbstract) continue;
|
||||||
|
|
||||||
|
var toolInstance = Activator.CreateInstance(toolType) as IEditorTool;
|
||||||
|
if (toolInstance == null) continue;
|
||||||
|
|
||||||
|
toolInstance.Init();
|
||||||
|
_tools.Add(toolInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildCatalog();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(selectedToolTypeName))
|
||||||
|
{
|
||||||
|
foreach (var tool in _tools)
|
||||||
|
{
|
||||||
|
if (!selectedToolTypeName.Equals(tool.GetType().Name)) continue;
|
||||||
|
_selectedTool = tool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_splitterWidget = new KSplitterWidget
|
||||||
|
{
|
||||||
|
onDrawFirstGUI = RenderToolsList,
|
||||||
|
onDrawSecondGUI = RenderTool,
|
||||||
|
orientation = SplitOrientation.Horizontal
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
EnsureDocsLinkStyle();
|
||||||
|
_splitterWidget.OnGUI(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildCatalog()
|
||||||
|
{
|
||||||
|
_categories.Clear();
|
||||||
|
|
||||||
|
foreach (var tool in _tools)
|
||||||
|
{
|
||||||
|
string[] categoryPath = SplitPath(tool.GetToolCategory());
|
||||||
|
string categoryLabel = categoryPath.Length > 0 ? categoryPath[0] : "General";
|
||||||
|
string[] groupPath = categoryPath.Length > 1 ? categoryPath.Skip(1).ToArray() : Array.Empty<string>();
|
||||||
|
|
||||||
|
var categoryNode = _categories.FirstOrDefault(node => node.label == categoryLabel);
|
||||||
|
if (categoryNode == null)
|
||||||
|
{
|
||||||
|
categoryNode = new DisplayCategoryNode { label = categoryLabel };
|
||||||
|
_categories.Add(categoryNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupPath.Length == 0)
|
||||||
|
{
|
||||||
|
categoryNode.tools.Add(new DisplayToolNode { tool = tool });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisplayGroupNode currentGroup = null;
|
||||||
|
|
||||||
|
for (int index = 0; index < groupPath.Length; index++)
|
||||||
|
{
|
||||||
|
string segment = groupPath[index];
|
||||||
|
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
var nextGroup = categoryNode.groups.FirstOrDefault(group => group.label == segment);
|
||||||
|
if (nextGroup == null)
|
||||||
|
{
|
||||||
|
nextGroup = new DisplayGroupNode { label = segment };
|
||||||
|
categoryNode.groups.Add(nextGroup);
|
||||||
|
}
|
||||||
|
currentGroup = nextGroup;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var nextGroup = currentGroup.groups.FirstOrDefault(group => group.label == segment);
|
||||||
|
if (nextGroup == null)
|
||||||
|
{
|
||||||
|
nextGroup = new DisplayGroupNode { label = segment };
|
||||||
|
currentGroup.groups.Add(nextGroup);
|
||||||
|
}
|
||||||
|
currentGroup = nextGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentGroup.tools.Add(new DisplayToolNode { tool = tool });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_categories.Sort((left, right) =>
|
||||||
|
string.Compare(left.label, right.label, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
foreach (var categoryNode in _categories)
|
||||||
|
{
|
||||||
|
SortGroupRecursive(categoryNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SortGroupRecursive(DisplayCategoryNode categoryNode)
|
||||||
|
{
|
||||||
|
categoryNode.groups.Sort((left, right) =>
|
||||||
|
string.Compare(left.label, right.label, StringComparison.OrdinalIgnoreCase));
|
||||||
|
categoryNode.tools.Sort((left, right) =>
|
||||||
|
string.Compare(left.tool.GetToolName(), right.tool.GetToolName(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
foreach (var childGroup in categoryNode.groups)
|
||||||
|
{
|
||||||
|
SortGroupRecursive(childGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SortGroupRecursive(DisplayGroupNode groupNode)
|
||||||
|
{
|
||||||
|
groupNode.groups.Sort((left, right) =>
|
||||||
|
string.Compare(left.label, right.label, StringComparison.OrdinalIgnoreCase));
|
||||||
|
groupNode.tools.Sort((left, right) =>
|
||||||
|
string.Compare(left.tool.GetToolName(), right.tool.GetToolName(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
foreach (var childGroup in groupNode.groups)
|
||||||
|
{
|
||||||
|
SortGroupRecursive(childGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] SplitPath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path)) return Array.Empty<string>();
|
||||||
|
return path
|
||||||
|
.Split(new[] { '/', '\\', '>' }, StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(segment => segment.Trim())
|
||||||
|
.Where(segment => !string.IsNullOrEmpty(segment))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderToolsList()
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginVertical(_toolWindowStyle);
|
||||||
|
|
||||||
|
foreach (var categoryNode in _categories)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(categoryNode.label, KEditorUtility.boldLabel);
|
||||||
|
|
||||||
|
foreach (var groupNode in categoryNode.groups)
|
||||||
|
{
|
||||||
|
DrawGroup(groupNode, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var toolNode in categoryNode.tools)
|
||||||
|
{
|
||||||
|
DrawTool(toolNode, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGroup(DisplayGroupNode groupNode, int depth)
|
||||||
|
{
|
||||||
|
Rect rowRect = GUILayoutUtility.GetRect(1f, RowHeight, GUILayout.ExpandWidth(true));
|
||||||
|
DrawHoverCard(rowRect, depth, false);
|
||||||
|
|
||||||
|
Rect foldoutRect = rowRect;
|
||||||
|
foldoutRect.xMin += depth * Indent + 6f;
|
||||||
|
groupNode.expanded = EditorGUI.Foldout(foldoutRect, groupNode.expanded, GUIContent.none, true);
|
||||||
|
|
||||||
|
Rect labelRect = rowRect;
|
||||||
|
labelRect.xMin = foldoutRect.xMin + 12f;
|
||||||
|
EditorGUI.LabelField(labelRect, groupNode.label);
|
||||||
|
|
||||||
|
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 &&
|
||||||
|
rowRect.Contains(Event.current.mousePosition))
|
||||||
|
{
|
||||||
|
groupNode.expanded = !groupNode.expanded;
|
||||||
|
Event.current.Use();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupNode.expanded)
|
||||||
|
{
|
||||||
|
foreach (var childGroup in groupNode.groups) DrawGroup(childGroup, depth + 1);
|
||||||
|
foreach (var toolNode in groupNode.tools) DrawTool(toolNode, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTool(DisplayToolNode toolNode, int depth)
|
||||||
|
{
|
||||||
|
Rect rowRect = GUILayoutUtility.GetRect(1f, RowHeight, GUILayout.ExpandWidth(true));
|
||||||
|
DrawHoverCard(rowRect, depth, toolNode.tool == _selectedTool);
|
||||||
|
|
||||||
|
Rect labelRect = rowRect;
|
||||||
|
labelRect.xMin += depth * Indent + 18f;
|
||||||
|
EditorGUI.LabelField(labelRect, toolNode.tool.GetToolName());
|
||||||
|
|
||||||
|
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 &&
|
||||||
|
rowRect.Contains(Event.current.mousePosition))
|
||||||
|
{
|
||||||
|
_selectedTool = toolNode.tool;
|
||||||
|
selectedToolTypeName = _selectedTool.GetType().Name;
|
||||||
|
Event.current.Use();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHoverCard(Rect rowRect, int depth, bool active)
|
||||||
|
{
|
||||||
|
bool hovered = rowRect.Contains(Event.current.mousePosition) || active;
|
||||||
|
if (!hovered) return;
|
||||||
|
|
||||||
|
Rect paddedRect = new Rect(
|
||||||
|
rowRect.x + depth * Indent + HoverPad,
|
||||||
|
rowRect.y,
|
||||||
|
rowRect.width - depth * Indent - HoverPad * 2f,
|
||||||
|
rowRect.height
|
||||||
|
);
|
||||||
|
|
||||||
|
GUI.Box(paddedRect, GUIContent.none, EditorStyles.helpBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderTool()
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginVertical(_toolWindowStyle);
|
||||||
|
|
||||||
|
if (_selectedTool == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Select a tool on the left to get started.", MessageType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(_selectedTool.GetToolName(), _toolNameStyle);
|
||||||
|
|
||||||
|
var content = EditorGUIUtility.TrTextContent("Documentation");
|
||||||
|
var size = _docsLinkStyle.CalcSize(content);
|
||||||
|
var rect = GUILayoutUtility.GetRect(size.x, size.y, _docsLinkStyle,
|
||||||
|
GUILayout.ExpandWidth(false));
|
||||||
|
|
||||||
|
if (GUI.Button(rect, "Documentation", _docsLinkStyle))
|
||||||
|
{
|
||||||
|
string url = _selectedTool.GetDocsURL();
|
||||||
|
if(!string.IsNullOrEmpty(url)) Application.OpenURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.Space(2f);
|
||||||
|
EditorGUILayout.HelpBox(_selectedTool.GetToolDescription(), MessageType.Info);
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
_selectedTool.Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureDocsLinkStyle()
|
||||||
|
{
|
||||||
|
if (_toolNameStyle == null)
|
||||||
|
{
|
||||||
|
_toolNameStyle = new GUIStyle(EditorStyles.label)
|
||||||
|
{
|
||||||
|
fontSize = 18,
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
richText = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_docsLinkStyle != null) return;
|
||||||
|
|
||||||
|
bool isProSkin = EditorGUIUtility.isProSkin;
|
||||||
|
string hexNormal = isProSkin ? "#60A5FA" : "#2563EB";
|
||||||
|
string hexHover = isProSkin ? "#93C5FD" : "#1D4ED8";
|
||||||
|
string hexActive = isProSkin ? "#3B82F6" : "#1E40AF";
|
||||||
|
|
||||||
|
Color normal, hover, active;
|
||||||
|
ColorUtility.TryParseHtmlString(hexNormal, out normal);
|
||||||
|
ColorUtility.TryParseHtmlString(hexHover, out hover);
|
||||||
|
ColorUtility.TryParseHtmlString(hexActive, out active);
|
||||||
|
|
||||||
|
_docsLinkStyle = new GUIStyle(EditorStyles.label)
|
||||||
|
{
|
||||||
|
alignment = TextAnchor.MiddleLeft,
|
||||||
|
richText = false,
|
||||||
|
wordWrap = false,
|
||||||
|
clipping = TextClipping.Overflow
|
||||||
|
};
|
||||||
|
|
||||||
|
// No backgrounds — render like a plain label
|
||||||
|
_docsLinkStyle.normal.background = null;
|
||||||
|
_docsLinkStyle.hover.background = null;
|
||||||
|
_docsLinkStyle.active.background = null;
|
||||||
|
_docsLinkStyle.focused.background = null;
|
||||||
|
_docsLinkStyle.onNormal.background = null;
|
||||||
|
_docsLinkStyle.onHover.background = null;
|
||||||
|
_docsLinkStyle.onActive.background = null;
|
||||||
|
_docsLinkStyle.onFocused.background = null;
|
||||||
|
|
||||||
|
// Link colors
|
||||||
|
_docsLinkStyle.normal.textColor = normal;
|
||||||
|
_docsLinkStyle.hover.textColor = hover;
|
||||||
|
_docsLinkStyle.active.textColor = active;
|
||||||
|
_docsLinkStyle.focused.textColor = hover;
|
||||||
|
_docsLinkStyle.onNormal.textColor = normal;
|
||||||
|
_docsLinkStyle.onHover.textColor = hover;
|
||||||
|
_docsLinkStyle.onActive.textColor = active;
|
||||||
|
_docsLinkStyle.onFocused.textColor = hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DisplayCategoryNode
|
||||||
|
{
|
||||||
|
public string label;
|
||||||
|
public List<DisplayGroupNode> groups = new List<DisplayGroupNode>();
|
||||||
|
public List<DisplayToolNode> tools = new List<DisplayToolNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DisplayGroupNode
|
||||||
|
{
|
||||||
|
public string label;
|
||||||
|
public bool expanded;
|
||||||
|
public List<DisplayGroupNode> groups = new List<DisplayGroupNode>();
|
||||||
|
public List<DisplayToolNode> tools = new List<DisplayToolNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DisplayToolNode
|
||||||
|
{
|
||||||
|
public IEditorTool tool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c15b0e5f1a0e458b82fb3f396d6b55be
|
||||||
|
timeCreated: 1756377409
|
||||||
3
Assets/KINEMATION/KAnimationCore/Editor/Widgets.meta
Normal file
3
Assets/KINEMATION/KAnimationCore/Editor/Widgets.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 087d9dacee544be0b975157be83f44f1
|
||||||
|
timeCreated: 1756475439
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using KINEMATION.KAnimationCore.Runtime.Misc;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Misc
|
||||||
|
{
|
||||||
|
public abstract class AssetDragAndDrop<T1, T2> where T1 : MonoBehaviour where T2 : ScriptableObject
|
||||||
|
{
|
||||||
|
protected static DragAndDropVisualMode HandleDragAndDrop(bool perform)
|
||||||
|
{
|
||||||
|
var asset = DragAndDrop.objectReferences[0] as T2;
|
||||||
|
if (asset == null)
|
||||||
|
{
|
||||||
|
return DragAndDropVisualMode.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perform)
|
||||||
|
{
|
||||||
|
var selection = Selection.activeGameObject;
|
||||||
|
if (selection != null)
|
||||||
|
{
|
||||||
|
var component = selection.GetComponent<T1>();
|
||||||
|
if (component == null) component = selection.AddComponent<T1>();
|
||||||
|
if(component is IAssetDragAndDrop assetDragAndDrop) assetDragAndDrop.SetAsset(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DragAndDropVisualMode.Copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static DragAndDropVisualMode OnHierarchyDrop(int dropTargetInstanceID, HierarchyDropFlags dropMode,
|
||||||
|
Transform parentForDraggedObjects, bool perform)
|
||||||
|
{
|
||||||
|
return HandleDragAndDrop(perform);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static DragAndDropVisualMode OnInspectorDrop(UnityEngine.Object[] targets, bool perform)
|
||||||
|
{
|
||||||
|
return HandleDragAndDrop(perform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dfc97017ea654dc9b3fe8cab522f0afc
|
||||||
|
timeCreated: 1744450300
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Misc
|
||||||
|
{
|
||||||
|
public class AssetObjectWidget<T> where T : Object
|
||||||
|
{
|
||||||
|
public UnityEditor.Editor editor;
|
||||||
|
|
||||||
|
public string _objectLabel;
|
||||||
|
|
||||||
|
private Object _cachedObject;
|
||||||
|
private Object _parentAsset;
|
||||||
|
|
||||||
|
private SerializedObject _targetObject;
|
||||||
|
private SerializedProperty _targetProperty;
|
||||||
|
|
||||||
|
private bool _isExpanded;
|
||||||
|
|
||||||
|
private bool IsSubAsset(Object asset)
|
||||||
|
{
|
||||||
|
return AssetDatabase.GetAssetPath(_parentAsset).Equals(AssetDatabase.GetAssetPath(asset));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DestroyObject(Object objectToDestroy, bool displayDialog)
|
||||||
|
{
|
||||||
|
if (objectToDestroy != null && IsSubAsset(objectToDestroy))
|
||||||
|
{
|
||||||
|
if (!displayDialog || EditorUtility.DisplayDialog("Deletion",
|
||||||
|
$"Are you sure you want to delete {objectToDestroy.name}?", "Yes", "No"))
|
||||||
|
{
|
||||||
|
Undo.DestroyObjectImmediate(objectToDestroy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCreatePressed()
|
||||||
|
{
|
||||||
|
DestroyObject(_targetProperty.objectReferenceValue, true);
|
||||||
|
|
||||||
|
Object newComponent = Activator.CreateInstance<T>();
|
||||||
|
newComponent.name = _objectLabel.Replace(" ", string.Empty);
|
||||||
|
newComponent.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy;
|
||||||
|
|
||||||
|
Undo.RegisterCreatedObjectUndo(newComponent, "Add Component");
|
||||||
|
AssetDatabase.AddObjectToAsset(newComponent, _parentAsset);
|
||||||
|
EditorUtility.SetDirty(_parentAsset);
|
||||||
|
AssetDatabase.SaveAssetIfDirty(_parentAsset);
|
||||||
|
|
||||||
|
_targetProperty.objectReferenceValue = newComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetObjectWidget(SerializedObject target, string propertyName, string label)
|
||||||
|
{
|
||||||
|
_objectLabel = label;
|
||||||
|
_targetObject = target;
|
||||||
|
_targetProperty = _targetObject.FindProperty(propertyName);
|
||||||
|
_parentAsset = target.targetObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
_targetObject.Update();
|
||||||
|
|
||||||
|
if (EditorUtility.IsPersistent(_parentAsset))
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
|
||||||
|
Object objectRef = _targetProperty.objectReferenceValue;
|
||||||
|
objectRef = (T) EditorGUILayout.ObjectField(_objectLabel, objectRef, typeof(T), false);
|
||||||
|
_targetProperty.objectReferenceValue = objectRef;
|
||||||
|
|
||||||
|
if (GUILayout.Button("Create"))
|
||||||
|
{
|
||||||
|
OnCreatePressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Show"))
|
||||||
|
{
|
||||||
|
if(editor != null) _isExpanded = !_isExpanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
if (_cachedObject != _targetProperty.objectReferenceValue)
|
||||||
|
{
|
||||||
|
if (!IsSubAsset(_targetProperty.objectReferenceValue))
|
||||||
|
{
|
||||||
|
DestroyObject(_cachedObject, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor = _targetProperty.objectReferenceValue == null
|
||||||
|
? null
|
||||||
|
: UnityEditor.Editor.CreateEditor(_targetProperty.objectReferenceValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cachedObject = _targetProperty.objectReferenceValue;
|
||||||
|
|
||||||
|
if (_isExpanded && editor != null)
|
||||||
|
{
|
||||||
|
var style = GUI.skin.box;
|
||||||
|
style.padding = new RectOffset(15, 5, 5, 5);
|
||||||
|
|
||||||
|
EditorGUILayout.BeginVertical(style);
|
||||||
|
editor.OnInspectorGUI();
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(_targetProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
_targetObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bea5203406494b3880cef647734069f5
|
||||||
|
timeCreated: 1722236084
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Widgets
|
||||||
|
{
|
||||||
|
public enum SplitOrientation
|
||||||
|
{
|
||||||
|
Horizontal,
|
||||||
|
Vertical
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KSplitterWidget
|
||||||
|
{
|
||||||
|
public Action onDrawFirstGUI;
|
||||||
|
public Action onDrawSecondGUI;
|
||||||
|
public SplitOrientation orientation = SplitOrientation.Horizontal;
|
||||||
|
public float splitRatio = 0.35f;
|
||||||
|
public float splitterSize = 4f;
|
||||||
|
public bool drawSplitterLine = true;
|
||||||
|
|
||||||
|
private bool _resizing;
|
||||||
|
private float _dragMinRatio;
|
||||||
|
private float _dragMaxRatio;
|
||||||
|
private Vector2 _firstScroll;
|
||||||
|
private Vector2 _secondScroll;
|
||||||
|
|
||||||
|
public void OnGUI()
|
||||||
|
{
|
||||||
|
var host = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none,
|
||||||
|
GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||||||
|
OnGUI(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGUI(Rect rect)
|
||||||
|
{
|
||||||
|
var e = Event.current;
|
||||||
|
|
||||||
|
float axis = orientation == SplitOrientation.Horizontal ? rect.width : rect.height;
|
||||||
|
float avail = Mathf.Max(0f, axis - splitterSize);
|
||||||
|
|
||||||
|
float firstSize = avail * Mathf.Clamp01(splitRatio);
|
||||||
|
float secondSize = Mathf.Max(0f, avail - firstSize);
|
||||||
|
|
||||||
|
Rect firstRect, splitterRect, secondRect;
|
||||||
|
if (orientation == SplitOrientation.Horizontal)
|
||||||
|
{
|
||||||
|
firstRect = new Rect(0f, 0f, firstSize, rect.height);
|
||||||
|
splitterRect = new Rect(firstRect.xMax, 0f, splitterSize, rect.height);
|
||||||
|
secondRect = new Rect(splitterRect.xMax, 0f, secondSize, rect.height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
firstRect = new Rect(0f, 0f, rect.width, firstSize);
|
||||||
|
splitterRect = new Rect(0f, firstRect.yMax, rect.width, splitterSize);
|
||||||
|
secondRect = new Rect(0f, splitterRect.yMax, rect.width, secondSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// First pane
|
||||||
|
GUILayout.BeginArea(firstRect);
|
||||||
|
_firstScroll = EditorGUILayout.BeginScrollView(_firstScroll);
|
||||||
|
onDrawFirstGUI?.Invoke();
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
GUILayout.EndArea();
|
||||||
|
|
||||||
|
// Splitter
|
||||||
|
var cursor = orientation == SplitOrientation.Horizontal
|
||||||
|
? MouseCursor.ResizeHorizontal
|
||||||
|
: MouseCursor.ResizeVertical;
|
||||||
|
EditorGUIUtility.AddCursorRect(splitterRect, cursor);
|
||||||
|
|
||||||
|
if (e.type == EventType.MouseDown && splitterRect.Contains(e.mousePosition))
|
||||||
|
{
|
||||||
|
_resizing = true;
|
||||||
|
|
||||||
|
// Capture allowed range based on current ratio
|
||||||
|
float r = Mathf.Clamp01(splitRatio);
|
||||||
|
float minR = Mathf.Min(r, 1f - r);
|
||||||
|
_dragMinRatio = minR;
|
||||||
|
_dragMaxRatio = 1f - minR;
|
||||||
|
|
||||||
|
e.Use();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_resizing && e.type == EventType.MouseDrag)
|
||||||
|
{
|
||||||
|
float rel = orientation == SplitOrientation.Horizontal ? e.mousePosition.x : e.mousePosition.y;
|
||||||
|
|
||||||
|
float desiredFirst = Mathf.Clamp(rel, 0f, avail);
|
||||||
|
float rawRatio = avail > 0f ? desiredFirst / avail : splitRatio;
|
||||||
|
|
||||||
|
// Clamp within the drag-bounded range
|
||||||
|
splitRatio = Mathf.Clamp(rawRatio, _dragMinRatio, _dragMaxRatio);
|
||||||
|
GUI.changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.type == EventType.MouseUp)
|
||||||
|
{
|
||||||
|
_resizing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawSplitterLine)
|
||||||
|
{
|
||||||
|
if (orientation == SplitOrientation.Horizontal)
|
||||||
|
EditorGUI.DrawRect(new Rect(splitterRect.x, splitterRect.y, 1f, splitterRect.height),
|
||||||
|
new Color(0f, 0f, 0f, 0.25f));
|
||||||
|
else
|
||||||
|
EditorGUI.DrawRect(new Rect(splitterRect.x, splitterRect.y, splitterRect.width, 1f),
|
||||||
|
new Color(0f, 0f, 0f, 0.25f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pane
|
||||||
|
GUILayout.BeginArea(secondRect);
|
||||||
|
_secondScroll = EditorGUILayout.BeginScrollView(_secondScroll);
|
||||||
|
onDrawSecondGUI?.Invoke();
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
GUILayout.EndArea();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b3f963d6ef48456893e2a2b9c4706c77
|
||||||
|
timeCreated: 1756475661
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Tools
|
||||||
|
{
|
||||||
|
public struct KToolbarTab
|
||||||
|
{
|
||||||
|
public delegate void KOnTabRendered();
|
||||||
|
|
||||||
|
public string name;
|
||||||
|
public KOnTabRendered onTabRendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KToolbarWidget
|
||||||
|
{
|
||||||
|
private int _toolbarIndex = 0;
|
||||||
|
private string[] _toolbarTabNames;
|
||||||
|
private KToolbarTab[] _toolbarTabs;
|
||||||
|
|
||||||
|
public KToolbarWidget(KToolbarTab[] tabs)
|
||||||
|
{
|
||||||
|
_toolbarTabs = tabs;
|
||||||
|
_toolbarTabNames = new string[_toolbarTabs.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < _toolbarTabs.Length; i++)
|
||||||
|
{
|
||||||
|
_toolbarTabNames[i] = _toolbarTabs[i].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render()
|
||||||
|
{
|
||||||
|
if (_toolbarTabNames.Length == 0) return;
|
||||||
|
|
||||||
|
_toolbarIndex = GUILayout.Toolbar(_toolbarIndex, _toolbarTabNames);
|
||||||
|
_toolbarTabs[_toolbarIndex].onTabRendered?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7a907f9eb19f45c58dd635954e1fb352
|
||||||
|
timeCreated: 1704274518
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Attributes;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Editor.Widgets
|
||||||
|
{
|
||||||
|
public struct EditorTab
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public List<SerializedProperty> properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TabInspectorWidget
|
||||||
|
{
|
||||||
|
private SerializedObject _serializedObject;
|
||||||
|
|
||||||
|
private List<SerializedProperty> _defaultProperties;
|
||||||
|
private List<EditorTab> _editorTabs;
|
||||||
|
|
||||||
|
private string[] _tabNames;
|
||||||
|
private bool _foundTab;
|
||||||
|
|
||||||
|
private int _selectedIndex = 0;
|
||||||
|
|
||||||
|
private T[] GetPropertyAttributes<T>(SerializedProperty property) where T : System.Attribute
|
||||||
|
{
|
||||||
|
T[] output = null;
|
||||||
|
|
||||||
|
FieldInfo fieldInfo = _serializedObject.targetObject.GetType().GetField(property.propertyPath,
|
||||||
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
if (fieldInfo != null)
|
||||||
|
{
|
||||||
|
output = (T[]) fieldInfo.GetCustomAttributes(typeof(T), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output == null || output.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TabInspectorWidget(SerializedObject targetObject)
|
||||||
|
{
|
||||||
|
_serializedObject = targetObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
_defaultProperties = new List<SerializedProperty>();
|
||||||
|
_editorTabs = new List<EditorTab>();
|
||||||
|
|
||||||
|
SerializedProperty property = _serializedObject.GetIterator();
|
||||||
|
property.NextVisible(true);
|
||||||
|
|
||||||
|
while (property.NextVisible(false))
|
||||||
|
{
|
||||||
|
TabAttribute[] tabAttributes = GetPropertyAttributes<TabAttribute>(property);
|
||||||
|
if (tabAttributes == null)
|
||||||
|
{
|
||||||
|
if (_foundTab)
|
||||||
|
{
|
||||||
|
_editorTabs[^1].properties.Add(property.Copy());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_defaultProperties.Add(property.Copy());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_editorTabs.Add(new EditorTab()
|
||||||
|
{
|
||||||
|
name = tabAttributes[0].tabName,
|
||||||
|
properties = new List<SerializedProperty>() { property.Copy() }
|
||||||
|
});
|
||||||
|
|
||||||
|
_foundTab = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tabNames = _editorTabs.Select(item => item.name).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGUI()
|
||||||
|
{
|
||||||
|
_serializedObject.Update();
|
||||||
|
|
||||||
|
foreach (var defaultProperty in _defaultProperties)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(defaultProperty, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_tabNames.Length > 0)
|
||||||
|
{
|
||||||
|
_selectedIndex = GUILayout.Toolbar(_selectedIndex, _tabNames);
|
||||||
|
foreach (var tabProperty in _editorTabs[_selectedIndex].properties)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(tabProperty, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3d77a1c3adc04e6fb61aa62d804e0a5a
|
||||||
|
timeCreated: 1712129746
|
||||||
8
Assets/KINEMATION/KAnimationCore/Icons.meta
Normal file
8
Assets/KINEMATION/KAnimationCore/Icons.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7f48f5cceb844d14eb2a4f6be49f0f20
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/KINEMATION/KAnimationCore/Icons/KAsset_Icon.png
Normal file
BIN
Assets/KINEMATION/KAnimationCore/Icons/KAsset_Icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
135
Assets/KINEMATION/KAnimationCore/Icons/KAsset_Icon.png.meta
Normal file
135
Assets/KINEMATION/KAnimationCore/Icons/KAsset_Icon.png.meta
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a497183ade831dc4aa44bf44b5ce27b8
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 12
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 1
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMasterTextureLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 0
|
||||||
|
wrapV: 0
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 1
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 0
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 0
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 0
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: Server
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
spritePackingTag:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
pSDShowRemoveMatteOption: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/KINEMATION/KAnimationCore/Icons/KComponent_Icon.png
Normal file
BIN
Assets/KINEMATION/KAnimationCore/Icons/KComponent_Icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
135
Assets/KINEMATION/KAnimationCore/Icons/KComponent_Icon.png.meta
Normal file
135
Assets/KINEMATION/KAnimationCore/Icons/KComponent_Icon.png.meta
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6fd4d99d3a1edc7408fe599214be6059
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 12
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 1
|
||||||
|
sRGBTexture: 1
|
||||||
|
linearTexture: 0
|
||||||
|
fadeOut: 0
|
||||||
|
borderMipMap: 0
|
||||||
|
mipMapsPreserveCoverage: 0
|
||||||
|
alphaTestReferenceValue: 0.5
|
||||||
|
mipMapFadeDistanceStart: 1
|
||||||
|
mipMapFadeDistanceEnd: 3
|
||||||
|
bumpmap:
|
||||||
|
convertToNormalMap: 0
|
||||||
|
externalNormalMap: 0
|
||||||
|
heightScale: 0.25
|
||||||
|
normalMapFilter: 0
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMasterTextureLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 0
|
||||||
|
wrapV: 0
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 1
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 0
|
||||||
|
spriteExtrude: 1
|
||||||
|
spriteMeshType: 1
|
||||||
|
alignment: 0
|
||||||
|
spritePivot: {x: 0.5, y: 0.5}
|
||||||
|
spritePixelsToUnits: 100
|
||||||
|
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
spriteGenerateFallbackPhysicsShape: 1
|
||||||
|
alphaUsage: 1
|
||||||
|
alphaIsTransparency: 0
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 0
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: Standalone
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: Server
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: WebGL
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
spritePackingTag:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
pSDShowRemoveMatteOption: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/KINEMATION/KAnimationCore/Runtime.meta
Normal file
8
Assets/KINEMATION/KAnimationCore/Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 615cb50f39ff69c46b82a5d758558f63
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
3
Assets/KINEMATION/KAnimationCore/Runtime/Attributes.meta
Normal file
3
Assets/KINEMATION/KAnimationCore/Runtime/Attributes.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9315a7a54d5b407cbdeba4a62a7a6d63
|
||||||
|
timeCreated: 1698339177
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Attributes
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class CurveSelectorAttribute : PropertyAttribute
|
||||||
|
{
|
||||||
|
public bool useAnimator;
|
||||||
|
public bool usePlayables;
|
||||||
|
public bool useInput;
|
||||||
|
|
||||||
|
public CurveSelectorAttribute(bool useAnimator = true, bool usePlayables = true, bool useInput = true)
|
||||||
|
{
|
||||||
|
this.useAnimator = useAnimator;
|
||||||
|
this.usePlayables = usePlayables;
|
||||||
|
this.useInput = useInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class InputProperty : PropertyAttribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class RigAssetSelectorAttribute : PropertyAttribute
|
||||||
|
{
|
||||||
|
public string assetName;
|
||||||
|
|
||||||
|
public RigAssetSelectorAttribute(string rigName = "")
|
||||||
|
{
|
||||||
|
assetName = rigName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class ElementChainSelectorAttribute : RigAssetSelectorAttribute
|
||||||
|
{
|
||||||
|
public ElementChainSelectorAttribute(string rigName = "")
|
||||||
|
{
|
||||||
|
assetName = rigName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class ReadOnlyAttribute : PropertyAttribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class UnfoldAttribute : PropertyAttribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class TabAttribute : PropertyAttribute
|
||||||
|
{
|
||||||
|
public string tabName;
|
||||||
|
|
||||||
|
public TabAttribute(string tabName)
|
||||||
|
{
|
||||||
|
this.tabName = tabName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
public class CustomElementChainDrawerAttribute : PropertyAttribute
|
||||||
|
{
|
||||||
|
public bool drawLabel;
|
||||||
|
public bool drawTextField;
|
||||||
|
|
||||||
|
public CustomElementChainDrawerAttribute(bool drawLabel, bool drawTextField)
|
||||||
|
{
|
||||||
|
this.drawLabel = drawLabel;
|
||||||
|
this.drawTextField = drawTextField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KAttributes { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ea0d7667c78b9aa49bd1f93eff9f3757
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
3
Assets/KINEMATION/KAnimationCore/Runtime/Core.meta
Normal file
3
Assets/KINEMATION/KAnimationCore/Runtime/Core.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4a8a77a412af4ca69c2e184eab017b7f
|
||||||
|
timeCreated: 1698339164
|
||||||
438
Assets/KINEMATION/KAnimationCore/Runtime/Core/KAnimationMath.cs
Normal file
438
Assets/KINEMATION/KAnimationCore/Runtime/Core/KAnimationMath.cs
Normal file
@@ -0,0 +1,438 @@
|
|||||||
|
// Designed by KINEMATION, 2023
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Rig;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Animations;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Core
|
||||||
|
{
|
||||||
|
public class KAnimationMath
|
||||||
|
{
|
||||||
|
public static Quaternion RotateInSpace(Quaternion space, Quaternion target, Quaternion rotation, float alpha)
|
||||||
|
{
|
||||||
|
return Quaternion.Slerp(target, space * rotation * (Quaternion.Inverse(space) * target), alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Quaternion RotateInSpace(KTransform space, KTransform target, Quaternion offset, float alpha)
|
||||||
|
{
|
||||||
|
return RotateInSpace(space.rotation, target.rotation, offset, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RotateInSpace(Transform space, Transform target, Quaternion offset, float alpha)
|
||||||
|
{
|
||||||
|
target.rotation = RotateInSpace(space.rotation, target.rotation, offset, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 MoveInSpace(KTransform space, KTransform target, Vector3 offset, float alpha)
|
||||||
|
{
|
||||||
|
return target.position + (space.TransformPoint(offset, false) - space.position) * alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MoveInSpace(Transform space, Transform target, Vector3 offset, float alpha)
|
||||||
|
{
|
||||||
|
target.position += (space.TransformPoint(offset) - space.position) * alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 MoveInSpace(Transform space, Vector3 target, Vector3 offset, float alpha)
|
||||||
|
{
|
||||||
|
return target + (space.TransformPoint(offset) - space.position) * alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KTransform GetTransform(AnimationStream stream, TransformStreamHandle handle,
|
||||||
|
bool isWorld = true)
|
||||||
|
{
|
||||||
|
if (!stream.isValid || !handle.IsValid(stream))
|
||||||
|
{
|
||||||
|
return KTransform.Identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
KTransform output = new KTransform()
|
||||||
|
{
|
||||||
|
position = isWorld ? handle.GetPosition(stream) : handle.GetLocalPosition(stream),
|
||||||
|
rotation = isWorld ? handle.GetRotation(stream) : handle.GetLocalRotation(stream),
|
||||||
|
};
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KTransform GetTransform(AnimationStream stream, TransformSceneHandle handle,
|
||||||
|
bool isWorld = true)
|
||||||
|
{
|
||||||
|
KTransform output = new KTransform()
|
||||||
|
{
|
||||||
|
position = isWorld ? handle.GetPosition(stream) : handle.GetLocalPosition(stream),
|
||||||
|
rotation = isWorld ? handle.GetRotation(stream) : handle.GetLocalRotation(stream),
|
||||||
|
};
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MoveInSpace(AnimationStream stream, TransformSceneHandle space,
|
||||||
|
TransformStreamHandle target, Vector3 offset, float weight)
|
||||||
|
{
|
||||||
|
KTransform spaceT = GetTransform(stream, space);
|
||||||
|
KTransform targetT = GetTransform(stream, target);
|
||||||
|
|
||||||
|
var result = MoveInSpace(spaceT, targetT, offset, weight);
|
||||||
|
target.SetPosition(stream, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MoveInSpace(AnimationStream stream, TransformStreamHandle space,
|
||||||
|
TransformStreamHandle target, Vector3 offset, float weight)
|
||||||
|
{
|
||||||
|
KTransform spaceT = GetTransform(stream, space);
|
||||||
|
KTransform targetT = GetTransform(stream, target);
|
||||||
|
|
||||||
|
var result = MoveInSpace(spaceT, targetT, offset, weight);
|
||||||
|
target.SetPosition(stream, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RotateInSpace(AnimationStream stream, TransformStreamHandle space,
|
||||||
|
TransformStreamHandle target, Quaternion offset, float weight)
|
||||||
|
{
|
||||||
|
KTransform spaceT = GetTransform(stream, space);
|
||||||
|
KTransform targetT = GetTransform(stream, target);
|
||||||
|
|
||||||
|
var result = RotateInSpace(spaceT, targetT, offset, weight);
|
||||||
|
target.SetRotation(stream, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RotateInSpace(AnimationStream stream, TransformSceneHandle space,
|
||||||
|
TransformStreamHandle target, Quaternion offset, float weight)
|
||||||
|
{
|
||||||
|
KTransform spaceT = GetTransform(stream, space);
|
||||||
|
KTransform targetT = GetTransform(stream, target);
|
||||||
|
|
||||||
|
var result = RotateInSpace(spaceT, targetT, offset, weight);
|
||||||
|
target.SetRotation(stream, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ModifyPosition(AnimationStream stream, TransformSceneHandle root, TransformStreamHandle bone,
|
||||||
|
Vector3 position, ESpaceType space, EModifyMode mode, float weight)
|
||||||
|
{
|
||||||
|
if (mode == EModifyMode.Ignore) return;
|
||||||
|
|
||||||
|
KTransform rootTransform = GetTransform(stream, root);
|
||||||
|
|
||||||
|
if (mode == EModifyMode.Add)
|
||||||
|
{
|
||||||
|
if (space == ESpaceType.BoneSpace)
|
||||||
|
{
|
||||||
|
MoveInSpace(stream, bone, bone, position, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space == ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
var local = GetTransform(stream, bone, false);
|
||||||
|
|
||||||
|
bone.SetLocalPosition(stream, Vector3.Lerp(local.position,
|
||||||
|
local.position + position, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
MoveInSpace(stream, root, bone, position, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KTransform world = GetTransform(stream, bone);
|
||||||
|
|
||||||
|
bone.SetPosition(stream,
|
||||||
|
Vector3.Lerp(world.position, world.position + position, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space is ESpaceType.BoneSpace or ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
bone.SetLocalPosition(stream,
|
||||||
|
Vector3.Lerp(bone.GetLocalPosition(stream), position, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
position = rootTransform.TransformPoint(position, false);
|
||||||
|
bone.SetPosition(stream, Vector3.Lerp(bone.GetPosition(stream), position, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bone.SetPosition(stream, Vector3.Lerp(bone.GetPosition(stream), position, weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ModifyRotation(AnimationStream stream, TransformSceneHandle root, TransformStreamHandle bone,
|
||||||
|
Quaternion rotation, ESpaceType space, EModifyMode mode, float weight)
|
||||||
|
{
|
||||||
|
if (mode == EModifyMode.Ignore) return;
|
||||||
|
|
||||||
|
KTransform rootTransform = GetTransform(stream, root);
|
||||||
|
|
||||||
|
if (mode == EModifyMode.Add)
|
||||||
|
{
|
||||||
|
if (space == ESpaceType.BoneSpace)
|
||||||
|
{
|
||||||
|
RotateInSpace(stream, bone, bone, rotation, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space == ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
var local = GetTransform(stream, bone, false);
|
||||||
|
|
||||||
|
bone.SetLocalRotation(stream, Quaternion.Slerp(local.rotation,
|
||||||
|
local.rotation * rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
RotateInSpace(stream, root, bone, rotation, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KTransform world = GetTransform(stream, bone);
|
||||||
|
|
||||||
|
bone.SetRotation(stream,
|
||||||
|
Quaternion.Slerp(world.rotation, world.rotation * rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space is ESpaceType.BoneSpace or ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
bone.SetLocalRotation(stream,
|
||||||
|
Quaternion.Slerp(bone.GetLocalRotation(stream), rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
rotation = rootTransform.rotation * rotation;
|
||||||
|
bone.SetRotation(stream, Quaternion.Slerp(bone.GetRotation(stream), rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bone.SetRotation(stream, Quaternion.Slerp(bone.GetRotation(stream), rotation,
|
||||||
|
weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ModifyTransform(AnimationStream stream, TransformSceneHandle root,
|
||||||
|
TransformStreamHandle target, KPose pose, float weight)
|
||||||
|
{
|
||||||
|
KTransform rootTransform = GetTransform(stream, root);
|
||||||
|
|
||||||
|
if (pose.modifyMode == EModifyMode.Add)
|
||||||
|
{
|
||||||
|
if (pose.space == ESpaceType.BoneSpace)
|
||||||
|
{
|
||||||
|
MoveInSpace(stream, target, target, pose.pose.position, weight);
|
||||||
|
RotateInSpace(stream, target, target, pose.pose.rotation, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
var local = GetTransform(stream, target, false);
|
||||||
|
|
||||||
|
target.SetLocalPosition(stream, Vector3.Lerp(local.position,
|
||||||
|
local.position + pose.pose.position, weight));
|
||||||
|
target.SetLocalRotation(stream, Quaternion.Slerp(local.rotation,
|
||||||
|
local.rotation * pose.pose.rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
MoveInSpace(stream, root, target, pose.pose.position, weight);
|
||||||
|
RotateInSpace(stream, root, target, pose.pose.rotation, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KTransform world = GetTransform(stream, target);
|
||||||
|
|
||||||
|
target.SetPosition(stream,
|
||||||
|
Vector3.Lerp(world.position, world.position + pose.pose.position, weight));
|
||||||
|
target.SetRotation(stream,
|
||||||
|
Quaternion.Slerp(world.rotation, world.rotation * pose.pose.rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space is ESpaceType.BoneSpace or ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
target.SetLocalPosition(stream,
|
||||||
|
Vector3.Lerp(target.GetLocalPosition(stream), pose.pose.position, weight));
|
||||||
|
target.SetLocalRotation(stream,
|
||||||
|
Quaternion.Slerp(target.GetLocalRotation(stream), pose.pose.rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
var worldTransform = rootTransform.GetWorldTransform(pose.pose, false);
|
||||||
|
worldTransform = KTransform.Lerp(GetTransform(stream, target), worldTransform, weight);
|
||||||
|
|
||||||
|
target.SetPosition(stream, worldTransform.position);
|
||||||
|
target.SetRotation(stream, worldTransform.rotation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.SetPosition(stream,
|
||||||
|
Vector3.Lerp(target.GetPosition(stream), pose.pose.position, weight));
|
||||||
|
target.SetRotation(stream, Quaternion.Slerp(target.GetRotation(stream), pose.pose.rotation,
|
||||||
|
weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ModifyTransform(AnimationStream stream, TransformStreamHandle root,
|
||||||
|
TransformStreamHandle target, KPose pose, float weight)
|
||||||
|
{
|
||||||
|
KTransform rootTransform = GetTransform(stream, root);
|
||||||
|
|
||||||
|
if (pose.modifyMode == EModifyMode.Add)
|
||||||
|
{
|
||||||
|
if (pose.space == ESpaceType.BoneSpace)
|
||||||
|
{
|
||||||
|
MoveInSpace(stream, target, target, pose.pose.position, weight);
|
||||||
|
RotateInSpace(stream, target, target, pose.pose.rotation, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
var local = GetTransform(stream, target, false);
|
||||||
|
|
||||||
|
target.SetLocalPosition(stream, Vector3.Lerp(local.position,
|
||||||
|
local.position + pose.pose.position, weight));
|
||||||
|
target.SetLocalRotation(stream, Quaternion.Slerp(local.rotation,
|
||||||
|
local.rotation * pose.pose.rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
MoveInSpace(stream, root, target, pose.pose.position, weight);
|
||||||
|
RotateInSpace(stream, root, target, pose.pose.rotation, weight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KTransform world = GetTransform(stream, target);
|
||||||
|
|
||||||
|
target.SetPosition(stream,
|
||||||
|
Vector3.Lerp(world.position, world.position + pose.pose.position, weight));
|
||||||
|
target.SetRotation(stream,
|
||||||
|
Quaternion.Slerp(world.rotation, world.rotation * pose.pose.rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space is ESpaceType.BoneSpace or ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
target.SetLocalPosition(stream,
|
||||||
|
Vector3.Lerp(target.GetLocalPosition(stream), pose.pose.position, weight));
|
||||||
|
target.SetLocalRotation(stream,
|
||||||
|
Quaternion.Slerp(target.GetLocalRotation(stream), pose.pose.rotation, weight));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
var worldTransform = rootTransform.GetWorldTransform(pose.pose, false);
|
||||||
|
target.SetPosition(stream, worldTransform.position);
|
||||||
|
target.SetRotation(stream, worldTransform.rotation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.SetPosition(stream,
|
||||||
|
Vector3.Lerp(target.GetPosition(stream), pose.pose.position, weight));
|
||||||
|
target.SetRotation(stream, Quaternion.Slerp(target.GetRotation(stream), pose.pose.rotation,
|
||||||
|
weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies a bone pose in world space.
|
||||||
|
public static void CopyBone(AnimationStream stream, TransformStreamHandle from, TransformStreamHandle to,
|
||||||
|
float weight = 1f)
|
||||||
|
{
|
||||||
|
to.SetPosition(stream, Vector3.Lerp(to.GetPosition(stream), from.GetPosition(stream), weight));
|
||||||
|
to.SetRotation(stream, Quaternion.Slerp(to.GetRotation(stream), from.GetRotation(stream), weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copies a bone pose in world space.
|
||||||
|
public static void CopyBone(AnimationStream stream, TransformSceneHandle from, TransformStreamHandle to,
|
||||||
|
float weight = 1f)
|
||||||
|
{
|
||||||
|
to.SetPosition(stream, Vector3.Lerp(to.GetPosition(stream), from.GetPosition(stream), weight));
|
||||||
|
to.SetRotation(stream, Quaternion.Slerp(to.GetRotation(stream), from.GetRotation(stream), weight));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsWeightFull(float weight)
|
||||||
|
{
|
||||||
|
return Mathf.Approximately(weight, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsWeightRelevant(float weight)
|
||||||
|
{
|
||||||
|
return !Mathf.Approximately(weight, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ModifyTransform(Transform component, Transform target, in KPose pose, float alpha = 1f)
|
||||||
|
{
|
||||||
|
if (pose.modifyMode == EModifyMode.Add)
|
||||||
|
{
|
||||||
|
AddTransform(component, target, in pose, alpha);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplaceTransform(component, target, in pose, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddTransform(Transform component, Transform target, in KPose pose, float alpha = 1f)
|
||||||
|
{
|
||||||
|
if (pose.space == ESpaceType.BoneSpace)
|
||||||
|
{
|
||||||
|
MoveInSpace(target, target, pose.pose.position, alpha);
|
||||||
|
RotateInSpace(target, target, pose.pose.rotation, alpha);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
Transform parent = target.parent;
|
||||||
|
|
||||||
|
MoveInSpace(parent, target, pose.pose.position, alpha);
|
||||||
|
RotateInSpace(parent, target, pose.pose.rotation, alpha);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
MoveInSpace(component, target, pose.pose.position, alpha);
|
||||||
|
RotateInSpace(component, target, pose.pose.rotation, alpha);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 position = target.position;
|
||||||
|
Quaternion rotation = target.rotation;
|
||||||
|
|
||||||
|
target.position = Vector3.Lerp(position, position + pose.pose.position, alpha);
|
||||||
|
target.rotation = Quaternion.Slerp(rotation, rotation * pose.pose.rotation, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReplaceTransform(Transform component, Transform target, in KPose pose, float alpha = 1f)
|
||||||
|
{
|
||||||
|
if (pose.space == ESpaceType.BoneSpace || pose.space == ESpaceType.ParentBoneSpace)
|
||||||
|
{
|
||||||
|
target.localPosition = Vector3.Lerp(target.localPosition, pose.pose.position, alpha);
|
||||||
|
target.localRotation = Quaternion.Slerp(target.localRotation, pose.pose.rotation, alpha);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pose.space == ESpaceType.ComponentSpace)
|
||||||
|
{
|
||||||
|
target.position = Vector3.Lerp(target.position, component.TransformPoint(pose.pose.position), alpha);
|
||||||
|
target.rotation = Quaternion.Slerp(target.rotation, component.rotation * pose.pose.rotation, alpha);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.position = Vector3.Lerp(target.position, pose.pose.position, alpha);
|
||||||
|
target.rotation = Quaternion.Slerp(target.rotation, pose.pose.rotation, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4980d4c84030441c922128e9a378b5fb
|
||||||
|
timeCreated: 1698338913
|
||||||
71
Assets/KINEMATION/KAnimationCore/Runtime/Core/KChainIK.cs
Normal file
71
Assets/KINEMATION/KAnimationCore/Runtime/Core/KChainIK.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Core
|
||||||
|
{
|
||||||
|
public struct ChainIKData
|
||||||
|
{
|
||||||
|
public Vector3[] positions;
|
||||||
|
public float[] lengths;
|
||||||
|
|
||||||
|
public Vector3 target;
|
||||||
|
public float tolerance;
|
||||||
|
public float maxReach;
|
||||||
|
public int maxIterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KChainIK
|
||||||
|
{
|
||||||
|
public static bool SolveFABRIK(ref ChainIKData ikData)
|
||||||
|
{
|
||||||
|
// If the target is unreachable
|
||||||
|
var rootToTargetDir = ikData.target - ikData.positions[0];
|
||||||
|
if (rootToTargetDir.sqrMagnitude > KMath.Square(ikData.maxReach))
|
||||||
|
{
|
||||||
|
// Line up chain towards target
|
||||||
|
var dir = rootToTargetDir.normalized;
|
||||||
|
for (int i = 1; i < ikData.positions.Length; ++i)
|
||||||
|
{
|
||||||
|
ikData.positions[i] = ikData.positions[i - 1] + dir * ikData.lengths[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tipIndex = ikData.positions.Length - 1;
|
||||||
|
float sqrTolerance = KMath.Square(ikData.tolerance);
|
||||||
|
|
||||||
|
if (KMath.SqrDistance(ikData.positions[tipIndex], ikData.target) > sqrTolerance)
|
||||||
|
{
|
||||||
|
var rootPos = ikData.positions[0];
|
||||||
|
int iteration = 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Forward reaching phase
|
||||||
|
// Set tip to target and propagate displacement to rest of chain
|
||||||
|
ikData.positions[tipIndex] = ikData.target;
|
||||||
|
for (int i = tipIndex - 1; i > -1; --i)
|
||||||
|
{
|
||||||
|
ikData.positions[i] = ikData.positions[i + 1] +
|
||||||
|
((ikData.positions[i] - ikData.positions[i + 1]).normalized *
|
||||||
|
ikData.lengths[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward reaching phase
|
||||||
|
// Set root back at it's original position and propagate displacement to rest of chain
|
||||||
|
ikData.positions[0] = rootPos;
|
||||||
|
for (int i = 1; i < ikData.positions.Length; ++i)
|
||||||
|
ikData.positions[i] = ikData.positions[i - 1] +
|
||||||
|
((ikData.positions[i] - ikData.positions[i - 1]).normalized * ikData.lengths[i - 1]);
|
||||||
|
} while ((KMath.SqrDistance(ikData.positions[tipIndex], ikData.target) > sqrTolerance) &&
|
||||||
|
(++iteration < ikData.maxIterations));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1308cae72dcf420ab9a43892668bf485
|
||||||
|
timeCreated: 1716450134
|
||||||
154
Assets/KINEMATION/KAnimationCore/Runtime/Core/KCurves.cs
Normal file
154
Assets/KINEMATION/KAnimationCore/Runtime/Core/KCurves.cs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Core
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public struct VectorCurve
|
||||||
|
{
|
||||||
|
public AnimationCurve x;
|
||||||
|
public AnimationCurve y;
|
||||||
|
public AnimationCurve z;
|
||||||
|
|
||||||
|
public static VectorCurve Linear(float timeStart, float timeEnd, float valueStart, float valueEnd)
|
||||||
|
{
|
||||||
|
VectorCurve result = new VectorCurve()
|
||||||
|
{
|
||||||
|
x = AnimationCurve.Linear(timeStart, timeEnd, valueStart, valueEnd),
|
||||||
|
y = AnimationCurve.Linear(timeStart, timeEnd, valueStart, valueEnd),
|
||||||
|
z = AnimationCurve.Linear(timeStart, timeEnd, valueStart, valueEnd)
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VectorCurve Constant(float timeStart, float timeEnd, float value)
|
||||||
|
{
|
||||||
|
VectorCurve result = new VectorCurve()
|
||||||
|
{
|
||||||
|
x = AnimationCurve.Constant(timeStart, timeEnd, value),
|
||||||
|
y = AnimationCurve.Constant(timeStart, timeEnd, value),
|
||||||
|
z = AnimationCurve.Constant(timeStart, timeEnd, value)
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetCurveLength()
|
||||||
|
{
|
||||||
|
float maxTime = -1f;
|
||||||
|
|
||||||
|
float curveTime = KCurves.GetCurveLength(x);
|
||||||
|
maxTime = curveTime > maxTime ? curveTime : maxTime;
|
||||||
|
|
||||||
|
curveTime = KCurves.GetCurveLength(y);
|
||||||
|
maxTime = curveTime > maxTime ? curveTime : maxTime;
|
||||||
|
|
||||||
|
curveTime = KCurves.GetCurveLength(z);
|
||||||
|
maxTime = curveTime > maxTime ? curveTime : maxTime;
|
||||||
|
|
||||||
|
return maxTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetValue(float time)
|
||||||
|
{
|
||||||
|
return new Vector3(x.Evaluate(time), y.Evaluate(time), z.Evaluate(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetLastValue()
|
||||||
|
{
|
||||||
|
float length = GetCurveLength();
|
||||||
|
return GetValue(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsValid()
|
||||||
|
{
|
||||||
|
return x != null && y != null && z != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VectorCurve(Keyframe[] keyFrame)
|
||||||
|
{
|
||||||
|
x = new AnimationCurve(keyFrame);
|
||||||
|
y = new AnimationCurve(keyFrame);
|
||||||
|
z = new AnimationCurve(keyFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public enum EEaseFunc
|
||||||
|
{
|
||||||
|
Linear,
|
||||||
|
Sine,
|
||||||
|
Cubic,
|
||||||
|
Custom
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct EaseMode
|
||||||
|
{
|
||||||
|
public EEaseFunc easeFunc;
|
||||||
|
public AnimationCurve curve;
|
||||||
|
|
||||||
|
public EaseMode(EEaseFunc func)
|
||||||
|
{
|
||||||
|
easeFunc = func;
|
||||||
|
curve = AnimationCurve.Linear(0f, 0f, 1f, 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KCurves
|
||||||
|
{
|
||||||
|
public static float GetCurveLength(AnimationCurve curve)
|
||||||
|
{
|
||||||
|
float length = 0f;
|
||||||
|
|
||||||
|
if (curve != null)
|
||||||
|
{
|
||||||
|
length = curve[curve.length - 1].time;
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float EaseSine(float a, float b, float alpha)
|
||||||
|
{
|
||||||
|
return Mathf.Lerp(a, b, -(Mathf.Cos(Mathf.PI * alpha) - 1) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float EaseCubic(float a, float b, float alpha)
|
||||||
|
{
|
||||||
|
alpha = alpha < 0.5 ? 4 * alpha * alpha * alpha : 1 - Mathf.Pow(-2 * alpha + 2, 3) / 2;
|
||||||
|
return Mathf.Lerp(a, b, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float EaseCurve(float a, float b, float alpha, AnimationCurve curve)
|
||||||
|
{
|
||||||
|
alpha = curve?.Evaluate(alpha) ?? alpha;
|
||||||
|
return Mathf.Lerp(a, b, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float Ease(float a, float b, float alpha, EaseMode ease)
|
||||||
|
{
|
||||||
|
alpha = Mathf.Clamp01(alpha);
|
||||||
|
|
||||||
|
if (ease.easeFunc == EEaseFunc.Sine)
|
||||||
|
{
|
||||||
|
return EaseSine(a, b, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ease.easeFunc == EEaseFunc.Cubic)
|
||||||
|
{
|
||||||
|
return EaseCubic(a, b, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ease.easeFunc == EEaseFunc.Custom)
|
||||||
|
{
|
||||||
|
return EaseCurve(a, b, alpha, ease.curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mathf.Lerp(a, b, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4594292dc1514661a692348534d3285c
|
||||||
|
timeCreated: 1698343937
|
||||||
104
Assets/KINEMATION/KAnimationCore/Runtime/Core/KMath.cs
Normal file
104
Assets/KINEMATION/KAnimationCore/Runtime/Core/KMath.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Core
|
||||||
|
{
|
||||||
|
public class KMath
|
||||||
|
{
|
||||||
|
public const float FloatMin = 1e-10f;
|
||||||
|
public const float SqrEpsilon = 1e-8f;
|
||||||
|
|
||||||
|
public static float Square(float value)
|
||||||
|
{
|
||||||
|
return value * value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float SqrDistance(Vector3 a, Vector3 b)
|
||||||
|
{
|
||||||
|
return (b - a).sqrMagnitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float NormalizeEulerAngle(float angle)
|
||||||
|
{
|
||||||
|
while (angle < -180f) angle += 360f;
|
||||||
|
while (angle >= 180f) angle -= 360f;
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float TriangleAngle(float aLen, float aLen1, float aLen2)
|
||||||
|
{
|
||||||
|
float c = Mathf.Clamp((aLen1 * aLen1 + aLen2 * aLen2 - aLen * aLen) / (aLen1 * aLen2) / 2.0f, -1.0f, 1.0f);
|
||||||
|
return Mathf.Acos(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Quaternion FromToRotation(Vector3 from, Vector3 to)
|
||||||
|
{
|
||||||
|
float theta = Vector3.Dot(from.normalized, to.normalized);
|
||||||
|
if (theta >= 1f) return Quaternion.identity;
|
||||||
|
|
||||||
|
if (theta <= -1f)
|
||||||
|
{
|
||||||
|
Vector3 axis = Vector3.Cross(from, Vector3.right);
|
||||||
|
if (axis.sqrMagnitude == 0f) axis = Vector3.Cross(from, Vector3.up);
|
||||||
|
|
||||||
|
return Quaternion.AngleAxis(180f, axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Quaternion.AngleAxis(Mathf.Acos(theta) * Mathf.Rad2Deg, Vector3.Cross(from, to).normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Quaternion NormalizeSafe(Quaternion q)
|
||||||
|
{
|
||||||
|
float dot = Quaternion.Dot(q, q);
|
||||||
|
if (dot > FloatMin)
|
||||||
|
{
|
||||||
|
float rsqrt = 1.0f / Mathf.Sqrt(dot);
|
||||||
|
return new Quaternion(q.x * rsqrt, q.y * rsqrt, q.z * rsqrt, q.w * rsqrt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Quaternion.identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float InvLerp(float value, float a, float b)
|
||||||
|
{
|
||||||
|
float alpha = 0f;
|
||||||
|
|
||||||
|
if (!Mathf.Approximately(a, b))
|
||||||
|
{
|
||||||
|
alpha = (value - a) / (b - a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mathf.Clamp01(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float ExpDecayAlpha(float speed, float deltaTime)
|
||||||
|
{
|
||||||
|
return 1 - Mathf.Exp(-speed * deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float FloatInterp(float a, float b, float speed, float deltaTime)
|
||||||
|
{
|
||||||
|
return speed > 0f ? Mathf.Lerp(a, b, ExpDecayAlpha(speed, deltaTime)) : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Quaternion SmoothSlerp(Quaternion a, Quaternion b, float speed, float deltaTime)
|
||||||
|
{
|
||||||
|
return speed > 0f ? Quaternion.Slerp(a, b, ExpDecayAlpha(speed, deltaTime)) : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 ComputeLookAtInput(Transform root, Transform from, Transform to)
|
||||||
|
{
|
||||||
|
Vector2 result = Vector2.zero;
|
||||||
|
|
||||||
|
Quaternion rot = Quaternion.LookRotation(to.position - from.position);
|
||||||
|
rot = Quaternion.Inverse(root.rotation) * rot;
|
||||||
|
|
||||||
|
Vector3 euler = rot.eulerAngles;
|
||||||
|
result.x = NormalizeEulerAngle(euler.x);
|
||||||
|
result.y = NormalizeEulerAngle(euler.y);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bd8cdd8e10864049b6b1119a7c3cc296
|
||||||
|
timeCreated: 1704002312
|
||||||
87
Assets/KINEMATION/KAnimationCore/Runtime/Core/KSpringMath.cs
Normal file
87
Assets/KINEMATION/KAnimationCore/Runtime/Core/KSpringMath.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// Designed by KINEMATION, 2023
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Core
|
||||||
|
{
|
||||||
|
public struct FloatSpringState
|
||||||
|
{
|
||||||
|
public float velocity;
|
||||||
|
public float error;
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
error = velocity = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct VectorSpringState
|
||||||
|
{
|
||||||
|
public FloatSpringState x;
|
||||||
|
public FloatSpringState y;
|
||||||
|
public FloatSpringState z;
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
x.Reset();
|
||||||
|
y.Reset();
|
||||||
|
z.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct VectorSpring
|
||||||
|
{
|
||||||
|
public Vector3 damping;
|
||||||
|
public Vector3 stiffness;
|
||||||
|
public Vector3 speed;
|
||||||
|
public Vector3 scale;
|
||||||
|
|
||||||
|
public static VectorSpring identity = new VectorSpring()
|
||||||
|
{
|
||||||
|
damping = Vector3.zero,
|
||||||
|
stiffness = Vector3.zero,
|
||||||
|
speed = Vector3.zero,
|
||||||
|
scale = Vector3.zero
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KSpringMath
|
||||||
|
{
|
||||||
|
public static float FloatSpringInterp(float current, float target, float speed, float criticalDamping,
|
||||||
|
float stiffness, float scale, ref FloatSpringState state, float deltaTime)
|
||||||
|
{
|
||||||
|
float interpSpeed = Mathf.Min(deltaTime * speed, 1f);
|
||||||
|
|
||||||
|
if (!Mathf.Approximately(interpSpeed, 0f))
|
||||||
|
{
|
||||||
|
float damping = 2 * Mathf.Sqrt(stiffness) * criticalDamping;
|
||||||
|
float error = target * scale - current;
|
||||||
|
float errorDeriv = error - state.error;
|
||||||
|
state.velocity += error * stiffness * interpSpeed + errorDeriv * damping;
|
||||||
|
state.error = error;
|
||||||
|
|
||||||
|
float value = current + state.velocity * interpSpeed;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 VectorSpringInterp(Vector3 current, in Vector3 target, in VectorSpring spring,
|
||||||
|
ref VectorSpringState state, float deltaTime)
|
||||||
|
{
|
||||||
|
current.x = FloatSpringInterp(current.x, target.x, spring.speed.x,
|
||||||
|
spring.damping.x, spring.stiffness.x, spring.scale.x, ref state.x, deltaTime);
|
||||||
|
|
||||||
|
current.y = FloatSpringInterp(current.y, target.y, spring.speed.y,
|
||||||
|
spring.damping.y, spring.stiffness.y, spring.scale.y, ref state.y, deltaTime);
|
||||||
|
|
||||||
|
current.z = FloatSpringInterp(current.z, target.z, spring.speed.z,
|
||||||
|
spring.damping.z, spring.stiffness.z, spring.scale.z, ref state.z, deltaTime);
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b715a4ee42b646169c5ce859e6d34964
|
||||||
|
timeCreated: 1698339846
|
||||||
153
Assets/KINEMATION/KAnimationCore/Runtime/Core/KTransform.cs
Normal file
153
Assets/KINEMATION/KAnimationCore/Runtime/Core/KTransform.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Designed by KINEMATION, 2024
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using Quaternion = UnityEngine.Quaternion;
|
||||||
|
using Vector3 = UnityEngine.Vector3;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Core
|
||||||
|
{
|
||||||
|
// Struct alternative for Transform.
|
||||||
|
[Serializable]
|
||||||
|
public struct KTransform
|
||||||
|
{
|
||||||
|
public static KTransform Identity = new(Vector3.zero, Quaternion.identity, Vector3.one);
|
||||||
|
|
||||||
|
public Vector3 position;
|
||||||
|
public Quaternion rotation;
|
||||||
|
public Vector3 scale;
|
||||||
|
|
||||||
|
public KTransform(Vector3 newPos, Quaternion newRot, Vector3 newScale)
|
||||||
|
{
|
||||||
|
position = newPos;
|
||||||
|
rotation = newRot;
|
||||||
|
scale = newScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KTransform(Vector3 newPos, Quaternion newRot)
|
||||||
|
{
|
||||||
|
position = newPos;
|
||||||
|
rotation = newRot;
|
||||||
|
scale = Vector3.one;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KTransform(Transform t, bool worldSpace = true)
|
||||||
|
{
|
||||||
|
if (worldSpace)
|
||||||
|
{
|
||||||
|
position = t.position;
|
||||||
|
rotation = t.rotation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
position = t.localPosition;
|
||||||
|
rotation = t.localRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
scale = t.localScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linearly interpolates translation and scale. Spherically interpolates rotation.
|
||||||
|
public static KTransform Lerp(KTransform a, KTransform b, float alpha)
|
||||||
|
{
|
||||||
|
Vector3 outPos = Vector3.Lerp(a.position, b.position, alpha);
|
||||||
|
Quaternion outRot = Quaternion.Slerp(a.rotation, b.rotation, alpha);
|
||||||
|
Vector3 outScale = Vector3.Lerp(a.scale, a.scale, alpha);
|
||||||
|
|
||||||
|
return new KTransform(outPos, outRot, outScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KTransform EaseLerp(KTransform a, KTransform b, float alpha, EaseMode easeMode)
|
||||||
|
{
|
||||||
|
return Lerp(a, b, KCurves.Ease(0f, 1f, alpha, easeMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame-rate independent interpolation.
|
||||||
|
public static KTransform ExpDecay(KTransform a, KTransform b, float speed, float deltaTime)
|
||||||
|
{
|
||||||
|
return Lerp(a, b, KMath.ExpDecayAlpha(speed, deltaTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(KTransform other, bool useScale)
|
||||||
|
{
|
||||||
|
bool result = position.Equals(other.position) && rotation.Equals(other.rotation);
|
||||||
|
|
||||||
|
if (useScale)
|
||||||
|
{
|
||||||
|
result = result && scale.Equals(other.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a point relative to this transform.
|
||||||
|
public Vector3 InverseTransformPoint(Vector3 worldPosition, bool useScale)
|
||||||
|
{
|
||||||
|
Vector3 result = Quaternion.Inverse(rotation) * (worldPosition - position);
|
||||||
|
|
||||||
|
if (useScale)
|
||||||
|
{
|
||||||
|
result = Vector3.Scale(scale, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a vector relative to this transform.
|
||||||
|
public Vector3 InverseTransformVector(Vector3 worldDirection, bool useScale)
|
||||||
|
{
|
||||||
|
Vector3 result = Quaternion.Inverse(rotation) * worldDirection;
|
||||||
|
|
||||||
|
if (useScale)
|
||||||
|
{
|
||||||
|
result = Vector3.Scale(scale, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a local position from this transform to world.
|
||||||
|
public Vector3 TransformPoint(Vector3 localPosition, bool useScale)
|
||||||
|
{
|
||||||
|
if (useScale)
|
||||||
|
{
|
||||||
|
localPosition = Vector3.Scale(scale, localPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return position + rotation * localPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a local vector from this transform to world.
|
||||||
|
public Vector3 TransformVector(Vector3 localDirection, bool useScale)
|
||||||
|
{
|
||||||
|
if (useScale)
|
||||||
|
{
|
||||||
|
localDirection = Vector3.Scale(scale, localDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rotation * localDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a transform relative to this transform.
|
||||||
|
public KTransform GetRelativeTransform(KTransform worldTransform, bool useScale)
|
||||||
|
{
|
||||||
|
return new KTransform()
|
||||||
|
{
|
||||||
|
position = InverseTransformPoint(worldTransform.position, useScale),
|
||||||
|
rotation = Quaternion.Inverse(rotation) * worldTransform.rotation,
|
||||||
|
scale = Vector3.Scale(scale, worldTransform.scale)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a local transform to world.
|
||||||
|
public KTransform GetWorldTransform(KTransform localTransform, bool useScale)
|
||||||
|
{
|
||||||
|
return new KTransform()
|
||||||
|
{
|
||||||
|
position = TransformPoint(localTransform.position, useScale),
|
||||||
|
rotation = rotation * localTransform.rotation,
|
||||||
|
scale = Vector3.Scale(scale, localTransform.scale)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d8ef53e57347421a8fcc4468bd018afd
|
||||||
|
timeCreated: 1698341198
|
||||||
121
Assets/KINEMATION/KAnimationCore/Runtime/Core/KTwoBoneIK.cs
Normal file
121
Assets/KINEMATION/KAnimationCore/Runtime/Core/KTwoBoneIK.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
// Designed by KINEMATION, 2023
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Core
|
||||||
|
{
|
||||||
|
public struct KTwoBoneIkData
|
||||||
|
{
|
||||||
|
public KTransform root;
|
||||||
|
public KTransform mid;
|
||||||
|
public KTransform tip;
|
||||||
|
public KTransform target;
|
||||||
|
public KTransform hint;
|
||||||
|
|
||||||
|
public float posWeight;
|
||||||
|
public float rotWeight;
|
||||||
|
public float hintWeight;
|
||||||
|
|
||||||
|
public bool hasValidHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KTwoBoneIK
|
||||||
|
{
|
||||||
|
public static void Solve(ref KTwoBoneIkData ikData)
|
||||||
|
{
|
||||||
|
Vector3 aPosition = ikData.root.position;
|
||||||
|
Vector3 bPosition = ikData.mid.position;
|
||||||
|
Vector3 cPosition = ikData.tip.position;
|
||||||
|
|
||||||
|
Vector3 tPosition = Vector3.Lerp(cPosition, ikData.target.position, ikData.posWeight);
|
||||||
|
Quaternion tRotation = Quaternion.Slerp(ikData.tip.rotation, ikData.target.rotation, ikData.rotWeight);
|
||||||
|
bool hasHint = ikData.hasValidHint && ikData.hintWeight > 0f;
|
||||||
|
|
||||||
|
Vector3 ab = bPosition - aPosition;
|
||||||
|
Vector3 bc = cPosition - bPosition;
|
||||||
|
Vector3 ac = cPosition - aPosition;
|
||||||
|
Vector3 at = tPosition - aPosition;
|
||||||
|
|
||||||
|
float abLen = ab.magnitude;
|
||||||
|
float bcLen = bc.magnitude;
|
||||||
|
float acLen = ac.magnitude;
|
||||||
|
float atLen = at.magnitude;
|
||||||
|
|
||||||
|
float oldAbcAngle = KMath.TriangleAngle(acLen, abLen, bcLen);
|
||||||
|
float newAbcAngle = KMath.TriangleAngle(atLen, abLen, bcLen);
|
||||||
|
|
||||||
|
// Bend normal strategy is to take whatever has been provided in the animation
|
||||||
|
// stream to minimize configuration changes, however if this is collinear
|
||||||
|
// try computing a bend normal given the desired target position.
|
||||||
|
// If this also fails, try resolving axis using hint if provided.
|
||||||
|
Vector3 axis = Vector3.Cross(ab, bc);
|
||||||
|
if (axis.sqrMagnitude < KMath.SqrEpsilon)
|
||||||
|
{
|
||||||
|
axis = hasHint ? Vector3.Cross(ikData.hint.position - aPosition, bc) : Vector3.zero;
|
||||||
|
|
||||||
|
if (axis.sqrMagnitude < KMath.SqrEpsilon)
|
||||||
|
axis = Vector3.Cross(at, bc);
|
||||||
|
|
||||||
|
if (axis.sqrMagnitude < KMath.SqrEpsilon)
|
||||||
|
axis = Vector3.up;
|
||||||
|
}
|
||||||
|
|
||||||
|
axis = Vector3.Normalize(axis);
|
||||||
|
|
||||||
|
float a = 0.5f * (oldAbcAngle - newAbcAngle);
|
||||||
|
float sin = Mathf.Sin(a);
|
||||||
|
float cos = Mathf.Cos(a);
|
||||||
|
Quaternion deltaR = new Quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos);
|
||||||
|
|
||||||
|
KTransform localTip = ikData.mid.GetRelativeTransform(ikData.tip, false);
|
||||||
|
ikData.mid.rotation = deltaR * ikData.mid.rotation;
|
||||||
|
|
||||||
|
// Update child transform.
|
||||||
|
ikData.tip = ikData.mid.GetWorldTransform(localTip, false);
|
||||||
|
|
||||||
|
cPosition = ikData.tip.position;
|
||||||
|
ac = cPosition - aPosition;
|
||||||
|
|
||||||
|
KTransform localMid = ikData.root.GetRelativeTransform(ikData.mid, false);
|
||||||
|
localTip = ikData.mid.GetRelativeTransform(ikData.tip, false);
|
||||||
|
ikData.root.rotation = KMath.FromToRotation(ac, at) * ikData.root.rotation;
|
||||||
|
|
||||||
|
// Update child transforms.
|
||||||
|
ikData.mid = ikData.root.GetWorldTransform(localMid, false);
|
||||||
|
ikData.tip = ikData.mid.GetWorldTransform(localTip, false);
|
||||||
|
|
||||||
|
if (hasHint)
|
||||||
|
{
|
||||||
|
float acSqrMag = ac.sqrMagnitude;
|
||||||
|
if (acSqrMag > 0f)
|
||||||
|
{
|
||||||
|
bPosition = ikData.mid.position;
|
||||||
|
cPosition = ikData.tip.position;
|
||||||
|
ab = bPosition - aPosition;
|
||||||
|
ac = cPosition - aPosition;
|
||||||
|
|
||||||
|
Vector3 acNorm = ac / Mathf.Sqrt(acSqrMag);
|
||||||
|
Vector3 ah = ikData.hint.position - aPosition;
|
||||||
|
Vector3 abProj = ab - acNorm * Vector3.Dot(ab, acNorm);
|
||||||
|
Vector3 ahProj = ah - acNorm * Vector3.Dot(ah, acNorm);
|
||||||
|
|
||||||
|
float maxReach = abLen + bcLen;
|
||||||
|
if (abProj.sqrMagnitude > (maxReach * maxReach * 0.001f) && ahProj.sqrMagnitude > 0f)
|
||||||
|
{
|
||||||
|
Quaternion hintR = KMath.FromToRotation(abProj, ahProj);
|
||||||
|
hintR.x *= ikData.hintWeight;
|
||||||
|
hintR.y *= ikData.hintWeight;
|
||||||
|
hintR.z *= ikData.hintWeight;
|
||||||
|
hintR = KMath.NormalizeSafe(hintR);
|
||||||
|
ikData.root.rotation = hintR * ikData.root.rotation;
|
||||||
|
|
||||||
|
ikData.mid = ikData.root.GetWorldTransform(localMid, false);
|
||||||
|
ikData.tip = ikData.mid.GetWorldTransform(localTip, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ikData.tip.rotation = tRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8d99a3775c3e469dbe146569b35cd7a6
|
||||||
|
timeCreated: 1698338938
|
||||||
53
Assets/KINEMATION/KAnimationCore/Runtime/Core/SetFloat.cs
Normal file
53
Assets/KINEMATION/KAnimationCore/Runtime/Core/SetFloat.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Core
|
||||||
|
{
|
||||||
|
public class SetFloat : StateMachineBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private string paramName;
|
||||||
|
[SerializeField] private float paramTargetValue;
|
||||||
|
[SerializeField] private EaseMode easeMode = new EaseMode(EEaseFunc.Linear);
|
||||||
|
|
||||||
|
private int _paramId;
|
||||||
|
private float _paramStartValue;
|
||||||
|
private bool _isInitialized;
|
||||||
|
|
||||||
|
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||||
|
{
|
||||||
|
if (!_isInitialized)
|
||||||
|
{
|
||||||
|
_paramId = Animator.StringToHash(paramName);
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_paramStartValue = animator.GetFloat(_paramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||||
|
{
|
||||||
|
int nextHash = animator.GetNextAnimatorStateInfo(layerIndex).fullPathHash;
|
||||||
|
if (nextHash != stateInfo.fullPathHash && nextHash != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float alpha = 0f;
|
||||||
|
if (animator.IsInTransition(layerIndex))
|
||||||
|
{
|
||||||
|
alpha = animator.GetAnimatorTransitionInfo(layerIndex).normalizedTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alpha = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
animator.SetFloat(_paramId, KCurves.Ease(_paramStartValue, paramTargetValue, alpha, easeMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d4437088df934c1a9375bf492c23d011
|
||||||
|
timeCreated: 1709881756
|
||||||
3
Assets/KINEMATION/KAnimationCore/Runtime/Input.meta
Normal file
3
Assets/KINEMATION/KAnimationCore/Runtime/Input.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9d01e94a2fa34eba9f2ebd278987b1dc
|
||||||
|
timeCreated: 1707394003
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Input
|
||||||
|
{
|
||||||
|
[Obsolete("use `UserInputController` instead.")]
|
||||||
|
public interface IUserInputController
|
||||||
|
{
|
||||||
|
public void Initialize();
|
||||||
|
|
||||||
|
public int GetPropertyIndex(string propertyName);
|
||||||
|
|
||||||
|
public void SetValue(string propertyName, object value);
|
||||||
|
|
||||||
|
public T GetValue<T>(string propertyName);
|
||||||
|
|
||||||
|
public void SetValue(int propertyIndex, object value);
|
||||||
|
|
||||||
|
public T GetValue<T>(int propertyIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ca9ff9d900444c4785ba2de65c0e0263
|
||||||
|
timeCreated: 1707563078
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Input
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public struct BoolProperty
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public bool defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct IntProperty
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public int defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct FloatProperty
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public float defaultValue;
|
||||||
|
public float interpolationSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct VectorProperty
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public Vector4 defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 37f35ec97fc94416a139ca21ae59beda
|
||||||
|
timeCreated: 1707463958
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Input
|
||||||
|
{
|
||||||
|
[CreateAssetMenu(fileName = "NewInputConfig", menuName = "KINEMATION/Input Config")]
|
||||||
|
public class UserInputConfig : ScriptableObject
|
||||||
|
{
|
||||||
|
public List<IntProperty> intProperties = new List<IntProperty>();
|
||||||
|
public List<FloatProperty> floatProperties = new List<FloatProperty>();
|
||||||
|
public List<BoolProperty> boolProperties = new List<BoolProperty>();
|
||||||
|
public List<VectorProperty> vectorProperties = new List<VectorProperty>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0e8993601b7c484b862c9c567134ed2e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {fileID: 2800000, guid: a497183ade831dc4aa44bf44b5ce27b8, type: 3}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
// Designed by KINEMATION, 2024.
|
||||||
|
|
||||||
|
using KINEMATION.KAnimationCore.Runtime.Core;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace KINEMATION.KAnimationCore.Runtime.Input
|
||||||
|
{
|
||||||
|
public class UserInputController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] public UserInputConfig inputConfig;
|
||||||
|
|
||||||
|
protected List<object> _inputProperties;
|
||||||
|
protected Dictionary<string, int> _inputPropertyMap;
|
||||||
|
protected (int, float, float)[] _floatsToInterpolate;
|
||||||
|
|
||||||
|
public UserInputConfig GetConfig()
|
||||||
|
{
|
||||||
|
return inputConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Update()
|
||||||
|
{
|
||||||
|
if (_floatsToInterpolate == null) return;
|
||||||
|
|
||||||
|
foreach (var tuple in _floatsToInterpolate)
|
||||||
|
{
|
||||||
|
float value = (float) _inputProperties[tuple.Item1];
|
||||||
|
|
||||||
|
if (Mathf.Approximately(value, tuple.Item3))
|
||||||
|
{
|
||||||
|
value = tuple.Item3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float alpha = KMath.ExpDecayAlpha(Time.deltaTime, tuple.Item2);
|
||||||
|
value = Mathf.LerpUnclamped(value, tuple.Item3, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
_inputProperties[tuple.Item1] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Initialize()
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
_propertyNames = new List<(string, object)>();
|
||||||
|
#endif
|
||||||
|
_inputProperties = new List<object>();
|
||||||
|
_inputPropertyMap = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
List<(int, float, float)> floatsToInterpolate = new List<(int, float, float)>();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
foreach (var property in inputConfig.boolProperties)
|
||||||
|
{
|
||||||
|
_inputProperties.Add(property.defaultValue);
|
||||||
|
_inputPropertyMap.TryAdd(property.name, index);
|
||||||
|
index++;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
_propertyNames.Add((property.name, null));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var property in inputConfig.intProperties)
|
||||||
|
{
|
||||||
|
_inputProperties.Add(property.defaultValue);
|
||||||
|
_inputPropertyMap.TryAdd(property.name, index);
|
||||||
|
index++;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
_propertyNames.Add((property.name, null));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var property in inputConfig.floatProperties)
|
||||||
|
{
|
||||||
|
_inputProperties.Add(property.defaultValue);
|
||||||
|
_inputPropertyMap.TryAdd(property.name, index);
|
||||||
|
|
||||||
|
if (!Mathf.Approximately(property.interpolationSpeed, 0f))
|
||||||
|
{
|
||||||
|
floatsToInterpolate.Add((index, property.interpolationSpeed, property.defaultValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
_propertyNames.Add((property.name, null));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (floatsToInterpolate.Count > 0)
|
||||||
|
{
|
||||||
|
_floatsToInterpolate = floatsToInterpolate.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var property in inputConfig.vectorProperties)
|
||||||
|
{
|
||||||
|
_inputProperties.Add(property.defaultValue);
|
||||||
|
_inputPropertyMap.TryAdd(property.name, index);
|
||||||
|
index++;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
_propertyNames.Add((property.name, null));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetPropertyIndex(string propertyName)
|
||||||
|
{
|
||||||
|
if (_inputPropertyMap.TryGetValue(propertyName, out int index))
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetValue(string propertyName, object value)
|
||||||
|
{
|
||||||
|
SetValue(GetPropertyIndex(propertyName), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual T GetValue<T>(string propertyName)
|
||||||
|
{
|
||||||
|
return GetValue<T>(GetPropertyIndex(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetValue(int propertyIndex, object value)
|
||||||
|
{
|
||||||
|
if (propertyIndex < 0 || propertyIndex > _inputProperties.Count - 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_floatsToInterpolate != null)
|
||||||
|
{
|
||||||
|
int floatToInterpolateIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < _floatsToInterpolate.Length; i++)
|
||||||
|
{
|
||||||
|
if (_floatsToInterpolate[i].Item1 == propertyIndex)
|
||||||
|
{
|
||||||
|
floatToInterpolateIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (floatToInterpolateIndex != -1)
|
||||||
|
{
|
||||||
|
var tuple = _floatsToInterpolate[floatToInterpolateIndex];
|
||||||
|
tuple.Item3 = (float) value;
|
||||||
|
_floatsToInterpolate[floatToInterpolateIndex] = tuple;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_inputProperties[propertyIndex] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual T GetValue<T>(int propertyIndex)
|
||||||
|
{
|
||||||
|
if (propertyIndex < 0 || propertyIndex > _inputProperties.Count - 1)
|
||||||
|
{
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T) _inputProperties[propertyIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
protected List<(string, object)> _propertyNames;
|
||||||
|
|
||||||
|
public virtual (string, object)[] GetPropertyBindings()
|
||||||
|
{
|
||||||
|
if (_propertyNames == null) return null;
|
||||||
|
|
||||||
|
int count = _propertyNames.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var item = _propertyNames[i];
|
||||||
|
item.Item2 = _inputProperties[i];
|
||||||
|
_propertyNames[i] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _propertyNames.ToArray();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 26b2896c936d4be59e98d9bf358c5a96
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {fileID: 2800000, guid: 6fd4d99d3a1edc7408fe599214be6059, type: 3}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"name": "KAnimationCore.Runtime"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d8548a9d25a091541a1fcde53694c91a
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
3
Assets/KINEMATION/KAnimationCore/Runtime/Misc.meta
Normal file
3
Assets/KINEMATION/KAnimationCore/Runtime/Misc.meta
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 373787767ed74d69a69333922d6c13a8
|
||||||
|
timeCreated: 1744450603
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user