initial upload
This commit is contained in:
@ -0,0 +1,189 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The base abstract class for all Rotation limits. Contains common functionality and static helper methods
|
||||
/// </summary>
|
||||
public abstract class RotationLimit : MonoBehaviour
|
||||
{
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The main axis of the rotation limit.
|
||||
/// </summary>
|
||||
public Vector3 axis = Vector3.forward;
|
||||
|
||||
/// <summary>
|
||||
/// Map the zero rotation point to the current local rotation of this gameobject.
|
||||
/// </summary>
|
||||
public void SetDefaultLocalRotation()
|
||||
{
|
||||
defaultLocalRotation = transform.localRotation;
|
||||
defaultLocalRotationSet = true;
|
||||
defaultLocalRotationOverride = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map the zero rotation point to the specified rotation.
|
||||
/// </summary>
|
||||
public void SetDefaultLocalRotation(Quaternion localRotation)
|
||||
{
|
||||
defaultLocalRotation = localRotation;
|
||||
defaultLocalRotationSet = true;
|
||||
defaultLocalRotationOverride = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the limited local rotation.
|
||||
/// </summary>
|
||||
public Quaternion GetLimitedLocalRotation(Quaternion localRotation, out bool changed)
|
||||
{
|
||||
// Making sure the Rotation Limit is initiated
|
||||
if (!initiated) Awake();
|
||||
|
||||
// Subtracting defaultLocalRotation
|
||||
Quaternion rotation = Quaternion.Inverse(defaultLocalRotation) * localRotation;
|
||||
|
||||
Quaternion limitedRotation = LimitRotation(rotation);
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
limitedRotation = Quaternion.Normalize(limitedRotation);
|
||||
#endif
|
||||
changed = limitedRotation != rotation;
|
||||
|
||||
if (!changed) return localRotation;
|
||||
|
||||
// Apply defaultLocalRotation back on
|
||||
return defaultLocalRotation * limitedRotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply the rotation limit to transform.localRotation. Returns true if the limit has changed the rotation.
|
||||
/// </summary>
|
||||
public bool Apply()
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
transform.localRotation = GetLimitedLocalRotation(transform.localRotation, out changed);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable this instance making sure it is initiated. Use this if you intend to manually control the updating of this Rotation Limit.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
if (initiated)
|
||||
{
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Awake();
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
/*
|
||||
* An arbitrary secondary axis that we get by simply switching the axes
|
||||
* */
|
||||
public Vector3 secondaryAxis { get { return new Vector3(axis.y, axis.z, axis.x); } }
|
||||
|
||||
/*
|
||||
* Cross product of axis and secondaryAxis
|
||||
* */
|
||||
public Vector3 crossAxis { get { return Vector3.Cross(axis, secondaryAxis); } }
|
||||
|
||||
/*
|
||||
* The default local rotation of the gameobject. By default stored in Awake.
|
||||
* */
|
||||
[HideInInspector] public Quaternion defaultLocalRotation;
|
||||
|
||||
public bool defaultLocalRotationOverride { get; private set; }
|
||||
|
||||
protected abstract Quaternion LimitRotation(Quaternion rotation);
|
||||
|
||||
private bool initiated;
|
||||
private bool applicationQuit;
|
||||
private bool defaultLocalRotationSet;
|
||||
|
||||
/*
|
||||
* Initiating the Rotation Limit
|
||||
* */
|
||||
void Awake()
|
||||
{
|
||||
// Store the local rotation to map the zero rotation point to the current rotation
|
||||
if (!defaultLocalRotationSet) SetDefaultLocalRotation();
|
||||
|
||||
if (axis == Vector3.zero) Debug.LogError("Axis is Vector3.zero.");
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Using LateUpdate here because you most probably want to apply the limits after animation.
|
||||
* If you need precise control over the execution order, disable this script and call Apply() whenever you need
|
||||
* */
|
||||
void LateUpdate()
|
||||
{
|
||||
Apply();
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs the warning if no other warning has beed logged in this session.
|
||||
* */
|
||||
public void LogWarning(string message)
|
||||
{
|
||||
Warning.Log(message, transform);
|
||||
}
|
||||
|
||||
#region Static helper methods for all Rotation Limits
|
||||
|
||||
/*
|
||||
* Limits rotation to a single degree of freedom (along axis)
|
||||
* */
|
||||
protected static Quaternion Limit1DOF(Quaternion rotation, Vector3 axis)
|
||||
{
|
||||
return Quaternion.FromToRotation(rotation * axis, axis) * rotation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Applies uniform twist limit to the rotation
|
||||
* */
|
||||
protected static Quaternion LimitTwist(Quaternion rotation, Vector3 axis, Vector3 orthoAxis, float twistLimit)
|
||||
{
|
||||
twistLimit = Mathf.Clamp(twistLimit, 0, 180);
|
||||
if (twistLimit >= 180) return rotation;
|
||||
|
||||
Vector3 normal = rotation * axis;
|
||||
Vector3 orthoTangent = orthoAxis;
|
||||
Vector3.OrthoNormalize(ref normal, ref orthoTangent);
|
||||
|
||||
Vector3 rotatedOrthoTangent = rotation * orthoAxis;
|
||||
Vector3.OrthoNormalize(ref normal, ref rotatedOrthoTangent);
|
||||
|
||||
Quaternion fixedRotation = Quaternion.FromToRotation(rotatedOrthoTangent, orthoTangent) * rotation;
|
||||
|
||||
if (twistLimit <= 0) return fixedRotation;
|
||||
|
||||
// Rotate from zero twist to free twist by the limited angle
|
||||
return Quaternion.RotateTowards(fixedRotation, rotation, twistLimit);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the angle between two vectors on a plane with the specified normal
|
||||
* */
|
||||
protected static float GetOrthogonalAngle(Vector3 v1, Vector3 v2, Vector3 normal)
|
||||
{
|
||||
Vector3.OrthoNormalize(ref normal, ref v1);
|
||||
Vector3.OrthoNormalize(ref normal, ref v2);
|
||||
return Vector3.Angle(v1, v2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 197a3a7b95f0e4ac48f171363db95b5b
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,82 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Simple angular rotation limit.
|
||||
/// </summary>
|
||||
[HelpURL("http://www.root-motion.com/finalikdox/html/page14.html")]
|
||||
[AddComponentMenu("Scripts/RootMotion.FinalIK/Rotation Limits/Rotation Limit Angle")]
|
||||
public class RotationLimitAngle : RotationLimit {
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
private void OpenUserManual() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page14.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
private void OpenScriptReference() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_rotation_limit_angle.html");
|
||||
}
|
||||
|
||||
// Link to the Final IK Google Group
|
||||
[ContextMenu("Support Group")]
|
||||
void SupportGroup() {
|
||||
Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
|
||||
}
|
||||
|
||||
// Link to the Final IK Asset Store thread in the Unity Community
|
||||
[ContextMenu("Asset Store Thread")]
|
||||
void ASThread() {
|
||||
Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
|
||||
}
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The swing limit.
|
||||
/// </summary>
|
||||
[Range(0f, 180f)] public float limit = 45;
|
||||
/// <summary>
|
||||
/// Limit of twist rotation around the main axis.
|
||||
/// </summary>
|
||||
[Range(0f, 180f)] public float twistLimit = 180;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
/*
|
||||
* Limits the rotation in the local space of this instance's Transform.
|
||||
* */
|
||||
protected override Quaternion LimitRotation(Quaternion rotation) {
|
||||
// Subtracting off-limits swing
|
||||
Quaternion swing = LimitSwing(rotation);
|
||||
|
||||
// Apply twist limits
|
||||
return LimitTwist(swing, axis, secondaryAxis, twistLimit);
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply swing limits
|
||||
* */
|
||||
private Quaternion LimitSwing(Quaternion rotation) {
|
||||
if (axis == Vector3.zero) return rotation; // Ignore with zero axes
|
||||
if (rotation == Quaternion.identity) return rotation; // Assuming initial rotation is in the reachable area
|
||||
if (limit >= 180) return rotation;
|
||||
|
||||
Vector3 swingAxis = rotation * axis;
|
||||
|
||||
// Get the limited swing axis
|
||||
Quaternion swingRotation = Quaternion.FromToRotation(axis, swingAxis);
|
||||
Quaternion limitedSwingRotation = Quaternion.RotateTowards(Quaternion.identity, swingRotation, limit);
|
||||
|
||||
// Rotation from current(illegal) swing rotation to the limited(legal) swing rotation
|
||||
Quaternion toLimits = Quaternion.FromToRotation(swingAxis, limitedSwingRotation * axis);
|
||||
|
||||
// Subtract the illegal rotation
|
||||
return toLimits * rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45281828b4c9247558c7c695124d6877
|
||||
labels:
|
||||
- Turrets
|
||||
- RotationLimits
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7e0729a4bf8e24db994fb1390c510a4e, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,84 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// The hinge rotation limit limits the rotation to 1 degree of freedom around Axis. This rotation limit is additive which means the limits can exceed 360 degrees.
|
||||
/// </summary>
|
||||
[HelpURL("http://www.root-motion.com/finalikdox/html/page14.html")]
|
||||
[AddComponentMenu("Scripts/RootMotion.FinalIK/Rotation Limits/Rotation Limit Hinge")]
|
||||
public class RotationLimitHinge : RotationLimit {
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
private void OpenUserManual() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page14.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
private void OpenScriptReference() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_rotation_limit_hinge.html");
|
||||
}
|
||||
|
||||
// Link to the Final IK Google Group
|
||||
[ContextMenu("Support Group")]
|
||||
void SupportGroup() {
|
||||
Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
|
||||
}
|
||||
|
||||
// Link to the Final IK Asset Store thread in the Unity Community
|
||||
[ContextMenu("Asset Store Thread")]
|
||||
void ASThread() {
|
||||
Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
|
||||
}
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Should the rotation be limited around the axis?
|
||||
/// </summary>
|
||||
public bool useLimits = true;
|
||||
/// <summary>
|
||||
/// The min limit around the axis.
|
||||
/// </summary>
|
||||
public float min = -45;
|
||||
/// <summary>
|
||||
/// The max limit around the axis.
|
||||
/// </summary>
|
||||
public float max = 90;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
/*
|
||||
* Limits the rotation in the local space of this instance's Transform.
|
||||
* */
|
||||
protected override Quaternion LimitRotation(Quaternion rotation) {
|
||||
return LimitHinge(rotation);
|
||||
}
|
||||
|
||||
[HideInInspector] public float zeroAxisDisplayOffset; // Angular offset of the scene view display of the Hinge rotation limit
|
||||
|
||||
private float lastAngle;
|
||||
|
||||
/*
|
||||
* Apply the hinge rotation limit
|
||||
* */
|
||||
private Quaternion LimitHinge(Quaternion rotation) {
|
||||
// If limit is zero return rotation fixed to axis
|
||||
if (min == 0 && max == 0 && useLimits) return Quaternion.AngleAxis(0, axis);
|
||||
|
||||
// Get 1 degree of freedom rotation along axis
|
||||
Quaternion free1DOF = Limit1DOF(rotation, axis);
|
||||
if (!useLimits) return free1DOF;
|
||||
|
||||
Quaternion workingSpace = Quaternion.Inverse(Quaternion.AngleAxis(lastAngle, axis) * Quaternion.LookRotation(secondaryAxis, axis));
|
||||
Vector3 d = workingSpace * free1DOF * secondaryAxis;
|
||||
float deltaAngle = Mathf.Atan2(d.x, d.z) * Mathf.Rad2Deg;
|
||||
|
||||
lastAngle = Mathf.Clamp(lastAngle + deltaAngle, min, max);
|
||||
return Quaternion.AngleAxis(lastAngle, axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 484718f2c4ab849829491782b508958a
|
||||
labels:
|
||||
- Turrets
|
||||
- RotationLimits
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: ac7ab65192f5c4348bdd26925b0bb42d, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,333 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Using a spherical polygon to limit the range of rotation on universal and ball-and-socket joints. A reach cone is specified as a spherical polygon
|
||||
/// on the surface of a a reach sphere that defines all positions the longitudinal segment axis beyond the joint can take.
|
||||
///
|
||||
/// This class is based on the "Fast and Easy Reach-Cone Joint Limits" paper by Jane Wilhelms and Allen Van Gelder.
|
||||
/// Computer Science Dept., University of California, Santa Cruz, CA 95064. August 2, 2001
|
||||
/// http://users.soe.ucsc.edu/~avg/Papers/jtl.pdf
|
||||
///
|
||||
/// </summary>
|
||||
[HelpURL("http://www.root-motion.com/finalikdox/html/page14.html")]
|
||||
[AddComponentMenu("Scripts/RootMotion.FinalIK/Rotation Limits/Rotation Limit Polygonal")]
|
||||
public class RotationLimitPolygonal : RotationLimit {
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
private void OpenUserManual() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page14.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
private void OpenScriptReference() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_rotation_limit_polygonal.html");
|
||||
}
|
||||
|
||||
// Link to the Final IK Google Group
|
||||
[ContextMenu("Support Group")]
|
||||
void SupportGroup() {
|
||||
Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
|
||||
}
|
||||
|
||||
// Link to the Final IK Asset Store thread in the Unity Community
|
||||
[ContextMenu("Asset Store Thread")]
|
||||
void ASThread() {
|
||||
Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
|
||||
}
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Limit of twist rotation around the main axis.
|
||||
/// </summary>
|
||||
[Range(0f, 180f)] public float twistLimit = 180;
|
||||
/// <summary>
|
||||
/// The number of smoothing iterations applied to the polygon.
|
||||
/// </summary>
|
||||
[Range(0, 3)] public int smoothIterations = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the limit points and recalculates the reach cones.
|
||||
/// </summary>
|
||||
/// <param name='_points'>
|
||||
/// _points.
|
||||
/// </param>
|
||||
public void SetLimitPoints(LimitPoint[] points) {
|
||||
if (points.Length < 3) {
|
||||
LogWarning("The polygon must have at least 3 Limit Points.");
|
||||
return;
|
||||
}
|
||||
this.points = points;
|
||||
BuildReachCones();
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
/*
|
||||
* Limits the rotation in the local space of this instance's Transform.
|
||||
* */
|
||||
protected override Quaternion LimitRotation(Quaternion rotation) {
|
||||
if (reachCones.Length == 0) Start();
|
||||
|
||||
// Subtracting off-limits swing
|
||||
Quaternion swing = LimitSwing(rotation);
|
||||
|
||||
// Apply twist limits
|
||||
return LimitTwist(swing, axis, secondaryAxis, twistLimit);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tetrahedron composed of 2 Limit points, the origin and an axis point.
|
||||
* */
|
||||
[System.Serializable]
|
||||
public class ReachCone {
|
||||
public Vector3[] tetrahedron;
|
||||
public float volume;
|
||||
public Vector3 S, B;
|
||||
|
||||
public Vector3 o { get { return tetrahedron[0]; }}
|
||||
public Vector3 a { get { return tetrahedron[1]; }}
|
||||
public Vector3 b { get { return tetrahedron[2]; }}
|
||||
public Vector3 c { get { return tetrahedron[3]; }}
|
||||
|
||||
public ReachCone(Vector3 _o, Vector3 _a, Vector3 _b, Vector3 _c) {
|
||||
this.tetrahedron = new Vector3[4];
|
||||
this.tetrahedron[0] = _o; // Origin
|
||||
this.tetrahedron[1] = _a; // Axis
|
||||
this.tetrahedron[2] = _b; // Limit Point 1
|
||||
this.tetrahedron[3] = _c; // Limit Point 2
|
||||
|
||||
this.volume = 0;
|
||||
this.S = Vector3.zero;
|
||||
this.B = Vector3.zero;
|
||||
}
|
||||
|
||||
public bool isValid { get { return volume > 0; }}
|
||||
|
||||
public void Calculate() {
|
||||
Vector3 crossAB = Vector3.Cross(a, b);
|
||||
volume = Vector3.Dot(crossAB, c) / 6.0f;
|
||||
|
||||
S = Vector3.Cross(a, b).normalized;
|
||||
B = Vector3.Cross(b, c).normalized;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The points defining the polygon
|
||||
* */
|
||||
[System.Serializable]
|
||||
public class LimitPoint {
|
||||
public Vector3 point;
|
||||
public float tangentWeight;
|
||||
|
||||
public LimitPoint() {
|
||||
this.point = Vector3.forward;
|
||||
this.tangentWeight = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[HideInInspector] public LimitPoint[] points;
|
||||
[HideInInspector] public Vector3[] P;
|
||||
[HideInInspector] public ReachCone[] reachCones = new ReachCone[0];
|
||||
|
||||
void Start() {
|
||||
if (points.Length < 3) ResetToDefault();
|
||||
|
||||
// Check if Limit Points are valid
|
||||
for (int i = 0; i < reachCones.Length; i++) {
|
||||
if (!reachCones[i].isValid) {
|
||||
if (smoothIterations <= 0) {
|
||||
int nextPoint = 0;
|
||||
if (i < reachCones.Length - 1) nextPoint = i + 1;
|
||||
else nextPoint = 0;
|
||||
LogWarning("Reach Cone {point " + i + ", point " + nextPoint + ", Origin} has negative volume. Make sure Axis vector is in the reachable area and the polygon is convex.");
|
||||
} else LogWarning("One of the Reach Cones in the polygon has negative volume. Make sure Axis vector is in the reachable area and the polygon is convex.");
|
||||
}
|
||||
}
|
||||
|
||||
axis = axis.normalized;
|
||||
}
|
||||
|
||||
#region Precalculations
|
||||
|
||||
/*
|
||||
* Apply the default initial setup of 4 Limit Points
|
||||
* */
|
||||
public void ResetToDefault() {
|
||||
points = new LimitPoint[4];
|
||||
for (int i = 0; i < points.Length; i++) points[i] = new LimitPoint();
|
||||
|
||||
Quaternion swing1Rotation = Quaternion.AngleAxis(45, Vector3.right);
|
||||
Quaternion swing2Rotation = Quaternion.AngleAxis(45, Vector3.up);
|
||||
|
||||
points[0].point = (swing1Rotation * swing2Rotation) * axis;
|
||||
points[1].point = (Quaternion.Inverse(swing1Rotation) * swing2Rotation) * axis;
|
||||
points[2].point = (Quaternion.Inverse(swing1Rotation) * Quaternion.Inverse(swing2Rotation)) * axis;
|
||||
points[3].point = (swing1Rotation * Quaternion.Inverse(swing2Rotation)) * axis;
|
||||
|
||||
BuildReachCones();
|
||||
}
|
||||
|
||||
/*
|
||||
* Recalculate reach cones if the Limit Points have changed
|
||||
* */
|
||||
public void BuildReachCones() {
|
||||
smoothIterations = Mathf.Clamp(smoothIterations, 0, 3);
|
||||
|
||||
// Make another array for the points so that they could be smoothed without changing the initial points
|
||||
P = new Vector3[points.Length];
|
||||
for (int i = 0; i < points.Length; i++) P[i] = points[i].point.normalized;
|
||||
|
||||
for (int i = 0; i < smoothIterations; i++) P = SmoothPoints();
|
||||
|
||||
// Calculating the reach cones
|
||||
reachCones = new ReachCone[P.Length];
|
||||
for (int i = 0; i < reachCones.Length - 1; i++) {
|
||||
reachCones[i] = new ReachCone(Vector3.zero, axis.normalized, P[i], P[i + 1]);
|
||||
}
|
||||
|
||||
reachCones[P.Length - 1] = new ReachCone(Vector3.zero, axis.normalized, P[P.Length - 1], P[0]);
|
||||
|
||||
for (int i = 0; i < reachCones.Length; i++) reachCones[i].Calculate();
|
||||
}
|
||||
|
||||
/*
|
||||
* Automatically adds virtual limit points to smooth the polygon
|
||||
* */
|
||||
private Vector3[] SmoothPoints() {
|
||||
// Create the new point array with double length
|
||||
Vector3[] Q = new Vector3[P.Length * 2];
|
||||
|
||||
float scalar = GetScalar(P.Length); // Get the constant used for interpolation
|
||||
|
||||
// Project all the existing points on a plane that is tangent to the unit sphere at the Axis point
|
||||
for (int i = 0; i < Q.Length; i+= 2) Q[i] = PointToTangentPlane(P[i / 2], 1);
|
||||
|
||||
// Interpolate the new points
|
||||
for (int i = 1; i < Q.Length; i+= 2) {
|
||||
Vector3 minus2 = Vector3.zero;
|
||||
Vector3 plus1 = Vector3.zero;
|
||||
Vector3 plus2 = Vector3.zero;
|
||||
|
||||
if (i > 1 && i < Q.Length - 2) {
|
||||
minus2 = Q[i - 2];
|
||||
plus2 = Q[i + 1];
|
||||
} else if (i == 1) {
|
||||
minus2 = Q[Q.Length - 2];
|
||||
plus2 = Q[i + 1];
|
||||
} else if (i == Q.Length - 1) {
|
||||
minus2 = Q[i - 2];
|
||||
plus2 = Q[0];
|
||||
}
|
||||
|
||||
if (i < Q.Length - 1) plus1 = Q[i + 1];
|
||||
else plus1 = Q[0];
|
||||
|
||||
int t = Q.Length / points.Length;
|
||||
|
||||
// Interpolation
|
||||
Q[i] = (0.5f * (Q[i - 1] + plus1)) + (scalar * points[i / t].tangentWeight * (plus1 - minus2)) + (scalar * points[i / t].tangentWeight * (Q[i - 1] - plus2));
|
||||
}
|
||||
|
||||
// Project the points from tangent plane to the sphere
|
||||
for (int i = 0; i < Q.Length; i++) Q[i] = TangentPointToSphere(Q[i], 1);
|
||||
|
||||
return Q;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns scalar values used for interpolating smooth positions between limit points
|
||||
* */
|
||||
private float GetScalar(int k) {
|
||||
// Values k (number of points) == 3, 4 and 6 are calculated by analytical geometry, values 5 and 7 were estimated by interpolation
|
||||
if (k <= 3) return .1667f;
|
||||
if (k == 4) return .1036f;
|
||||
if (k == 5) return .0850f;
|
||||
if (k == 6) return .0773f;
|
||||
if (k == 7) return .0700f;
|
||||
return .0625f; // Cubic spline fit
|
||||
}
|
||||
|
||||
/*
|
||||
* Project a point on the sphere to a plane that is tangent to the unit sphere at the Axis point
|
||||
* */
|
||||
private Vector3 PointToTangentPlane(Vector3 p, float r) {
|
||||
float d = Vector3.Dot(axis, p);
|
||||
float u = (2 * r * r) / ((r * r) + d);
|
||||
return (u * p) + ((1 - u) * -axis);
|
||||
}
|
||||
|
||||
/*
|
||||
* Project a point on the tangent plane to the sphere
|
||||
* */
|
||||
private Vector3 TangentPointToSphere(Vector3 q, float r) {
|
||||
float d = Vector3.Dot(q - axis, q - axis);
|
||||
float u = (4 * r * r) / ((4 * r * r) + d);
|
||||
return (u * q) + ((1 - u) * -axis);
|
||||
}
|
||||
|
||||
#endregion Precalculations
|
||||
|
||||
#region Runtime calculations
|
||||
|
||||
/*
|
||||
* Applies Swing limit to the rotation
|
||||
* */
|
||||
private Quaternion LimitSwing(Quaternion rotation) {
|
||||
if (rotation == Quaternion.identity) return rotation; // Assuming initial rotation is in the reachable area
|
||||
|
||||
Vector3 L = rotation * axis; // Test this vector against the reach cones
|
||||
|
||||
int r = GetReachCone(L); // Get the reach cone to test against (can be only 1)
|
||||
|
||||
// Just in case we are running our application with invalid reach cones
|
||||
if (r == -1) {
|
||||
if (!Warning.logged) LogWarning("RotationLimitPolygonal reach cones are invalid.");
|
||||
return rotation;
|
||||
}
|
||||
|
||||
// Dot product of cone normal and rotated axis
|
||||
float v = Vector3.Dot(reachCones[r].B, L);
|
||||
if (v > 0) return rotation; // Rotation is reachable
|
||||
|
||||
// Find normal for a plane defined by origin, axis, and rotated axis
|
||||
Vector3 rotationNormal = Vector3.Cross(axis, L);
|
||||
|
||||
// Find the line where this plane intersects with the reach cone plane
|
||||
L = Vector3.Cross(-reachCones[r].B, rotationNormal);
|
||||
|
||||
// Rotation from current(illegal) swing rotation to the limited(legal) swing rotation
|
||||
Quaternion toLimits = Quaternion.FromToRotation(rotation * axis, L);
|
||||
|
||||
// Subtract the illegal rotation
|
||||
return toLimits * rotation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finding the reach cone to test against
|
||||
* */
|
||||
private int GetReachCone(Vector3 L) {
|
||||
float p = 0;
|
||||
float p1 = Vector3.Dot(reachCones[0].S, L);
|
||||
|
||||
for (int i = 0; i < reachCones.Length; i++) {
|
||||
p = p1;
|
||||
|
||||
if (i < reachCones.Length - 1) p1 = Vector3.Dot(reachCones[i + 1].S, L);
|
||||
else p1 = Vector3.Dot(reachCones[0].S, L);
|
||||
|
||||
if (p >= 0 && p1 < 0) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion Runtime calculations
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dae00b1bdc58d499396776ce508a5078
|
||||
labels:
|
||||
- Turrets
|
||||
- RotationLimits
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 03a93cc74a1364909b1ddd071896ad75, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,102 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Using a spline to limit the range of rotation on universal and ball-and-socket joints.
|
||||
/// Reachable area is defined by an AnimationCurve orthogonally mapped onto a sphere.
|
||||
/// </summary>
|
||||
[HelpURL("http://www.root-motion.com/finalikdox/html/page14.html")]
|
||||
[AddComponentMenu("Scripts/RootMotion.FinalIK/Rotation Limits/Rotation Limit Spline")]
|
||||
public class RotationLimitSpline : RotationLimit {
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
private void OpenUserManual() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page14.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
private void OpenScriptReference() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_rotation_limit_spline.html");
|
||||
}
|
||||
|
||||
// Link to the Final IK Google Group
|
||||
[ContextMenu("Support Group")]
|
||||
void SupportGroup() {
|
||||
Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
|
||||
}
|
||||
|
||||
// Link to the Final IK Asset Store thread in the Unity Community
|
||||
[ContextMenu("Asset Store Thread")]
|
||||
void ASThread() {
|
||||
Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
|
||||
}
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Limit of twist rotation around the main axis.
|
||||
/// </summary>
|
||||
[Range(0f, 180f)] public float twistLimit = 180;
|
||||
|
||||
/// <summary>
|
||||
/// Set the spline keyframes.
|
||||
/// </summary>
|
||||
/// <param name='keyframes'>
|
||||
/// Keyframes.
|
||||
/// </param>
|
||||
public void SetSpline(Keyframe[] keyframes) {
|
||||
spline.keys = keyframes;
|
||||
}
|
||||
|
||||
/*
|
||||
* The AnimationCurve orthogonally mapped onto a sphere that defines the swing limits
|
||||
* */
|
||||
[HideInInspector] public AnimationCurve spline;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
/*
|
||||
* Limits the rotation in the local space of this instance's Transform.
|
||||
* */
|
||||
protected override Quaternion LimitRotation(Quaternion rotation) {
|
||||
// Subtracting off-limits swing
|
||||
Quaternion swing = LimitSwing(rotation);
|
||||
|
||||
// Apply twist limits
|
||||
return LimitTwist(swing, axis, secondaryAxis, twistLimit);
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply the swing rotation limits
|
||||
* */
|
||||
public Quaternion LimitSwing(Quaternion rotation) {
|
||||
if (axis == Vector3.zero) return rotation; // Ignore with zero axes
|
||||
if (rotation == Quaternion.identity) return rotation; // Assuming initial rotation is in the reachable area
|
||||
|
||||
// Get the rotation angle orthogonal to Axis
|
||||
Vector3 swingAxis = rotation * axis;
|
||||
float angle = GetOrthogonalAngle(swingAxis, secondaryAxis, axis);
|
||||
|
||||
// Convert angle from 180 to 360 degrees representation
|
||||
float dot = Vector3.Dot(swingAxis, crossAxis);
|
||||
if (dot < 0) angle = 180 + (180 - angle);
|
||||
|
||||
// Evaluate the limit for this angle
|
||||
float limit = spline.Evaluate(angle);
|
||||
|
||||
// Get the limited swing axis
|
||||
Quaternion swingRotation = Quaternion.FromToRotation(axis, swingAxis);
|
||||
Quaternion limitedSwingRotation = Quaternion.RotateTowards(Quaternion.identity, swingRotation, limit);
|
||||
|
||||
// Rotation from current(illegal) swing rotation to the limited(legal) swing rotation
|
||||
Quaternion toLimits = Quaternion.FromToRotation(swingAxis, limitedSwingRotation * axis);
|
||||
|
||||
// Subtract the illegal rotation
|
||||
return toLimits * rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ccb80eac3b2b4909a63c818e38ae6b8
|
||||
labels:
|
||||
- Turrets
|
||||
- RotationLimits
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: f6c289ca5c243470198f39f6517123fd, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user