testss
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.2D.IK.Tests.EditorTests")]
|
@@ -0,0 +1,527 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D.Common;
|
||||
using UnityEngine.U2D.IK;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace UnityEditor.U2D.IK
|
||||
{
|
||||
internal class IKEditorManager : ScriptableSingleton<IKEditorManager>
|
||||
{
|
||||
private readonly HashSet<IKManager2D> m_DirtyManagers = new HashSet<IKManager2D>();
|
||||
private readonly HashSet<Solver2D> m_IKSolvers = new HashSet<Solver2D>();
|
||||
private readonly List<IKManager2D> m_IKManagers = new List<IKManager2D>();
|
||||
private readonly Dictionary<IKChain2D, Vector3> m_ChainPositionOverrides = new Dictionary<IKChain2D, Vector3>();
|
||||
private readonly List<Vector3> m_TargetPositions = new List<Vector3>();
|
||||
|
||||
private GameObject m_Helper;
|
||||
private GameObject[] m_SelectedGameobjects;
|
||||
private bool m_IgnorePostProcessModifications = false;
|
||||
private HashSet<Transform> m_IgnoreTransformsOnUndo = new HashSet<Transform>();
|
||||
internal bool isDraggingATool { get; private set; }
|
||||
internal bool isDragging { get { return IKGizmos.instance.isDragging || isDraggingATool; } }
|
||||
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void Setup()
|
||||
{
|
||||
instance.Create();
|
||||
}
|
||||
|
||||
private void Create() {}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
SetupLateUpdateHelper();
|
||||
RegisterCallbacks();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnregisterCallbacks();
|
||||
DestroyLateUpdateHelper();
|
||||
}
|
||||
|
||||
private void RegisterCallbacks()
|
||||
{
|
||||
EditorApplication.hierarchyChanged += Initialize;
|
||||
Undo.postprocessModifications += OnPostProcessModifications;
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
#else
|
||||
SceneView.onSceneGUIDelegate += OnSceneGUI;
|
||||
#endif
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
}
|
||||
|
||||
private void UnregisterCallbacks()
|
||||
{
|
||||
EditorApplication.hierarchyChanged -= Initialize;
|
||||
Undo.postprocessModifications -= OnPostProcessModifications;
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
#else
|
||||
SceneView.onSceneGUIDelegate -= OnSceneGUI;
|
||||
#endif
|
||||
Selection.selectionChanged -= OnSelectionChanged;
|
||||
}
|
||||
|
||||
private bool m_EnableGizmos;
|
||||
private bool m_CurrentEnableGizmoState;
|
||||
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
m_EnableGizmos = true;
|
||||
IKManager2D.onDrawGizmos.RemoveListener(OnDrawGizmos);
|
||||
}
|
||||
|
||||
public void CheckGizmoToggle()
|
||||
{
|
||||
//Ignore events other than Repaint
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
if (m_CurrentEnableGizmoState != m_EnableGizmos)
|
||||
SceneView.RepaintAll();
|
||||
|
||||
m_CurrentEnableGizmoState = m_EnableGizmos;
|
||||
|
||||
//Assume the Gizmo toggle is disabled and listen to the event again
|
||||
m_EnableGizmos = false;
|
||||
IKManager2D.onDrawGizmos.RemoveListener(OnDrawGizmos);
|
||||
IKManager2D.onDrawGizmos.AddListener(OnDrawGizmos);
|
||||
}
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
m_SelectedGameobjects = null;
|
||||
}
|
||||
|
||||
private void SetupLateUpdateHelper()
|
||||
{
|
||||
if (m_Helper != null)
|
||||
return;
|
||||
|
||||
m_Helper = new GameObject("IKEditorManagerHelper");
|
||||
m_Helper.hideFlags = HideFlags.HideAndDontSave;
|
||||
var helper = m_Helper.AddComponent<IKEditorManagerHelper>();
|
||||
helper.onLateUpdate.AddListener(OnLateUpdate);
|
||||
}
|
||||
|
||||
private void DestroyLateUpdateHelper()
|
||||
{
|
||||
if (m_Helper != null)
|
||||
GameObject.DestroyImmediate(m_Helper);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
m_IKManagers.Clear();
|
||||
m_IKSolvers.Clear();
|
||||
m_DirtyManagers.Clear();
|
||||
m_ChainPositionOverrides.Clear();
|
||||
|
||||
var currentStage = StageUtility.GetCurrentStageHandle();
|
||||
var managers = currentStage.FindComponentsOfType<IKManager2D>().Where(x => x.gameObject.scene.isLoaded).ToArray();
|
||||
m_IKManagers.AddRange(managers);
|
||||
|
||||
foreach (IKManager2D manager in m_IKManagers)
|
||||
{
|
||||
foreach (Solver2D solver in manager.solvers)
|
||||
{
|
||||
if (solver)
|
||||
m_IKSolvers.Add(solver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IKManager2D FindManager(Solver2D solver)
|
||||
{
|
||||
foreach (IKManager2D manager in m_IKManagers)
|
||||
{
|
||||
if (manager == null)
|
||||
continue;
|
||||
|
||||
foreach (Solver2D s in manager.solvers)
|
||||
{
|
||||
if (s == null)
|
||||
continue;
|
||||
|
||||
if (s == solver)
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Record(Solver2D solver, string undoName)
|
||||
{
|
||||
var manager = FindManager(solver);
|
||||
|
||||
DoUndo(manager, undoName, true);
|
||||
}
|
||||
|
||||
public void RegisterUndo(Solver2D solver, string undoName)
|
||||
{
|
||||
var manager = FindManager(solver);
|
||||
|
||||
DoUndo(manager, undoName, false);
|
||||
}
|
||||
|
||||
public void Record(IKManager2D manager, string undoName)
|
||||
{
|
||||
DoUndo(manager, undoName, true);
|
||||
}
|
||||
|
||||
public void RegisterUndo(IKManager2D manager, string undoName)
|
||||
{
|
||||
DoUndo(manager, undoName, false);
|
||||
}
|
||||
|
||||
private void DoUndo(IKManager2D manager, string undoName, bool record)
|
||||
{
|
||||
if (manager == null)
|
||||
return;
|
||||
foreach (var solver in manager.solvers)
|
||||
{
|
||||
if (solver == null || !solver.isActiveAndEnabled)
|
||||
continue;
|
||||
|
||||
if (!solver.isValid)
|
||||
solver.Initialize();
|
||||
|
||||
if (!solver.isValid)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
|
||||
if (record)
|
||||
{
|
||||
foreach(var t in chain.transforms)
|
||||
{
|
||||
if(m_IgnoreTransformsOnUndo.Contains(t))
|
||||
continue;
|
||||
|
||||
Undo.RecordObject(t, undoName);
|
||||
}
|
||||
|
||||
|
||||
if(chain.target && !m_IgnoreTransformsOnUndo.Contains(chain.target))
|
||||
Undo.RecordObject(chain.target, undoName);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach(var t in chain.transforms)
|
||||
{
|
||||
if(m_IgnoreTransformsOnUndo.Contains(t))
|
||||
continue;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(t, undoName);
|
||||
}
|
||||
|
||||
if(chain.target && !m_IgnoreTransformsOnUndo.Contains(chain.target))
|
||||
Undo.RegisterCompleteObjectUndo(chain.target, undoName);
|
||||
}
|
||||
}
|
||||
|
||||
m_IgnorePostProcessModifications = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateManagerImmediate(IKManager2D manager, bool recordRootLoops)
|
||||
{
|
||||
SetManagerDirty(manager);
|
||||
UpdateDirtyManagers(recordRootLoops);
|
||||
}
|
||||
|
||||
public void UpdateSolverImmediate(Solver2D solver, bool recordRootLoops)
|
||||
{
|
||||
SetSolverDirty(solver);
|
||||
UpdateDirtyManagers(recordRootLoops);
|
||||
}
|
||||
|
||||
public void UpdateHierarchyImmediate(Transform hierarchyRoot, bool recordRootLoops)
|
||||
{
|
||||
SetDirtyUnderHierarchy(hierarchyRoot);
|
||||
UpdateDirtyManagers(recordRootLoops);
|
||||
}
|
||||
|
||||
public void SetChainPositionOverride(IKChain2D chain, Vector3 position)
|
||||
{
|
||||
m_ChainPositionOverrides[chain] = position;
|
||||
}
|
||||
|
||||
private bool IsViewToolActive()
|
||||
{
|
||||
int button = Event.current.button;
|
||||
return Tools.current == Tool.View || Event.current.alt || (button == 1) || (button == 2);
|
||||
}
|
||||
|
||||
private bool IsDraggingATool()
|
||||
{
|
||||
//If a tool has used EventType.MouseDrag, we won't be able to detect it. Instead we check for delta magnitude
|
||||
return GUIUtility.hotControl != 0 && Event.current.button == 0 && Event.current.delta.sqrMagnitude > 0f && !IsViewToolActive();
|
||||
}
|
||||
|
||||
private void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
CheckGizmoToggle();
|
||||
if (!m_CurrentEnableGizmoState)
|
||||
return;
|
||||
|
||||
if (m_SelectedGameobjects == null)
|
||||
m_SelectedGameobjects = Selection.gameObjects;
|
||||
|
||||
foreach (Solver2D solver in m_IKSolvers)
|
||||
IKGizmos.instance.DoSolverGUI(solver);
|
||||
|
||||
if (!IKGizmos.instance.isDragging && IsDraggingATool())
|
||||
{
|
||||
//We expect the object to be selected while dragged
|
||||
foreach (var gameObject in m_SelectedGameobjects)
|
||||
{
|
||||
if (gameObject != null && gameObject.transform != null)
|
||||
SetDirtySolversAffectedByTransform(gameObject.transform);
|
||||
}
|
||||
|
||||
if(m_DirtyManagers.Count > 0 && !isDraggingATool)
|
||||
{
|
||||
isDraggingATool = true;
|
||||
Undo.SetCurrentGroupName("IK Update");
|
||||
|
||||
RegisterUndoForDirtyManagers();
|
||||
}
|
||||
}
|
||||
|
||||
if(GUIUtility.hotControl == 0)
|
||||
isDraggingATool = false;
|
||||
}
|
||||
|
||||
internal void OnLateUpdate()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
Profiler.BeginSample("IKEditorManager.OnLateUpdate");
|
||||
|
||||
SetAllManagersDirty();
|
||||
UpdateDirtyManagers(false);
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
private bool ProcessTransformPropertyModification(UndoPropertyModification modification, out Transform transform)
|
||||
{
|
||||
transform = null;
|
||||
var targetType = modification.currentValue.target.GetType();
|
||||
if ((targetType == typeof(Transform) || targetType.IsSubclassOf(typeof(Transform))))
|
||||
{
|
||||
transform = (Transform)modification.currentValue.target;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private UndoPropertyModification[] OnPostProcessModifications(UndoPropertyModification[] modifications)
|
||||
{
|
||||
if(!m_IgnorePostProcessModifications && !isDragging)
|
||||
{
|
||||
//Prepare transforms that already have an undo modification
|
||||
foreach (var modification in modifications)
|
||||
{
|
||||
if (modification.currentValue == null)
|
||||
continue;
|
||||
Transform transform;
|
||||
if (ProcessTransformPropertyModification(modification, out transform))
|
||||
m_IgnoreTransformsOnUndo.Add(transform);
|
||||
}
|
||||
|
||||
var processedObjectList = new HashSet<Object>();
|
||||
|
||||
foreach (var modification in modifications)
|
||||
{
|
||||
if (modification.currentValue == null)
|
||||
continue;
|
||||
var target = modification.currentValue.target;
|
||||
|
||||
if(processedObjectList.Contains(target))
|
||||
continue;
|
||||
|
||||
processedObjectList.Add(target);
|
||||
|
||||
var targetType = target.GetType();
|
||||
Transform transform;
|
||||
if (ProcessTransformPropertyModification(modification, out transform))
|
||||
{
|
||||
SetDirtySolversAffectedByTransform(transform);
|
||||
RegisterUndoForDirtyManagers();
|
||||
}
|
||||
if (targetType == typeof(Solver2D) || targetType.IsSubclassOf(typeof(Solver2D)))
|
||||
{
|
||||
var solver = (Solver2D)modification.currentValue.target;
|
||||
SetSolverDirty(solver);
|
||||
RegisterUndoForDirtyManagers();
|
||||
}
|
||||
if (targetType == typeof(IKManager2D))
|
||||
{
|
||||
var dirtyManager = (IKManager2D)modification.currentValue.target;
|
||||
SetManagerDirty(dirtyManager);
|
||||
RegisterUndoForDirtyManagers();
|
||||
}
|
||||
}
|
||||
|
||||
m_IgnoreTransformsOnUndo.Clear();
|
||||
}
|
||||
|
||||
m_IgnorePostProcessModifications = false;
|
||||
|
||||
return modifications;
|
||||
}
|
||||
|
||||
private void SetSolverDirty(Solver2D solver)
|
||||
{
|
||||
if (solver && solver.isValid && solver.isActiveAndEnabled)
|
||||
SetManagerDirty(FindManager(solver));
|
||||
}
|
||||
|
||||
private void SetManagerDirty(IKManager2D manager)
|
||||
{
|
||||
if (manager && manager.isActiveAndEnabled)
|
||||
m_DirtyManagers.Add(manager);
|
||||
}
|
||||
|
||||
private void SetAllManagersDirty()
|
||||
{
|
||||
m_DirtyManagers.Clear();
|
||||
|
||||
foreach (IKManager2D manager in m_IKManagers)
|
||||
SetManagerDirty(manager);
|
||||
}
|
||||
|
||||
private void SetDirtyUnderHierarchy(Transform hierarchyRoot)
|
||||
{
|
||||
if (hierarchyRoot == null)
|
||||
return;
|
||||
|
||||
foreach (Solver2D solver in m_IKSolvers)
|
||||
{
|
||||
if (solver.isValid)
|
||||
{
|
||||
for (int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
|
||||
if(chain.target == null)
|
||||
continue;
|
||||
|
||||
if (hierarchyRoot == chain.target ||
|
||||
IKUtility.IsDescendentOf(chain.target, hierarchyRoot) ||
|
||||
IKUtility.IsDescendentOf(chain.effector, hierarchyRoot))
|
||||
{
|
||||
SetSolverDirty(solver);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDirtySolversAffectedByTransform(Transform transform)
|
||||
{
|
||||
foreach (Solver2D solver in m_IKSolvers)
|
||||
{
|
||||
if (solver.isValid)
|
||||
{
|
||||
for (int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
|
||||
if(chain.target == null)
|
||||
continue;
|
||||
|
||||
if (!(IKUtility.IsDescendentOf(chain.target, transform) && IKUtility.IsDescendentOf(chain.rootTransform, transform)) &&
|
||||
(chain.target == transform || IKUtility.IsDescendentOf(chain.target, transform) || IKUtility.IsDescendentOf(chain.effector, transform)))
|
||||
{
|
||||
SetSolverDirty(solver);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterUndoForDirtyManagers()
|
||||
{
|
||||
foreach (var manager in m_DirtyManagers)
|
||||
RegisterUndo(manager, Undo.GetCurrentGroupName());
|
||||
}
|
||||
|
||||
private void UpdateDirtyManagers(bool recordRootLoops)
|
||||
{
|
||||
foreach (var manager in m_DirtyManagers)
|
||||
{
|
||||
if (manager == null || !manager.isActiveAndEnabled)
|
||||
continue;
|
||||
|
||||
foreach (var solver in manager.solvers)
|
||||
{
|
||||
if (solver == null || !solver.isActiveAndEnabled)
|
||||
continue;
|
||||
|
||||
if (!solver.isValid)
|
||||
solver.Initialize();
|
||||
|
||||
if (!solver.isValid)
|
||||
continue;
|
||||
|
||||
if(solver.allChainsHaveTargets)
|
||||
solver.UpdateIK(manager.weight);
|
||||
else if(PrepareTargetOverrides(solver))
|
||||
solver.UpdateIK(m_TargetPositions, manager.weight);
|
||||
|
||||
for (int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
|
||||
if (recordRootLoops)
|
||||
InternalEngineBridge.SetLocalEulerHint(chain.rootTransform);
|
||||
|
||||
if(solver.constrainRotation && chain.target != null)
|
||||
InternalEngineBridge.SetLocalEulerHint(chain.effector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_DirtyManagers.Clear();
|
||||
m_ChainPositionOverrides.Clear();
|
||||
}
|
||||
|
||||
private bool PrepareTargetOverrides(Solver2D solver)
|
||||
{
|
||||
m_TargetPositions.Clear();
|
||||
|
||||
for (int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
|
||||
Vector3 positionOverride;
|
||||
if (!m_ChainPositionOverrides.TryGetValue(chain, out positionOverride))
|
||||
{
|
||||
m_TargetPositions.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_TargetPositions.Add(positionOverride);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D.IK;
|
||||
|
||||
namespace UnityEditor.U2D.IK
|
||||
{
|
||||
internal class IKGizmos : ScriptableSingleton<IKGizmos>
|
||||
{
|
||||
private static readonly int kTargetHashCode = "IkTarget".GetHashCode();
|
||||
private Color enabledColor = Color.green;
|
||||
private Color disabledColor = Color.grey;
|
||||
private const float kCircleHandleRadius = 0.1f;
|
||||
private const float kNodeRadius = 0.05f;
|
||||
private const float kDottedLineLength = 5f;
|
||||
private const float kFadeStart = 0.75f;
|
||||
private const float kFadeEnd = 1.75f;
|
||||
private Dictionary<IKChain2D, Vector3> m_ChainPositionOverrides = new Dictionary<IKChain2D, Vector3>();
|
||||
public bool isDragging { get; private set; }
|
||||
|
||||
public void DoSolverGUI(Solver2D solver)
|
||||
{
|
||||
if (solver == null || !solver.isValid)
|
||||
return;
|
||||
|
||||
IKManager2D manager = IKEditorManager.instance.FindManager(solver);
|
||||
if (!solver.isActiveAndEnabled || manager == null || !manager.isActiveAndEnabled)
|
||||
return;
|
||||
|
||||
var solverData = manager.GetSolverEditorData(solver);
|
||||
if (!solverData.showGizmo)
|
||||
return;
|
||||
DrawSolver(solver, solverData);
|
||||
|
||||
var allChainsHaveTargets = solver.allChainsHaveTargets;
|
||||
|
||||
for (int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
if (chain == null)
|
||||
continue;
|
||||
|
||||
if (allChainsHaveTargets)
|
||||
{
|
||||
if (!IsTargetTransformSelected(chain))
|
||||
DoTargetGUI(solver, chain);
|
||||
}
|
||||
else if(chain.target == null)
|
||||
DoIkPoseGUI(solver, chain);
|
||||
}
|
||||
|
||||
if(GUIUtility.hotControl == 0)
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
private void DoTargetGUI(Solver2D solver, IKChain2D chain)
|
||||
{
|
||||
int controlId = GUIUtility.GetControlID(kTargetHashCode, FocusType.Passive);
|
||||
|
||||
var color = FadeFromChain(Color.white, chain);
|
||||
|
||||
if (!isDragging && (color.a == 0f || !IsVisible(chain.target.position)))
|
||||
return;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
Handles.color = color;
|
||||
var newPosition = Handles.Slider2D(controlId, chain.target.position, chain.target.forward, chain.target.up, chain.target.right, HandleUtility.GetHandleSize(chain.effector.position) * kCircleHandleRadius, Handles.CircleHandleCap, Vector2.zero);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(!isDragging)
|
||||
{
|
||||
isDragging = true;
|
||||
IKEditorManager.instance.RegisterUndo(solver, "Move Target");
|
||||
}
|
||||
|
||||
Undo.RecordObject(chain.target, "Move Target");
|
||||
|
||||
chain.target.position = newPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void DoIkPoseGUI(Solver2D solver, IKChain2D chain)
|
||||
{
|
||||
int controlId = GUIUtility.GetControlID(kTargetHashCode, FocusType.Passive);
|
||||
|
||||
var color = FadeFromChain(Color.white, chain);
|
||||
|
||||
if (!isDragging && (color.a == 0f || !IsVisible(chain.effector.position)))
|
||||
return;
|
||||
|
||||
if (HandleUtility.nearestControl == controlId && Event.current.type == EventType.MouseDown && Event.current.button == 0)
|
||||
StoreSolverPositionOverrides(solver);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
Handles.color = color;
|
||||
Vector3 newPosition = Handles.Slider2D(controlId, chain.effector.position, chain.effector.forward, chain.effector.up, chain.effector.right, HandleUtility.GetHandleSize(chain.effector.position) * kCircleHandleRadius, Handles.CircleHandleCap, Vector2.zero);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if(!isDragging)
|
||||
isDragging = true;
|
||||
|
||||
IKEditorManager.instance.Record(solver, "IK Pose");
|
||||
|
||||
SetSolverPositionOverrides();
|
||||
IKEditorManager.instance.SetChainPositionOverride(chain, newPosition);
|
||||
IKEditorManager.instance.UpdateSolverImmediate(solver, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void StoreSolverPositionOverrides(Solver2D solver)
|
||||
{
|
||||
Debug.Assert(solver.allChainsHaveTargets == false);
|
||||
|
||||
m_ChainPositionOverrides.Clear();
|
||||
|
||||
IKManager2D manager = IKEditorManager.instance.FindManager(solver);
|
||||
foreach (Solver2D l_solver in manager.solvers)
|
||||
{
|
||||
if(l_solver == null || l_solver.allChainsHaveTargets)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < l_solver.chainCount; ++i)
|
||||
{
|
||||
var chain = l_solver.GetChain(i);
|
||||
if (chain.effector != null)
|
||||
m_ChainPositionOverrides[chain] = chain.effector.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSolverPositionOverrides()
|
||||
{
|
||||
foreach (var pair in m_ChainPositionOverrides)
|
||||
IKEditorManager.instance.SetChainPositionOverride(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
private bool IsTargetTransformSelected(IKChain2D chain)
|
||||
{
|
||||
Debug.Assert(chain.target != null);
|
||||
|
||||
return Selection.Contains(chain.target.gameObject);
|
||||
}
|
||||
|
||||
private void DrawSolver(Solver2D solver, IKManager2D.SolverEditorData editorData)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
if (chain != null)
|
||||
DrawChain(chain, editorData.color, solver.allChainsHaveTargets);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChain(IKChain2D chain, Color solverColor, bool solverHasTargets)
|
||||
{
|
||||
Handles.matrix = Matrix4x4.identity;
|
||||
Color color = FadeFromChain(solverColor, chain);
|
||||
|
||||
if (color.a == 0f)
|
||||
return;
|
||||
|
||||
Transform currentTransform = chain.effector;
|
||||
for (int i = 0; i < chain.transformCount - 1; ++i)
|
||||
{
|
||||
var parentPosition = currentTransform.parent.position;
|
||||
var position = currentTransform.position;
|
||||
Vector3 projectedLocalPosition = Vector3.Project(currentTransform.localPosition, Vector3.right);
|
||||
Vector3 projectedEndPoint = currentTransform.parent.position + currentTransform.parent.TransformVector(projectedLocalPosition);
|
||||
var visible = IsVisible(projectedEndPoint) || IsVisible(position);
|
||||
|
||||
if (visible && currentTransform.localPosition.sqrMagnitude != projectedLocalPosition.sqrMagnitude)
|
||||
{
|
||||
Color red = Color.red;
|
||||
red.a = color.a;
|
||||
Handles.color = red;
|
||||
Handles.DrawDottedLine(projectedEndPoint, position, kDottedLineLength);
|
||||
}
|
||||
|
||||
visible = IsVisible(parentPosition) || IsVisible(projectedEndPoint);
|
||||
|
||||
Handles.color = color;
|
||||
if (visible)
|
||||
Handles.DrawDottedLine(parentPosition, projectedEndPoint, kDottedLineLength);
|
||||
|
||||
currentTransform = currentTransform.parent;
|
||||
}
|
||||
|
||||
Handles.color = color;
|
||||
currentTransform = chain.effector;
|
||||
for (int i = 0; i < chain.transformCount; ++i)
|
||||
{
|
||||
var position = currentTransform.position;
|
||||
var size = HandleUtility.GetHandleSize(position);
|
||||
|
||||
if (IsVisible(position))
|
||||
Handles.DrawSolidDisc(position, currentTransform.forward, kNodeRadius * size);
|
||||
|
||||
currentTransform = currentTransform.parent;
|
||||
}
|
||||
|
||||
Handles.color = Color.white;
|
||||
}
|
||||
|
||||
private Color FadeFromChain(Color color, IKChain2D chain)
|
||||
{
|
||||
var size = HandleUtility.GetHandleSize(chain.effector.position);
|
||||
var scaleFactor = 1f;
|
||||
var lengths = chain.lengths;
|
||||
foreach (var length in lengths)
|
||||
scaleFactor = Mathf.Max(scaleFactor, length);
|
||||
|
||||
return FadeFromSize(color, size, kFadeStart * scaleFactor, kFadeEnd * scaleFactor);
|
||||
}
|
||||
|
||||
private Color FadeFromSize(Color color, float size, float fadeStart, float fadeEnd)
|
||||
{
|
||||
float alpha = Mathf.Lerp(1f, 0f, (size - fadeStart) / (fadeEnd - fadeStart));
|
||||
color.a = alpha;
|
||||
return color;
|
||||
}
|
||||
|
||||
private bool IsVisible(Vector3 position)
|
||||
{
|
||||
var screenPos = HandleUtility.GUIPointToScreenPixelCoordinate(HandleUtility.WorldToGUIPoint(position));
|
||||
if (screenPos.x < 0f || screenPos.x > Camera.current.pixelWidth || screenPos.y < 0f || screenPos.y > Camera.current.pixelHeight)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D.IK;
|
||||
|
||||
namespace UnityEditor.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom Inspector for CCDSolver2D.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(CCDSolver2D))]
|
||||
[CanEditMultipleObjects]
|
||||
public class CCDSolver2DEditor : Solver2DEditor
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent effectorLabel = new GUIContent("Effector", "The last Transform of a hierarchy constrained by the target");
|
||||
public static readonly GUIContent targetLabel = new GUIContent("Target", "Transfrom which the effector will follow");
|
||||
public static readonly GUIContent chainLengthLabel = new GUIContent("Chain Length", "Number of Transforms handled by the IK");
|
||||
public static readonly GUIContent iterationsLabel = new GUIContent("Iterations", "Number of iterations the IK solver is run per frame");
|
||||
public static readonly GUIContent toleranceLabel = new GUIContent("Tolerance", "How close the target is to the goal to be considered as successful");
|
||||
public static readonly GUIContent velocityLabel = new GUIContent("Velocity", "How fast the chain elements rotate to the effector per iteration");
|
||||
}
|
||||
|
||||
private SerializedProperty m_TargetProperty;
|
||||
private SerializedProperty m_EffectorProperty;
|
||||
private SerializedProperty m_TransformCountProperty;
|
||||
|
||||
private SerializedProperty m_IterationsProperty;
|
||||
private SerializedProperty m_ToleranceProperty;
|
||||
private SerializedProperty m_VelocityProperty;
|
||||
private CCDSolver2D m_Solver;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Solver = target as CCDSolver2D;
|
||||
var chainProperty = serializedObject.FindProperty("m_Chain");
|
||||
m_TargetProperty = chainProperty.FindPropertyRelative("m_TargetTransform");
|
||||
m_EffectorProperty = chainProperty.FindPropertyRelative("m_EffectorTransform");
|
||||
m_TransformCountProperty = chainProperty.FindPropertyRelative("m_TransformCount");
|
||||
m_IterationsProperty = serializedObject.FindProperty("m_Iterations");
|
||||
m_ToleranceProperty = serializedObject.FindProperty("m_Tolerance");
|
||||
m_VelocityProperty = serializedObject.FindProperty("m_Velocity");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom Inspector OnInspectorGUI override.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
IKChain2D chain = m_Solver.GetChain(0);
|
||||
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_EffectorProperty, Contents.effectorLabel);
|
||||
EditorGUILayout.PropertyField(m_TargetProperty, Contents.targetLabel);
|
||||
EditorGUILayout.IntSlider(m_TransformCountProperty, 0, IKUtility.GetMaxChainCount(chain), Contents.chainLengthLabel);
|
||||
EditorGUILayout.PropertyField(m_IterationsProperty, Contents.iterationsLabel);
|
||||
EditorGUILayout.PropertyField(m_ToleranceProperty, Contents.toleranceLabel);
|
||||
EditorGUILayout.PropertyField(m_VelocityProperty, Contents.velocityLabel);
|
||||
|
||||
DrawCommonSolverInspector();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D.IK;
|
||||
|
||||
namespace UnityEditor.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom Inspector for FabrikSolver2D.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(FabrikSolver2D))]
|
||||
[CanEditMultipleObjects]
|
||||
public class FabrikSolver2DEditor : Solver2DEditor
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent effectorLabel = new GUIContent("Effector", "The last Transform of a hierarchy constrained by the target");
|
||||
public static readonly GUIContent targetLabel = new GUIContent("Target", "Transfrom which the effector will follow");
|
||||
public static readonly GUIContent chainLengthLabel = new GUIContent("Chain Length", "Number of Transforms handled by the IK");
|
||||
public static readonly GUIContent iterationsLabel = new GUIContent("Iterations", "Number of iterations the IK solver is run per frame");
|
||||
public static readonly GUIContent toleranceLabel = new GUIContent("Tolerance", "How close the target is to the goal to be considered as successful");
|
||||
}
|
||||
|
||||
private SerializedProperty m_TargetProperty;
|
||||
private SerializedProperty m_EffectorProperty;
|
||||
private SerializedProperty m_TransformCountProperty;
|
||||
private SerializedProperty m_IterationsProperty;
|
||||
private SerializedProperty m_ToleranceProperty;
|
||||
private FabrikSolver2D m_Solver;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Solver = target as FabrikSolver2D;
|
||||
var chainProperty = serializedObject.FindProperty("m_Chain");
|
||||
m_TargetProperty = chainProperty.FindPropertyRelative("m_TargetTransform");
|
||||
m_EffectorProperty = chainProperty.FindPropertyRelative("m_EffectorTransform");
|
||||
m_TransformCountProperty = chainProperty.FindPropertyRelative("m_TransformCount");
|
||||
m_IterationsProperty = serializedObject.FindProperty("m_Iterations");
|
||||
m_ToleranceProperty = serializedObject.FindProperty("m_Tolerance");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom Inspector OnInspectorGUI override.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
IKChain2D chain = m_Solver.GetChain(0);
|
||||
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_EffectorProperty, Contents.effectorLabel);
|
||||
EditorGUILayout.PropertyField(m_TargetProperty, Contents.targetLabel);
|
||||
EditorGUILayout.IntSlider(m_TransformCountProperty, 0, IKUtility.GetMaxChainCount(chain), Contents.chainLengthLabel);
|
||||
EditorGUILayout.PropertyField(m_IterationsProperty, Contents.iterationsLabel);
|
||||
EditorGUILayout.PropertyField(m_ToleranceProperty, Contents.toleranceLabel);
|
||||
|
||||
DrawCommonSolverInspector();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,219 @@
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D.IK;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using UnityEditor.U2D.Animation;
|
||||
|
||||
namespace UnityEditor.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom Inspector for IKManager2D.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(IKManager2D))]
|
||||
[CanEditMultipleObjects]
|
||||
class IKManager2DEditor : Editor
|
||||
{
|
||||
private class Contents
|
||||
{
|
||||
public static readonly GUIContent findAllSolversLabel = new GUIContent("Find Solvers", "Find all applicable solvers handled by this manager");
|
||||
public static readonly GUIContent weightLabel = new GUIContent("Weight", "Blend between Forward and Inverse Kinematics");
|
||||
public static readonly string listHeaderLabel = "IK Solvers";
|
||||
public static readonly string createSolverString = "Create Solver";
|
||||
public static readonly string restoreDefaultPoseString = "Restore Default Pose";
|
||||
public static readonly GUIContent gizmoColorTooltip = new GUIContent("","Customizes the IK Chain's Gizmo color");
|
||||
public static int showGizmoPropertyWidth = 20;
|
||||
public static int solverPropertyWidth = 100;
|
||||
public static int solverColorPropertyWidth = 40;
|
||||
public static readonly GUIContent gizmoVisibilityToolTip = new GUIContent("",L10n.Tr("Show/Hide Gizmo"));
|
||||
public readonly GUIStyle visibilityToggleStyle;
|
||||
|
||||
public Contents()
|
||||
{
|
||||
visibilityToggleStyle = new GUIStyle();
|
||||
visibilityToggleStyle.fixedWidth = EditorGUIUtility.singleLineHeight;
|
||||
visibilityToggleStyle.onNormal.background = IconUtility.LoadIconResource("Visibility_Tool", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath);
|
||||
visibilityToggleStyle.normal.background = IconUtility.LoadIconResource("Visibility_Hidded", IconUtility.k_LightIconResourcePath, IconUtility.k_DarkIconResourcePath);
|
||||
}
|
||||
}
|
||||
|
||||
static Contents k_Contents;
|
||||
private ReorderableList m_ReorderableList;
|
||||
private Solver2D m_SelectedSolver;
|
||||
private Editor m_SelectedSolverEditor;
|
||||
|
||||
SerializedProperty m_SolversProperty;
|
||||
SerializedProperty m_SolverEditorDataProperty;
|
||||
SerializedProperty m_WeightProperty;
|
||||
List<Type> m_SolverTypes;
|
||||
IKManager2D m_Manager;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Manager = target as IKManager2D;
|
||||
m_SolverTypes = GetDerivedTypes<Solver2D>();
|
||||
m_SolversProperty = serializedObject.FindProperty("m_Solvers");
|
||||
m_SolverEditorDataProperty = serializedObject.FindProperty("m_SolverEditorData");
|
||||
m_WeightProperty = serializedObject.FindProperty("m_Weight");
|
||||
SetupReordeableList();
|
||||
}
|
||||
|
||||
void SetupReordeableList()
|
||||
{
|
||||
m_ReorderableList = new ReorderableList(serializedObject, m_SolversProperty, true, true, true, true);
|
||||
m_ReorderableList.drawHeaderCallback = (Rect rect) =>
|
||||
{
|
||||
GUI.Label(rect, Contents.listHeaderLabel);
|
||||
};
|
||||
m_ReorderableList.elementHeightCallback = (int index) =>
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight + 6;
|
||||
};
|
||||
m_ReorderableList.drawElementCallback = (Rect rect, int index, bool isactive, bool isfocused) =>
|
||||
{
|
||||
rect.y += 2f;
|
||||
rect.height = EditorGUIUtility.singleLineHeight;
|
||||
SerializedProperty element = m_SolversProperty.GetArrayElementAtIndex(index);
|
||||
SerializedProperty elementData = m_SolverEditorDataProperty.GetArrayElementAtIndex(index);
|
||||
var width = rect.width;
|
||||
rect.width = width > Contents.showGizmoPropertyWidth ? Contents.showGizmoPropertyWidth : width;
|
||||
var showGizmoProperty = elementData.FindPropertyRelative("showGizmo");
|
||||
showGizmoProperty.boolValue = GUI.Toggle(rect, showGizmoProperty.boolValue, Contents.gizmoVisibilityToolTip, k_Contents.visibilityToggleStyle);
|
||||
rect.x += rect.width;
|
||||
width -= rect.width;
|
||||
rect.width = width > Contents.solverPropertyWidth ? width - Contents.solverColorPropertyWidth : Contents.solverPropertyWidth;
|
||||
EditorGUI.PropertyField(rect, element, GUIContent.none);
|
||||
rect.x += rect.width;
|
||||
width -= 100;
|
||||
rect.width = width > Contents.solverColorPropertyWidth ? Contents.solverColorPropertyWidth : width;
|
||||
EditorGUI.PropertyField(rect, elementData.FindPropertyRelative("color"), Contents.gizmoColorTooltip);
|
||||
};
|
||||
m_ReorderableList.onAddCallback = (ReorderableList list) =>
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
|
||||
foreach (Type type in m_SolverTypes)
|
||||
{
|
||||
Solver2DMenuAttribute attribute = Attribute.GetCustomAttribute(type, typeof(Solver2DMenuAttribute)) as Solver2DMenuAttribute;
|
||||
|
||||
if (attribute != null)
|
||||
menu.AddItem(new GUIContent(attribute.menuPath), false, OnSelectMenu, type);
|
||||
else
|
||||
menu.AddItem(new GUIContent(type.Name), false, OnSelectMenu, type);
|
||||
}
|
||||
|
||||
menu.ShowAsContext();
|
||||
};
|
||||
m_ReorderableList.onRemoveCallback = (ReorderableList list) =>
|
||||
{
|
||||
Solver2D solver = m_Manager.solvers[list.index];
|
||||
if (solver)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(m_Manager, Undo.GetCurrentGroupName());
|
||||
|
||||
m_Manager.RemoveSolver(solver);
|
||||
|
||||
GameObject solverGO = solver.gameObject;
|
||||
|
||||
if (solverGO.transform.childCount == 0)
|
||||
Undo.DestroyObjectImmediate(solverGO);
|
||||
else
|
||||
Undo.DestroyObjectImmediate(solver);
|
||||
|
||||
EditorUtility.SetDirty(m_Manager);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReorderableList.defaultBehaviours.DoRemoveButton(list);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void OnSelectMenu(object param)
|
||||
{
|
||||
Type solverType = param as Type;
|
||||
|
||||
GameObject solverGO = new GameObject(GameObjectUtility.GetUniqueNameForSibling(m_Manager.transform, "New " + solverType.Name));
|
||||
solverGO.transform.SetParent(m_Manager.transform);
|
||||
solverGO.transform.localPosition = Vector3.zero;
|
||||
solverGO.transform.rotation = Quaternion.identity;
|
||||
solverGO.transform.localScale = Vector3.one;
|
||||
|
||||
Solver2D solver = solverGO.AddComponent(solverType) as Solver2D;
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(solverGO, Contents.createSolverString);
|
||||
Undo.RegisterCompleteObjectUndo(m_Manager, Contents.createSolverString);
|
||||
m_Manager.AddSolver(solver);
|
||||
EditorUtility.SetDirty(m_Manager);
|
||||
|
||||
Selection.activeGameObject = solverGO;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom Inspector OnInspectorGUI override.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if(k_Contents == null)
|
||||
k_Contents = new Contents();
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
m_ReorderableList.DoLayoutList();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(m_WeightProperty, Contents.weightLabel);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
DoRestoreDefaultPoseButton();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DoRestoreDefaultPoseButton()
|
||||
{
|
||||
if (GUILayout.Button(Contents.restoreDefaultPoseString, GUILayout.MaxWidth(150f)))
|
||||
{
|
||||
foreach (var l_target in targets)
|
||||
{
|
||||
var manager = l_target as IKManager2D;
|
||||
|
||||
IKEditorManager.instance.Record(manager, Contents.restoreDefaultPoseString);
|
||||
|
||||
foreach(var solver in manager.solvers)
|
||||
{
|
||||
for(int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
chain.RestoreDefaultPose(solver.constrainRotation);
|
||||
|
||||
if(chain.target)
|
||||
{
|
||||
chain.target.position = chain.effector.position;
|
||||
chain.target.rotation = chain.effector.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IKEditorManager.instance.UpdateManagerImmediate(manager, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Type> GetDerivedTypes<T>() where T : class
|
||||
{
|
||||
List<Type> types = Assembly.GetAssembly(typeof(T)).GetTypes().Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(T))).ToList();
|
||||
return types;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D.IK;
|
||||
|
||||
namespace UnityEditor.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom Inspector for LimbSolver2D.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(LimbSolver2D))]
|
||||
[CanEditMultipleObjects]
|
||||
public class LimbSolver2DEditor : Solver2DEditor
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent effectorLabel = new GUIContent("Effector", "The last Transform of a hierarchy constrained by the target");
|
||||
public static readonly GUIContent targetLabel = new GUIContent("Target", "Transfrom which the effector will follow");
|
||||
public static readonly GUIContent flipLabel = new GUIContent("Flip", "Select between the two possible solutions of the solver");
|
||||
}
|
||||
|
||||
private SerializedProperty m_ChainProperty;
|
||||
private SerializedProperty m_FlipProperty;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_ChainProperty = serializedObject.FindProperty("m_Chain");
|
||||
m_FlipProperty = serializedObject.FindProperty("m_Flip");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom Inspector OnInspectorGUI override.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_ChainProperty.FindPropertyRelative("m_EffectorTransform"), Contents.effectorLabel);
|
||||
EditorGUILayout.PropertyField(m_ChainProperty.FindPropertyRelative("m_TargetTransform"), Contents.targetLabel);
|
||||
EditorGUILayout.PropertyField(m_FlipProperty, Contents.flipLabel);
|
||||
|
||||
DrawCommonSolverInspector();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D.IK;
|
||||
|
||||
namespace UnityEditor.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom Inspector for Solver2D.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(Solver2D))]
|
||||
[CanEditMultipleObjects]
|
||||
public class Solver2DEditor : Editor
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent constrainRotationLabel = new GUIContent("Constrain Rotation", "Set Effector's rotation to Target");
|
||||
public static readonly GUIContent solveFromDefaultPoseLabel = new GUIContent("Solve from Default Pose", "Restore transform's rotation to default value before solving the IK");
|
||||
public static readonly GUIContent weightLabel = new GUIContent("Weight", "Blend between Forward and Inverse Kinematics");
|
||||
public static readonly string restoreDefaultPoseString = "Restore Default Pose";
|
||||
public static readonly string createTargetString = "Create Target";
|
||||
}
|
||||
|
||||
private SerializedProperty m_ConstrainRotationProperty;
|
||||
private SerializedProperty m_SolveFromDefaultPoseProperty;
|
||||
private SerializedProperty m_WeightProperty;
|
||||
private SerializedProperty m_SolverColorProperty;
|
||||
|
||||
private void SetupProperties()
|
||||
{
|
||||
if(m_ConstrainRotationProperty == null || m_SolveFromDefaultPoseProperty == null || m_WeightProperty == null)
|
||||
{
|
||||
m_ConstrainRotationProperty = serializedObject.FindProperty("m_ConstrainRotation");
|
||||
m_SolveFromDefaultPoseProperty = serializedObject.FindProperty("m_SolveFromDefaultPose");
|
||||
m_WeightProperty = serializedObject.FindProperty("m_Weight");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom Inspector GUI for Solver2D.
|
||||
/// </summary>
|
||||
protected void DrawCommonSolverInspector()
|
||||
{
|
||||
SetupProperties();
|
||||
|
||||
EditorGUILayout.PropertyField(m_ConstrainRotationProperty, Contents.constrainRotationLabel);
|
||||
EditorGUILayout.PropertyField(m_SolveFromDefaultPoseProperty, Contents.solveFromDefaultPoseLabel);
|
||||
EditorGUILayout.PropertyField(m_WeightProperty, Contents.weightLabel);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
EditorGUI.BeginDisabledGroup(!EnableCreateTarget());
|
||||
DoCreateTargetButton();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUI.BeginDisabledGroup(!EnableRestoreDefaultPose());
|
||||
DoRestoreDefaultPoseButton();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
private bool EnableRestoreDefaultPose()
|
||||
{
|
||||
foreach (var l_target in targets)
|
||||
{
|
||||
var solver = l_target as Solver2D;
|
||||
|
||||
if (!solver.isValid || IKEditorManager.instance.FindManager(solver) == null)
|
||||
continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool EnableCreateTarget()
|
||||
{
|
||||
foreach (var l_target in targets)
|
||||
{
|
||||
var solver = l_target as Solver2D;
|
||||
|
||||
if (!solver.isValid)
|
||||
continue;
|
||||
|
||||
for(int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
|
||||
if(chain.target == null)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DoRestoreDefaultPoseButton()
|
||||
{
|
||||
if (GUILayout.Button(Contents.restoreDefaultPoseString, GUILayout.MaxWidth(150f)))
|
||||
{
|
||||
foreach (var l_target in targets)
|
||||
{
|
||||
var solver = l_target as Solver2D;
|
||||
|
||||
if (!solver.isValid)
|
||||
continue;
|
||||
|
||||
IKEditorManager.instance.Record(solver, Contents.restoreDefaultPoseString);
|
||||
|
||||
for(int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
chain.RestoreDefaultPose(solver.constrainRotation);
|
||||
|
||||
if(chain.target)
|
||||
{
|
||||
chain.target.position = chain.effector.position;
|
||||
chain.target.rotation = chain.effector.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
IKEditorManager.instance.UpdateSolverImmediate(solver, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DoCreateTargetButton()
|
||||
{
|
||||
if (GUILayout.Button(Contents.createTargetString, GUILayout.MaxWidth(125f)))
|
||||
{
|
||||
foreach (var l_target in targets)
|
||||
{
|
||||
var solver = l_target as Solver2D;
|
||||
|
||||
if (!solver.isValid)
|
||||
continue;
|
||||
|
||||
for(int i = 0; i < solver.chainCount; ++i)
|
||||
{
|
||||
var chain = solver.GetChain(i);
|
||||
|
||||
if(chain.target == null)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(solver, Contents.createTargetString);
|
||||
|
||||
chain.target = new GameObject(GameObjectUtility.GetUniqueNameForSibling(solver.transform, solver.name + "_Target")).transform;
|
||||
chain.target.SetParent(solver.transform);
|
||||
chain.target.position = chain.effector.position;
|
||||
chain.target.rotation = chain.effector.rotation;
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(chain.target.gameObject, Contents.createTargetString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "Unity.2D.IK.Editor",
|
||||
"references": [
|
||||
"Unity.InternalAPIEditorBridge.001",
|
||||
"Unity.InternalAPIEngineBridge.001",
|
||||
"Unity.2D.Common.Editor",
|
||||
"Unity.2D.Common.Runtime",
|
||||
"Unity.2D.IK.Runtime",
|
||||
"Unity.2D.Animation.Editor"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.2D.IK.Editor")]
|
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Component for 2D Cyclic Coordinate Descent (CCD) IK.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
[Solver2DMenuAttribute("Chain (CCD)")]
|
||||
public class CCDSolver2D : Solver2D
|
||||
{
|
||||
private const float kMinTolerance = 0.001f;
|
||||
private const int kMinIterations = 1;
|
||||
private const float kMinVelocity = 0.01f;
|
||||
private const float kMaxVelocity = 1f;
|
||||
|
||||
[SerializeField]
|
||||
private IKChain2D m_Chain = new IKChain2D();
|
||||
|
||||
[SerializeField][Range(kMinIterations, 50)]
|
||||
private int m_Iterations = 10;
|
||||
[SerializeField][Range(kMinTolerance, 0.1f)]
|
||||
private float m_Tolerance = 0.01f;
|
||||
[SerializeField][Range(0f, 1f)]
|
||||
private float m_Velocity = 0.5f;
|
||||
|
||||
private Vector3[] m_Positions;
|
||||
|
||||
/// <summary>
|
||||
/// Get and Set the solver's itegration count.
|
||||
/// </summary>
|
||||
public int iterations
|
||||
{
|
||||
get { return m_Iterations; }
|
||||
set { m_Iterations = Mathf.Max(value, kMinIterations); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get and Set target distance tolerance.
|
||||
/// </summary>
|
||||
public float tolerance
|
||||
{
|
||||
get { return m_Tolerance; }
|
||||
set { m_Tolerance = Mathf.Max(value, kMinTolerance); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get and Set the solver velocity.
|
||||
/// </summary>
|
||||
public float velocity
|
||||
{
|
||||
get { return m_Velocity; }
|
||||
set { m_Velocity = Mathf.Clamp01(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of chain in the solver.
|
||||
/// </summary>
|
||||
/// <returns>This always returns 1</returns>
|
||||
protected override int GetChainCount()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chain in the solver by index.
|
||||
/// </summary>
|
||||
/// <param name="index">Chain index.</param>
|
||||
/// <returns>Returns IKChain2D at the index.</returns>
|
||||
public override IKChain2D GetChain(int index)
|
||||
{
|
||||
return m_Chain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DoPrepare override from base class.
|
||||
/// </summary>
|
||||
protected override void DoPrepare()
|
||||
{
|
||||
if (m_Positions == null || m_Positions.Length != m_Chain.transformCount)
|
||||
m_Positions = new Vector3[m_Chain.transformCount];
|
||||
|
||||
for (int i = 0; i < m_Chain.transformCount; ++i)
|
||||
m_Positions[i] = m_Chain.transforms[i].position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DoUpdateIK override from base class.
|
||||
/// </summary>
|
||||
/// <param name="effectorPositions">Target position for the chain.</param>
|
||||
protected override void DoUpdateIK(List<Vector3> effectorPositions)
|
||||
{
|
||||
Profiler.BeginSample("CCDSolver2D.DoUpdateIK");
|
||||
|
||||
Vector3 effectorPosition = effectorPositions[0];
|
||||
Vector2 effectorLocalPosition2D = m_Chain.transforms[0].InverseTransformPoint(effectorPosition);
|
||||
effectorPosition = m_Chain.transforms[0].TransformPoint(effectorLocalPosition2D);
|
||||
|
||||
if (CCD2D.Solve(effectorPosition, GetPlaneRootTransform().forward, iterations, tolerance, Mathf.Lerp(kMinVelocity, kMaxVelocity, m_Velocity), ref m_Positions))
|
||||
{
|
||||
for (int i = 0; i < m_Chain.transformCount - 1; ++i)
|
||||
{
|
||||
Vector3 startLocalPosition = m_Chain.transforms[i + 1].localPosition;
|
||||
Vector3 endLocalPosition = m_Chain.transforms[i].InverseTransformPoint(m_Positions[i + 1]);
|
||||
m_Chain.transforms[i].localRotation *= Quaternion.FromToRotation(startLocalPosition, endLocalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Component for 2D Forward And Backward Reaching Inverse Kinematics (FABRIK) IK.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
[Solver2DMenu("Chain (FABRIK)")]
|
||||
public class FabrikSolver2D : Solver2D
|
||||
{
|
||||
private const float kMinTolerance = 0.001f;
|
||||
private const int kMinIterations = 1;
|
||||
|
||||
[SerializeField]
|
||||
private IKChain2D m_Chain = new IKChain2D();
|
||||
[SerializeField][Range(kMinIterations, 50)]
|
||||
private int m_Iterations = 10;
|
||||
[SerializeField][Range(kMinTolerance, 0.1f)]
|
||||
private float m_Tolerance = 0.01f;
|
||||
|
||||
private float[] m_Lengths;
|
||||
private Vector2[] m_Positions;
|
||||
private Vector3[] m_WorldPositions;
|
||||
|
||||
/// <summary>
|
||||
/// Get and Set the solver's itegration count.
|
||||
/// </summary>
|
||||
public int iterations
|
||||
{
|
||||
get { return m_Iterations; }
|
||||
set { m_Iterations = Mathf.Max(value, kMinIterations); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get and Set target distance tolerance.
|
||||
/// </summary>
|
||||
public float tolerance
|
||||
{
|
||||
get { return m_Tolerance; }
|
||||
set { m_Tolerance = Mathf.Max(value, kMinTolerance); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of chain in the solver.
|
||||
/// </summary>
|
||||
/// <returns>This always returns 1.</returns>
|
||||
protected override int GetChainCount()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chain in the solver by index.
|
||||
/// </summary>
|
||||
/// <param name="index">Chain index.</param>
|
||||
/// <returns>Returns IKChain2D at the index.</returns>
|
||||
public override IKChain2D GetChain(int index)
|
||||
{
|
||||
return m_Chain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DoPrepare override from base class.
|
||||
/// </summary>
|
||||
protected override void DoPrepare()
|
||||
{
|
||||
if (m_Positions == null || m_Positions.Length != m_Chain.transformCount)
|
||||
{
|
||||
m_Positions = new Vector2[m_Chain.transformCount];
|
||||
m_Lengths = new float[m_Chain.transformCount - 1];
|
||||
m_WorldPositions = new Vector3[m_Chain.transformCount];
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_Chain.transformCount; ++i)
|
||||
{
|
||||
m_Positions[i] = GetPointOnSolverPlane(m_Chain.transforms[i].position);
|
||||
}
|
||||
for (int i = 0; i < m_Chain.transformCount - 1; ++i)
|
||||
{
|
||||
m_Lengths[i] = (m_Positions[i + 1] - m_Positions[i]).magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DoUpdateIK override from base class.
|
||||
/// </summary>
|
||||
/// <param name="effectorPositions">Target position for the chain.</param>
|
||||
protected override void DoUpdateIK(List<Vector3> effectorPositions)
|
||||
{
|
||||
Profiler.BeginSample("FABRIKSolver2D.DoUpdateIK");
|
||||
|
||||
Vector3 effectorPosition = effectorPositions[0];
|
||||
effectorPosition = GetPointOnSolverPlane(effectorPosition);
|
||||
if (FABRIK2D.Solve(effectorPosition, iterations, tolerance, m_Lengths, ref m_Positions))
|
||||
{
|
||||
// Convert all plane positions to world positions
|
||||
for (int i = 0; i < m_Positions.Length; ++i)
|
||||
{
|
||||
m_WorldPositions[i] = GetWorldPositionFromSolverPlanePoint(m_Positions[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_Chain.transformCount - 1; ++i)
|
||||
{
|
||||
Vector3 startLocalPosition = m_Chain.transforms[i + 1].localPosition;
|
||||
Vector3 endLocalPosition = m_Chain.transforms[i].InverseTransformPoint(m_WorldPositions[i + 1]);
|
||||
m_Chain.transforms[i].localRotation *= Quaternion.FromToRotation(startLocalPosition, endLocalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for storing data for a 2D IK Chain.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
[Serializable]
|
||||
public class IKChain2D
|
||||
{
|
||||
[SerializeField][FormerlySerializedAs("m_Target")]
|
||||
private Transform m_EffectorTransform;
|
||||
[SerializeField][FormerlySerializedAs("m_Effector")]
|
||||
private Transform m_TargetTransform;
|
||||
[SerializeField]
|
||||
private int m_TransformCount;
|
||||
[SerializeField]
|
||||
private Transform[] m_Transforms;
|
||||
[SerializeField]
|
||||
private Quaternion[] m_DefaultLocalRotations;
|
||||
[SerializeField]
|
||||
private Quaternion[] m_StoredLocalRotations;
|
||||
|
||||
protected float[] m_Lengths;
|
||||
|
||||
/// <summary>
|
||||
/// Get Set the Unity Transform used as IK Effector.
|
||||
/// </summary>
|
||||
public Transform effector
|
||||
{
|
||||
get { return m_EffectorTransform; }
|
||||
set { m_EffectorTransform = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Set the Unity Transform used as IK Target.
|
||||
/// </summary>
|
||||
public Transform target
|
||||
{
|
||||
get { return m_TargetTransform; }
|
||||
set { m_TargetTransform = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Unity Transforms that are in the IK Chain.
|
||||
/// </summary>
|
||||
public Transform[] transforms
|
||||
{
|
||||
get { return m_Transforms; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the root Unity Transform for the IK Chain.
|
||||
/// </summary>
|
||||
public Transform rootTransform
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Transforms != null && transformCount > 0 && m_Transforms.Length == transformCount)
|
||||
return m_Transforms[0];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Transform lastTransform
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Transforms != null && transformCount > 0 && m_Transforms.Length == transformCount)
|
||||
return m_Transforms[transformCount - 1];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get and Set the number of Unity Transforms in the IK Chain.
|
||||
/// </summary>
|
||||
public int transformCount
|
||||
{
|
||||
get { return m_TransformCount; }
|
||||
set { m_TransformCount = Mathf.Max(0, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the IK Chain is valid. False otherwise.
|
||||
/// </summary>
|
||||
public bool isValid
|
||||
{
|
||||
get { return Validate(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the IK Chain.
|
||||
/// </summary>
|
||||
public float[] lengths
|
||||
{
|
||||
get
|
||||
{
|
||||
if(isValid)
|
||||
{
|
||||
PrepareLengths();
|
||||
return m_Lengths;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Validate()
|
||||
{
|
||||
if (effector == null)
|
||||
return false;
|
||||
if (transformCount == 0)
|
||||
return false;
|
||||
if (m_Transforms == null || m_Transforms.Length != transformCount)
|
||||
return false;
|
||||
if (m_DefaultLocalRotations == null || m_DefaultLocalRotations.Length != transformCount)
|
||||
return false;
|
||||
if (m_StoredLocalRotations == null || m_StoredLocalRotations.Length != transformCount)
|
||||
return false;
|
||||
if (rootTransform == null)
|
||||
return false;
|
||||
if (lastTransform != effector)
|
||||
return false;
|
||||
if (target && IKUtility.IsDescendentOf(target, rootTransform))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the IK Chain.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
if (effector == null || transformCount == 0 || IKUtility.GetAncestorCount(effector) < transformCount - 1)
|
||||
return;
|
||||
|
||||
m_Transforms = new Transform[transformCount];
|
||||
m_DefaultLocalRotations = new Quaternion[transformCount];
|
||||
m_StoredLocalRotations = new Quaternion[transformCount];
|
||||
|
||||
var currentTransform = effector;
|
||||
int index = transformCount - 1;
|
||||
|
||||
while (currentTransform && index >= 0)
|
||||
{
|
||||
m_Transforms[index] = currentTransform;
|
||||
m_DefaultLocalRotations[index] = currentTransform.localRotation;
|
||||
|
||||
currentTransform = currentTransform.parent;
|
||||
--index;
|
||||
}
|
||||
}
|
||||
|
||||
private void PrepareLengths()
|
||||
{
|
||||
var currentTransform = effector;
|
||||
int index = transformCount - 1;
|
||||
|
||||
if (m_Lengths == null || m_Lengths.Length != transformCount - 1)
|
||||
m_Lengths = new float[transformCount - 1];
|
||||
|
||||
while (currentTransform && index >= 0)
|
||||
{
|
||||
if (currentTransform.parent && index > 0)
|
||||
m_Lengths[index - 1] = (currentTransform.position - currentTransform.parent.position).magnitude;
|
||||
|
||||
currentTransform = currentTransform.parent;
|
||||
--index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores IK Chain to it's default pose.
|
||||
/// </summary>
|
||||
/// <param name="targetRotationIsConstrained">True to constrain the target rotation. False otherwise.</param>
|
||||
public void RestoreDefaultPose(bool targetRotationIsConstrained)
|
||||
{
|
||||
var count = targetRotationIsConstrained ? transformCount : transformCount-1;
|
||||
for (int i = 0; i < count; ++i)
|
||||
m_Transforms[i].localRotation = m_DefaultLocalRotations[i];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly stores the local rotation
|
||||
/// </summary>
|
||||
public void StoreLocalRotations()
|
||||
{
|
||||
for (int i = 0; i < m_Transforms.Length; ++i)
|
||||
m_StoredLocalRotations[i] = m_Transforms[i].localRotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend between Forward Kinematics and Inverse Kinematics.
|
||||
/// </summary>
|
||||
/// <param name="finalWeight">Weight for blend</param>
|
||||
/// <param name="targetRotationIsConstrained">True to constrain target rotation. False otherwise.</param>
|
||||
public void BlendFkToIk(float finalWeight, bool targetRotationIsConstrained)
|
||||
{
|
||||
var count = targetRotationIsConstrained ? transformCount : transformCount-1;
|
||||
for (int i = 0; i < count; ++i)
|
||||
m_Transforms[i].localRotation = Quaternion.Slerp(m_StoredLocalRotations[i], m_Transforms[i].localRotation, finalWeight);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace UnityEditor.U2D.IK
|
||||
{
|
||||
[DefaultExecutionOrder(-2)]
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("")]
|
||||
[MovedFrom("UnityEditor.Experimental.U2D.IK")]
|
||||
internal class IKEditorManagerHelper : MonoBehaviour
|
||||
{
|
||||
public UnityEvent onLateUpdate = new UnityEvent();
|
||||
|
||||
void Start()
|
||||
{
|
||||
if(hideFlags != HideFlags.HideAndDontSave)
|
||||
Debug.LogWarning("This is an internal IK Component. Please remove it from your GameObject", this.gameObject);
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
onLateUpdate.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Component to manager 2D IK Solvers.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-2)]
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
public partial class IKManager2D : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private List<Solver2D> m_Solvers = new List<Solver2D>();
|
||||
[SerializeField][Range(0f, 1f)]
|
||||
private float m_Weight = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Get and Set the weight for solvers.
|
||||
/// </summary>
|
||||
public float weight
|
||||
{
|
||||
get { return m_Weight; }
|
||||
set { m_Weight = Mathf.Clamp01(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Solvers that are managed by this manager.
|
||||
/// </summary>
|
||||
public List<Solver2D> solvers
|
||||
{
|
||||
get { return m_Solvers; }
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
m_Weight = Mathf.Clamp01(m_Weight);
|
||||
OnEditorDataValidate();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
FindChildSolvers();
|
||||
OnEditorDataValidate();
|
||||
}
|
||||
|
||||
private void FindChildSolvers()
|
||||
{
|
||||
m_Solvers.Clear();
|
||||
|
||||
List<Solver2D> solvers = new List<Solver2D>();
|
||||
transform.GetComponentsInChildren<Solver2D>(true, solvers);
|
||||
|
||||
foreach (Solver2D solver in solvers)
|
||||
{
|
||||
if (solver.GetComponentInParent<IKManager2D>() == this)
|
||||
AddSolver(solver);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add Solver to the manager.
|
||||
/// </summary>
|
||||
/// <param name="solver">Solver to add.</param>
|
||||
public void AddSolver(Solver2D solver)
|
||||
{
|
||||
if (!m_Solvers.Contains(solver))
|
||||
{
|
||||
m_Solvers.Add(solver);
|
||||
AddSolverEditorData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove Solver from the manager.
|
||||
/// </summary>
|
||||
/// <param name="solver">Solver to remove.</param>
|
||||
public void RemoveSolver(Solver2D solver)
|
||||
{
|
||||
RemoveSolverEditorData(solver);
|
||||
m_Solvers.Remove(solver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Solvers in this manager.
|
||||
/// </summary>
|
||||
public void UpdateManager()
|
||||
{
|
||||
foreach (var solver in m_Solvers)
|
||||
{
|
||||
if (solver == null || !solver.isActiveAndEnabled)
|
||||
continue;
|
||||
|
||||
if (!solver.isValid)
|
||||
solver.Initialize();
|
||||
|
||||
solver.UpdateIK(weight);
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
UpdateManager();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal static Events.UnityEvent onDrawGizmos = new Events.UnityEvent();
|
||||
private void OnDrawGizmos() { onDrawGizmos.Invoke(); }
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
public partial class IKManager2D : MonoBehaviour
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
[Serializable]
|
||||
internal struct SolverEditorData
|
||||
{
|
||||
public Color color;
|
||||
public bool showGizmo;
|
||||
public static SolverEditorData defaultValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return new SolverEditorData(){ color = Color.green, showGizmo = true};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private List<SolverEditorData> m_SolverEditorData = new List<SolverEditorData>();
|
||||
|
||||
void OnEditorDataValidate()
|
||||
{
|
||||
var solverDataLength = m_SolverEditorData.Count;
|
||||
for (int i = solverDataLength; i < m_Solvers.Count; ++i)
|
||||
{
|
||||
AddSolverEditorData();
|
||||
}
|
||||
}
|
||||
|
||||
internal SolverEditorData GetSolverEditorData(Solver2D solver)
|
||||
{
|
||||
var index = m_Solvers.FindIndex(x => x == solver);
|
||||
if (index >= 0)
|
||||
{
|
||||
if(index >= m_SolverEditorData.Count)
|
||||
OnEditorDataValidate();
|
||||
return m_SolverEditorData[index];
|
||||
}
|
||||
|
||||
return SolverEditorData.defaultValue;
|
||||
}
|
||||
|
||||
void AddSolverEditorData()
|
||||
{
|
||||
m_SolverEditorData.Add(new SolverEditorData()
|
||||
{
|
||||
color = Color.green,
|
||||
showGizmo = true
|
||||
});
|
||||
}
|
||||
|
||||
void RemoveSolverEditorData(Solver2D solver)
|
||||
{
|
||||
var index = m_Solvers.FindIndex(x => x == solver);
|
||||
if(index >= 0)
|
||||
m_SolverEditorData.RemoveAt(index);
|
||||
}
|
||||
#else
|
||||
void OnEditorDataValidate(){}
|
||||
void AddSolverEditorData(){}
|
||||
void RemoveSolverEditorData(Solver2D solver){}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,70 @@
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// General utilities for 2D IK.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
public class IKUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a Unity Transform is a descendent of another Unity Transform.
|
||||
/// </summary>
|
||||
/// <param name="transform">Unity Transform to check.</param>
|
||||
/// <param name="ancestor">Unity Transform ancestor.</param>
|
||||
/// <returns>Returns true if the Unity Transform is a descendent. False otherwise.</returns>
|
||||
public static bool IsDescendentOf(Transform transform, Transform ancestor)
|
||||
{
|
||||
Debug.Assert(transform != null, "Transform is null");
|
||||
|
||||
Transform currentParent = transform.parent;
|
||||
|
||||
while (currentParent)
|
||||
{
|
||||
if (currentParent == ancestor)
|
||||
return true;
|
||||
|
||||
currentParent = currentParent.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hierarchy depth of a Unity Transform.
|
||||
/// </summary>
|
||||
/// <param name="transform">Unity Transform to check.</param>
|
||||
/// <returns>Integer value for hierarchy depth.</returns>
|
||||
public static int GetAncestorCount(Transform transform)
|
||||
{
|
||||
Debug.Assert(transform != null, "Transform is null");
|
||||
|
||||
int ancestorCount = 0;
|
||||
|
||||
while (transform.parent)
|
||||
{
|
||||
++ancestorCount;
|
||||
|
||||
transform = transform.parent;
|
||||
}
|
||||
|
||||
return ancestorCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum chain count for a IKChain2D.
|
||||
/// </summary>
|
||||
/// <param name="chain">IKChain2D to query.</param>
|
||||
/// <returns>Integer value for the maximum chain count.</returns>
|
||||
public static int GetMaxChainCount(IKChain2D chain)
|
||||
{
|
||||
int maxChainCount = 0;
|
||||
|
||||
if (chain.effector)
|
||||
maxChainCount = GetAncestorCount(chain.effector) + 1;
|
||||
|
||||
return maxChainCount;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Component for 2D Limb IK.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
[Solver2DMenuAttribute("Limb")]
|
||||
public class LimbSolver2D : Solver2D
|
||||
{
|
||||
[SerializeField]
|
||||
private IKChain2D m_Chain = new IKChain2D();
|
||||
|
||||
[SerializeField]
|
||||
private bool m_Flip;
|
||||
private Vector3[] m_Positions = new Vector3[3];
|
||||
private float[] m_Lengths = new float[2];
|
||||
private float[] m_Angles = new float[2];
|
||||
|
||||
/// <summary>
|
||||
/// Get Set for flip property.
|
||||
/// </summary>
|
||||
public bool flip
|
||||
{
|
||||
get { return m_Flip; }
|
||||
set { m_Flip = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override base class DoInitialize.
|
||||
/// </summary>
|
||||
protected override void DoInitialize()
|
||||
{
|
||||
m_Chain.transformCount = m_Chain.effector == null || IKUtility.GetAncestorCount(m_Chain.effector) < 2 ? 0 : 3;
|
||||
base.DoInitialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override base class GetChainCount.
|
||||
/// </summary>
|
||||
/// <returns>Always returns 1.</returns>
|
||||
protected override int GetChainCount()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override base class GetChain.
|
||||
/// </summary>
|
||||
/// <param name="index">Index to query.</param>
|
||||
/// <returns>Returns IKChain2D for the Solver.</returns>
|
||||
public override IKChain2D GetChain(int index)
|
||||
{
|
||||
return m_Chain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override base class DoPrepare.
|
||||
/// </summary>
|
||||
protected override void DoPrepare()
|
||||
{
|
||||
var lengths = m_Chain.lengths;
|
||||
m_Positions[0] = m_Chain.transforms[0].position;
|
||||
m_Positions[1] = m_Chain.transforms[1].position;
|
||||
m_Positions[2] = m_Chain.transforms[2].position;
|
||||
m_Lengths[0] = lengths[0];
|
||||
m_Lengths[1] = lengths[1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OVerride base class DoUpdateIK.
|
||||
/// </summary>
|
||||
/// <param name="effectorPositions">List of effector positions.</param>
|
||||
protected override void DoUpdateIK(List<Vector3> effectorPositions)
|
||||
{
|
||||
Vector3 effectorPosition = effectorPositions[0];
|
||||
Vector2 effectorLocalPosition2D = m_Chain.transforms[0].InverseTransformPoint(effectorPosition);
|
||||
effectorPosition = m_Chain.transforms[0].TransformPoint(effectorLocalPosition2D);
|
||||
|
||||
if (effectorLocalPosition2D.sqrMagnitude > 0f && Limb.Solve(effectorPosition, m_Lengths, m_Positions, ref m_Angles))
|
||||
{
|
||||
float flipSign = flip ? -1f : 1f;
|
||||
m_Chain.transforms[0].localRotation *= Quaternion.FromToRotation(Vector3.right, effectorLocalPosition2D) * Quaternion.FromToRotation(m_Chain.transforms[1].localPosition, Vector3.right);
|
||||
m_Chain.transforms[0].localRotation *= Quaternion.AngleAxis(flipSign * m_Angles[0], Vector3.forward);
|
||||
m_Chain.transforms[1].localRotation *= Quaternion.FromToRotation(Vector3.right, m_Chain.transforms[1].InverseTransformPoint(effectorPosition)) * Quaternion.FromToRotation(m_Chain.transforms[2].localPosition, Vector3.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract class for implementing a 2D IK Solver.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
public abstract class Solver2D : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private bool m_ConstrainRotation = true;
|
||||
[FormerlySerializedAs("m_RestoreDefaultPose")]
|
||||
[SerializeField]
|
||||
private bool m_SolveFromDefaultPose = true;
|
||||
[SerializeField][Range(0f, 1f)]
|
||||
private float m_Weight = 1f;
|
||||
|
||||
private Plane m_Plane;
|
||||
private List<Vector3> m_TargetPositions = new List<Vector3>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of IKChain2D in the solver.
|
||||
/// </summary>
|
||||
public int chainCount
|
||||
{
|
||||
get { return GetChainCount(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Set for rotation constrain property.
|
||||
/// </summary>
|
||||
public bool constrainRotation
|
||||
{
|
||||
get { return m_ConstrainRotation; }
|
||||
set { m_ConstrainRotation = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Set for restoring default pose.
|
||||
/// </summary>
|
||||
public bool solveFromDefaultPose
|
||||
{
|
||||
get { return m_SolveFromDefaultPose; }
|
||||
set { m_SolveFromDefaultPose = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Solver2D is in a valid state.
|
||||
/// </summary>
|
||||
public bool isValid
|
||||
{
|
||||
get { return Validate(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if all chains in the Solver has a target.
|
||||
/// </summary>
|
||||
public bool allChainsHaveTargets
|
||||
{
|
||||
get { return HasTargets(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get and Set Solver weights.
|
||||
/// </summary>
|
||||
public float weight
|
||||
{
|
||||
get { return m_Weight; }
|
||||
set { m_Weight = Mathf.Clamp01(value); }
|
||||
}
|
||||
|
||||
private void OnEnable() {}
|
||||
|
||||
/// <summary>
|
||||
/// Validate and initialize the Solver.
|
||||
/// </summary>
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
m_Weight = Mathf.Clamp01(m_Weight);
|
||||
|
||||
if (!isValid)
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private bool Validate()
|
||||
{
|
||||
for (int i = 0; i < GetChainCount(); ++i)
|
||||
{
|
||||
var chain = GetChain(i);
|
||||
if (!chain.isValid)
|
||||
return false;
|
||||
}
|
||||
return DoValidate();
|
||||
}
|
||||
|
||||
private bool HasTargets()
|
||||
{
|
||||
for (int i = 0; i < GetChainCount(); ++i)
|
||||
{
|
||||
var chain = GetChain(i);
|
||||
if (chain.target == null)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the solver.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
DoInitialize();
|
||||
|
||||
for (int i = 0; i < GetChainCount(); ++i)
|
||||
{
|
||||
var chain = GetChain(i);
|
||||
chain.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
private void Prepare()
|
||||
{
|
||||
var rootTransform = GetPlaneRootTransform();
|
||||
if (rootTransform != null)
|
||||
{
|
||||
m_Plane.normal = rootTransform.forward;
|
||||
m_Plane.distance = -Vector3.Dot(m_Plane.normal, rootTransform.position);
|
||||
}
|
||||
|
||||
for (int i = 0; i < GetChainCount(); ++i)
|
||||
{
|
||||
var chain = GetChain(i);
|
||||
var constrainTargetRotation = constrainRotation && chain.target != null;
|
||||
|
||||
if (m_SolveFromDefaultPose)
|
||||
chain.RestoreDefaultPose(constrainTargetRotation);
|
||||
}
|
||||
|
||||
DoPrepare();
|
||||
}
|
||||
|
||||
private void PrepareEffectorPositions()
|
||||
{
|
||||
m_TargetPositions.Clear();
|
||||
|
||||
for (int i = 0; i < GetChainCount(); ++i)
|
||||
{
|
||||
var chain = GetChain(i);
|
||||
|
||||
if (chain.target)
|
||||
m_TargetPositions.Add(chain.target.position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perfom Solver IK update.
|
||||
/// </summary>
|
||||
/// <param name="globalWeight">Weight for position solving.</param>
|
||||
public void UpdateIK(float globalWeight)
|
||||
{
|
||||
if(allChainsHaveTargets)
|
||||
{
|
||||
PrepareEffectorPositions();
|
||||
UpdateIK(m_TargetPositions, globalWeight);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform Solver IK update.
|
||||
/// </summary>
|
||||
/// <param name="positions">Positions of chain.</param>
|
||||
/// <param name="globalWeight">Weight for position solving.</param>
|
||||
public void UpdateIK(List<Vector3> positions, float globalWeight)
|
||||
{
|
||||
if(positions.Count != chainCount)
|
||||
return;
|
||||
|
||||
float finalWeight = globalWeight * weight;
|
||||
if (finalWeight == 0f)
|
||||
return;
|
||||
|
||||
if (!isValid)
|
||||
return;
|
||||
|
||||
Prepare();
|
||||
|
||||
if (finalWeight < 1f)
|
||||
StoreLocalRotations();
|
||||
|
||||
DoUpdateIK(positions);
|
||||
|
||||
if (constrainRotation)
|
||||
{
|
||||
for (int i = 0; i < GetChainCount(); ++i)
|
||||
{
|
||||
var chain = GetChain(i);
|
||||
|
||||
if (chain.target)
|
||||
chain.effector.rotation = chain.target.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
if (finalWeight < 1f)
|
||||
BlendFkToIk(finalWeight);
|
||||
}
|
||||
|
||||
private void StoreLocalRotations()
|
||||
{
|
||||
for (int i = 0; i < GetChainCount(); ++i)
|
||||
{
|
||||
var chain = GetChain(i);
|
||||
chain.StoreLocalRotations();
|
||||
}
|
||||
}
|
||||
|
||||
private void BlendFkToIk(float finalWeight)
|
||||
{
|
||||
for (int i = 0; i < GetChainCount(); ++i)
|
||||
{
|
||||
var chain = GetChain(i);
|
||||
var constrainTargetRotation = constrainRotation && chain.target != null;
|
||||
chain.BlendFkToIk(finalWeight, constrainTargetRotation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to return the IKChain2D at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">Index for IKChain2D.</param>
|
||||
/// <returns></returns>
|
||||
public abstract IKChain2D GetChain(int index);
|
||||
|
||||
/// <summary>
|
||||
/// OVerride to return the number of chains in the Solver
|
||||
/// </summary>
|
||||
/// <returns>Integer represents IKChain2D count.</returns>
|
||||
protected abstract int GetChainCount();
|
||||
|
||||
/// <summary>
|
||||
/// Override to perform Solver IK update
|
||||
/// </summary>
|
||||
/// <param name="effectorPositions">Position of the effectors.</param>
|
||||
protected abstract void DoUpdateIK(List<Vector3> effectorPositions);
|
||||
|
||||
/// <summary>
|
||||
/// Override to perform custom validation.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the Solver is in a valid state. False otherwise.</returns>
|
||||
protected virtual bool DoValidate() { return true; }
|
||||
|
||||
/// <summary>
|
||||
/// Override to perform initialize the solver
|
||||
/// </summary>
|
||||
protected virtual void DoInitialize() {}
|
||||
|
||||
/// <summary>
|
||||
/// Override to prepare the solver for update
|
||||
/// </summary>
|
||||
protected virtual void DoPrepare() {}
|
||||
|
||||
/// <summary>
|
||||
/// Override to return the root Unity Transform of the Solver. The default implementation returns the root
|
||||
/// transform of the first chain.
|
||||
/// </summary>
|
||||
/// <returns>Unity Transform that represents the root.</returns>
|
||||
protected virtual Transform GetPlaneRootTransform()
|
||||
{
|
||||
if (chainCount > 0)
|
||||
return GetChain(0).rootTransform;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a world position coordinate to the solver's plane space
|
||||
/// </summary>
|
||||
/// <param name="worldPosition">Vector3 representing world position</param>
|
||||
/// <returns>Converted position in solver's plane</returns>
|
||||
protected Vector3 GetPointOnSolverPlane(Vector3 worldPosition)
|
||||
{
|
||||
return GetPlaneRootTransform().InverseTransformPoint(m_Plane.ClosestPointOnPlane(worldPosition));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a position from solver's plane to world coordinate
|
||||
/// </summary>
|
||||
/// <param name="planePoint">Vector3 representing a position in the Solver's plane.</param>
|
||||
/// <returns>Converted position to world coordinate.</returns>
|
||||
protected Vector3 GetWorldPositionFromSolverPlanePoint(Vector2 planePoint)
|
||||
{
|
||||
return GetPlaneRootTransform().TransformPoint(planePoint);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute to add a menu item in IKManager2D to create the Solver.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class Solver2DMenuAttribute : Attribute
|
||||
{
|
||||
string m_MenuPath;
|
||||
|
||||
/// <summary>
|
||||
/// Menu path.
|
||||
/// </summary>
|
||||
public string menuPath
|
||||
{
|
||||
get { return m_MenuPath; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="_menuPath">Menu item path.</param>
|
||||
public Solver2DMenuAttribute(string _menuPath)
|
||||
{
|
||||
m_MenuPath = _menuPath;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility for 2D based Cyclic Coordinate Descent (CCD) IK Solver.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
public static class CCD2D
|
||||
{
|
||||
/// <summary>
|
||||
/// Solve IK Chain based on CCD.
|
||||
/// </summary>
|
||||
/// <param name="targetPosition">Target position.</param>
|
||||
/// <param name="forward">Forward vector for solver.</param>
|
||||
/// <param name="solverLimit">Solver iteration count.</param>
|
||||
/// <param name="tolerance">Target position's tolerance.</param>
|
||||
/// <param name="velocity">Velocity towards target position.</param>
|
||||
/// <param name="positions">Chain positions.</param>
|
||||
/// <returns>Returns true if solver successfully completes within iteration limit. False otherwise.</returns>
|
||||
public static bool Solve(Vector3 targetPosition, Vector3 forward, int solverLimit, float tolerance, float velocity, ref Vector3[] positions)
|
||||
{
|
||||
int last = positions.Length - 1;
|
||||
int iterations = 0;
|
||||
float sqrTolerance = tolerance * tolerance;
|
||||
float sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude;
|
||||
while (sqrDistanceToTarget > sqrTolerance)
|
||||
{
|
||||
DoIteration(targetPosition, forward, last, velocity, ref positions);
|
||||
sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude;
|
||||
if (++iterations >= solverLimit)
|
||||
break;
|
||||
}
|
||||
return iterations != 0;
|
||||
}
|
||||
|
||||
static void DoIteration(Vector3 targetPosition, Vector3 forward, int last, float velocity, ref Vector3[] positions)
|
||||
{
|
||||
for (int i = last - 1; i >= 0; --i)
|
||||
{
|
||||
Vector3 toTarget = targetPosition - positions[i];
|
||||
Vector3 toLast = positions[last] - positions[i];
|
||||
|
||||
float angle = Vector3.SignedAngle(toLast, toTarget, forward);
|
||||
angle = Mathf.Lerp(0f, angle, velocity);
|
||||
|
||||
Quaternion deltaRotation = Quaternion.AngleAxis(angle, forward);
|
||||
for (int j = last; j > i; --j)
|
||||
positions[j] = RotatePositionFrom(positions[j], positions[i], deltaRotation);
|
||||
}
|
||||
}
|
||||
|
||||
static Vector3 RotatePositionFrom(Vector3 position, Vector3 pivot, Quaternion rotation)
|
||||
{
|
||||
Vector3 v = position - pivot;
|
||||
v = rotation * v;
|
||||
return pivot + v;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Structure to store FABRIK Chain data.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
public struct FABRIKChain2D
|
||||
{
|
||||
public Vector2 first
|
||||
{
|
||||
get { return positions[0]; }
|
||||
}
|
||||
|
||||
public Vector2 last
|
||||
{
|
||||
get { return positions[positions.Length - 1]; }
|
||||
}
|
||||
|
||||
public Vector2 origin;
|
||||
public Vector2 target;
|
||||
public float sqrTolerance;
|
||||
public Vector2[] positions;
|
||||
public float[] lengths;
|
||||
public int[] subChainIndices;
|
||||
public Vector3[] worldPositions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility for 2D Forward And Backward Reaching Inverse Kinematics (FABRIK) IK Solver.
|
||||
/// </summary>
|
||||
public static class FABRIK2D
|
||||
{
|
||||
/// <summary>
|
||||
/// Solve IK based on FABRIK
|
||||
/// </summary>
|
||||
/// <param name="targetPosition">Target position.</param>
|
||||
/// <param name="solverLimit">Solver iteration count.</param>
|
||||
/// <param name="tolerance">Target position's tolerance.</param>
|
||||
/// <param name="lengths">Length of the chains.</param>
|
||||
/// <param name="positions">Chain positions.</param>
|
||||
/// <returns>Returns true if solver successfully completes within iteration limit. False otherwise.</returns>
|
||||
public static bool Solve(Vector2 targetPosition, int solverLimit, float tolerance, float[] lengths, ref Vector2[] positions)
|
||||
{
|
||||
int last = positions.Length - 1;
|
||||
int iterations = 0;
|
||||
float sqrTolerance = tolerance * tolerance;
|
||||
float sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude;
|
||||
Vector2 originPosition = positions[0];
|
||||
while (sqrDistanceToTarget > sqrTolerance)
|
||||
{
|
||||
Forward(targetPosition, lengths, ref positions);
|
||||
Backward(originPosition, lengths, ref positions);
|
||||
sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude;
|
||||
if (++iterations >= solverLimit)
|
||||
break;
|
||||
}
|
||||
|
||||
// Return whether positions have changed
|
||||
return iterations != 0;
|
||||
}
|
||||
|
||||
public static bool SolveChain(int solverLimit, ref FABRIKChain2D[] chains)
|
||||
{
|
||||
// Do a quick validation of the end points that it has not been solved
|
||||
if (ValidateChain(chains))
|
||||
return false;
|
||||
|
||||
// Validation failed, solve chain
|
||||
for (int iterations = 0; iterations < solverLimit; ++iterations)
|
||||
{
|
||||
SolveForwardsChain(0, ref chains);
|
||||
// Break if solution is solved
|
||||
if (!SolveBackwardsChain(0, ref chains))
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ValidateChain(FABRIKChain2D[] chains)
|
||||
{
|
||||
foreach (var chain in chains)
|
||||
{
|
||||
if (chain.subChainIndices.Length == 0 && (chain.target - chain.last).sqrMagnitude > chain.sqrTolerance)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void SolveForwardsChain(int idx, ref FABRIKChain2D[] chains)
|
||||
{
|
||||
var target = chains[idx].target;
|
||||
if (chains[idx].subChainIndices.Length > 0)
|
||||
{
|
||||
target = Vector2.zero;
|
||||
for (int i = 0; i < chains[idx].subChainIndices.Length; ++i)
|
||||
{
|
||||
var childIdx = chains[idx].subChainIndices[i];
|
||||
SolveForwardsChain(childIdx, ref chains);
|
||||
target += chains[childIdx].first;
|
||||
}
|
||||
target = target / chains[idx].subChainIndices.Length;
|
||||
}
|
||||
Forward(target, chains[idx].lengths, ref chains[idx].positions);
|
||||
}
|
||||
|
||||
static bool SolveBackwardsChain(int idx, ref FABRIKChain2D[] chains)
|
||||
{
|
||||
bool notSolved = false;
|
||||
Backward(chains[idx].origin, chains[idx].lengths, ref chains[idx].positions);
|
||||
for (int i = 0; i < chains[idx].subChainIndices.Length; ++i)
|
||||
{
|
||||
var childIdx = chains[idx].subChainIndices[i];
|
||||
chains[childIdx].origin = chains[idx].last;
|
||||
notSolved |= SolveBackwardsChain(childIdx, ref chains);
|
||||
}
|
||||
// Check if end point has reached the target
|
||||
if (chains[idx].subChainIndices.Length == 0)
|
||||
{
|
||||
notSolved |= (chains[idx].target - chains[idx].last).sqrMagnitude > chains[idx].sqrTolerance;
|
||||
}
|
||||
return notSolved;
|
||||
}
|
||||
|
||||
static void Forward(Vector2 targetPosition, float[] lengths, ref Vector2[] positions)
|
||||
{
|
||||
var last = positions.Length - 1;
|
||||
positions[last] = targetPosition;
|
||||
for (int i = last - 1; i >= 0; --i)
|
||||
{
|
||||
var r = positions[i + 1] - positions[i];
|
||||
var l = lengths[i] / r.magnitude;
|
||||
var position = (1f - l) * positions[i + 1] + l * positions[i];
|
||||
positions[i] = position;
|
||||
}
|
||||
}
|
||||
|
||||
static void Backward(Vector2 originPosition, float[] lengths, ref Vector2[] positions)
|
||||
{
|
||||
positions[0] = originPosition;
|
||||
var last = positions.Length - 1;
|
||||
for (int i = 0; i < last; ++i)
|
||||
{
|
||||
var r = positions[i + 1] - positions[i];
|
||||
var l = lengths[i] / r.magnitude;
|
||||
var position = (1f - l) * positions[i] + l * positions[i + 1];
|
||||
positions[i + 1] = position;
|
||||
}
|
||||
}
|
||||
|
||||
// For constraints
|
||||
static Vector2 ValidateJoint(Vector2 endPosition, Vector2 startPosition, Vector2 right, float min, float max)
|
||||
{
|
||||
var localDifference = endPosition - startPosition;
|
||||
var angle = Vector2.SignedAngle(right, localDifference);
|
||||
var validatedPosition = endPosition;
|
||||
if (angle < min)
|
||||
{
|
||||
var minRotation = Quaternion.Euler(0f, 0f, min);
|
||||
validatedPosition = startPosition + (Vector2)(minRotation * right * localDifference.magnitude);
|
||||
}
|
||||
else if (angle > max)
|
||||
{
|
||||
var maxRotation = Quaternion.Euler(0f, 0f, max);
|
||||
validatedPosition = startPosition + (Vector2)(maxRotation * right * localDifference.magnitude);
|
||||
}
|
||||
return validatedPosition;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.U2D.IK
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility for 2D Limb IK Solver.
|
||||
/// </summary>
|
||||
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
|
||||
public static class Limb
|
||||
{
|
||||
/// <summary>
|
||||
/// Solve based on Limb IK
|
||||
/// </summary>
|
||||
/// <param name="targetPosition">Target position.</param>
|
||||
/// <param name="lengths">Length of the chains.</param>
|
||||
/// <param name="positions">Chain positions.</param>
|
||||
/// <param name="outAngles">Output angles for the chain's position.</param>
|
||||
/// <returns>Always returns true.</returns>
|
||||
public static bool Solve(Vector3 targetPosition, float[] lengths, Vector3[] positions, ref float[] outAngles)
|
||||
{
|
||||
outAngles[0] = 0f;
|
||||
outAngles[1] = 0f;
|
||||
|
||||
if (lengths[0] == 0f || lengths[1] == 0f)
|
||||
return false;
|
||||
|
||||
Vector3 startToEnd = targetPosition - positions[0];
|
||||
float distanceMagnitude = startToEnd.magnitude;
|
||||
float sqrDistance = startToEnd.sqrMagnitude;
|
||||
|
||||
float sqrParentLength = (lengths[0] * lengths[0]);
|
||||
float sqrTargetLength = (lengths[1] * lengths[1]);
|
||||
|
||||
float angle0Cos = (sqrDistance + sqrParentLength - sqrTargetLength) / (2f * lengths[0] * distanceMagnitude);
|
||||
float angle1Cos = (sqrDistance - sqrParentLength - sqrTargetLength) / (2f * lengths[0] * lengths[1]);
|
||||
|
||||
if ((angle0Cos >= -1f && angle0Cos <= 1f) && (angle1Cos >= -1f && angle1Cos <= 1f))
|
||||
{
|
||||
outAngles[0] = Mathf.Acos(angle0Cos) * Mathf.Rad2Deg;
|
||||
outAngles[1] = Mathf.Acos(angle1Cos) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Unity.2D.IK.Runtime",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": []
|
||||
}
|
Reference in New Issue
Block a user