Before 优化 机场

This commit is contained in:
CortexCore
2025-03-10 18:06:44 +08:00
parent 350e6d67b2
commit 1f4e20f512
178 changed files with 17534 additions and 821 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2476cab887f4452997180c674b7b43cd
timeCreated: 1688991425

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f6d9117845df4a049f092765c3987cfd
timeCreated: 1705478497

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d0d2c068e5aa4321893ea76aa6528f17
timeCreated: 1698238712

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: db7fde80939c4b2e8ae89d39d25ecb82
timeCreated: 1696662790

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 85fcb84c5a6c45f98c4c9202abcd7547
timeCreated: 1698770974

View File

@@ -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
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6ac99bb1de1ddac4fbefd00ae5306a38
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6b6288be5f59459d8b3e249b1be7ee92
timeCreated: 1705417126

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a55d3fce016f48c4bb363262cf69bd3b
timeCreated: 1699005765

View File

@@ -0,0 +1,9 @@
// Designed by KINEMATION, 2024.
namespace KINEMATION.MotionWarping.Editor.Widgets
{
public interface IWarpWidgetInterface
{
public void Render();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 46f96b7c548b4e4199d76c2ee0f20d80
timeCreated: 1699005897

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fc18a424cded448f88f5c3f619a1ffe6
timeCreated: 1698053326

View File

@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://github.com/kinemation/motion-warping

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9b1840bd7279c834dbf54d353cb90aea
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0ec45b3c964658a4d89fae90225ba634
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://kinemation.gitbook.io/motion-warping-for-unity

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7dbcfd4331e397f4d936c904252f120a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8bcffe28623a91242a8a0c9aafa18e51
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1c7d9229e5994163a5595aaa1d7ee59c
timeCreated: 1695198318

View File

@@ -0,0 +1,427 @@
// Designed by KINEMATION, 2024.
using KINEMATION.MotionWarping.Runtime.Core;
using Kinemation.MotionWarping.Runtime.Utility;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Debug = UnityEngine.Debug;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;
namespace Kinemation.MotionWarping.Runtime.Core
{
public class MotionWarping : MonoBehaviour
{
[Header("Warping")]
[SerializeField] private bool scalePlayRate = true;
[Header("Animator")]
[SerializeField, Tooltip("Will try to play the animation.")] private bool playAnimator;
[SerializeField, Range(0f, 1f)] private float blendTime;
[Header("Events")]
public UnityEvent onWarpStarted;
public UnityEvent onWarpEnded;
[SerializeField]
private Animator animator;
public WarpPhase[] warpPhases;
private Vector3 _endCurveValue;
private Vector3 _startCurveValue;
private int _phaseIndex;
private MotionWarpingAsset _asset;
private WarpPhase _warpPhase;
private float _nextPhaseTime;
private Vector3 _originPosition;
private Quaternion _originRotation;
private bool _bUpdateWarping;
private float _warpPlayback;
private float _rateScale = 1f;
private float _warpLength;
private Vector3 _accumRootMotion;
private Vector3 _rootMotion;
private bool _hasActivePhase;
private static readonly int WarpRate = Animator.StringToHash("WarpRate");
private MotionWarpingIk _motionWarpingIk;
private void Start()
{
animator = GetComponentInChildren<Animator>();
_motionWarpingIk = GetComponent<MotionWarpingIk>();
}
private void OnDestroy()
{
onWarpEnded = onWarpStarted = null;
}
private float InvLerp(float startValue, float targetValue, float curveValue)
{
if (Mathf.Approximately(startValue, targetValue)) return 0f;
float numerator = curveValue - startValue;
float denominator = targetValue - startValue;
return Mathf.Approximately(denominator, 0f) ? 0f : numerator / denominator;
}
private float SafeDivide(float a, float b)
{
if (Mathf.Approximately(b, 0f)) return 0f;
return a / b;
}
private float GetNormalizedPlayback()
{
return _warpPlayback / _warpLength;
}
private float GetPhaseProgress()
{
float alpha = InvLerp(_warpPhase.startTime, _warpPhase.endTime, _warpPlayback);
return Mathf.Clamp01(alpha);
}
private void EnterNewPhase()
{
_originPosition = transform.position;
_originRotation = transform.rotation;
_accumRootMotion = _rootMotion = Vector3.zero;
_warpPhase = warpPhases[_phaseIndex];
_nextPhaseTime = _phaseIndex == warpPhases.Length - 1 ? _warpLength : warpPhases[_phaseIndex + 1].startTime;
_hasActivePhase = true;
_startCurveValue = _asset.GetVectorValue(_warpPhase.startTime);
_endCurveValue = _asset.GetVectorValue(_warpPhase.endTime);
_phaseIndex++;
if (scalePlayRate && animator != null)
{
float curveVec = (_endCurveValue - _startCurveValue).magnitude;
float realVec = (_warpPhase.Target.GetPosition() - _originPosition).magnitude;
realVec = Mathf.Max(0.001f, realVec);
_rateScale = Mathf.Clamp(curveVec / realVec, _warpPhase.minRate, _warpPhase.maxRate);
_rateScale *= _asset.playRateBasis;
animator.SetFloat(WarpRate, _rateScale);
}
else
{
_rateScale = 1f;
}
if (_warpPhase.Target.transform != null)
{
_originPosition = _warpPhase.Target.transform.InverseTransformPoint(transform.position);
_originRotation = Quaternion.Inverse(_warpPhase.Target.transform.rotation) * transform.rotation;
}
}
private void ExitCurrentPhase()
{
_hasActivePhase = false;
if (_warpPhase.Target.transform == null)
{
_originPosition = transform.position;
_originRotation = transform.rotation;
}
else
{
_originPosition = _warpPhase.Target.transform.InverseTransformPoint(transform.position);
_originRotation = Quaternion.Inverse(_warpPhase.Target.transform.rotation) * transform.rotation;
}
_startCurveValue = _endCurveValue = _asset.GetVectorValue(_warpPlayback);
}
private Quaternion WarpRotation()
{
float alpha = _hasActivePhase ? GetPhaseProgress() : 0f;
return Quaternion.Slerp(transform.rotation, _warpPhase.Target.GetRotation(), alpha);
}
private Vector3 WarpTranslation()
{
// 1. Compute the original additive curve value
Vector3 prevRootMotion = _rootMotion;
_rootMotion = _asset.GetVectorValue(_warpPlayback) - _startCurveValue;
if (!_hasActivePhase)
{
// 2. If not in the segment - play the animation itself.
Vector3 modifiedRootMotion = _rootMotion;
modifiedRootMotion.x = _asset.useAnimation.x ? modifiedRootMotion.x : 0f;
modifiedRootMotion.y = _asset.useAnimation.y ? modifiedRootMotion.y : 0f;
modifiedRootMotion.z = _asset.useAnimation.z ? modifiedRootMotion.z : 0f;
return modifiedRootMotion;
}
// 3. Compute the target in the origin space
Vector3 localTarget = transform.InverseTransformPoint(_warpPhase.Target.GetPosition());
// 4. Compute the deltas.
Vector3 animationTarget = _endCurveValue - _startCurveValue;
animationTarget.x = _asset.useAnimation.x ? animationTarget.x : 0f;
animationTarget.y = _asset.useAnimation.y ? animationTarget.y : 0f;
animationTarget.z = _asset.useAnimation.z ? animationTarget.z : 0f;
Vector3 targetDelta = localTarget - animationTarget;
Vector3 rootMotionDelta = _rootMotion - prevRootMotion;
_accumRootMotion.x += Mathf.Abs(rootMotionDelta.x);
_accumRootMotion.y += Mathf.Abs(rootMotionDelta.y);
_accumRootMotion.z += Mathf.Abs(rootMotionDelta.z);
// 5. Finally warp the motion.
targetDelta.x *= _asset.useLinear.x
? GetPhaseProgress()
: Mathf.Clamp01(SafeDivide(_accumRootMotion.x, _warpPhase.totalRootMotion.x));
targetDelta.y *= _asset.useLinear.y
? GetPhaseProgress()
: Mathf.Clamp01(SafeDivide(_accumRootMotion.y, _warpPhase.totalRootMotion.y));
targetDelta.z *= _asset.useLinear.z
? GetPhaseProgress()
: Mathf.Clamp01(SafeDivide(_accumRootMotion.z, _warpPhase.totalRootMotion.z));
Vector3 rootAnimation = Vector3.zero;
rootAnimation.x = _asset.useAnimation.x ? _rootMotion.x : 0f;
rootAnimation.y = _asset.useAnimation.y ? _rootMotion.y : 0f;
rootAnimation.z = _asset.useAnimation.z ? _rootMotion.z : 0f;
return rootAnimation + targetDelta;
}
private void WarpAnimation()
{
Vector3 cachedPosition = transform.position;
if (_warpPhase.Target.transform == null)
{
transform.position = _originPosition;
transform.rotation = _originRotation;
}
else
{
transform.position = _warpPhase.Target.transform.TransformPoint(_originPosition);
transform.rotation = _warpPhase.Target.transform.rotation * _originRotation;
}
Vector3 warpedTranslation = WarpTranslation();
Quaternion warpedRotation = WarpRotation();
warpedTranslation = transform.TransformPoint(warpedTranslation);
if (_asset.useCollision)
{
}
else
{
transform.position = warpedTranslation;
}
transform.rotation = warpedRotation;
}
private void UpdateWarping()
{
if (_warpPlayback > _warpPhase.endTime && _hasActivePhase)
{
ExitCurrentPhase();
}
if (!_hasActivePhase && _warpPlayback > _nextPhaseTime)
{
EnterNewPhase();
}
WarpAnimation();
// Update playback
_warpPlayback += Time.deltaTime * _rateScale;
_warpPlayback = Mathf.Clamp(_warpPlayback, 0f, _warpLength);
if (Mathf.Approximately(GetNormalizedPlayback(), 1f))
{
Stop();
}
}
private void LateUpdate()
{
if (!_bUpdateWarping) return;
UpdateWarping();
if (_motionWarpingIk == null) return;
_motionWarpingIk.ApplyIK();
}
private void Play_Internal(MotionWarpingAsset motionWarpingAsset)
{
if (playAnimator && animator != null)
{
animator.CrossFade(motionWarpingAsset.animation.name, blendTime);
}
_startCurveValue = _endCurveValue = _accumRootMotion = _rootMotion = Vector3.zero;
_warpPhase.Target.transform = null;
_originPosition = transform.position;
_originRotation = transform.rotation;
_asset = motionWarpingAsset;
_phaseIndex = 0;
_nextPhaseTime = warpPhases[0].startTime;
_bUpdateWarping = true;
_hasActivePhase = false;
_rateScale = 1f;
_warpLength = motionWarpingAsset.GetLength();
onWarpStarted.Invoke();
}
public bool Interact(GameObject target)
{
if (target == null)
{
return false;
}
return Interact(target.GetComponent<IWarpPointProvider>());
}
public bool Interact(IWarpPointProvider target)
{
if (target == null)
{
return false;
}
var result = target.Interact(gameObject);
if (!result.IsValid())
{
return false;
}
Play(result.asset, result.points);
return true;
}
public void Play(MotionWarpingAsset motionWarpingAsset, WarpPoint[] warpPoints)
{
if (motionWarpingAsset == null)
{
Debug.LogError("MotionWarping: WarpPoint[] warpPoints is null!");
return;
}
if (warpPoints == null)
{
Debug.LogError("MotionWarping: Warp Points array is null!");
return;
}
warpPhases = motionWarpingAsset.warpPhases.ToArray();
if (warpPhases.Length != warpPoints.Length)
{
Debug.LogError("MotionWarping: Warp Phases and Warp Points array do not match!");
return;
}
for (int i = 0; i < warpPhases.Length; i++)
{
WarpPhase phase = warpPhases[i];
WarpPoint target = warpPoints[i];
if (target.transform == null)
{
phase.Target.position = WarpingUtility.ToWorld(target.position, target.rotation,
phase.tOffset);
phase.Target.rotation = target.rotation * Quaternion.Euler(phase.rOffset);
}
else
{
phase.Target.transform = target.transform;
phase.Target.position = target.position;
phase.Target.rotation = target.rotation;
phase.Target.localPosition = phase.tOffset;
phase.Target.localRotation = phase.rOffset;
}
warpPhases[i] = phase;
}
Play_Internal(motionWarpingAsset);
}
public void Stop()
{
_bUpdateWarping = false;
_warpPlayback = 0f;
onWarpEnded.Invoke();
}
public bool IsActive()
{
return _bUpdateWarping;
}
#if UNITY_EDITOR
private List<WarpDebugData> _warpDebugData = new List<WarpDebugData>();
public static void AddWarpDebugData(MotionWarping target, WarpDebugData warpDebugData)
{
if (target == null) return;
target._warpDebugData.Add(warpDebugData);
}
private void OnDrawGizmos()
{
for (int i = 0; i < _warpDebugData.Count; i++)
{
var debugData = _warpDebugData[i];
debugData.onDrawGizmos?.Invoke();
if(debugData.duration < 0f) continue;
// Progress the timer.
debugData.timer = Mathf.Clamp(debugData.timer + Time.deltaTime, 0f, debugData.duration);
_warpDebugData[i] = debugData;
if (Mathf.Approximately(debugData.timer, debugData.duration))
{
_warpDebugData.RemoveAt(i);
i--;
}
}
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a3164a26014538e45b7dbfa103dabef0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 100
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
// Designed by KINEMATION, 2024.
using Kinemation.MotionWarping.Runtime.Utility;
using System.Collections.Generic;
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Core
{
[CreateAssetMenu(menuName = "KINEMATION/MotionWarping/WarpingAsset", fileName = "NewWarpingAsset", order = 0)]
public class MotionWarpingAsset : ScriptableObject
{
[Header("Animation")]
public AnimationClip animation;
public AnimationCurve rootX;
public AnimationCurve rootY;
public AnimationCurve rootZ;
public VectorBool useLinear = VectorBool.Enabled;
public VectorBool useAnimation = VectorBool.Enabled;
public VectorBool useWarping = VectorBool.Enabled;
[Tooltip("If true, CharacterController or a RigidBody will be moved with physics enabled.")]
public bool useCollision = false;
[Range(0f, 2f)] public float playRateBasis = 1f;
[Range(1, 10)] public int phasesAmount = 1;
public List<WarpPhase> warpPhases = new List<WarpPhase>();
public Vector3 GetVectorValue(float time)
{
if (rootX == null || rootY == null || rootZ == null)
{
Debug.LogError(name + ": null reference curve!");
return Vector3.zero;
}
return new Vector3(rootX.Evaluate(time), rootY.Evaluate(time), rootZ.Evaluate(time));
}
public float GetLength()
{
if (animation == null)
{
return 0f;
}
return animation.length;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 928e2ecfbd1ec0f469e1838b49a4dda9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,96 @@
// Designed by KINEMATION, 2024.
using System;
using UnityEngine;
namespace KINEMATION.MotionWarping.Runtime.Core
{
[Serializable]
public struct LockState
{
public string controlCurveName;
public Transform boneReference;
[Min(0f)] public float interpSpeed;
[NonSerialized] public float Weight;
[NonSerialized] public Quaternion LockRotation;
[NonSerialized] public Vector3 LockPosition;
[NonSerialized] public bool IsLocked;
[NonSerialized] public bool Interpolate;
}
public class MotionWarpingIk : MonoBehaviour
{
[SerializeField] [Range(0f, 1f)] private float componentWeight = 1f;
[SerializeField] private LockState rightHandLockState;
[SerializeField] private LockState leftHandLockState;
[SerializeField] private LockState rightFootLockState;
[SerializeField] private LockState leftFootLockState;
private Animator _animator;
private void Start()
{
_animator = GetComponent<Animator>();
}
// Updated the current bone lock state.
private void UpdateBoneLock(ref LockState lockState)
{
Transform boneReference = lockState.boneReference;
if (boneReference == null)
{
return;
}
float weight = _animator.GetFloat(lockState.controlCurveName);
if (!lockState.IsLocked && Mathf.Approximately(weight, 1f))
{
lockState.LockPosition = boneReference.position;
lockState.LockRotation = boneReference.rotation;
lockState.IsLocked = true;
lockState.Interpolate = false;
lockState.Weight = 1f;
return;
}
if (lockState.IsLocked && Mathf.Approximately(weight, 0f))
{
lockState.IsLocked = false;
lockState.Interpolate = true;
}
if (!lockState.Interpolate)
{
return;
}
float alpha = 1f - Mathf.Exp(-lockState.interpSpeed * Time.deltaTime);
lockState.Weight = Mathf.Lerp(lockState.Weight, 0f, alpha);
}
private void ApplyLock(ref LockState lockState)
{
Transform tip = lockState.boneReference;
if (tip == null)
{
return;
}
Transform mid = tip.parent;
TwoBoneIk.SolveTwoBoneIK(mid.parent, mid, tip, (lockState.LockPosition, lockState.LockRotation),
mid, lockState.Weight * componentWeight, 1f);
UpdateBoneLock(ref lockState);
}
public void ApplyIK()
{
ApplyLock(ref rightHandLockState);
ApplyLock(ref leftHandLockState);
ApplyLock(ref rightFootLockState);
ApplyLock(ref leftFootLockState);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f2c56b0df3354ea4a3ab76ef0923d868
timeCreated: 1708838078

View File

@@ -0,0 +1,142 @@
// Designed by KINEMATION, 2024.
using UnityEngine;
namespace KINEMATION.MotionWarping.Runtime.Core
{
public class TwoBoneIk
{
private const float FloatMin = 1e-10f;
private const float SqrEpsilon = 1e-8f;
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 void SolveTwoBoneIK(
Transform root,
Transform mid,
Transform tip,
(Vector3, Quaternion) target,
Transform hint,
float targetWeight,
float hintWeight
)
{
if (Mathf.Approximately(targetWeight, 0f))
{
return;
}
Vector3 aPosition = root.position;
Vector3 bPosition = mid.position;
Vector3 cPosition = tip.position;
Vector3 tPosition = Vector3.Lerp(cPosition, target.Item1, targetWeight);
Quaternion tRotation = Quaternion.Lerp(tip.rotation, target.Item2, targetWeight);
bool hasHint = hint != null && 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 = TriangleAngle(acLen, abLen, bcLen);
float newAbcAngle = 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 < SqrEpsilon)
{
axis = hasHint ? Vector3.Cross(hint.position - aPosition, bc) : Vector3.zero;
if (axis.sqrMagnitude < SqrEpsilon)
axis = Vector3.Cross(at, bc);
if (axis.sqrMagnitude < 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);
mid.rotation = deltaR * mid.rotation;
cPosition = tip.position;
ac = cPosition - aPosition;
root.rotation = FromToRotation(ac, at) * root.rotation;
if (hasHint)
{
float acSqrMag = ac.sqrMagnitude;
if (acSqrMag > 0f)
{
bPosition = mid.position;
cPosition = tip.position;
ab = bPosition - aPosition;
ac = cPosition - aPosition;
Vector3 acNorm = ac / Mathf.Sqrt(acSqrMag);
Vector3 ah = 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 = FromToRotation(abProj, ahProj);
hintR.x *= hintWeight;
hintR.y *= hintWeight;
hintR.z *= hintWeight;
hintR = NormalizeSafe(hintR);
root.rotation = hintR * root.rotation;
}
}
}
tip.rotation = tRotation;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9637a7f6138e4e12a4db8b42a5c0738a
timeCreated: 1708838183

View File

@@ -0,0 +1,62 @@
// Designed by KINEMATION, 2024.
using System.Collections.Generic;
using Kinemation.MotionWarping.Runtime.Utility;
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Core
{
// Obstacle with pre-defined Warp Points
public class WarpObstacle : MonoBehaviour, IWarpPointProvider
{
[SerializeField] private MotionWarpingAsset motionWarpingAsset;
[SerializeField] private List<Transform> points = new List<Transform>();
[SerializeField] private bool useTransforms = false;
[SerializeField] private bool drawDebugPoints = false;
private void OnDrawGizmos()
{
if (!drawDebugPoints) return;
var color = Gizmos.color;
Gizmos.color = Color.green;
foreach (var point in points)
{
if(point == null) continue;
Gizmos.DrawWireSphere(point.position, 0.07f);
}
Gizmos.color = color;
}
public WarpInteractionResult Interact(GameObject instigator)
{
WarpInteractionResult result;
result.points = new WarpPoint[points.Count];
result.asset = motionWarpingAsset;
result.success = points.Count > 0;
for (int i = 0; i < result.points.Length; i++)
{
if (useTransforms)
{
result.points[i] = new WarpPoint()
{
transform = points[i]
};
}
else
{
result.points[i] = new WarpPoint()
{
position = points[i].position,
rotation = points[i].rotation
};
}
}
return result;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ceff3fd151a844759bf3cc1490295ef7
timeCreated: 1694707273

View File

@@ -0,0 +1,24 @@
// Designed by KINEMATION, 2024.
using Kinemation.MotionWarping.Runtime.Utility;
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Core
{
public struct WarpInteractionResult
{
public WarpPoint[] points;
public MotionWarpingAsset asset;
public bool success;
public bool IsValid()
{
return success && points != null && asset != null;
}
}
public interface IWarpPointProvider
{
public WarpInteractionResult Interact(GameObject instigator);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: af4b4f93ff874248959946ba0a83a8ea
timeCreated: 1699699303

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5f330a55deff4a9e9e9ff0379a74eb82
timeCreated: 1695198325

View File

@@ -0,0 +1,94 @@
// Designed by KINEMATION, 2024.
using Kinemation.MotionWarping.Runtime.Core;
using Kinemation.MotionWarping.Runtime.Utility;
using UnityEngine;
using Quaternion = UnityEngine.Quaternion;
using Vector3 = UnityEngine.Vector3;
namespace Kinemation.MotionWarping.Runtime.Examples
{
public class AlignComponent : MonoBehaviour, IWarpPointProvider
{
[SerializeField] [Range(0f, 180f)] private float interactionAngle = 0f;
[SerializeField] [Range(-180f, 180f)] private float offsetAngle = 0f;
[SerializeField] [Min(0f)] private float distance = 0f;
[SerializeField] private MotionWarpingAsset motionWarpingAsset;
[SerializeField] private string targetAnimName;
private Animator _animator;
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Vector3 position = transform.position;
Vector3 forward = Quaternion.Euler(0f, offsetAngle, 0f) * transform.forward;
float debugRadius = 0.05f;
Vector3 left = Quaternion.Euler(0f, interactionAngle, 0f) * forward;
Vector3 right = Quaternion.Euler(0f, -interactionAngle, 0f) * forward;
Gizmos.DrawLine(position, position + left * distance);
Gizmos.DrawLine(position, position + right * distance);
Gizmos.DrawWireSphere(position, debugRadius);
Gizmos.DrawWireSphere(position + left * distance, debugRadius);
Gizmos.DrawWireSphere(position + right * distance, debugRadius);
}
private void Start()
{
_animator = GetComponent<Animator>();
}
public WarpInteractionResult Interact(GameObject instigator)
{
WarpInteractionResult result = new WarpInteractionResult()
{
success = false,
points = null,
asset = null,
};
if (instigator == null || motionWarpingAsset == null)
{
return result;
}
if ((instigator.transform.position - transform.position).magnitude > distance)
{
return result;
}
Vector3 forward = Quaternion.Euler(0f, offsetAngle, 0f) * transform.forward;
float angle = Mathf.Acos(Vector3.Dot(-instigator.transform.forward, forward)) * Mathf.Rad2Deg;
if (angle > interactionAngle)
{
return result;
}
result.asset = motionWarpingAsset;
result.points = new[]
{
new WarpPoint()
{
transform = this.transform,
position = Vector3.zero,
rotation = Quaternion.identity
}
};
result.success = true;
if (_animator != null)
{
_animator.CrossFade(targetAnimName, 0.15f);
}
return result;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cce7418bf5c1f1f4b85e97a5470f0bd5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,137 @@
// Designed by KINEMATION, 2024.
using Kinemation.MotionWarping.Runtime.Core;
using Kinemation.MotionWarping.Runtime.Utility;
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Examples
{
public class HangComponent : MonoBehaviour, IWarpPointProvider
{
[SerializeField] private MotionWarpingAsset hangAsset;
[SerializeField] private MotionWarpingAsset jumpDownAsset;
[Header("Trace Settings")]
[SerializeField] [Min(0f)] private float capsuleRadius;
[SerializeField] [Min(0f)] private float sphereCheckRadius;
[SerializeField] [Min(0f)] private float minLedgeLength;
[SerializeField] [Min(0f)] private LayerMask layerMask;
[Header("Hanging")]
[SerializeField] [Min(0f)] private float maxAllowedDistance;
[SerializeField] [Min(0f)] private float maxAllowedHeight;
[SerializeField] [Min(0f)] private float minAllowedHeight;
private bool _isHanging;
private WarpInteractionResult TryToJumpDown()
{
WarpInteractionResult result = new WarpInteractionResult()
{
success = false,
points = null,
asset = null,
};
Vector3 start = transform.position;
bool bHit = Physics.SphereCast(start, sphereCheckRadius, -transform.up, out var hit,
maxAllowedHeight, layerMask);
if (!bHit)
{
return result;
}
Quaternion resultRotation = transform.rotation;
Vector3 resultPosition = hit.point;
result.points = new[]
{
new WarpPoint()
{
position = resultPosition,
rotation = resultRotation
}
};
result.asset = jumpDownAsset;
result.success = true;
_isHanging = false;
return result;
}
private WarpInteractionResult TryToHang()
{
WarpInteractionResult result = new WarpInteractionResult()
{
success = false,
points = null,
asset = null,
};
Vector3 start = transform.position;
Vector3 end = start;
start.y += minAllowedHeight;
end.y += minAllowedHeight + maxAllowedHeight;
Vector3 direction = transform.forward;
float distance = maxAllowedDistance;
bool bHit = Physics.CapsuleCast(start, end, capsuleRadius, direction,
out var hit, distance, layerMask);
if (!bHit)
{
return result;
}
Quaternion targetRotation = Quaternion.LookRotation(-hit.normal, transform.up);
distance = (end - start).magnitude;
start = hit.point;
start.y = end.y;
bHit = Physics.SphereCast(start, sphereCheckRadius, -transform.up, out hit, distance,
layerMask);
if (!bHit)
{
return result;
}
start = hit.point;
start.y += sphereCheckRadius + 0.01f;
Vector3 targetPosition = hit.point;
bHit = Physics.SphereCast(start, sphereCheckRadius, targetRotation * Vector3.forward, out hit,
minLedgeLength, layerMask);
if (bHit)
{
return result;
}
result.success = true;
result.asset = hangAsset;
result.points = new[]
{
new WarpPoint()
{
position = targetPosition,
rotation = targetRotation
}
};
_isHanging = true;
return result;
}
public WarpInteractionResult Interact(GameObject instigator)
{
return _isHanging ? TryToJumpDown() : TryToHang();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 18c8d51c4bfa48c69c050aa993b9b441
timeCreated: 1705657565

View File

@@ -0,0 +1,131 @@
// Designed by KINEMATION, 2024.
using Kinemation.MotionWarping.Runtime.Core;
using Kinemation.MotionWarping.Runtime.Utility;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Kinemation.MotionWarping.Runtime.Examples
{
public class MantleComponent : MonoBehaviour, IWarpPointProvider
{
[SerializeField] private MotionWarpingAsset mantleHigh;
[SerializeField] private MotionWarpingAsset mantleLow;
[SerializeField] private MantleSettings settings;
private Vector3 _targetPosition;
private Quaternion _targetRotation;
public WarpInteractionResult Interact(GameObject instigator)
{
WarpInteractionResult result = new WarpInteractionResult()
{
points = null,
asset = null,
success = false
};
if (settings == null)
{
return result;
}
var motionWarping = instigator.GetComponent<Core.MotionWarping>();
Vector3 start = transform.position;
Vector3 end = start;
start.y += settings.minHeight + settings.characterCapsuleRadius;
end.y += settings.maxHeight;
Vector3 direction = transform.forward;
float distance = settings.maxDistance;
bool bHit = Physics.CapsuleCast(start, end, settings.characterCapsuleRadius, direction,
out var hit, distance, settings.layerMask);
if (!bHit)
{
return result;
}
_targetRotation = Quaternion.LookRotation(-hit.normal, transform.up);
distance = (end - start).magnitude;
start = hit.point;
start += (_targetRotation * Vector3.forward) * settings.forwardOffset;
start.y = end.y;
bHit = Physics.SphereCast(start, settings.sphereEdgeCheckRadius, -transform.up, out hit,
distance, settings.layerMask);
start = hit.point;
if (!bHit)
{
return result;
}
Vector3 surfaceNormal = hit.normal;
start += surfaceNormal * (0.02f + settings.characterCapsuleRadius);
end = start + surfaceNormal * settings.characterCapsuleHeight;
bHit = Physics.CheckCapsule(start, end, settings.characterCapsuleRadius, settings.layerMask);
if (bHit)
{
return result;
}
float surfaceIncline = Mathf.Clamp(Vector3.Dot(transform.up, surfaceNormal), -1f, 1f);
surfaceIncline = Mathf.Acos(surfaceIncline) * Mathf.Rad2Deg;
if (surfaceIncline > settings.maxSurfaceInclineAngle)
{
return result;
}
Vector3 forwardVector = _targetRotation * Vector3.forward;
_targetRotation = Quaternion.LookRotation(forwardVector);
_targetPosition = hit.point;
result.points = new[]
{
new WarpPoint()
{
transform = hit.transform,
position = hit.transform.InverseTransformPoint(_targetPosition),
rotation = Quaternion.Inverse(hit.transform.rotation) * _targetRotation
}
};
float height = _targetPosition.y - transform.position.y;
result.asset = height > settings.lowHeight ? mantleHigh : mantleLow;
result.success = true;
#if UNITY_EDITOR
Core.MotionWarping.AddWarpDebugData(motionWarping, new WarpDebugData()
{
duration = 5f,
onDrawGizmos = () =>
{
var color = Gizmos.color;
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(_targetPosition, 0.1f);
Handles.Label(_targetPosition, "Mantle Target Point");
Gizmos.color = color;
}
});
#endif
return result;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b86a5418587f4d5c9107dabe449506cb
timeCreated: 1695198402

View File

@@ -0,0 +1,25 @@
// Designed by KINEMATION, 2024
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Examples
{
[CreateAssetMenu(menuName = "KINEMATION/MotionWarping/MantleSettings", fileName = "NewMantleSettings", order = 2)]
public class MantleSettings : ScriptableObject
{
public LayerMask layerMask;
[Min(0f)] public float maxHeight;
[Min(0f)] public float lowHeight;
[Min(0f)] public float minHeight;
[Min(0f)] public float maxDistance;
[Min(0f)] public float characterCapsuleRadius;
[Min(0f)] public float characterCapsuleHeight;
[Min(0f)] public float sphereEdgeCheckRadius;
[Range(0f, 90f)] public float maxSurfaceInclineAngle;
[Min(0f)] public float forwardOffset;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6eaf18cf8aa0654d98c494153a7b6e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,183 @@
// Designed by KINEMATION, 2024.
using Kinemation.MotionWarping.Runtime.Core;
using Kinemation.MotionWarping.Runtime.Utility;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Kinemation.MotionWarping.Runtime.Examples
{
public class VaultComponent : MonoBehaviour, IWarpPointProvider
{
[SerializeField] private MotionWarpingAsset motionWarpingAsset;
[SerializeField] private VaultSettings settings;
private Vector3 _closeEdge;
private Vector3 _farEdge;
private Vector3 _endPoint;
private Quaternion _targetRotation;
private bool FindCloseEdge()
{
if (settings == null)
{
return false;
}
Vector3 start = transform.position;
Vector3 end = start + transform.up * settings.maxAllowedStartHeight;
start.y += settings.characterCapsuleRadius + settings.minAllowedStartHeight;
Vector3 direction = transform.forward;
bool bHit = Physics.CapsuleCast(start, end, settings.characterCapsuleRadius, direction,
out var hit, settings.maxAllowedStartLength, settings.layerMask);
if (!bHit)
{
return false;
}
_targetRotation = Quaternion.LookRotation(-hit.normal, transform.up);
start = hit.point;
start.y = end.y;
direction = -transform.up;
bHit = Physics.SphereCast(start, settings.sphereEdgeCheckRadius, direction, out hit,
settings.maxAllowedStartHeight, settings.layerMask);
if (!bHit)
{
return false;
}
_closeEdge = hit.point;
return true;
}
private bool FindEndPoint()
{
Vector3 start = _farEdge + (_targetRotation * Vector3.forward) * settings.farEdgeOffset;
Vector3 direction = -transform.up;
float distance = settings.maxAllowedEndHeight;
bool bHit = Physics.SphereCast(start, settings.sphereEdgeCheckRadius, direction, out var hit,
distance, settings.layerMask);
if (!bHit || (hit.point - start).magnitude < settings.minAllowedEndHeight)
{
return false;
}
_endPoint = hit.point;
return true;
}
private bool FindEndEdge()
{
Vector3 forward = (_targetRotation * Vector3.forward).normalized;
float length = settings.maxObstacleLength + settings.characterCapsuleRadius;
Vector3 start = _closeEdge + forward * length;
Vector3 end = start;
start.y = _closeEdge.y - settings.closeEdgeDeviation;
end.y = _closeEdge.y + settings.closeEdgeDeviation;
length -= settings.minObstacleLength;
bool bHit = Physics.CapsuleCast(start, end, settings.characterCapsuleRadius,
-forward, out var hit, length, settings.layerMask);
if (!bHit) return false;
start = hit.point;
start.y = _closeEdge.y + settings.closeEdgeDeviation + settings.sphereEdgeCheckRadius;
bHit = Physics.SphereCast(start, settings.sphereEdgeCheckRadius, -transform.up, out hit,
settings.closeEdgeDeviation * 2f, settings.layerMask);
if (!bHit) return false;
_farEdge = hit.point;
return true;
}
public WarpInteractionResult Interact(GameObject instigator)
{
WarpInteractionResult result = new WarpInteractionResult()
{
points = null,
asset = null,
success = false
};
if (motionWarpingAsset == null)
{
return result;
}
var motionWarping = instigator.GetComponent<Core.MotionWarping>();
bool success = FindCloseEdge() && FindEndEdge() && FindEndPoint();
if (!success)
{
return result;
}
result.asset = motionWarpingAsset;
result.points = new WarpPoint[]
{
new WarpPoint()
{
position = _closeEdge,
rotation = _targetRotation
},
new WarpPoint()
{
position = _farEdge,
rotation = _targetRotation
},
new WarpPoint()
{
position = _endPoint,
rotation = _targetRotation
}
};
result.success = true;
#if UNITY_EDITOR
Core.MotionWarping.AddWarpDebugData(motionWarping, new WarpDebugData()
{
duration = 5f,
onDrawGizmos = () =>
{
var color = Gizmos.color;
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(_closeEdge, 0.1f);
Handles.Label(_closeEdge, "Close Edge");
Gizmos.DrawWireSphere(_farEdge, 0.1f);
Handles.Label(_farEdge, "Far Edge");
Gizmos.DrawWireSphere(_endPoint, 0.1f);
Handles.Label(_endPoint, "End Point");
Gizmos.color = color;
}
});
#endif
return result;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 854419404bdc4b2a9990c6557b00e445
timeCreated: 1695198460

View File

@@ -0,0 +1,30 @@
// Designed by KINEMATION, 2024
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Examples
{
[CreateAssetMenu(menuName = "KINEMATION/MotionWarping/VaultSettings", fileName = "New VaultSettings", order = 1)]
public class VaultSettings : ScriptableObject
{
[Header("General Settings")]
public LayerMask layerMask;
[Min(0f)] public float characterCapsuleRadius;
[Min(0f)] public float maxObstacleLength;
[Min(0f)] public float minObstacleLength;
[Min(0f)] public float sphereEdgeCheckRadius;
[Header("Close Edge Check")]
[Min(0f)] public float maxAllowedStartLength;
[Min(0f)] public float maxAllowedStartHeight;
[Min(0f)] public float minAllowedStartHeight;
[Header("Far Edge Check")]
[Min(0f)] public float closeEdgeDeviation;
[Header("End Check")]
[Min(0f)] public float farEdgeOffset;
[Min(0f)] public float maxAllowedEndHeight;
[Min(0f)] public float minAllowedEndHeight;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 16c489fe43e24410b556466ad9c96890
timeCreated: 1695738291

View File

@@ -0,0 +1,3 @@
{
"name": "MotionWarping.Runtime"
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4c36dcf6d8eeb6d44ab73ef966534b46
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 618709f05a7d454696422458ea36d4b5
timeCreated: 1695456982

View File

@@ -0,0 +1,10 @@
// Designed by KINEMATION, 2024
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Utility
{
public class ReadOnlyAttribute : PropertyAttribute
{
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6cf5c593ae6f4fc98d24284c067a5514
timeCreated: 1700464610

View File

@@ -0,0 +1,134 @@
// Designed by KINEMATION, 2024
using System;
using KINEMATION.MotionWarping.Runtime.Core;
using UnityEngine;
namespace Kinemation.MotionWarping.Runtime.Utility
{
public struct WarpDebugData
{
public float duration;
public float timer;
public Action onDrawGizmos;
}
public struct WarpPoint
{
// Target transform in world space
public Transform transform;
public Vector3 localPosition;
public Vector3 localRotation;
// Target position in world space
public Vector3 position;
// Target rotation in world space
public Quaternion rotation;
public WarpPoint(Transform transform)
{
position = transform.position;
rotation = transform.rotation;
localPosition = localRotation = Vector3.zero;
this.transform = null;
}
public Vector3 GetPosition()
{
if (transform == null)
{
return position;
}
// Get the raw warp point in world space.
Vector3 rawPosition = transform.TransformPoint(position);
Quaternion rawRotation = transform.rotation * rotation;
return WarpingUtility.ToWorld(rawPosition, rawRotation, localPosition);
}
public Quaternion GetRotation()
{
if (transform == null)
{
return rotation;
}
return transform.rotation * rotation * Quaternion.Euler(localRotation);
}
}
[Serializable]
public struct WarpPhase
{
// Target point
public WarpPoint Target;
// Translation offset for the B point
public Vector3 tOffset;
// Angular offset for the B point
public Vector3 rOffset;
[Min(0f)] public float startTime;
[Min(0f)] public float endTime;
// Min allowed play rate
[Range(0f, 1f)] public float minRate;
// Max allowed play rate
[Range(1f, 2f)] public float maxRate;
[ReadOnly] public Vector3 totalRootMotion;
}
public struct WarpingCurve
{
public AnimationCurve X;
public AnimationCurve Y;
public AnimationCurve Z;
}
[Serializable]
public struct VectorBool
{
public bool x;
public bool y;
public bool z;
public VectorBool(bool x, bool y, bool z)
{
this.x = x;
this.y = y;
this.z = z;
}
public static VectorBool Enabled = new VectorBool(true, true, true);
}
public static class WarpingUtility
{
public static float ExpDecayAlpha(float speed, float deltaTime)
{
return 1 - Mathf.Exp(-speed * deltaTime);
}
public static Vector3 ToWorld(Vector3 tWorld, Quaternion rWorld, Vector3 localOffset)
{
return tWorld + rWorld * localOffset;
}
public static Vector3 ToLocal(Vector3 tWorld, Quaternion rWorld, Vector3 worldOffset)
{
return Quaternion.Inverse(rWorld) * (worldOffset - tWorld);
}
public static void SolveTwoBoneIK(Transform bone, Transform target, float weight)
{
Transform mid = bone.parent;
TwoBoneIk.SolveTwoBoneIK(mid.parent, mid, bone, (target.position, target.rotation), mid,
weight, 1f);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c787d691183bbd0448a95349d4776b18
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: