Before 优化 机场
This commit is contained in:
3
Assets/Plugins/KINEMATION/MotionWarping/Editor/Core.meta
Normal file
3
Assets/Plugins/KINEMATION/MotionWarping/Editor/Core.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6d9117845df4a049f092765c3987cfd
|
||||
timeCreated: 1705478497
|
@@ -0,0 +1,343 @@
|
||||
// Designed by KINEMATION, 2024.
|
||||
|
||||
using KINEMATION.MotionWarping.Editor.Widgets;
|
||||
using Kinemation.MotionWarping.Runtime.Core;
|
||||
using Kinemation.MotionWarping.Runtime.Utility;
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Vector3 = UnityEngine.Vector3;
|
||||
|
||||
namespace KINEMATION.MotionWarping.Editor.Core
|
||||
{
|
||||
[CustomEditor(typeof(MotionWarpingAsset))]
|
||||
public class MotionWarpingAssetEditor : UnityEditor.Editor
|
||||
{
|
||||
private const BindingFlags PrivateFieldBindingFlags =
|
||||
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField;
|
||||
|
||||
private const BindingFlags PublicFieldBindingFlags =
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField;
|
||||
|
||||
private const BindingFlags PublicPropertyBindingFlags =
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty;
|
||||
|
||||
private const BindingFlags PublicMethodBindingFlags =
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod;
|
||||
|
||||
private static Type _animationClipEditorType;
|
||||
private static Type _avatarPreviewType;
|
||||
private static Type _timeControlType;
|
||||
|
||||
private MotionWarpingAsset _asset;
|
||||
private UnityEditor.Editor _meshEditor;
|
||||
|
||||
private float _frameSlider = 0f;
|
||||
private float _length = 0f;
|
||||
private bool _manualPlaybackOverride;
|
||||
|
||||
private WarpWindowWidget _warpWindowWidget;
|
||||
private WarpPosePreviewWidget _posePreviewer;
|
||||
|
||||
private string[] _toolbarOptions = new string[] { "Motion Warping", "Pose Preview", "Animation Settings"};
|
||||
private int _selectedTab = 0;
|
||||
|
||||
public override bool HasPreviewGUI() => true;
|
||||
|
||||
private void OnAreaModified(int areaIndex)
|
||||
{
|
||||
float warpLength = _asset.GetLength();
|
||||
|
||||
var phase = _asset.warpPhases[areaIndex];
|
||||
var size = _warpWindowWidget.GetAreaSize(areaIndex);
|
||||
|
||||
phase.startTime = (float) Math.Round(size.Item1 * warpLength, 4);
|
||||
phase.endTime = (float) Math.Round(size.Item2 * warpLength, 4);
|
||||
_asset.warpPhases[areaIndex] = phase;
|
||||
}
|
||||
|
||||
private void StopLoop()
|
||||
{
|
||||
var avatarPreview = _animationClipEditorType.GetField("m_AvatarPreview", PrivateFieldBindingFlags)?.GetValue(_meshEditor);
|
||||
if (avatarPreview == null) return;
|
||||
|
||||
var timeControl = _avatarPreviewType.GetField("timeControl", PublicFieldBindingFlags)?.GetValue(avatarPreview);
|
||||
if (timeControl == null) return;
|
||||
|
||||
var stopTime = _timeControlType.GetProperty("playing", PublicPropertyBindingFlags);
|
||||
if (stopTime == null) return;
|
||||
|
||||
stopTime.SetValue(timeControl, false);
|
||||
}
|
||||
|
||||
private void UpdatePlayback()
|
||||
{
|
||||
if (!_manualPlaybackOverride) return;
|
||||
|
||||
var avatarPreview = _animationClipEditorType.GetField("m_AvatarPreview", PrivateFieldBindingFlags)
|
||||
?.GetValue(_meshEditor);
|
||||
if (avatarPreview == null) return;
|
||||
|
||||
var timeControl = _avatarPreviewType.GetField("timeControl", PublicFieldBindingFlags)
|
||||
?.GetValue(avatarPreview);
|
||||
if (timeControl == null) return;
|
||||
|
||||
var stopTime = _timeControlType.GetField("stopTime", PublicFieldBindingFlags);
|
||||
if (stopTime == null) return;
|
||||
|
||||
stopTime.SetValue(timeControl, _length);
|
||||
|
||||
var timeProperty = _timeControlType.GetField("currentTime", PublicFieldBindingFlags);
|
||||
if (timeProperty == null) return;
|
||||
|
||||
timeProperty.SetValue(timeControl, _frameSlider);
|
||||
}
|
||||
|
||||
private void GenerateAreas()
|
||||
{
|
||||
float totalTime = _asset.GetLength();
|
||||
|
||||
if (Mathf.Approximately(totalTime, 0f)) return;
|
||||
_warpWindowWidget.ClearPhases();
|
||||
|
||||
foreach (var phase in _asset.warpPhases)
|
||||
{
|
||||
_warpWindowWidget.AddWarpPhase(phase.startTime / totalTime, phase.endTime / totalTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void GeneratePhases()
|
||||
{
|
||||
float totalTime = _asset.GetLength();
|
||||
if (Mathf.Approximately(totalTime, 0f)) return;
|
||||
|
||||
_asset.warpPhases.Clear();
|
||||
|
||||
float timeStep = totalTime / _asset.phasesAmount;
|
||||
|
||||
for (int i = 0; i < _asset.phasesAmount; i++)
|
||||
{
|
||||
WarpPhase phase = new WarpPhase()
|
||||
{
|
||||
minRate = 0f,
|
||||
maxRate = 1f,
|
||||
startTime = timeStep * i,
|
||||
endTime = timeStep * i + timeStep,
|
||||
};
|
||||
|
||||
_asset.warpPhases.Add(phase);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtractCurves()
|
||||
{
|
||||
if (_asset == null || _asset.animation == null)
|
||||
{
|
||||
Debug.LogError("WarpingAsset or AnimationClip is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorCurveBinding[] tBindings = new EditorCurveBinding[3];
|
||||
|
||||
var curveBindings = AnimationUtility.GetCurveBindings(_asset.animation);
|
||||
foreach (var binding in curveBindings)
|
||||
{
|
||||
if (_asset.animation.isHumanMotion)
|
||||
{
|
||||
if (binding.propertyName.ToLower().Contains("roott.x"))
|
||||
{
|
||||
tBindings[0] = binding;
|
||||
}
|
||||
else if (binding.propertyName.ToLower().Contains("roott.y"))
|
||||
{
|
||||
tBindings[1] = binding;
|
||||
}
|
||||
else if (binding.propertyName.ToLower().Contains("roott.z"))
|
||||
{
|
||||
tBindings[2] = binding;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!binding.path.ToLower().EndsWith("root")) continue;
|
||||
|
||||
if (binding.propertyName.ToLower().Contains("localposition.x"))
|
||||
{
|
||||
tBindings[0] = binding;
|
||||
}
|
||||
else if (binding.propertyName.ToLower().Contains("localposition.y"))
|
||||
{
|
||||
tBindings[1] = binding;
|
||||
}
|
||||
else if (binding.propertyName.ToLower().Contains("localposition.z"))
|
||||
{
|
||||
tBindings[2] = binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var curves = WarpingEditorUtility.ValidateCurves(_asset, 60f, tBindings);
|
||||
|
||||
_asset.rootX = curves.X;
|
||||
_asset.rootY = curves.Y;
|
||||
_asset.rootZ = curves.Z;
|
||||
|
||||
ComputeTotalRootMotion();
|
||||
}
|
||||
|
||||
private void RenderMotionWarpingTab()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
if (_asset.animation == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Specify the animation", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_length = _asset.animation.length;
|
||||
|
||||
_warpWindowWidget.Render();
|
||||
|
||||
var prevSlider = _frameSlider;
|
||||
_frameSlider = _length * _warpWindowWidget.GetPlayback();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button("Generate Phases"))
|
||||
{
|
||||
GeneratePhases();
|
||||
GenerateAreas();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Extract Curves"))
|
||||
{
|
||||
ExtractCurves();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (!Mathf.Approximately(prevSlider, _frameSlider) && !_manualPlaybackOverride)
|
||||
{
|
||||
_manualPlaybackOverride = true;
|
||||
StopLoop();
|
||||
}
|
||||
else
|
||||
{
|
||||
_manualPlaybackOverride = false;
|
||||
}
|
||||
|
||||
UpdatePlayback();
|
||||
}
|
||||
|
||||
private void RenderAnimationSettingsTab()
|
||||
{
|
||||
if (_meshEditor == null) return;
|
||||
_meshEditor.OnInspectorGUI();
|
||||
}
|
||||
|
||||
private void ComputeTotalRootMotion()
|
||||
{
|
||||
if (_asset.animation == null) return;
|
||||
|
||||
float sampleRate = _asset.animation.frameRate;
|
||||
if (Mathf.Approximately(sampleRate, 0f)) return;
|
||||
|
||||
for (int i = 0; i < _asset.warpPhases.Count; i++)
|
||||
{
|
||||
var phase = _asset.warpPhases[i];
|
||||
|
||||
float playback = phase.startTime;
|
||||
Vector3 lastValue = _asset.GetVectorValue(playback);
|
||||
|
||||
phase.totalRootMotion = Vector3.zero;
|
||||
|
||||
while (playback <= phase.endTime)
|
||||
{
|
||||
// Accumulate the delta.
|
||||
Vector3 value = _asset.GetVectorValue(playback);
|
||||
Vector3 delta = value - lastValue;
|
||||
|
||||
phase.totalRootMotion.x += Mathf.Abs(delta.x);
|
||||
phase.totalRootMotion.y += Mathf.Abs(delta.y);
|
||||
phase.totalRootMotion.z += Mathf.Abs(delta.z);
|
||||
|
||||
lastValue = value;
|
||||
|
||||
playback += 1f / sampleRate;
|
||||
}
|
||||
|
||||
_asset.warpPhases[i] = phase;
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(_asset);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_asset = (target) as MotionWarpingAsset;
|
||||
|
||||
if (_asset == null)
|
||||
{
|
||||
Debug.LogError("Target MotionWarpingAsset is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
_meshEditor = CreateEditor(_asset.animation);
|
||||
|
||||
_animationClipEditorType = Type.GetType("UnityEditor.AnimationClipEditor,UnityEditor");
|
||||
_avatarPreviewType = Type.GetType("UnityEditor.AvatarPreview,UnityEditor");
|
||||
_timeControlType = Type.GetType("UnityEditor.TimeControl,UnityEditor");
|
||||
|
||||
_warpWindowWidget = new WarpWindowWidget();
|
||||
_warpWindowWidget.OnAreaModified += OnAreaModified;
|
||||
|
||||
_posePreviewer = new WarpPosePreviewWidget(_asset);
|
||||
|
||||
GenerateAreas();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ComputeTotalRootMotion();
|
||||
|
||||
_posePreviewer.RestorePose();
|
||||
_meshEditor = null;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
_selectedTab = GUILayout.Toolbar(_selectedTab, _toolbarOptions);
|
||||
|
||||
if (_selectedTab == 0)
|
||||
{
|
||||
RenderMotionWarpingTab();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selectedTab == 1)
|
||||
{
|
||||
_posePreviewer?.Render();
|
||||
return;
|
||||
}
|
||||
|
||||
RenderAnimationSettingsTab();
|
||||
}
|
||||
|
||||
public override void OnPreviewGUI(Rect r, GUIStyle background)
|
||||
{
|
||||
if (_meshEditor == null || !_meshEditor.HasPreviewGUI()) return;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
_meshEditor.OnPreviewSettings();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
_meshEditor.OnInteractivePreviewGUI(r, GUIStyle.none);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0d2c068e5aa4321893ea76aa6528f17
|
||||
timeCreated: 1698238712
|
@@ -0,0 +1,20 @@
|
||||
// Designed by KINEMATION, 2024.
|
||||
|
||||
using Kinemation.MotionWarping.Runtime.Utility;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace KINEMATION.MotionWarping.Editor.Core
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
|
||||
public class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
GUI.enabled = false;
|
||||
EditorGUI.PropertyField(position, property, label, true);
|
||||
GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db7fde80939c4b2e8ae89d39d25ecb82
|
||||
timeCreated: 1696662790
|
@@ -0,0 +1,58 @@
|
||||
// Designed by KINEMATION, 2024.
|
||||
|
||||
using Kinemation.MotionWarping.Runtime.Core;
|
||||
using Kinemation.MotionWarping.Runtime.Utility;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace KINEMATION.MotionWarping.Editor.Core
|
||||
{
|
||||
public class WarpingEditorUtility
|
||||
{
|
||||
public 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);
|
||||
}
|
||||
|
||||
public static WarpingCurve ValidateCurves(MotionWarpingAsset motionWarpingAsset, float sampleRate = 30f,
|
||||
EditorCurveBinding[] tBindings = null)
|
||||
{
|
||||
WarpingCurve warpingCurve = new WarpingCurve();
|
||||
if (tBindings == null) return warpingCurve;
|
||||
|
||||
warpingCurve.X = new AnimationCurve();
|
||||
warpingCurve.Y = new AnimationCurve();
|
||||
warpingCurve.Z = new AnimationCurve();
|
||||
|
||||
float playback = 0f, length = motionWarpingAsset.GetLength();
|
||||
|
||||
Vector3 refVector3 = GetVectorValue(motionWarpingAsset.animation, tBindings, playback);
|
||||
|
||||
Vector3 refT = new Vector3()
|
||||
{
|
||||
x = refVector3.x,
|
||||
y = refVector3.y,
|
||||
z = refVector3.z
|
||||
};
|
||||
|
||||
while (playback <= length)
|
||||
{
|
||||
Vector3 root = GetVectorValue(motionWarpingAsset.animation, tBindings, playback);
|
||||
Vector3 delta = root - refT;
|
||||
|
||||
warpingCurve.X.AddKey(playback, delta.x);
|
||||
warpingCurve.Y.AddKey(playback, delta.y);
|
||||
warpingCurve.Z.AddKey(playback, delta.z);
|
||||
|
||||
playback += 1f / sampleRate;
|
||||
}
|
||||
|
||||
return warpingCurve;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85fcb84c5a6c45f98c4c9202abcd7547
|
||||
timeCreated: 1698770974
|
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "MotionWarping.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:4c36dcf6d8eeb6d44ab73ef966534b46"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [
|
||||
"Android",
|
||||
"EmbeddedLinux",
|
||||
"GameCoreScarlett",
|
||||
"GameCoreXboxOne",
|
||||
"iOS",
|
||||
"LinuxStandalone64",
|
||||
"CloudRendering",
|
||||
"Lumin",
|
||||
"macOSStandalone",
|
||||
"PS4",
|
||||
"PS5",
|
||||
"Stadia",
|
||||
"Switch",
|
||||
"tvOS",
|
||||
"WSA",
|
||||
"WebGL",
|
||||
"WindowsStandalone32",
|
||||
"WindowsStandalone64",
|
||||
"XboxOne"
|
||||
],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ac99bb1de1ddac4fbefd00ae5306a38
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b6288be5f59459d8b3e249b1be7ee92
|
||||
timeCreated: 1705417126
|
@@ -0,0 +1,221 @@
|
||||
// Designed by KINEMATION, 2024.
|
||||
|
||||
using Kinemation.MotionWarping.Runtime.Core;
|
||||
using Kinemation.MotionWarping.Runtime.Utility;
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace KINEMATION.MotionWarping.Editor.Widgets
|
||||
{
|
||||
public class WarpPosePreviewWidget : IWarpWidgetInterface
|
||||
{
|
||||
private GameObject _warpTracerObject;
|
||||
private Vector3 _basePos;
|
||||
private Quaternion _baseRot;
|
||||
|
||||
private GameObject _sceneCharacter;
|
||||
private AnimationClipPlayable _previewMotion;
|
||||
private int _phaseSlider = 0;
|
||||
private bool _preview = false;
|
||||
|
||||
private WarpPoint[] _warpPoints = null;
|
||||
private Dictionary<string, (Vector3, Quaternion)> _cachedTransforms
|
||||
= new Dictionary<string, (Vector3, Quaternion)>();
|
||||
|
||||
private PlayableGraph _playableGraph;
|
||||
private MotionWarpingAsset _motionWarpingAsset;
|
||||
|
||||
public WarpPosePreviewWidget(MotionWarpingAsset asset)
|
||||
{
|
||||
_motionWarpingAsset = asset;
|
||||
}
|
||||
|
||||
private void CacheTransforms()
|
||||
{
|
||||
_cachedTransforms.Clear();
|
||||
Transform[] allChildren = _sceneCharacter.GetComponentsInChildren<Transform>();
|
||||
|
||||
foreach (Transform child in allChildren)
|
||||
{
|
||||
(Vector3, Quaternion) data;
|
||||
data.Item1 = child.localPosition;
|
||||
data.Item2 = child.localRotation;
|
||||
|
||||
_cachedTransforms[child.name] = data;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyCachedTransforms()
|
||||
{
|
||||
if (_sceneCharacter == null) return;
|
||||
|
||||
Transform[] allChildren = _sceneCharacter.GetComponentsInChildren<Transform>();
|
||||
|
||||
foreach (Transform child in allChildren)
|
||||
{
|
||||
if (_cachedTransforms.ContainsKey(child.name))
|
||||
{
|
||||
child.localPosition = _cachedTransforms[child.name].Item1;
|
||||
child.localRotation = _cachedTransforms[child.name].Item2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RestorePose()
|
||||
{
|
||||
if (_playableGraph.IsValid())
|
||||
{
|
||||
_playableGraph.Destroy();
|
||||
}
|
||||
|
||||
if (_previewMotion.IsValid())
|
||||
{
|
||||
_previewMotion.Destroy();
|
||||
}
|
||||
|
||||
if (_preview)
|
||||
{
|
||||
ApplyCachedTransforms();
|
||||
}
|
||||
|
||||
_cachedTransforms.Clear();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
_sceneCharacter = (GameObject) EditorGUILayout.ObjectField("Character", _sceneCharacter,
|
||||
typeof(GameObject), true);
|
||||
|
||||
if (_sceneCharacter == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Missing Character Reference!", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Animator animator = _sceneCharacter.GetComponentInChildren<Animator>();
|
||||
|
||||
if (animator == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No Animator Found!", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_warpTracerObject = (GameObject) EditorGUILayout.ObjectField("Source", _warpTracerObject,
|
||||
typeof(GameObject), true);
|
||||
|
||||
if (_warpTracerObject == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Select Warp Tracer game object.", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
AnimationPlayableOutput playableOutput;
|
||||
|
||||
if (GUILayout.Button("Play"))
|
||||
{
|
||||
CacheTransforms();
|
||||
|
||||
_playableGraph = PlayableGraph.Create("WarpPosePreviewGraph");
|
||||
|
||||
playableOutput = AnimationPlayableOutput.Create(_playableGraph, "Animation",
|
||||
animator);
|
||||
|
||||
_previewMotion = AnimationClipPlayable.Create(_playableGraph, _motionWarpingAsset.animation);
|
||||
playableOutput.SetSourcePlayable(_previewMotion);
|
||||
_preview = true;
|
||||
animator.fireEvents = false;
|
||||
animator.applyRootMotion = false;
|
||||
|
||||
if (_warpTracerObject.GetComponent<IWarpPointProvider>() is var tracer)
|
||||
{
|
||||
var result = tracer.Interact(_sceneCharacter);
|
||||
_warpPoints = result.points;
|
||||
}
|
||||
|
||||
_phaseSlider = -1;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Stop"))
|
||||
{
|
||||
_preview = false;
|
||||
animator.Rebind();
|
||||
animator.fireEvents = true;
|
||||
animator.applyRootMotion = true;
|
||||
_phaseSlider = 0;
|
||||
|
||||
if (_playableGraph.IsValid())
|
||||
{
|
||||
_playableGraph.Stop();
|
||||
_playableGraph.Destroy();
|
||||
}
|
||||
|
||||
if (_previewMotion.IsValid())
|
||||
{
|
||||
_previewMotion.Destroy();
|
||||
}
|
||||
|
||||
ApplyCachedTransforms();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (!_preview) return;
|
||||
|
||||
if (_warpPoints == null || _warpPoints.Length != _motionWarpingAsset.warpPhases.Count)
|
||||
{
|
||||
string msg = "MotionWarping: WarpObject points are null or size does not match!";
|
||||
EditorGUILayout.HelpBox(msg, MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
int max = _motionWarpingAsset.warpPhases.Count - 1;
|
||||
|
||||
int prevSlider = _phaseSlider;
|
||||
_phaseSlider = EditorGUILayout.IntSlider("Phase", _phaseSlider, 0, max);
|
||||
|
||||
if (_previewMotion.IsValid())
|
||||
{
|
||||
float time = _motionWarpingAsset.warpPhases[_phaseSlider].endTime;
|
||||
_previewMotion.SetTime(time);
|
||||
}
|
||||
|
||||
var phase = _motionWarpingAsset.warpPhases[_phaseSlider];
|
||||
|
||||
if (prevSlider != _phaseSlider)
|
||||
{
|
||||
_playableGraph.Evaluate();
|
||||
|
||||
_sceneCharacter.transform.position = _warpPoints[_phaseSlider].GetPosition();
|
||||
_sceneCharacter.transform.rotation = _warpPoints[_phaseSlider].GetRotation();
|
||||
|
||||
_basePos = _sceneCharacter.transform.position;
|
||||
_baseRot = _sceneCharacter.transform.rotation;
|
||||
|
||||
var pos = _sceneCharacter.transform.TransformPoint(phase.tOffset);
|
||||
var rot = _sceneCharacter.transform.rotation * Quaternion.Euler(phase.rOffset);
|
||||
|
||||
_sceneCharacter.transform.position = pos;
|
||||
_sceneCharacter.transform.rotation = rot;
|
||||
}
|
||||
|
||||
var tDelta = _sceneCharacter.transform.position - _basePos;
|
||||
var tLocalDelta = Quaternion.Inverse(_baseRot) * tDelta;
|
||||
|
||||
var rLocalDelta = Quaternion.Inverse(_baseRot) * _sceneCharacter.transform.rotation;
|
||||
|
||||
phase.tOffset = tLocalDelta;
|
||||
phase.rOffset = rLocalDelta.eulerAngles;
|
||||
|
||||
_motionWarpingAsset.warpPhases[_phaseSlider] = phase;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a55d3fce016f48c4bb363262cf69bd3b
|
||||
timeCreated: 1699005765
|
@@ -0,0 +1,9 @@
|
||||
// Designed by KINEMATION, 2024.
|
||||
|
||||
namespace KINEMATION.MotionWarping.Editor.Widgets
|
||||
{
|
||||
public interface IWarpWidgetInterface
|
||||
{
|
||||
public void Render();
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46f96b7c548b4e4199d76c2ee0f20d80
|
||||
timeCreated: 1699005897
|
@@ -0,0 +1,388 @@
|
||||
// Designed by KINEMATION, 2024.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace KINEMATION.MotionWarping.Editor.Widgets
|
||||
{
|
||||
public class WarpWindowWidget : IWarpWidgetInterface
|
||||
{
|
||||
class DraggableArea
|
||||
{
|
||||
private const float MinAreaWidth = 10f;
|
||||
private const float BorderTolerance = 5f;
|
||||
private const float BorderWidth = 2f;
|
||||
|
||||
public Rect Parent;
|
||||
public float LocalStart;
|
||||
public float LocalEnd;
|
||||
|
||||
private Color _color;
|
||||
|
||||
// -2: not hovered, -1: left border, 0: body, 1: right border.
|
||||
public int GetHoveredPart(Vector2 mousePosition)
|
||||
{
|
||||
if (mousePosition.x >= GetRange().Item1 - BorderTolerance
|
||||
&& mousePosition.x <= GetRange().Item1 + BorderTolerance)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mousePosition.x >= GetRange().Item2 - BorderTolerance
|
||||
&& mousePosition.x <= GetRange().Item2 + BorderTolerance)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public DraggableArea(float start, float end, Rect parent)
|
||||
{
|
||||
LocalStart = start;
|
||||
LocalEnd = end;
|
||||
_color = new Color(0f,0.5f,0.5f);
|
||||
this.Parent = parent;
|
||||
}
|
||||
|
||||
public float GetThickness()
|
||||
{
|
||||
float worldStart = Mathf.Lerp(Parent.xMin, Parent.xMax, LocalStart);
|
||||
float worldEnd = Mathf.Lerp(Parent.xMin, Parent.xMax, LocalEnd);
|
||||
|
||||
return worldEnd - worldStart;
|
||||
}
|
||||
|
||||
public Rect GetRect()
|
||||
{
|
||||
Rect rect = Parent;
|
||||
|
||||
rect.x = GetRange().Item1;
|
||||
rect.width = GetThickness();
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public (float, float) GetRange()
|
||||
{
|
||||
float worldStart = Mathf.LerpUnclamped(Parent.xMin, Parent.xMax, LocalStart);
|
||||
float worldEnd = Mathf.LerpUnclamped(Parent.xMin, Parent.xMax, LocalEnd);
|
||||
|
||||
return (worldStart, worldEnd);
|
||||
}
|
||||
|
||||
public float GetLocal(float value)
|
||||
{
|
||||
if (Mathf.Approximately(Parent.xMin, Parent.xMax)) return 0f;
|
||||
|
||||
return (value - Parent.xMin) / (Parent.xMax - Parent.xMin);
|
||||
}
|
||||
|
||||
public void RenderArea(float opacity)
|
||||
{
|
||||
// Draw body.
|
||||
Rect areaRect = Parent;
|
||||
areaRect.x = GetRange().Item1;
|
||||
areaRect.width = GetThickness();
|
||||
|
||||
_color.a = opacity;
|
||||
EditorGUI.DrawRect(areaRect, _color);
|
||||
|
||||
// Draw borders.
|
||||
areaRect.x -= BorderWidth / 2f;
|
||||
areaRect.width = BorderWidth;
|
||||
EditorGUI.DrawRect(areaRect, new Color(1f, 1f, 1f, opacity));
|
||||
|
||||
areaRect.x = GetRange().Item2 - BorderWidth / 2f;
|
||||
EditorGUI.DrawRect(areaRect, new Color(1f, 1f, 1f, opacity));
|
||||
}
|
||||
|
||||
public bool Contains(Vector2 checkPosition)
|
||||
{
|
||||
bool x = checkPosition.x >= GetRange().Item1 && checkPosition.x <= GetRange().Item2;
|
||||
bool y = checkPosition.y >= Parent.yMin && checkPosition.y <= Parent.yMax;
|
||||
|
||||
return x && y;
|
||||
}
|
||||
|
||||
public void Resize(float mouseDelta, int part)
|
||||
{
|
||||
// Cache the values
|
||||
float start = LocalStart;
|
||||
float end = LocalEnd;
|
||||
|
||||
float left = GetRange().Item1;
|
||||
float right = GetRange().Item2;
|
||||
|
||||
if (part == -1)
|
||||
{
|
||||
left -= part * mouseDelta;
|
||||
}
|
||||
|
||||
if (part == 0)
|
||||
{
|
||||
left += mouseDelta;
|
||||
right += mouseDelta;
|
||||
}
|
||||
|
||||
if (part == 1)
|
||||
{
|
||||
right += mouseDelta;
|
||||
}
|
||||
|
||||
LocalStart = GetLocal(left);
|
||||
LocalEnd = GetLocal(right);
|
||||
|
||||
if (GetThickness() - BorderTolerance * 2f < MinAreaWidth)
|
||||
{
|
||||
LocalStart = start;
|
||||
LocalEnd = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void WarpWindowCallback(int modifiedArea);
|
||||
public WarpWindowCallback OnAreaModified;
|
||||
|
||||
private const float TimelineHeight = 20f;
|
||||
|
||||
private const float PlaybackTolerance = 8f;
|
||||
private const float PlaybackWidth = 2f;
|
||||
|
||||
private Rect _timelineRect;
|
||||
private List<DraggableArea> _draggableAreas = new List<DraggableArea>();
|
||||
|
||||
private DraggableArea _activeArea;
|
||||
private int _resizeAction;
|
||||
private bool _mousePressed;
|
||||
|
||||
private float _playbackPosition;
|
||||
private bool _movingPlayback;
|
||||
|
||||
private bool IsAreaColliding(float proposedPosition, int areaIndex)
|
||||
{
|
||||
int areasCount = _draggableAreas.Count;
|
||||
|
||||
if (areasCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int leftIndex = areaIndex - 1;
|
||||
int rightIndex = areaIndex + 1;
|
||||
|
||||
// Check left border
|
||||
var leftBorderCollision = proposedPosition <
|
||||
(leftIndex < 0 ? _timelineRect.xMin : _draggableAreas[leftIndex].GetRange().Item2);
|
||||
|
||||
// Check right border
|
||||
var rightBorderCollision = proposedPosition >
|
||||
(rightIndex > areasCount - 1
|
||||
? _timelineRect.xMax
|
||||
: _draggableAreas[rightIndex].GetRange().Item1);
|
||||
|
||||
return leftBorderCollision || rightBorderCollision;
|
||||
}
|
||||
|
||||
private void UpdateArea(int areaIndex, bool mouseAction)
|
||||
{
|
||||
Vector2 mousePosition = Event.current.mousePosition;
|
||||
Vector2 mouseDelta = Event.current.delta;
|
||||
DraggableArea area = _draggableAreas[areaIndex];
|
||||
|
||||
area.Parent = _timelineRect;
|
||||
|
||||
// Enable editing if the cursor is within the bounds.
|
||||
if (mouseAction && _mousePressed && area.Contains(mousePosition))
|
||||
{
|
||||
_activeArea = area;
|
||||
_resizeAction = area.GetHoveredPart(mousePosition);
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
if (_activeArea != null && mouseAction && !_mousePressed)
|
||||
{
|
||||
_activeArea = null;
|
||||
_resizeAction = -2;
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
if (_activeArea == null || area != _activeArea)
|
||||
{
|
||||
area.RenderArea(1f);
|
||||
return;
|
||||
}
|
||||
|
||||
RenderCursorIcon(area, _resizeAction);
|
||||
|
||||
if (Event.current.type == EventType.MouseDrag)
|
||||
{
|
||||
// Cache the area current size
|
||||
float areaStart = area.LocalStart;
|
||||
float areaEnd = area.LocalEnd;
|
||||
|
||||
// Resize the area
|
||||
area.Resize(mouseDelta.x, _resizeAction);
|
||||
|
||||
float start = area.GetRange().Item1;
|
||||
float end = area.GetRange().Item2;
|
||||
|
||||
bool collideLeft = IsAreaColliding(start, areaIndex);
|
||||
bool collideRight = IsAreaColliding(end, areaIndex);
|
||||
|
||||
// Check for any collisions
|
||||
if (collideLeft || collideRight)
|
||||
{
|
||||
area.LocalStart = areaStart;
|
||||
area.LocalEnd = areaEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
OnAreaModified?.Invoke(areaIndex);
|
||||
}
|
||||
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
area.RenderArea(0.7f);
|
||||
}
|
||||
|
||||
private void RenderCursorIcon(DraggableArea area, int hoveredPart)
|
||||
{
|
||||
if (hoveredPart == 0)
|
||||
{
|
||||
EditorGUIUtility.AddCursorRect(area.GetRect(), MouseCursor.Pan);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUIUtility.AddCursorRect(area.GetRect(), MouseCursor.SlideArrow);
|
||||
}
|
||||
|
||||
private void UpdateDraggableAreas()
|
||||
{
|
||||
bool prevMousePressed = _mousePressed;
|
||||
|
||||
if (Event.current.type == EventType.MouseDown)
|
||||
{
|
||||
_mousePressed = true;
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.MouseUp)
|
||||
{
|
||||
_mousePressed = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _draggableAreas.Count; i++)
|
||||
{
|
||||
UpdateArea(i, prevMousePressed != _mousePressed);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearPhases()
|
||||
{
|
||||
_draggableAreas.Clear();
|
||||
}
|
||||
|
||||
public void AddWarpPhase(float start, float end)
|
||||
{
|
||||
DraggableArea newArea = new DraggableArea(start, end, _timelineRect);
|
||||
_draggableAreas.Add(newArea);
|
||||
}
|
||||
|
||||
public (float, float) GetAreaSize(int areaIndex)
|
||||
{
|
||||
(float, float) size = (0f, 0f);
|
||||
|
||||
if (_draggableAreas.Count == 0 || areaIndex < 0 || areaIndex > _draggableAreas.Count - 1) return size;
|
||||
|
||||
var area = _draggableAreas[areaIndex];
|
||||
|
||||
size.Item1 = area.LocalStart;
|
||||
size.Item2 = area.LocalEnd;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private void RenderPlayback(bool bAction = false)
|
||||
{
|
||||
float worldPlayback = Mathf.Lerp(_timelineRect.xMin, _timelineRect.xMax, _playbackPosition);
|
||||
var playbackRect = _timelineRect;
|
||||
playbackRect.y -= TimelineHeight;
|
||||
|
||||
Vector2 mousePosition = Event.current.mousePosition;
|
||||
|
||||
if (bAction && _mousePressed && playbackRect.Contains(mousePosition))
|
||||
{
|
||||
if (mousePosition.x >= worldPlayback - PlaybackTolerance
|
||||
&& mousePosition.x <= worldPlayback + PlaybackTolerance)
|
||||
{
|
||||
_movingPlayback = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bAction && !_mousePressed)
|
||||
{
|
||||
_movingPlayback = false;
|
||||
}
|
||||
|
||||
if (_movingPlayback)
|
||||
{
|
||||
playbackRect.height *= 2f;
|
||||
EditorGUIUtility.AddCursorRect(playbackRect, MouseCursor.SlideArrow);
|
||||
playbackRect.height /= 2f;
|
||||
if (Event.current.type == EventType.MouseDrag)
|
||||
{
|
||||
worldPlayback += Event.current.delta.x;
|
||||
_playbackPosition = Mathf.InverseLerp(_timelineRect.xMin, _timelineRect.xMax, worldPlayback);
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
if(_activeArea != null && Event.current.shift)
|
||||
{
|
||||
if (_resizeAction is 0 or -1)
|
||||
{
|
||||
_playbackPosition = _activeArea.LocalStart;
|
||||
}
|
||||
|
||||
if (_resizeAction == 1)
|
||||
{
|
||||
_playbackPosition = _activeArea.LocalEnd;
|
||||
}
|
||||
}
|
||||
|
||||
playbackRect.x = Mathf.Lerp(_timelineRect.xMin, _timelineRect.xMax, _playbackPosition);
|
||||
playbackRect.x -= PlaybackWidth / 2f;
|
||||
playbackRect.width = PlaybackWidth;
|
||||
playbackRect.height = _timelineRect.yMax - playbackRect.y;
|
||||
|
||||
EditorGUI.DrawRect(playbackRect, Color.red);
|
||||
}
|
||||
|
||||
public float GetPlayback()
|
||||
{
|
||||
return _playbackPosition;
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
float width = EditorGUIUtility.currentViewWidth;
|
||||
|
||||
EditorGUI.DrawRect(GUILayoutUtility.GetRect(width, TimelineHeight), new Color(0.15f, 0.15f, 0.15f));
|
||||
|
||||
var cacheRect = _timelineRect;
|
||||
_timelineRect = GUILayoutUtility.GetRect(width, TimelineHeight);
|
||||
|
||||
if (Mathf.Approximately(_timelineRect.width, 1f))
|
||||
{
|
||||
_timelineRect = cacheRect;
|
||||
}
|
||||
|
||||
EditorGUI.DrawRect(_timelineRect, new Color(0.15f, 0.15f, 0.15f));
|
||||
bool action = _mousePressed;
|
||||
UpdateDraggableAreas();
|
||||
RenderPlayback(action != _mousePressed);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc18a424cded448f88f5c3f619a1ffe6
|
||||
timeCreated: 1698053326
|
Reference in New Issue
Block a user