initial upload
This commit is contained in:
@ -0,0 +1,95 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// The Cartesian axes.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum Axis {
|
||||
X,
|
||||
Y,
|
||||
Z
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains tools for working with Axes that have no positive/negative directions.
|
||||
/// </summary>
|
||||
public class AxisTools {
|
||||
|
||||
/// <summary>
|
||||
/// Converts an Axis to Vector3.
|
||||
/// </summary>
|
||||
public static Vector3 ToVector3(Axis axis) {
|
||||
if (axis == Axis.X) return Vector3.right;
|
||||
if (axis == Axis.Y) return Vector3.up;
|
||||
return Vector3.forward;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Vector3 to Axis.
|
||||
/// </summary>
|
||||
public static Axis ToAxis(Vector3 v) {
|
||||
float absX = Mathf.Abs(v.x);
|
||||
float absY = Mathf.Abs(v.y);
|
||||
float absZ = Mathf.Abs(v.z);
|
||||
|
||||
Axis d = Axis.X;
|
||||
if (absY > absX && absY > absZ) d = Axis.Y;
|
||||
if (absZ > absX && absZ > absY) d = Axis.Z;
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Axis of the Transform towards a world space position.
|
||||
/// </summary>
|
||||
public static Axis GetAxisToPoint(Transform t, Vector3 worldPosition) {
|
||||
Vector3 axis = GetAxisVectorToPoint(t, worldPosition);
|
||||
if (axis == Vector3.right) return Axis.X;
|
||||
if (axis == Vector3.up) return Axis.Y;
|
||||
return Axis.Z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Axis of the Transform towards a world space direction.
|
||||
/// </summary>
|
||||
public static Axis GetAxisToDirection(Transform t, Vector3 direction) {
|
||||
Vector3 axis = GetAxisVectorToDirection(t, direction);
|
||||
if (axis == Vector3.right) return Axis.X;
|
||||
if (axis == Vector3.up) return Axis.Y;
|
||||
return Axis.Z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the local axis of the Transform towards a world space position.
|
||||
/// </summary>
|
||||
public static Vector3 GetAxisVectorToPoint(Transform t, Vector3 worldPosition) {
|
||||
return GetAxisVectorToDirection(t, worldPosition - t.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the local axis of the Transform that aligns the most with a direction.
|
||||
/// </summary>
|
||||
public static Vector3 GetAxisVectorToDirection(Transform t, Vector3 direction) {
|
||||
return GetAxisVectorToDirection(t.rotation, direction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the local axis of a rotation space that aligns the most with a direction.
|
||||
/// </summary>
|
||||
public static Vector3 GetAxisVectorToDirection(Quaternion r, Vector3 direction)
|
||||
{
|
||||
direction = direction.normalized;
|
||||
Vector3 axis = Vector3.right;
|
||||
|
||||
float dotX = Mathf.Abs(Vector3.Dot(r * Vector3.right, direction));
|
||||
float dotY = Mathf.Abs(Vector3.Dot(r * Vector3.up, direction));
|
||||
if (dotY > dotX) axis = Vector3.up;
|
||||
float dotZ = Mathf.Abs(Vector3.Dot(r * Vector3.forward, direction));
|
||||
if (dotZ > dotX && dotZ > dotY) axis = Vector3.forward;
|
||||
|
||||
return axis;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd8e6d88a74a14a3195290100d6d4a99
|
||||
timeCreated: 1439820535
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,137 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Contains the information about which way the limbs should be bent.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class BipedLimbOrientations {
|
||||
|
||||
[System.Serializable]
|
||||
public class LimbOrientation {
|
||||
public Vector3 upperBoneForwardAxis;
|
||||
public Vector3 lowerBoneForwardAxis;
|
||||
public Vector3 lastBoneLeftAxis;
|
||||
|
||||
public LimbOrientation(Vector3 upperBoneForwardAxis, Vector3 lowerBoneForwardAxis, Vector3 lastBoneLeftAxis) {
|
||||
this.upperBoneForwardAxis = upperBoneForwardAxis;
|
||||
this.lowerBoneForwardAxis = lowerBoneForwardAxis;
|
||||
this.lastBoneLeftAxis = lastBoneLeftAxis;
|
||||
}
|
||||
}
|
||||
|
||||
public LimbOrientation leftArm, rightArm, leftLeg, rightLeg;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RootMotion.FinalIK.FullBodyBipedBendDirections"/> class.
|
||||
/// </summary>
|
||||
public BipedLimbOrientations (LimbOrientation leftArm, LimbOrientation rightArm, LimbOrientation leftLeg, LimbOrientation rightLeg) {
|
||||
this.leftArm = leftArm;
|
||||
this.rightArm = rightArm;
|
||||
this.leftLeg = leftLeg;
|
||||
this.rightLeg = rightLeg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local bend directions of the standard UMA skeleton.
|
||||
/// </summary>
|
||||
public static BipedLimbOrientations UMA {
|
||||
get {
|
||||
return new BipedLimbOrientations(
|
||||
new LimbOrientation(Vector3.forward, Vector3.forward, Vector3.forward),
|
||||
new LimbOrientation(Vector3.forward, Vector3.forward, Vector3.back),
|
||||
new LimbOrientation(Vector3.forward, Vector3.forward, Vector3.down),
|
||||
new LimbOrientation(Vector3.forward, Vector3.forward, Vector3.down)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local bend directions of the standard 3ds Max Biped skeleton.
|
||||
/// </summary>
|
||||
public static BipedLimbOrientations MaxBiped {
|
||||
get {
|
||||
return new BipedLimbOrientations(
|
||||
new LimbOrientation(Vector3.down, Vector3.down, Vector3.down),
|
||||
new LimbOrientation(Vector3.down, Vector3.down, Vector3.up),
|
||||
new LimbOrientation(Vector3.up, Vector3.up, Vector3.back),
|
||||
new LimbOrientation(Vector3.up, Vector3.up, Vector3.back)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Contains the local axes of the limb bones that they should bend towards.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Limb {
|
||||
public Vector3 upper = Vector3.forward, lower = Vector3.forward, last = Vector3.right;
|
||||
|
||||
public Limb (Vector3 common) {
|
||||
this.upper = common;
|
||||
this.lower = common;
|
||||
this.last = common;
|
||||
}
|
||||
|
||||
public Limb (Vector3 upper, Vector3 lower, Vector3 last) {
|
||||
this.upper = upper;
|
||||
this.lower = lower;
|
||||
this.last = last;
|
||||
}
|
||||
}
|
||||
|
||||
public Limb leftArm, rightArm, leftLeg, rightLeg;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RootMotion.FinalIK.FullBodyBipedBendDirections"/> class.
|
||||
/// </summary>
|
||||
public FullBodyBipedBendDirections(Vector3 upper, Vector3 lower, Vector3 last) {
|
||||
this.leftArm = new Limb(upper, lower, last);
|
||||
this.rightArm = new Limb(upper, lower, last);
|
||||
this.leftLeg = new Limb(upper, lower, last);
|
||||
this.rightLeg = new Limb(upper, lower, last);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RootMotion.FinalIK.FullBodyBipedBendAxes"/> struct.
|
||||
/// </summary>
|
||||
public FullBodyBipedBendDirections(Limb leftArm, Limb rightArm, Limb leftLeg, Limb rightLeg) {
|
||||
this.leftArm = leftArm;
|
||||
this.rightArm = rightArm;
|
||||
this.leftLeg = leftLeg;
|
||||
this.rightLeg = rightLeg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local bend directions of the standard UMA skeleton.
|
||||
/// </summary>
|
||||
public static FullBodyBipedBendDirections UMA {
|
||||
get {
|
||||
return new FullBodyBipedBendDirections(
|
||||
new Limb(Vector3.back, Vector3.back, Vector3.back),
|
||||
new Limb(Vector3.back, Vector3.back, Vector3.forward),
|
||||
new Limb(Vector3.forward, Vector3.forward, Vector3.down),
|
||||
new Limb(Vector3.forward, Vector3.forward, Vector3.down)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local bend directions of the standard 3ds Max Biped skeleton.
|
||||
/// </summary>
|
||||
public static FullBodyBipedBendDirections MaxBiped {
|
||||
get {
|
||||
return new FullBodyBipedBendDirections(
|
||||
new Limb(Vector3.up, Vector3.up, Vector3.up),
|
||||
new Limb(Vector3.up, Vector3.up, Vector3.down),
|
||||
new Limb(Vector3.up, Vector3.up, Vector3.back),
|
||||
new Limb(Vector3.up, Vector3.up, Vector3.back)
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bdb26a92391749189aae4c5290f8395
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,240 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Class for identifying biped bones based on most common naming conventions.
|
||||
/// </summary>
|
||||
public static class BipedNaming {
|
||||
|
||||
/// <summary>
|
||||
/// Type of the bone.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum BoneType {
|
||||
Unassigned,
|
||||
Spine,
|
||||
Head,
|
||||
Arm,
|
||||
Leg,
|
||||
Tail,
|
||||
Eye
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bone side: Left and Right for limbs and Center for spine, head and tail.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum BoneSide {
|
||||
Center,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
// Bone identifications
|
||||
public static string[]
|
||||
typeLeft = {" L ", "_L_", "-L-", " l ", "_l_", "-l-", "Left", "left", "CATRigL"},
|
||||
typeRight = {" R ", "_R_", "-R-", " r ", "_r_", "-r-", "Right", "right", "CATRigR"},
|
||||
|
||||
typeSpine = {"Spine", "spine", "Pelvis", "pelvis", "Root", "root", "Torso", "torso", "Body", "body", "Hips", "hips", "Neck", "neck", "Chest", "chest"},
|
||||
typeHead = {"Head", "head"},
|
||||
typeArm = {"Arm", "arm", "Hand", "hand", "Wrist", "Wrist", "Elbow", "elbow", "Palm", "palm"},
|
||||
typeLeg = {"Leg", "leg", "Thigh", "thigh", "Calf", "calf", "Femur", "femur", "Knee", "knee", "Foot", "foot", "Ankle", "ankle", "Hip", "hip"},
|
||||
typeTail = {"Tail", "tail"},
|
||||
typeEye = {"Eye", "eye"},
|
||||
|
||||
typeExclude = {"Nub", "Dummy", "dummy", "Tip", "IK", "Mesh"},
|
||||
typeExcludeSpine = {"Head", "head"},
|
||||
typeExcludeHead = {"Top", "End" },
|
||||
typeExcludeArm = {"Collar", "collar", "Clavicle", "clavicle", "Finger", "finger", "Index", "index", "Mid", "mid", "Pinky", "pinky", "Ring", "Thumb", "thumb", "Adjust", "adjust", "Twist", "twist"},
|
||||
typeExcludeLeg = {"Toe", "toe", "Platform", "Adjust", "adjust", "Twist", "twist"},
|
||||
typeExcludeTail = {},
|
||||
typeExcludeEye = {"Lid", "lid", "Brow", "brow", "Lash", "lash"},
|
||||
|
||||
pelvis = {"Pelvis", "pelvis", "Hip", "hip"},
|
||||
hand = {"Hand", "hand", "Wrist", "wrist", "Palm", "palm"},
|
||||
foot = {"Foot", "foot", "Ankle", "ankle"};
|
||||
|
||||
#region Public methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns only the bones with the specified BoneType.
|
||||
/// </summary>
|
||||
public static Transform[] GetBonesOfType(BoneType boneType, Transform[] bones) {
|
||||
Transform[] r = new Transform[0];
|
||||
foreach (Transform bone in bones) {
|
||||
if (bone != null && GetBoneType(bone.name) == boneType) {
|
||||
Array.Resize(ref r, r.Length + 1);
|
||||
r[r.Length - 1] = bone;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns only the bones with the specified BoneSide.
|
||||
/// </summary>
|
||||
public static Transform[] GetBonesOfSide(BoneSide boneSide, Transform[] bones) {
|
||||
Transform[] r = new Transform[0];
|
||||
foreach (Transform bone in bones) {
|
||||
if (bone != null && GetBoneSide(bone.name) == boneSide) {
|
||||
Array.Resize(ref r, r.Length + 1);
|
||||
r[r.Length - 1] = bone;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bones of type and side.
|
||||
/// </summary>
|
||||
public static Transform[] GetBonesOfTypeAndSide(BoneType boneType, BoneSide boneSide, Transform[] bones) {
|
||||
Transform[] bonesOfType = GetBonesOfType(boneType, bones);
|
||||
return GetBonesOfSide(boneSide, bonesOfType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bone of type and side. If more than one is found, will return the first in the array.
|
||||
/// </summary>
|
||||
public static Transform GetFirstBoneOfTypeAndSide(BoneType boneType, BoneSide boneSide, Transform[] bones) {
|
||||
Transform[] b = GetBonesOfTypeAndSide(boneType, boneSide, bones);
|
||||
if (b.Length == 0) return null;
|
||||
return b[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns only the bones that match all the namings in params string[][] namings
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The matching Transforms
|
||||
/// </returns>
|
||||
/// <param name='transforms'>
|
||||
/// Transforms.
|
||||
/// </param>
|
||||
/// <param name='namings'>
|
||||
/// Namings.
|
||||
/// </param>
|
||||
public static Transform GetNamingMatch(Transform[] transforms, params string[][] namings) {
|
||||
foreach (Transform t in transforms) {
|
||||
bool match = true;
|
||||
foreach (string[] naming in namings) {
|
||||
if (!matchesNaming(t.name, naming)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the bone.
|
||||
/// </summary>
|
||||
public static BoneType GetBoneType(string boneName) {
|
||||
if (isSpine(boneName)) return BoneType.Spine;
|
||||
if (isHead(boneName)) return BoneType.Head;
|
||||
if (isArm (boneName)) return BoneType.Arm;
|
||||
if (isLeg(boneName)) return BoneType.Leg;
|
||||
if (isTail(boneName)) return BoneType.Tail;
|
||||
if (isEye(boneName)) return BoneType.Eye;
|
||||
|
||||
return BoneType.Unassigned;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bone side.
|
||||
/// </summary>
|
||||
public static BoneSide GetBoneSide(string boneName) {
|
||||
if (isLeft(boneName)) return BoneSide.Left;
|
||||
if (isRight(boneName)) return BoneSide.Right;
|
||||
return BoneSide.Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the bone of type and side with additional naming parameters.
|
||||
/// </summary>
|
||||
public static Transform GetBone(Transform[] transforms, BoneType boneType, BoneSide boneSide = BoneSide.Center, params string[][] namings) {
|
||||
Transform[] bones = GetBonesOfTypeAndSide(boneType, boneSide, transforms);
|
||||
return GetNamingMatch(bones, namings);
|
||||
}
|
||||
|
||||
#endregion Public methods
|
||||
|
||||
private static bool isLeft(string boneName) {
|
||||
return matchesNaming(boneName, typeLeft) || lastLetter(boneName) == "L" || firstLetter(boneName) == "L";
|
||||
}
|
||||
|
||||
private static bool isRight(string boneName) {
|
||||
return matchesNaming(boneName, typeRight) || lastLetter(boneName) == "R" || firstLetter(boneName) == "R";
|
||||
}
|
||||
|
||||
private static bool isSpine(string boneName) {
|
||||
return matchesNaming(boneName, typeSpine) && !excludesNaming(boneName, typeExcludeSpine);
|
||||
}
|
||||
|
||||
private static bool isHead(string boneName) {
|
||||
return matchesNaming(boneName, typeHead) && !excludesNaming(boneName, typeExcludeHead);
|
||||
}
|
||||
|
||||
private static bool isArm(string boneName) {
|
||||
return matchesNaming(boneName, typeArm) && !excludesNaming(boneName, typeExcludeArm);
|
||||
}
|
||||
|
||||
private static bool isLeg(string boneName) {
|
||||
return matchesNaming(boneName, typeLeg) && !excludesNaming(boneName, typeExcludeLeg);
|
||||
}
|
||||
|
||||
private static bool isTail(string boneName) {
|
||||
return matchesNaming(boneName, typeTail) && !excludesNaming(boneName, typeExcludeTail);
|
||||
}
|
||||
|
||||
private static bool isEye(string boneName) {
|
||||
return matchesNaming(boneName, typeEye) && !excludesNaming(boneName, typeExcludeEye);
|
||||
}
|
||||
|
||||
private static bool isTypeExclude(string boneName) {
|
||||
return matchesNaming(boneName, typeExclude);
|
||||
}
|
||||
|
||||
private static bool matchesNaming(string boneName, string[] namingConvention) {
|
||||
if (excludesNaming(boneName, typeExclude)) return false;
|
||||
|
||||
foreach(string n in namingConvention) {
|
||||
if (boneName.Contains(n)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool excludesNaming(string boneName, string[] namingConvention) {
|
||||
foreach(string n in namingConvention) {
|
||||
if (boneName.Contains(n)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool matchesLastLetter(string boneName, string[] namingConvention) {
|
||||
foreach(string n in namingConvention) {
|
||||
if (LastLetterIs(boneName, n)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool LastLetterIs(string boneName, string letter) {
|
||||
string lastLetter = boneName.Substring(boneName.Length - 1, 1);
|
||||
return lastLetter == letter;
|
||||
}
|
||||
|
||||
private static string firstLetter(string boneName) {
|
||||
if (boneName.Length > 0) return boneName.Substring(0, 1);
|
||||
return "";
|
||||
}
|
||||
|
||||
private static string lastLetter(string boneName) {
|
||||
if (boneName.Length > 0) return boneName.Substring(boneName.Length - 1, 1);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b405eec4454c42e3b38bafdae66701a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,596 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Contains references to bones common to all biped characters.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class BipedReferences {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The root transform is the parent of all the biped's bones and should be located at ground level.
|
||||
/// </summary>
|
||||
public Transform root;
|
||||
/// <summary>
|
||||
/// The pelvis (hip) bone.
|
||||
/// </summary>
|
||||
public Transform pelvis;
|
||||
/// <summary>
|
||||
/// The first bone of the left leg.
|
||||
/// </summary>
|
||||
public Transform leftThigh;
|
||||
/// <summary>
|
||||
/// The second bone of the left leg.
|
||||
/// </summary>
|
||||
public Transform leftCalf;
|
||||
/// <summary>
|
||||
/// The third bone of the left leg.
|
||||
/// </summary>
|
||||
public Transform leftFoot;
|
||||
/// <summary>
|
||||
/// The first bone of the right leg.
|
||||
/// </summary>
|
||||
public Transform rightThigh;
|
||||
/// <summary>
|
||||
/// The second bone of the right leg.
|
||||
/// </summary>
|
||||
public Transform rightCalf;
|
||||
/// <summary>
|
||||
/// The third bone of the right leg.
|
||||
/// </summary>
|
||||
public Transform rightFoot;
|
||||
/// <summary>
|
||||
/// The first bone of the left arm.
|
||||
/// </summary>
|
||||
public Transform leftUpperArm;
|
||||
/// <summary>
|
||||
/// The second bone of the left arm.
|
||||
/// </summary>
|
||||
public Transform leftForearm;
|
||||
/// <summary>
|
||||
/// The third bone of the left arm.
|
||||
/// </summary>
|
||||
public Transform leftHand;
|
||||
/// <summary>
|
||||
/// The first bone of the right arm.
|
||||
/// </summary>
|
||||
public Transform rightUpperArm;
|
||||
/// <summary>
|
||||
/// The second bone of the right arm.
|
||||
/// </summary>
|
||||
public Transform rightForearm;
|
||||
/// <summary>
|
||||
/// The third bone of the right arm.
|
||||
/// </summary>
|
||||
public Transform rightHand;
|
||||
/// <summary>
|
||||
/// The head.
|
||||
/// </summary>
|
||||
public Transform head;
|
||||
/// <summary>
|
||||
/// The spine hierarchy. Should not contain any bone deeper in the hierarchy than the arms (neck or head).
|
||||
/// </summary>
|
||||
public Transform[] spine = new Transform[0];
|
||||
/// <summary>
|
||||
/// The eyes.
|
||||
/// </summary>
|
||||
public Transform[] eyes = new Transform[0];
|
||||
|
||||
/// <summary>
|
||||
/// Check for null references.
|
||||
/// </summary>
|
||||
public virtual bool isFilled {
|
||||
get {
|
||||
if (root == null) return false;
|
||||
if (pelvis == null) return false;
|
||||
if (leftThigh == null || leftCalf == null || leftFoot == null) return false;
|
||||
if (rightThigh == null || rightCalf == null || rightFoot == null) return false;
|
||||
if (leftUpperArm == null || leftForearm == null || leftHand == null) return false;
|
||||
if (rightUpperArm == null || rightForearm == null || rightHand == null) return false;
|
||||
|
||||
foreach (Transform s in spine) if (s == null) return false;
|
||||
foreach (Transform eye in eyes) if (eye == null) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this <see cref="BipedReferences"/> is empty.
|
||||
/// </summary>
|
||||
public bool isEmpty {
|
||||
get {
|
||||
return IsEmpty(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this <see cref="BipedReferences"/> is empty. If includeRoot is false, returns true(is empty) even if root Transform has been assigned.
|
||||
/// </summary>
|
||||
public virtual bool IsEmpty(bool includeRoot) {
|
||||
if (includeRoot && root != null) return false;
|
||||
if (pelvis != null || head != null) return false;
|
||||
if (leftThigh != null || leftCalf != null || leftFoot != null) return false;
|
||||
if (rightThigh != null || rightCalf != null || rightFoot != null) return false;
|
||||
if (leftUpperArm != null || leftForearm != null || leftHand != null) return false;
|
||||
if (rightUpperArm != null || rightForearm != null || rightHand != null) return false;
|
||||
|
||||
foreach (Transform s in spine) if (s != null) return false;
|
||||
foreach (Transform eye in eyes) if (eye != null) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the References contain the specified Transform
|
||||
/// </summary>
|
||||
public virtual bool Contains(Transform t, bool ignoreRoot = false) {
|
||||
if (!ignoreRoot && root == t) return true;
|
||||
if (pelvis == t) return true;
|
||||
if (leftThigh == t) return true;
|
||||
if (leftCalf == t) return true;
|
||||
if (leftFoot == t) return true;
|
||||
if (rightThigh == t) return true;
|
||||
if (rightCalf == t) return true;
|
||||
if (rightFoot == t) return true;
|
||||
if (leftUpperArm == t) return true;
|
||||
if (leftForearm == t) return true;
|
||||
if (leftHand == t) return true;
|
||||
if (rightUpperArm == t) return true;
|
||||
if (rightForearm == t) return true;
|
||||
if (rightHand == t) return true;
|
||||
if (head == t) return true;
|
||||
|
||||
foreach (Transform s in spine) if (s == t) return true;
|
||||
foreach (Transform e in eyes) if (e == t) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Params for automatic biped recognition. (Using a struct here because I might need to add more parameters in the future).
|
||||
/// </summary>
|
||||
public struct AutoDetectParams {
|
||||
|
||||
/// <summary>
|
||||
/// Should the immediate parent of the legs be included in the spine?.
|
||||
/// </summary>
|
||||
public bool legsParentInSpine;
|
||||
public bool includeEyes;
|
||||
|
||||
public AutoDetectParams(bool legsParentInSpine, bool includeEyes) {
|
||||
this.legsParentInSpine = legsParentInSpine;
|
||||
this.includeEyes = includeEyes;
|
||||
}
|
||||
|
||||
public static AutoDetectParams Default {
|
||||
get {
|
||||
return new AutoDetectParams(true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically detects biped bones. Returns true if a valid biped has been referenced.
|
||||
/// </summary>
|
||||
public static bool AutoDetectReferences(ref BipedReferences references, Transform root, AutoDetectParams autoDetectParams) {
|
||||
if (references == null) references = new BipedReferences();
|
||||
references.root = root;
|
||||
|
||||
// If that failed try the Animator
|
||||
var animator = root.GetComponent<Animator>();
|
||||
if (animator != null && animator.isHuman) {
|
||||
AssignHumanoidReferences(ref references, animator, autoDetectParams);
|
||||
return true; // Assume humanoids are always valid
|
||||
}
|
||||
|
||||
// Try with naming and hierarchy first
|
||||
DetectReferencesByNaming(ref references, root, autoDetectParams);
|
||||
|
||||
Warning.logged = false;
|
||||
|
||||
if (!references.isFilled) {
|
||||
Warning.Log("BipedReferences contains one or more missing Transforms.", root, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
string message = "";
|
||||
if (SetupError(references, ref message)) {
|
||||
Warning.Log(message, references.root, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SetupWarning(references, ref message)) {
|
||||
Warning.Log(message, references.root, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects the references based on naming and hierarchy.
|
||||
/// </summary>
|
||||
public static void DetectReferencesByNaming(ref BipedReferences references, Transform root, AutoDetectParams autoDetectParams) {
|
||||
if (references == null) references = new BipedReferences();
|
||||
|
||||
Transform[] children = root.GetComponentsInChildren<Transform>();
|
||||
|
||||
// Find limbs
|
||||
DetectLimb(BipedNaming.BoneType.Arm, BipedNaming.BoneSide.Left, ref references.leftUpperArm, ref references.leftForearm, ref references.leftHand, children);
|
||||
DetectLimb(BipedNaming.BoneType.Arm, BipedNaming.BoneSide.Right, ref references.rightUpperArm, ref references.rightForearm, ref references.rightHand, children);
|
||||
DetectLimb(BipedNaming.BoneType.Leg, BipedNaming.BoneSide.Left, ref references.leftThigh, ref references.leftCalf, ref references.leftFoot, children);
|
||||
DetectLimb(BipedNaming.BoneType.Leg, BipedNaming.BoneSide.Right, ref references.rightThigh, ref references.rightCalf, ref references.rightFoot, children);
|
||||
|
||||
// Find head bone
|
||||
references.head = BipedNaming.GetBone(children, BipedNaming.BoneType.Head);
|
||||
|
||||
// Find Pelvis
|
||||
references.pelvis = BipedNaming.GetNamingMatch(children, BipedNaming.pelvis);
|
||||
|
||||
// If pelvis is not an ancestor of a leg, it is not a valid pelvis
|
||||
if (references.pelvis == null || !Hierarchy.IsAncestor(references.leftThigh, references.pelvis)) {
|
||||
if (references.leftThigh != null) references.pelvis = references.leftThigh.parent;
|
||||
}
|
||||
|
||||
// Find spine and head bones
|
||||
if (references.leftUpperArm != null && references.rightUpperArm != null && references.pelvis != null && references.leftThigh != null) {
|
||||
Transform neck = Hierarchy.GetFirstCommonAncestor(references.leftUpperArm, references.rightUpperArm);
|
||||
|
||||
if (neck != null) {
|
||||
Transform[] inverseSpine = new Transform[1] { neck };
|
||||
Hierarchy.AddAncestors(inverseSpine[0], references.pelvis, ref inverseSpine);
|
||||
|
||||
references.spine = new Transform[0];
|
||||
for (int i = inverseSpine.Length - 1; i > -1; i--) {
|
||||
if (AddBoneToSpine(inverseSpine[i], ref references, autoDetectParams)) {
|
||||
Array.Resize(ref references.spine, references.spine.Length + 1);
|
||||
references.spine[references.spine.Length - 1] = inverseSpine[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Head
|
||||
if (references.head == null) {
|
||||
for (int i = 0; i < neck.childCount; i++) {
|
||||
Transform child = neck.GetChild(i);
|
||||
|
||||
if (!Hierarchy.ContainsChild(child, references.leftUpperArm) && !Hierarchy.ContainsChild(child, references.rightUpperArm)) {
|
||||
references.head = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find eye bones
|
||||
Transform[] eyes = BipedNaming.GetBonesOfType(BipedNaming.BoneType.Eye, children);
|
||||
references.eyes = new Transform[0];
|
||||
|
||||
if (autoDetectParams.includeEyes) {
|
||||
for (int i = 0; i < eyes.Length; i++) {
|
||||
if (AddBoneToEyes(eyes[i], ref references, autoDetectParams)) {
|
||||
Array.Resize(ref references.eyes, references.eyes.Length + 1);
|
||||
references.eyes[references.eyes.Length - 1] = eyes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills in BipedReferences using Animator.GetBoneTransform().
|
||||
/// </summary>
|
||||
public static void AssignHumanoidReferences(ref BipedReferences references, Animator animator, AutoDetectParams autoDetectParams) {
|
||||
if (references == null) references = new BipedReferences();
|
||||
|
||||
if (animator == null || !animator.isHuman) return;
|
||||
|
||||
references.spine = new Transform[0];
|
||||
references.eyes = new Transform[0];
|
||||
|
||||
references.head = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
|
||||
references.leftThigh = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
|
||||
references.leftCalf = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
|
||||
references.leftFoot = animator.GetBoneTransform(HumanBodyBones.LeftFoot);
|
||||
|
||||
references.rightThigh = animator.GetBoneTransform(HumanBodyBones.RightUpperLeg);
|
||||
references.rightCalf = animator.GetBoneTransform(HumanBodyBones.RightLowerLeg);
|
||||
references.rightFoot = animator.GetBoneTransform(HumanBodyBones.RightFoot);
|
||||
|
||||
references.leftUpperArm = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm);
|
||||
references.leftForearm = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm);
|
||||
references.leftHand = animator.GetBoneTransform(HumanBodyBones.LeftHand);
|
||||
|
||||
references.rightUpperArm = animator.GetBoneTransform(HumanBodyBones.RightUpperArm);
|
||||
references.rightForearm = animator.GetBoneTransform(HumanBodyBones.RightLowerArm);
|
||||
references.rightHand = animator.GetBoneTransform(HumanBodyBones.RightHand);
|
||||
|
||||
references.pelvis = animator.GetBoneTransform(HumanBodyBones.Hips);
|
||||
|
||||
AddBoneToHierarchy(ref references.spine, animator.GetBoneTransform(HumanBodyBones.Spine));
|
||||
AddBoneToHierarchy(ref references.spine, animator.GetBoneTransform(HumanBodyBones.Chest));
|
||||
|
||||
// Make sure the neck bone is not above the arms
|
||||
if (references.leftUpperArm != null) {
|
||||
if (!IsNeckBone(animator.GetBoneTransform(HumanBodyBones.Neck), references.leftUpperArm)) AddBoneToHierarchy(ref references.spine, animator.GetBoneTransform(HumanBodyBones.Neck));
|
||||
}
|
||||
|
||||
if (autoDetectParams.includeEyes) {
|
||||
AddBoneToHierarchy(ref references.eyes, animator.GetBoneTransform(HumanBodyBones.LeftEye));
|
||||
AddBoneToHierarchy(ref references.eyes, animator.GetBoneTransform(HumanBodyBones.RightEye));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the setup for definite problems.
|
||||
/// </summary>
|
||||
public static bool SetupError(BipedReferences references, ref string errorMessage) {
|
||||
if (!references.isFilled) {
|
||||
errorMessage = "BipedReferences contains one or more missing Transforms.";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LimbError(references.leftThigh, references.leftCalf, references.leftFoot, ref errorMessage)) return true;
|
||||
if (LimbError(references.rightThigh, references.rightCalf, references.rightFoot, ref errorMessage)) return true;
|
||||
if (LimbError(references.leftUpperArm, references.leftForearm, references.leftHand, ref errorMessage)) return true;
|
||||
if (LimbError(references.rightUpperArm, references.rightForearm, references.rightHand, ref errorMessage)) return true;
|
||||
if (SpineError(references, ref errorMessage)) return true;
|
||||
if (EyesError(references, ref errorMessage)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the setup for possible problems.
|
||||
/// </summary>
|
||||
public static bool SetupWarning(BipedReferences references, ref string warningMessage) {
|
||||
if (LimbWarning(references.leftThigh, references.leftCalf, references.leftFoot, ref warningMessage)) return true;
|
||||
if (LimbWarning(references.rightThigh, references.rightCalf, references.rightFoot, ref warningMessage)) return true;
|
||||
if (LimbWarning(references.leftUpperArm, references.leftForearm, references.leftHand, ref warningMessage)) return true;
|
||||
if (LimbWarning(references.rightUpperArm, references.rightForearm, references.rightHand, ref warningMessage)) return true;
|
||||
if (SpineWarning(references, ref warningMessage)) return true;
|
||||
if (EyesWarning(references, ref warningMessage)) return true;
|
||||
if (RootHeightWarning(references, ref warningMessage)) return true;
|
||||
if (FacingAxisWarning(references, ref warningMessage)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
// Determines whether a Transform is above the arms
|
||||
private static bool IsNeckBone(Transform bone, Transform leftUpperArm) {
|
||||
if (leftUpperArm.parent != null && leftUpperArm.parent == bone) return false;
|
||||
if (Hierarchy.IsAncestor(leftUpperArm, bone)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determines whether a bone is valid for being added into the eyes array
|
||||
private static bool AddBoneToEyes(Transform bone, ref BipedReferences references, AutoDetectParams autoDetectParams) {
|
||||
if (references.head != null) {
|
||||
if (!Hierarchy.IsAncestor(bone, references.head)) return false;
|
||||
}
|
||||
|
||||
if (bone.GetComponent<SkinnedMeshRenderer>() != null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determines whether a bone is valid for being added into the spine
|
||||
private static bool AddBoneToSpine(Transform bone, ref BipedReferences references, AutoDetectParams autoDetectParams) {
|
||||
if (bone == references.root) return false;
|
||||
|
||||
bool isLegsParent = bone == references.leftThigh.parent;
|
||||
if (isLegsParent && !autoDetectParams.legsParentInSpine) return false;
|
||||
|
||||
if (references.pelvis != null) {
|
||||
if (bone == references.pelvis) return false;
|
||||
if (Hierarchy.IsAncestor(references.pelvis, bone)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Tries to guess the limb bones based on naming
|
||||
private static void DetectLimb(BipedNaming.BoneType boneType, BipedNaming.BoneSide boneSide, ref Transform firstBone, ref Transform secondBone, ref Transform lastBone, Transform[] transforms) {
|
||||
Transform[] limb = BipedNaming.GetBonesOfTypeAndSide(boneType, boneSide, transforms);
|
||||
|
||||
if (limb.Length < 3) {
|
||||
//Warning.Log("Unable to detect biped bones by bone names. Please manually assign bone references.", firstBone, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Standard biped characters
|
||||
if (limb.Length == 3) {
|
||||
firstBone = limb[0];
|
||||
secondBone = limb[1];
|
||||
lastBone = limb[2];
|
||||
}
|
||||
|
||||
// For Bootcamp soldier type of characters with more than 3 limb bones
|
||||
if (limb.Length > 3) {
|
||||
firstBone = limb[0];
|
||||
secondBone = limb[2];
|
||||
lastBone = limb[limb.Length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Adds transform to hierarchy if not null
|
||||
private static void AddBoneToHierarchy(ref Transform[] bones, Transform transform) {
|
||||
if (transform == null) return;
|
||||
|
||||
Array.Resize(ref bones, bones.Length + 1);
|
||||
bones[bones.Length - 1] = transform;
|
||||
}
|
||||
|
||||
// Check if the limb is properly set up
|
||||
private static bool LimbError(Transform bone1, Transform bone2, Transform bone3, ref string errorMessage) {
|
||||
if (bone1 == null) {
|
||||
errorMessage = "Bone 1 of a BipedReferences limb is null.";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bone2 == null) {
|
||||
errorMessage = "Bone 2 of a BipedReferences limb is null.";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bone3 == null) {
|
||||
errorMessage = "Bone 3 of a BipedReferences limb is null.";
|
||||
return true;
|
||||
}
|
||||
|
||||
Transform duplicate = (Transform)Hierarchy.ContainsDuplicate(new Transform[3] { bone1, bone2, bone3 });
|
||||
if (duplicate != null) {
|
||||
errorMessage = duplicate.name + " is represented multiple times in the same BipedReferences limb.";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bone2.position == bone1.position) {
|
||||
errorMessage = "Second bone's position equals first bone's position in the biped's limb.";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bone3.position == bone2.position) {
|
||||
errorMessage = "Third bone's position equals second bone's position in the biped's limb.";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Hierarchy.HierarchyIsValid(new Transform[3] { bone1, bone2, bone3 })) {
|
||||
errorMessage = "BipedReferences limb hierarchy is invalid. Bone transforms in a limb do not belong to the same ancestry. Please make sure the bones are parented to each other. " +
|
||||
"Bones: " + bone1.name + ", " + bone2.name + ", " + bone3.name;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the limb is properly set up
|
||||
private static bool LimbWarning(Transform bone1, Transform bone2, Transform bone3, ref string warningMessage) {
|
||||
Vector3 cross = Vector3.Cross(bone2.position - bone1.position, bone3.position - bone1.position);
|
||||
|
||||
if (cross == Vector3.zero) {
|
||||
warningMessage = "BipedReferences limb is completely stretched out in the initial pose. IK solver can not calculate the default bend plane for the limb. " +
|
||||
"Please make sure you character's limbs are at least slightly bent in the initial pose. " +
|
||||
"First bone: " + bone1.name + ", second bone: " + bone2.name + ".";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if spine is properly set up
|
||||
private static bool SpineError(BipedReferences references, ref string errorMessage) {
|
||||
// No spine might be a valid setup in some cases
|
||||
if (references.spine.Length == 0) return false;
|
||||
|
||||
for (int i = 0; i < references.spine.Length; i++) {
|
||||
if (references.spine[i] == null) {
|
||||
errorMessage = "BipedReferences spine bone at index " + i + " is null.";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Transform duplicate = (Transform)Hierarchy.ContainsDuplicate(references.spine);
|
||||
if (duplicate != null) {
|
||||
errorMessage = duplicate.name + " is represented multiple times in BipedReferences spine.";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Hierarchy.HierarchyIsValid(references.spine)) {
|
||||
errorMessage = "BipedReferences spine hierarchy is invalid. Bone transforms in the spine do not belong to the same ancestry. Please make sure the bones are parented to each other.";
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < references.spine.Length; i++) {
|
||||
bool matchesParentPosition = false;
|
||||
if (i == 0 && references.spine[i].position == references.pelvis.position) matchesParentPosition = true;
|
||||
if (i != 0 && references.spine.Length > 1 && references.spine[i].position == references.spine[i - 1].position) matchesParentPosition = true;
|
||||
|
||||
if (matchesParentPosition) {
|
||||
errorMessage = "Biped's spine bone nr " + i + " position is the same as its parent spine/pelvis bone's position. Please remove this bone from the spine.";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if spine is properly set up
|
||||
private static bool SpineWarning(BipedReferences references, ref string warningMessage) {
|
||||
// Maybe need to add something here in the future
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if eyes are properly set up
|
||||
private static bool EyesError(BipedReferences references, ref string errorMessage) {
|
||||
// No eyes might be a valid setup
|
||||
if (references.eyes.Length == 0) return false;
|
||||
|
||||
for (int i = 0; i < references.eyes.Length; i++) {
|
||||
if (references.eyes[i] == null) {
|
||||
errorMessage = "BipedReferences eye bone at index " + i + " is null.";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Transform duplicate = (Transform)Hierarchy.ContainsDuplicate(references.eyes);
|
||||
if (duplicate != null) {
|
||||
errorMessage = duplicate.name + " is represented multiple times in BipedReferences eyes.";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if eyes are properly set up
|
||||
private static bool EyesWarning(BipedReferences references, ref string warningMessage) {
|
||||
// Maybe need to add something here in the future
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if BipedIK transform position is at the character's feet
|
||||
private static bool RootHeightWarning(BipedReferences references, ref string warningMessage) {
|
||||
if (references.head == null) return false;
|
||||
|
||||
float headHeight = GetVerticalOffset(references.head.position, references.leftFoot.position, references.root.rotation);
|
||||
float rootHeight = GetVerticalOffset(references.root.position, references.leftFoot.position, references.root.rotation);
|
||||
|
||||
if (rootHeight / headHeight > 0.2f) {
|
||||
warningMessage = "Biped's root Transform's position should be at ground level relative to the character (at the character's feet not at its pelvis).";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the character is facing the correct axis
|
||||
private static bool FacingAxisWarning(BipedReferences references, ref string warningMessage) {
|
||||
Vector3 handsLeftToRight = references.rightHand.position - references.leftHand.position;
|
||||
Vector3 feetLeftToRight = references.rightFoot.position - references.leftFoot.position;
|
||||
|
||||
float dotHands = Vector3.Dot(handsLeftToRight.normalized, references.root.right);
|
||||
float dotFeet = Vector3.Dot(feetLeftToRight.normalized, references.root.right);
|
||||
|
||||
if (dotHands < 0 || dotFeet < 0) {
|
||||
warningMessage = "Biped does not seem to be facing its forward axis. " +
|
||||
"Please make sure that in the initial pose the character is facing towards the positive Z axis of the Biped root gameobject.";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gets vertical offset relative to a rotation
|
||||
private static float GetVerticalOffset(Vector3 p1, Vector3 p2, Quaternion rotation) {
|
||||
Vector3 v = Quaternion.Inverse(rotation) * (p1 - p2);
|
||||
return v.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bb327e2dbd674e81906dcacf4777dbf
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,17 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Adding comments to GameObjects in the Inspector.
|
||||
/// </summary>
|
||||
public class Comments : MonoBehaviour {
|
||||
|
||||
/// <summary>
|
||||
/// The comment.
|
||||
/// </summary>
|
||||
[Multiline]
|
||||
public string text;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92471cc1733c49042be56cd39b4b6e5e
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
// Just for displaying a GUI text in the Game View.
|
||||
public class DemoGUIMessage : MonoBehaviour {
|
||||
|
||||
public string text;
|
||||
public Color color = Color.white;
|
||||
|
||||
void OnGUI() {
|
||||
GUI.color = color;
|
||||
GUILayout.Label(text);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b527358a3a7e442d3b6f2f52806aac84
|
||||
timeCreated: 1444293804
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,162 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Contains tools for working on Transform hierarchies.
|
||||
/// </summary>
|
||||
public class Hierarchy {
|
||||
|
||||
/// <summary>
|
||||
/// Make sure the bones are in valid %Hierarchy
|
||||
/// </summary>
|
||||
public static bool HierarchyIsValid(Transform[] bones) {
|
||||
for (int i = 1; i < bones.Length; i++) {
|
||||
// If parent bone is not an ancestor of bone, the hierarchy is invalid
|
||||
if (!IsAncestor(bones[i], bones[i - 1])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an array of objects contains any duplicates.
|
||||
/// </summary>
|
||||
public static UnityEngine.Object ContainsDuplicate(UnityEngine.Object[] objects) {
|
||||
for (int i = 0; i < objects.Length; i++) {
|
||||
for (int i2 = 0; i2 < objects.Length; i2++) {
|
||||
if (i != i2 && objects[i] == objects[i2]) return objects[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the second Transform is an ancestor to the first Transform.
|
||||
/// </summary>
|
||||
public static bool IsAncestor(Transform transform, Transform ancestor) {
|
||||
if (transform == null) return true;
|
||||
if (ancestor == null) return true;
|
||||
if (transform.parent == null) return false;
|
||||
if (transform.parent == ancestor) return true;
|
||||
return IsAncestor(transform.parent, ancestor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the transforms contains the child
|
||||
/// </summary>
|
||||
public static bool ContainsChild(Transform transform, Transform child) {
|
||||
if (transform == child) return true;
|
||||
|
||||
Transform[] children = transform.GetComponentsInChildren<Transform>() as Transform[];
|
||||
foreach (Transform c in children) if (c == child) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all Transforms until the blocker to the array
|
||||
/// </summary>
|
||||
public static void AddAncestors(Transform transform, Transform blocker, ref Transform[] array) {
|
||||
if (transform.parent != null && transform.parent != blocker) {
|
||||
if (transform.parent.position != transform.position && transform.parent.position != blocker.position) {
|
||||
Array.Resize(ref array, array.Length + 1);
|
||||
array[array.Length - 1] = transform.parent;
|
||||
}
|
||||
AddAncestors(transform.parent, blocker, ref array);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last ancestor that has more than minChildCount number of child Transforms
|
||||
/// </summary>
|
||||
public static Transform GetAncestor(Transform transform, int minChildCount) {
|
||||
if (transform == null) return null;
|
||||
|
||||
if (transform.parent != null) {
|
||||
if (transform.parent.childCount >= minChildCount) return transform.parent;
|
||||
return GetAncestor(transform.parent, minChildCount);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first common ancestor up the hierarchy
|
||||
/// </summary>
|
||||
public static Transform GetFirstCommonAncestor(Transform t1, Transform t2) {
|
||||
if (t1 == null) return null;
|
||||
if (t2 == null) return null;
|
||||
if (t1.parent == null) return null;
|
||||
if (t2.parent == null) return null;
|
||||
|
||||
if (IsAncestor(t2, t1.parent)) return t1.parent;
|
||||
return GetFirstCommonAncestor(t1.parent, t2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first common ancestor of the specified transforms.
|
||||
/// </summary>
|
||||
public static Transform GetFirstCommonAncestor(Transform[] transforms) {
|
||||
if (transforms == null) {
|
||||
Debug.LogWarning("Transforms is null.");
|
||||
return null;
|
||||
}
|
||||
if (transforms.Length == 0) {
|
||||
Debug.LogWarning("Transforms.Length is 0.");
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < transforms.Length; i++) {
|
||||
if (transforms[i] == null) return null;
|
||||
|
||||
if (IsCommonAncestor(transforms[i], transforms)) return transforms[i];
|
||||
}
|
||||
|
||||
return GetFirstCommonAncestorRecursive(transforms[0], transforms);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first common ancestor recursively.
|
||||
/// </summary>
|
||||
public static Transform GetFirstCommonAncestorRecursive(Transform transform, Transform[] transforms) {
|
||||
if (transform == null) {
|
||||
Debug.LogWarning("Transform is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (transforms == null) {
|
||||
Debug.LogWarning("Transforms is null.");
|
||||
return null;
|
||||
}
|
||||
if (transforms.Length == 0) {
|
||||
Debug.LogWarning("Transforms.Length is 0.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (IsCommonAncestor(transform, transforms)) return transform;
|
||||
if (transform.parent == null) return null;
|
||||
return GetFirstCommonAncestorRecursive(transform.parent, transforms);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the first parameter is the common ancestor of all the other specified transforms.
|
||||
/// </summary>
|
||||
public static bool IsCommonAncestor(Transform transform, Transform[] transforms) {
|
||||
if (transform == null) {
|
||||
Debug.LogWarning("Transform is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < transforms.Length; i++) {
|
||||
if (transforms[i] == null) {
|
||||
Debug.Log("Transforms[" + i + "] is null.");
|
||||
return false;
|
||||
}
|
||||
if (!IsAncestor(transforms[i], transform) && transforms[i] != transform) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a3d07bf34b8e46d69638784c913e10a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Comment attribute for Editor.
|
||||
/// </summary>
|
||||
public class InspectorComment : PropertyAttribute
|
||||
{
|
||||
|
||||
public string name;
|
||||
public string color = "white";
|
||||
|
||||
public InspectorComment(string name)
|
||||
{
|
||||
this.name = name;
|
||||
this.color = "white";
|
||||
}
|
||||
|
||||
public InspectorComment(string name, string color)
|
||||
{
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8445754565080cd469dedb4d0423de77
|
||||
timeCreated: 1533045022
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
363
Unity-Master/Assets/Plugins/RootMotion/Shared Scripts/Interp.cs
Normal file
363
Unity-Master/Assets/Plugins/RootMotion/Shared Scripts/Interp.cs
Normal file
@ -0,0 +1,363 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Interpolation mode.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum InterpolationMode
|
||||
{
|
||||
None,
|
||||
InOutCubic,
|
||||
InOutQuintic,
|
||||
InOutSine,
|
||||
InQuintic,
|
||||
InQuartic,
|
||||
InCubic,
|
||||
InQuadratic,
|
||||
InElastic,
|
||||
InElasticSmall,
|
||||
InElasticBig,
|
||||
InSine,
|
||||
InBack,
|
||||
OutQuintic,
|
||||
OutQuartic,
|
||||
OutCubic,
|
||||
OutInCubic,
|
||||
OutInQuartic,
|
||||
OutElastic,
|
||||
OutElasticSmall,
|
||||
OutElasticBig,
|
||||
OutSine,
|
||||
OutBack,
|
||||
OutBackCubic,
|
||||
OutBackQuartic,
|
||||
BackInCubic,
|
||||
BackInQuartic,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Class for various interpolation methods.
|
||||
/// </summary>
|
||||
public class Interp
|
||||
{
|
||||
|
||||
#region Public methods
|
||||
|
||||
/// <summary>
|
||||
/// Interpolate the specified t by InterpolationMode mode.
|
||||
/// </summary>
|
||||
/// <param name='t'>
|
||||
/// T.
|
||||
/// </param>
|
||||
/// <param name='mode'>
|
||||
/// InterpolationMode.
|
||||
/// </param>
|
||||
public static float Float(float t, InterpolationMode mode) {
|
||||
float interpT = 0;
|
||||
|
||||
switch (mode) {
|
||||
case InterpolationMode.None:
|
||||
interpT = Interp.None(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InOutCubic:
|
||||
interpT = Interp.InOutCubic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InOutQuintic:
|
||||
interpT = Interp.InOutQuintic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InQuintic:
|
||||
interpT = Interp.InQuintic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InQuartic:
|
||||
interpT = Interp.InQuartic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InCubic:
|
||||
interpT = Interp.InCubic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InQuadratic:
|
||||
interpT = Interp.InQuadratic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutQuintic:
|
||||
interpT = Interp.OutQuintic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutQuartic:
|
||||
interpT = Interp.OutQuartic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutCubic:
|
||||
interpT = Interp.OutCubic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutInCubic:
|
||||
interpT = Interp.OutInCubic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutInQuartic:
|
||||
interpT = Interp.OutInCubic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.BackInCubic:
|
||||
interpT = Interp.BackInCubic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.BackInQuartic:
|
||||
interpT = Interp.BackInQuartic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutBackCubic:
|
||||
interpT = Interp.OutBackCubic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutBackQuartic:
|
||||
interpT = Interp.OutBackQuartic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutElasticSmall:
|
||||
interpT = Interp.OutElasticSmall(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutElasticBig:
|
||||
interpT = Interp.OutElasticBig(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InElasticSmall:
|
||||
interpT = Interp.InElasticSmall(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InElasticBig:
|
||||
interpT = Interp.InElasticBig(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InSine:
|
||||
interpT = Interp.InSine(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutSine:
|
||||
interpT = Interp.OutSine(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InOutSine:
|
||||
interpT = Interp.InOutSine(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InElastic:
|
||||
interpT = Interp.OutElastic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutElastic:
|
||||
interpT = Interp.OutElastic(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.InBack:
|
||||
interpT = Interp.InBack(t, 0, 1);
|
||||
break;
|
||||
case InterpolationMode.OutBack:
|
||||
interpT = Interp.OutBack(t, 0, 1);
|
||||
break;
|
||||
default: interpT = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return interpT;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolate between two verctors by InterpolationMode mode
|
||||
/// </summary>
|
||||
public static Vector3 V3(Vector3 v1, Vector3 v2, float t, InterpolationMode mode) {
|
||||
float interpT = Interp.Float(t, mode);
|
||||
return ((1 - interpT) * v1) + (interpT * v2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear interpolation of value towards target.
|
||||
/// </summary>
|
||||
public static float LerpValue(float value, float target, float increaseSpeed, float decreaseSpeed) {
|
||||
if (value == target) return target;
|
||||
if (value < target) return Mathf.Clamp(value + Time.deltaTime * increaseSpeed, -Mathf.Infinity, target);
|
||||
else return Mathf.Clamp(value - Time.deltaTime * decreaseSpeed, target, Mathf.Infinity);
|
||||
}
|
||||
|
||||
#endregion Public methods
|
||||
|
||||
#region Interpolation modes
|
||||
|
||||
private static float None (float t, float b, float c) { // time, b, distance,
|
||||
return b + c * (t);
|
||||
}
|
||||
|
||||
private static float InOutCubic(float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (-2 * tc + 3 * ts);
|
||||
}
|
||||
|
||||
private static float InOutQuintic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (6 * tc * ts + -15 * ts * ts + 10 * tc);
|
||||
}
|
||||
|
||||
private static float InQuintic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (tc * ts);
|
||||
}
|
||||
|
||||
private static float InQuartic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
return b + c * (ts * ts);
|
||||
}
|
||||
|
||||
private static float InCubic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (tc);
|
||||
}
|
||||
|
||||
private static float InQuadratic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
return b + c * (ts);
|
||||
}
|
||||
|
||||
private static float OutQuintic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (tc * ts + -5 * ts * ts + 10 * tc + -10 * ts + 5 * t);
|
||||
}
|
||||
|
||||
private static float OutQuartic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (-1 * ts * ts + 4 * tc + -6 * ts + 4 * t);
|
||||
}
|
||||
|
||||
private static float OutCubic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (tc + -3 * ts + 3 * t);
|
||||
}
|
||||
|
||||
private static float OutInCubic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (4 * tc + -6 * ts + 3 * t);
|
||||
}
|
||||
|
||||
private static float OutInQuartic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (6 * tc + -9 * ts + 4 * t);
|
||||
}
|
||||
|
||||
private static float BackInCubic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c *(4 * tc + -3 * ts);
|
||||
}
|
||||
|
||||
private static float BackInQuartic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (2 * ts * ts + 2 * tc + -3 * ts);
|
||||
}
|
||||
|
||||
private static float OutBackCubic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (4 * tc + -9 * ts + 6 * t);
|
||||
}
|
||||
|
||||
private static float OutBackQuartic (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (-2 * ts * ts + 10 * tc + -15 * ts + 8 * t);
|
||||
}
|
||||
|
||||
private static float OutElasticSmall (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (33 * tc * ts + -106 * ts * ts + 126 * tc + -67 * ts + 15 * t);
|
||||
}
|
||||
|
||||
private static float OutElasticBig (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b+c*(56*tc*ts + -175*ts*ts + 200*tc + -100*ts + 20*t);
|
||||
}
|
||||
|
||||
private static float InElasticSmall (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (33 * tc * ts + -59 * ts * ts + 32 * tc + -5 * ts);
|
||||
}
|
||||
|
||||
private static float InElasticBig (float t, float b, float c) {
|
||||
float ts = t * t;
|
||||
float tc = ts * t;
|
||||
return b + c * (56 * tc * ts + -105 * ts * ts + 60 * tc + -10 * ts);
|
||||
}
|
||||
|
||||
private static float InSine (float t, float b, float c) {
|
||||
c -= b;
|
||||
return -c * Mathf.Cos(t / 1 * (Mathf.PI / 2)) + c + b;
|
||||
}
|
||||
|
||||
private static float OutSine (float t, float b, float c) {
|
||||
c -= b;
|
||||
return c * Mathf.Sin(t / 1 * (Mathf.PI / 2)) + b;
|
||||
}
|
||||
|
||||
private static float InOutSine (float t, float b, float c) {
|
||||
c -= b;
|
||||
return -c / 2 * (Mathf.Cos(Mathf.PI * t / 1) - 1) + b;
|
||||
}
|
||||
|
||||
private static float InElastic (float t, float b, float c) {
|
||||
c -= b;
|
||||
|
||||
float d = 1f;
|
||||
float p = d * .3f;
|
||||
float s = 0;
|
||||
float a = 0;
|
||||
|
||||
if (t == 0) return b;
|
||||
|
||||
if ((t /= d) == 1) return b + c;
|
||||
|
||||
if (a == 0f || a < Mathf.Abs(c)){
|
||||
a = c;
|
||||
s = p / 4;
|
||||
}else{
|
||||
s = p / (2 * Mathf.PI) * Mathf.Asin(c / a);
|
||||
}
|
||||
|
||||
return -(a * Mathf.Pow(2, 10 * (t-=1)) * Mathf.Sin((t * d - s) * (2 * Mathf.PI) / p)) + b;
|
||||
}
|
||||
|
||||
private static float OutElastic (float t, float b, float c) {
|
||||
c -= b;
|
||||
|
||||
float d = 1f;
|
||||
float p = d * .3f;
|
||||
float s = 0;
|
||||
float a = 0;
|
||||
|
||||
if (t == 0) return b;
|
||||
|
||||
if ((t /= d) == 1) return b + c;
|
||||
|
||||
if (a == 0f || a < Mathf.Abs(c)){
|
||||
a = c;
|
||||
s = p / 4;
|
||||
}else{
|
||||
s = p / (2 * Mathf.PI) * Mathf.Asin(c / a);
|
||||
}
|
||||
|
||||
return (a * Mathf.Pow(2, -10 * t) * Mathf.Sin((t * d - s) * (2 * Mathf.PI) / p) + c + b);
|
||||
}
|
||||
|
||||
private static float InBack(float t, float b, float c){
|
||||
c -= b;
|
||||
t /= 1;
|
||||
float s = 1.70158f;
|
||||
return c * (t) * t * ((s + 1) * t - s) + b;
|
||||
}
|
||||
|
||||
|
||||
private static float OutBack (float t, float b, float c) {
|
||||
float s = 1.70158f;
|
||||
c -= b;
|
||||
t = (t / 1) - 1;
|
||||
return c * ((t) * t * ((s + 1) * t + s) + 1) + b;
|
||||
}
|
||||
|
||||
#endregion Interpolation modes
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d23298dba9a44325997366a9dc5314f
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,142 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// This class contains tools for working with LayerMasks.
|
||||
/// Most of this was copied from Unity Wiki: http://wiki.unity3d.com/index.php?title=LayerMaskExtensions.
|
||||
/// </summary>
|
||||
public static class LayerMaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Does the LayerMask contain a specific layer index?
|
||||
/// </summary>
|
||||
public static bool Contains(LayerMask mask, int layer) {
|
||||
return mask == (mask | (1 << layer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LayerMask from an array of layer names.
|
||||
/// </summary>
|
||||
public static LayerMask Create(params string[] layerNames)
|
||||
{
|
||||
return NamesToMask(layerNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LayerMask from an array of layer indexes.
|
||||
/// </summary>
|
||||
public static LayerMask Create(params int[] layerNumbers)
|
||||
{
|
||||
return LayerNumbersToMask(layerNumbers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LayerMask from a number of layer names.
|
||||
/// </summary>
|
||||
public static LayerMask NamesToMask(params string[] layerNames)
|
||||
{
|
||||
LayerMask ret = (LayerMask)0;
|
||||
foreach(var name in layerNames)
|
||||
{
|
||||
ret |= (1 << LayerMask.NameToLayer(name));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LayerMask from a number of layer indexes.
|
||||
/// </summary>
|
||||
public static LayerMask LayerNumbersToMask(params int[] layerNumbers)
|
||||
{
|
||||
LayerMask ret = (LayerMask)0;
|
||||
foreach(var layer in layerNumbers)
|
||||
{
|
||||
ret |= (1 << layer);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inverts a LayerMask.
|
||||
/// </summary>
|
||||
public static LayerMask Inverse(this LayerMask original)
|
||||
{
|
||||
return ~original;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a number of layer names to an existing LayerMask.
|
||||
/// </summary>
|
||||
public static LayerMask AddToMask(this LayerMask original, params string[] layerNames)
|
||||
{
|
||||
return original | NamesToMask(layerNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a number of layer names from an existing LayerMask.
|
||||
/// </summary>
|
||||
public static LayerMask RemoveFromMask(this LayerMask original, params string[] layerNames)
|
||||
{
|
||||
LayerMask invertedOriginal = ~original;
|
||||
return ~(invertedOriginal | NamesToMask(layerNames));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string array of layer names from a LayerMask.
|
||||
/// </summary>
|
||||
public static string[] MaskToNames(this LayerMask original)
|
||||
{
|
||||
var output = new List<string>();
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
int shifted = 1 << i;
|
||||
if ((original & shifted) == shifted)
|
||||
{
|
||||
string layerName = LayerMask.LayerToName(i);
|
||||
if (!string.IsNullOrEmpty(layerName))
|
||||
{
|
||||
output.Add(layerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array of layer indexes from a LayerMask.
|
||||
/// </summary>
|
||||
public static int[] MaskToNumbers(this LayerMask original)
|
||||
{
|
||||
var output = new List<int>();
|
||||
|
||||
for (int i = 0; i < 32; ++i)
|
||||
{
|
||||
int shifted = 1 << i;
|
||||
if ((original & shifted) == shifted)
|
||||
{
|
||||
output.Add(i);
|
||||
}
|
||||
}
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a LayerMask to a string.
|
||||
/// </summary>
|
||||
public static string MaskToString(this LayerMask original)
|
||||
{
|
||||
return MaskToString(original, ", ");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a LayerMask to a string using the specified delimiter.
|
||||
/// </summary>
|
||||
public static string MaskToString(this LayerMask original, string delimiter)
|
||||
{
|
||||
return string.Join(delimiter, MaskToNames(original));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9bbb833ec06b64849830a149f1e7b7e6
|
||||
timeCreated: 1435745915
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Auto-instantiated singleton base class.
|
||||
/// </summary>
|
||||
public abstract class LazySingleton<T> : MonoBehaviour where T : LazySingleton<T>
|
||||
{
|
||||
|
||||
private static T sInstance = null;
|
||||
|
||||
public static bool hasInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
return sInstance != null;
|
||||
}
|
||||
}
|
||||
|
||||
public static T instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (sInstance == null)
|
||||
{
|
||||
string name = typeof(T).ToString();
|
||||
sInstance = new GameObject(name).AddComponent<T>();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
sInstance = (T)this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38a253ce1de8036419e9f6c792893d7e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,218 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.Demos
|
||||
{
|
||||
|
||||
// Custom navigator for Unity Navigation.
|
||||
[System.Serializable]
|
||||
public class Navigator
|
||||
{
|
||||
|
||||
public enum State
|
||||
{
|
||||
Idle,
|
||||
Seeking,
|
||||
OnPath,
|
||||
}
|
||||
|
||||
[Tooltip("Should this Navigator be actively seeking a path.")]
|
||||
public bool activeTargetSeeking;
|
||||
|
||||
[Tooltip("Increase this value if the character starts running in a circle, not able to reach the corner because of a too large turning radius.")]
|
||||
public float cornerRadius = 0.5f;
|
||||
|
||||
[Tooltip("Recalculate path if target position has moved by this distance from the position it was at when the path was originally calculated")]
|
||||
public float recalculateOnPathDistance = 1f;
|
||||
//public float recalculateBadTargetDistance = 1f;
|
||||
|
||||
[Tooltip("Sample within this distance from sourcePosition.")]
|
||||
public float maxSampleDistance = 5f;
|
||||
|
||||
[Tooltip("Interval of updating the path")]
|
||||
public float nextPathInterval = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Get the move direction vector (normalized). If nowhere to go or path finished, will return Vector3.zero.
|
||||
/// </summary>
|
||||
public Vector3 normalizedDeltaPosition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the current state of this Navigator (Idle, Seeking, OnPath).
|
||||
/// </summary>
|
||||
public State state { get; private set; }
|
||||
|
||||
private Transform transform;
|
||||
private int cornerIndex;
|
||||
private Vector3[] corners = new Vector3[0];
|
||||
private UnityEngine.AI.NavMeshPath path;
|
||||
private Vector3 lastTargetPosition;
|
||||
private bool initiated;
|
||||
private float nextPathTime;
|
||||
|
||||
public void Initiate(Transform transform)
|
||||
{
|
||||
this.transform = transform;
|
||||
path = new UnityEngine.AI.NavMeshPath();
|
||||
initiated = true;
|
||||
cornerIndex = 0;
|
||||
corners = new Vector3[0];
|
||||
state = State.Idle;
|
||||
lastTargetPosition = new Vector3(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity);
|
||||
}
|
||||
|
||||
public void Update(Vector3 targetPosition)
|
||||
{
|
||||
if (!initiated)
|
||||
{
|
||||
Debug.LogError("Trying to update an uninitiated Navigator.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
// When seeking path
|
||||
case State.Seeking:
|
||||
normalizedDeltaPosition = Vector3.zero;
|
||||
|
||||
if (path.status == UnityEngine.AI.NavMeshPathStatus.PathComplete)
|
||||
{
|
||||
corners = path.corners;
|
||||
cornerIndex = 0;
|
||||
|
||||
if (corners.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("Zero Corner Path", transform);
|
||||
|
||||
Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State.OnPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.status == UnityEngine.AI.NavMeshPathStatus.PathPartial)
|
||||
{
|
||||
Debug.LogWarning("Path Partial", transform);
|
||||
}
|
||||
|
||||
if (path.status == UnityEngine.AI.NavMeshPathStatus.PathInvalid)
|
||||
{
|
||||
Debug.LogWarning("Path Invalid", transform);
|
||||
}
|
||||
break;
|
||||
|
||||
// When already on path
|
||||
case State.OnPath:
|
||||
if (activeTargetSeeking && Time.time > nextPathTime && HorDistance(targetPosition, lastTargetPosition) > recalculateOnPathDistance)
|
||||
{
|
||||
CalculatePath(targetPosition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cornerIndex < corners.Length)
|
||||
{
|
||||
Vector3 d = corners[cornerIndex] - transform.position;
|
||||
d.y = 0f;
|
||||
float mag = d.magnitude;
|
||||
|
||||
if (mag > 0f) normalizedDeltaPosition = (d / d.magnitude);
|
||||
else normalizedDeltaPosition = Vector3.zero;
|
||||
|
||||
if (mag < cornerRadius)
|
||||
{
|
||||
cornerIndex++;
|
||||
|
||||
if (cornerIndex >= corners.Length) Stop();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Not on path, not seeking
|
||||
case State.Idle:
|
||||
if (activeTargetSeeking && Time.time > nextPathTime) CalculatePath(targetPosition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculatePath(Vector3 targetPosition)
|
||||
{
|
||||
if (Find(targetPosition))
|
||||
{
|
||||
lastTargetPosition = targetPosition;
|
||||
state = State.Seeking;
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
nextPathTime = Time.time + nextPathInterval;
|
||||
}
|
||||
|
||||
private bool Find(Vector3 targetPosition)
|
||||
{
|
||||
if (HorDistance(transform.position, targetPosition) < cornerRadius * 2f) return false;
|
||||
//if (HorDistance(targetPosition, lastTargetPosition) < recalculateBadTargetDistance) return false;
|
||||
if (UnityEngine.AI.NavMesh.CalculatePath(transform.position, targetPosition, UnityEngine.AI.NavMesh.AllAreas, path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.AI.NavMeshHit hit = new UnityEngine.AI.NavMeshHit();
|
||||
|
||||
if (UnityEngine.AI.NavMesh.SamplePosition(targetPosition, out hit, maxSampleDistance, UnityEngine.AI.NavMesh.AllAreas))
|
||||
{
|
||||
if (UnityEngine.AI.NavMesh.CalculatePath(transform.position, hit.position, UnityEngine.AI.NavMesh.AllAreas, path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Stop()
|
||||
{
|
||||
state = State.Idle;
|
||||
normalizedDeltaPosition = Vector3.zero;
|
||||
}
|
||||
|
||||
private float HorDistance(Vector3 p1, Vector3 p2)
|
||||
{
|
||||
return Vector2.Distance(new Vector2(p1.x, p1.z), new Vector2(p2.x, p2.z));
|
||||
}
|
||||
|
||||
public void Visualize()
|
||||
{
|
||||
if (state == State.Idle) Gizmos.color = Color.gray;
|
||||
if (state == State.Seeking) Gizmos.color = Color.red;
|
||||
if (state == State.OnPath) Gizmos.color = Color.green;
|
||||
|
||||
if (corners.Length > 0 && state == State.OnPath && cornerIndex == 0)
|
||||
{
|
||||
Gizmos.DrawLine(transform.position, corners[0]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < corners.Length; i++)
|
||||
{
|
||||
Gizmos.DrawSphere(corners[i], 0.1f);
|
||||
}
|
||||
|
||||
if (corners.Length > 1)
|
||||
{
|
||||
for (int i = 0; i < corners.Length - 1; i++)
|
||||
{
|
||||
Gizmos.DrawLine(corners[i], corners[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
Gizmos.color = Color.white;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 733f93bd37122dd4889c53b5fa05a147
|
||||
timeCreated: 1536568016
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,89 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
public enum ShowIfMode
|
||||
{
|
||||
Disabled = 0,
|
||||
Hidden = 1
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class ShowIfAttribute : PropertyAttribute
|
||||
{
|
||||
public string propName { get; protected set; }
|
||||
public object propValue { get; protected set; }
|
||||
public object otherPropValue { get; protected set; }
|
||||
public bool indent { get; private set; }
|
||||
public ShowIfMode mode { get; protected set; }
|
||||
|
||||
public ShowIfAttribute(string propertyName, object propertyValue = null, object otherPropertyValue = null, bool indent = false, ShowIfMode mode = ShowIfMode.Hidden)
|
||||
{
|
||||
this.propName = propertyName;
|
||||
this.propValue = propertyValue;
|
||||
this.otherPropValue = otherPropertyValue;
|
||||
this.indent = indent;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
|
||||
public class ShowRangeIfAttribute : ShowIfAttribute
|
||||
{
|
||||
public float min { get; private set; }
|
||||
public float max { get; private set; }
|
||||
|
||||
public ShowRangeIfAttribute(float min, float max, string propertyName, object propertyValue = null, object otherPropertyValue = null, bool indent = false, ShowIfMode mode = ShowIfMode.Hidden) : base (propertyName, propertyValue, otherPropertyValue, indent, mode)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Large header attribute for Editor.
|
||||
/// </summary>
|
||||
public class ShowLargeHeaderIf : ShowIfAttribute
|
||||
{
|
||||
|
||||
public string name;
|
||||
public string color = "white";
|
||||
|
||||
public ShowLargeHeaderIf(string name, string propertyName, object propertyValue = null, object otherPropertyValue = null, bool indent = false, ShowIfMode mode = ShowIfMode.Hidden) : base(propertyName, propertyValue, otherPropertyValue, indent, mode)
|
||||
{
|
||||
this.name = name;
|
||||
this.color = "white";
|
||||
}
|
||||
|
||||
public ShowLargeHeaderIf(string name, string color, string propertyName, object propertyValue = null, object otherPropertyValue = null, bool indent = false, ShowIfMode mode = ShowIfMode.Hidden) : base(propertyName, propertyValue, otherPropertyValue, indent, mode)
|
||||
{
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Large header attribute for Editor.
|
||||
/// </summary>
|
||||
public class LargeHeader : PropertyAttribute
|
||||
{
|
||||
|
||||
public string name;
|
||||
public string color = "white";
|
||||
|
||||
public LargeHeader(string name)
|
||||
{
|
||||
this.name = name;
|
||||
this.color = "white";
|
||||
}
|
||||
|
||||
public LargeHeader(string name, string color)
|
||||
{
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 075dde4e7ae31b440915c8bd52e13197
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,303 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for dealing with Quaternions.
|
||||
/// </summary>
|
||||
public static class QuaTools {
|
||||
|
||||
/// <summary>
|
||||
/// Returns yaw angle (-180 - 180) of 'forward' vector relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetYaw(Quaternion space, Vector3 forward)
|
||||
{
|
||||
Vector3 dirLocal = Quaternion.Inverse(space) * forward;
|
||||
if (dirLocal.x == 0f && dirLocal.z == 0f) return 0f;
|
||||
if (float.IsInfinity(dirLocal.x) || float.IsInfinity(dirLocal.z)) return 0;
|
||||
|
||||
return Mathf.Atan2(dirLocal.x, dirLocal.z) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns pitch angle (-90 - 90) of 'forward' vector relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetPitch(Quaternion space, Vector3 forward)
|
||||
{
|
||||
forward = forward.normalized;
|
||||
Vector3 dirLocal = Quaternion.Inverse(space) * forward;
|
||||
if (Mathf.Abs(dirLocal.y) > 1f) dirLocal.Normalize();
|
||||
return -Mathf.Asin(dirLocal.y) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns bank angle (-180 - 180) of 'forward' and 'up' vectors relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetBank(Quaternion space, Vector3 forward, Vector3 up)
|
||||
{
|
||||
Vector3 spaceUp = space * Vector3.up;
|
||||
|
||||
Quaternion invSpace = Quaternion.Inverse(space);
|
||||
forward = invSpace * forward;
|
||||
up = invSpace * up;
|
||||
|
||||
Quaternion q = Quaternion.Inverse(Quaternion.LookRotation(spaceUp, forward));
|
||||
up = q * up;
|
||||
float result = Mathf.Atan2(up.x, up.z) * Mathf.Rad2Deg;
|
||||
return Mathf.Clamp(result, -180f, 180f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns yaw angle (-180 - 180) of 'forward' vector relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetYaw(Quaternion space, Quaternion rotation)
|
||||
{
|
||||
Vector3 dirLocal = Quaternion.Inverse(space) * (rotation * Vector3.forward);
|
||||
if (dirLocal.x == 0f && dirLocal.z == 0f) return 0f;
|
||||
if (float.IsInfinity(dirLocal.x) || float.IsInfinity(dirLocal.z)) return 0;
|
||||
return Mathf.Atan2(dirLocal.x, dirLocal.z) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns pitch angle (-90 - 90) of 'forward' vector relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetPitch(Quaternion space, Quaternion rotation)
|
||||
{
|
||||
Vector3 dirLocal = Quaternion.Inverse(space) * (rotation * Vector3.forward);
|
||||
if (Mathf.Abs(dirLocal.y) > 1f) dirLocal.Normalize();
|
||||
return -Mathf.Asin(dirLocal.y) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns bank angle (-180 - 180) of 'forward' and 'up' vectors relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetBank(Quaternion space, Quaternion rotation)
|
||||
{
|
||||
Vector3 spaceUp = space * Vector3.up;
|
||||
|
||||
Quaternion invSpace = Quaternion.Inverse(space);
|
||||
Vector3 forward = invSpace * (rotation * Vector3.forward);
|
||||
Vector3 up = invSpace * (rotation * Vector3.up);
|
||||
|
||||
Quaternion q = Quaternion.Inverse(Quaternion.LookRotation(spaceUp, forward));
|
||||
up = q * up;
|
||||
float result = Mathf.Atan2(up.x, up.z) * Mathf.Rad2Deg;
|
||||
return Mathf.Clamp(result, -180f, 180f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized Quaternion.Lerp
|
||||
/// </summary>
|
||||
public static Quaternion Lerp(Quaternion fromRotation, Quaternion toRotation, float weight) {
|
||||
if (weight <= 0f) return fromRotation;
|
||||
if (weight >= 1f) return toRotation;
|
||||
|
||||
return Quaternion.Lerp(fromRotation, toRotation, weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized Quaternion.Slerp
|
||||
/// </summary>
|
||||
public static Quaternion Slerp(Quaternion fromRotation, Quaternion toRotation, float weight) {
|
||||
if (weight <= 0f) return fromRotation;
|
||||
if (weight >= 1f) return toRotation;
|
||||
|
||||
return Quaternion.Slerp(fromRotation, toRotation, weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the rotation from identity Quaternion to "q", interpolated linearily by "weight".
|
||||
/// </summary>
|
||||
public static Quaternion LinearBlend(Quaternion q, float weight) {
|
||||
if (weight <= 0f) return Quaternion.identity;
|
||||
if (weight >= 1f) return q;
|
||||
return Quaternion.Lerp(Quaternion.identity, q, weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the rotation from identity Quaternion to "q", interpolated spherically by "weight".
|
||||
/// </summary>
|
||||
public static Quaternion SphericalBlend(Quaternion q, float weight) {
|
||||
if (weight <= 0f) return Quaternion.identity;
|
||||
if (weight >= 1f) return q;
|
||||
return Quaternion.Slerp(Quaternion.identity, q, weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a FromToRotation, but makes sure its axis remains fixed near to the Quaternion singularity point.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The from to rotation around an axis.
|
||||
/// </returns>
|
||||
/// <param name='fromDirection'>
|
||||
/// From direction.
|
||||
/// </param>
|
||||
/// <param name='toDirection'>
|
||||
/// To direction.
|
||||
/// </param>
|
||||
/// <param name='axis'>
|
||||
/// Axis. Should be normalized before passing into this method.
|
||||
/// </param>
|
||||
public static Quaternion FromToAroundAxis(Vector3 fromDirection, Vector3 toDirection, Vector3 axis) {
|
||||
Quaternion fromTo = Quaternion.FromToRotation(fromDirection, toDirection);
|
||||
|
||||
float angle = 0;
|
||||
Vector3 freeAxis = Vector3.zero;
|
||||
|
||||
fromTo.ToAngleAxis(out angle, out freeAxis);
|
||||
|
||||
float dot = Vector3.Dot(freeAxis, axis);
|
||||
if (dot < 0) angle = -angle;
|
||||
|
||||
return Quaternion.AngleAxis(angle, axis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation that can be used to convert a rotation from one axis space to another.
|
||||
/// </summary>
|
||||
public static Quaternion RotationToLocalSpace(Quaternion space, Quaternion rotation) {
|
||||
return Quaternion.Inverse(Quaternion.Inverse(space) * rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Quaternion from rotation "from" to rotation "to".
|
||||
/// </summary>
|
||||
public static Quaternion FromToRotation(Quaternion from, Quaternion to) {
|
||||
if (to == from) return Quaternion.identity;
|
||||
|
||||
return to * Quaternion.Inverse(from);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest direction axis to a vector. Input vector must be normalized!
|
||||
/// </summary>
|
||||
public static Vector3 GetAxis(Vector3 v) {
|
||||
Vector3 closest = Vector3.right;
|
||||
bool neg = false;
|
||||
|
||||
float x = Vector3.Dot(v, Vector3.right);
|
||||
float maxAbsDot = Mathf.Abs(x);
|
||||
if (x < 0f) neg = true;
|
||||
|
||||
float y = Vector3.Dot(v, Vector3.up);
|
||||
float absDot = Mathf.Abs(y);
|
||||
if (absDot > maxAbsDot) {
|
||||
maxAbsDot = absDot;
|
||||
closest = Vector3.up;
|
||||
neg = y < 0f;
|
||||
}
|
||||
|
||||
float z = Vector3.Dot(v, Vector3.forward);
|
||||
absDot = Mathf.Abs(z);
|
||||
if (absDot > maxAbsDot) {
|
||||
closest = Vector3.forward;
|
||||
neg = z < 0f;
|
||||
}
|
||||
|
||||
if (neg) closest = -closest;
|
||||
return closest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the rotation similar to V3Tools.ClampDirection.
|
||||
/// </summary>
|
||||
public static Quaternion ClampRotation(Quaternion rotation, float clampWeight, int clampSmoothing) {
|
||||
if (clampWeight >= 1f) return Quaternion.identity;
|
||||
if (clampWeight <= 0f) return rotation;
|
||||
|
||||
float angle = Quaternion.Angle(Quaternion.identity, rotation);
|
||||
float dot = 1f - (angle / 180f);
|
||||
float targetClampMlp = Mathf.Clamp(1f - ((clampWeight - dot) / (1f - dot)), 0f, 1f);
|
||||
float clampMlp = Mathf.Clamp(dot / clampWeight, 0f, 1f);
|
||||
|
||||
// Sine smoothing iterations
|
||||
for (int i = 0; i < clampSmoothing; i++) {
|
||||
float sinF = clampMlp * Mathf.PI * 0.5f;
|
||||
clampMlp = Mathf.Sin(sinF);
|
||||
}
|
||||
|
||||
return Quaternion.Slerp(Quaternion.identity, rotation, clampMlp * targetClampMlp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps an angular value.
|
||||
/// </summary>
|
||||
public static float ClampAngle(float angle, float clampWeight, int clampSmoothing) {
|
||||
if (clampWeight >= 1f) return 0f;
|
||||
if (clampWeight <= 0f) return angle;
|
||||
|
||||
float dot = 1f - (Mathf.Abs(angle) / 180f);
|
||||
float targetClampMlp = Mathf.Clamp(1f - ((clampWeight - dot) / (1f - dot)), 0f, 1f);
|
||||
float clampMlp = Mathf.Clamp(dot / clampWeight, 0f, 1f);
|
||||
|
||||
// Sine smoothing iterations
|
||||
for (int i = 0; i < clampSmoothing; i++) {
|
||||
float sinF = clampMlp * Mathf.PI * 0.5f;
|
||||
clampMlp = Mathf.Sin(sinF);
|
||||
}
|
||||
|
||||
return Mathf.Lerp(0f, angle, clampMlp * targetClampMlp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for matching the rotations of objects that have different orientations.
|
||||
/// </summary>
|
||||
public static Quaternion MatchRotation(Quaternion targetRotation, Vector3 targetAxis1, Vector3 targetAxis2, Vector3 axis1, Vector3 axis2) {
|
||||
Quaternion f = Quaternion.LookRotation(axis1, axis2);
|
||||
Quaternion fTarget = Quaternion.LookRotation(targetAxis1, targetAxis2);
|
||||
|
||||
Quaternion d = targetRotation * fTarget;
|
||||
return d * Quaternion.Inverse(f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an Euler rotation from 0 to 360 representation to -180 to 180.
|
||||
/// </summary>
|
||||
public static Vector3 ToBiPolar(Vector3 euler)
|
||||
{
|
||||
return new Vector3(ToBiPolar(euler.x), ToBiPolar(euler.y), ToBiPolar(euler.z));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an angular value from 0 to 360 representation to -180 to 180.
|
||||
/// </summary>
|
||||
public static float ToBiPolar(float angle)
|
||||
{
|
||||
angle = angle % 360f;
|
||||
if (angle >= 180f) return angle - 360f;
|
||||
if (angle <= -180f) return angle + 360f;
|
||||
return angle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors a Quaternion on the YZ plane in provided rotation space.
|
||||
/// </summary>
|
||||
public static Quaternion MirrorYZ(Quaternion r, Quaternion space)
|
||||
{
|
||||
r = Quaternion.Inverse(space) * r;
|
||||
Vector3 forward = r * Vector3.forward;
|
||||
Vector3 up = r * Vector3.up;
|
||||
|
||||
forward.x *= -1;
|
||||
up.x *= -1;
|
||||
|
||||
return space * Quaternion.LookRotation(forward, up);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors a Quaternion on the world space YZ plane.
|
||||
/// </summary>
|
||||
public static Quaternion MirrorYZ(Quaternion r)
|
||||
{
|
||||
Vector3 forward = r * Vector3.forward;
|
||||
Vector3 up = r * Vector3.up;
|
||||
|
||||
forward.x *= -1;
|
||||
up.x *= -1;
|
||||
|
||||
return Quaternion.LookRotation(forward, up);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5860b124f5b94f3fb04cf4180b7ad67
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,35 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The base abstract Singleton class.
|
||||
/// </summary>
|
||||
public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
|
||||
{
|
||||
|
||||
private static T sInstance = null;
|
||||
|
||||
public static T instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return sInstance;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
sInstance = null;
|
||||
}
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
if (sInstance != null) Debug.LogError(name + "error: already initialized", this);
|
||||
|
||||
sInstance = (T)this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1c8f0459a8434e80acd2f821a30412d
|
||||
timeCreated: 1435745822
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,127 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Manages solver initiation and updating
|
||||
/// </summary>
|
||||
public class SolverManager: MonoBehaviour {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// If true, will fix all the Transforms used by the solver to their initial state in each Update. This prevents potential problems with unanimated bones and animator culling with a small cost of performance. Not recommended for CCD and FABRIK solvers.
|
||||
/// </summary>
|
||||
[Tooltip("If true, will fix all the Transforms used by the solver to their initial state in each Update. This prevents potential problems with unanimated bones and animator culling with a small cost of performance. Not recommended for CCD and FABRIK solvers.")]
|
||||
public bool fixTransforms = true;
|
||||
|
||||
/// <summary>
|
||||
/// [DEPRECATED] Use "enabled = false" instead.
|
||||
/// </summary>
|
||||
public void Disable() {
|
||||
Debug.Log("IK.Disable() is deprecated. Use enabled = false instead", transform);
|
||||
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
#endregion Main
|
||||
|
||||
protected virtual void InitiateSolver() {}
|
||||
protected virtual void UpdateSolver() {}
|
||||
protected virtual void FixTransforms() {}
|
||||
|
||||
private Animator animator;
|
||||
private Animation legacy;
|
||||
private bool updateFrame;
|
||||
private bool componentInitiated;
|
||||
|
||||
void OnDisable() {
|
||||
if (!Application.isPlaying) return;
|
||||
Initiate();
|
||||
}
|
||||
|
||||
void Start() {
|
||||
Initiate();
|
||||
}
|
||||
|
||||
private bool animatePhysics {
|
||||
get {
|
||||
if (animator != null) return animator.updateMode == AnimatorUpdateMode.AnimatePhysics;
|
||||
if (legacy != null) return legacy.animatePhysics;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Initiate() {
|
||||
if (componentInitiated) return;
|
||||
|
||||
FindAnimatorRecursive(transform, true);
|
||||
|
||||
InitiateSolver();
|
||||
componentInitiated = true;
|
||||
}
|
||||
|
||||
void Update() {
|
||||
if (skipSolverUpdate) return;
|
||||
if (animatePhysics) return;
|
||||
|
||||
if (fixTransforms) FixTransforms();
|
||||
}
|
||||
|
||||
// Finds the first Animator/Animation up the hierarchy
|
||||
private void FindAnimatorRecursive(Transform t, bool findInChildren) {
|
||||
if (isAnimated) return;
|
||||
|
||||
animator = t.GetComponent<Animator>();
|
||||
legacy = t.GetComponent<Animation>();
|
||||
|
||||
if (isAnimated) return;
|
||||
|
||||
if (animator == null && findInChildren) animator = t.GetComponentInChildren<Animator>();
|
||||
if (legacy == null && findInChildren) legacy = t.GetComponentInChildren<Animation>();
|
||||
|
||||
if (!isAnimated && t.parent != null) FindAnimatorRecursive(t.parent, false);
|
||||
}
|
||||
|
||||
private bool isAnimated {
|
||||
get {
|
||||
return animator != null || legacy != null;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround hack for the solver to work with animatePhysics
|
||||
void FixedUpdate() {
|
||||
if (skipSolverUpdate) {
|
||||
skipSolverUpdate = false;
|
||||
}
|
||||
|
||||
updateFrame = true;
|
||||
|
||||
if (animatePhysics && fixTransforms) FixTransforms();
|
||||
}
|
||||
|
||||
// Updating
|
||||
void LateUpdate() {
|
||||
if (skipSolverUpdate) return;
|
||||
|
||||
// Check if either animatePhysics is false or FixedUpdate has been called
|
||||
if (!animatePhysics) updateFrame = true;
|
||||
if (!updateFrame) return;
|
||||
updateFrame = false;
|
||||
|
||||
UpdateSolver();
|
||||
}
|
||||
|
||||
// This enables other scripts to update the Animator on in FixedUpdate and the solvers with it
|
||||
private bool skipSolverUpdate;
|
||||
|
||||
public void UpdateSolverExternal() {
|
||||
if (!enabled) return;
|
||||
|
||||
skipSolverUpdate = true;
|
||||
|
||||
UpdateSolver();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24f5c8273305e4aed8dd09bf3a1a379a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Forwards collider OnTrigger.. events.
|
||||
/// </summary>
|
||||
public class TriggerEventBroadcaster : MonoBehaviour {
|
||||
|
||||
public GameObject target;
|
||||
|
||||
void OnTriggerEnter(Collider collider) {
|
||||
if (target != null) target.SendMessage("OnTriggerEnter", collider, SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
|
||||
void OnTriggerStay(Collider collider) {
|
||||
if (target != null) target.SendMessage("OnTriggerStay", collider, SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider collider) {
|
||||
if (target != null) target.SendMessage("OnTriggerExit", collider, SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee28b2c81966748468e9371d12c933a2
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,82 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for dealing with 2-dimensional vectors.
|
||||
/// </summary>
|
||||
public static class V2Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts Vector3 to Vector2 on the XZ plane
|
||||
/// </summary>
|
||||
public static Vector2 XZ(Vector3 v)
|
||||
{
|
||||
return new Vector2(v.x, v.z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns delta angle from 'dir1' to 'dir2' in degrees.
|
||||
/// </summary>
|
||||
public static float DeltaAngle(Vector2 dir1, Vector2 dir2)
|
||||
{
|
||||
float angle1 = Mathf.Atan2(dir1.x, dir1.y) * Mathf.Rad2Deg;
|
||||
float angle2 = Mathf.Atan2(dir2.x, dir2.y) * Mathf.Rad2Deg;
|
||||
return Mathf.DeltaAngle(angle1, angle2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns delta angle from Vector3 'dir1' to Vector3 'dir2' on the XZ plane in degrees.
|
||||
/// </summary>
|
||||
public static float DeltaAngleXZ(Vector3 dir1, Vector3 dir2)
|
||||
{
|
||||
float angle1 = Mathf.Atan2(dir1.x, dir1.z) * Mathf.Rad2Deg;
|
||||
float angle2 = Mathf.Atan2(dir2.x, dir2.z) * Mathf.Rad2Deg;
|
||||
return Mathf.DeltaAngle(angle1, angle2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a line from 'p1' to 'p2' intersects a circle with position 'c' and radius 'r'.
|
||||
/// </summary>
|
||||
public static bool LineCircleIntersect(Vector2 p1, Vector2 p2, Vector2 c, float r)
|
||||
{
|
||||
Vector2 d = p2 - p1;
|
||||
Vector2 f = c - p1;
|
||||
|
||||
float a = Vector2.Dot(d, d);
|
||||
float b = 2f * Vector2.Dot(f, d);
|
||||
float z = Vector2.Dot(f, f) - r * r;
|
||||
|
||||
float discr = b * b - 4f * a * z;
|
||||
if (discr < 0f) return false;
|
||||
|
||||
discr = Mathf.Sqrt(discr);
|
||||
float a2 = 2f * a;
|
||||
float t1 = (b - discr) / a2;
|
||||
float t2 = (b + discr) / a2;
|
||||
|
||||
if (t1 >= 0f && t1 <= 1f) return true;
|
||||
if (t2 >= 0f && t2 <= 1f) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if an infinite ray 'dir' from position 'p1' intersects a circle with position 'c' and radius 'r'.
|
||||
/// </summary>
|
||||
public static bool RayCircleIntersect(Vector2 p1, Vector2 dir, Vector2 c, float r)
|
||||
{
|
||||
Vector2 p2 = p1 + dir;
|
||||
p1 -= c;
|
||||
p2 -= c;
|
||||
float dx = p2.x - p1.x;
|
||||
float dy = p2.y - p1.y;
|
||||
float dr = Mathf.Sqrt(Mathf.Pow(dx, 2f) + Mathf.Pow(dy, 2f));
|
||||
float D = p1.x * p2.y - p2.x * p1.y;
|
||||
|
||||
float discr = Mathf.Pow(r, 2f) * Mathf.Pow(dr, 2f) - Mathf.Pow(D, 2f);
|
||||
return discr >= 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28fecd3bd2d91b84bb40145282fa8bf5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
305
Unity-Master/Assets/Plugins/RootMotion/Shared Scripts/V3Tools.cs
Normal file
305
Unity-Master/Assets/Plugins/RootMotion/Shared Scripts/V3Tools.cs
Normal file
@ -0,0 +1,305 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for dealing with 3-dimensional vectors.
|
||||
/// </summary>
|
||||
public static class V3Tools {
|
||||
|
||||
/// <summary>
|
||||
/// Returns yaw angle (-180 - 180) of 'forward' vector.
|
||||
/// </summary>
|
||||
public static float GetYaw(Vector3 forward)
|
||||
{
|
||||
if (forward.x == 0f && forward.z == 0f) return 0f;
|
||||
if (float.IsInfinity(forward.x) || float.IsInfinity(forward.z)) return 0;
|
||||
return Mathf.Atan2(forward.x, forward.z) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns pitch angle (-90 - 90) of 'forward' vector.
|
||||
/// </summary>
|
||||
public static float GetPitch(Vector3 forward)
|
||||
{
|
||||
forward = forward.normalized; // Asin range -1 - 1
|
||||
return -Mathf.Asin(forward.y) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns bank angle (-180 - 180) of 'forward' and 'up' vectors.
|
||||
/// </summary>
|
||||
public static float GetBank(Vector3 forward, Vector3 up)
|
||||
{
|
||||
Quaternion q = Quaternion.Inverse(Quaternion.LookRotation(Vector3.up, forward));
|
||||
up = q * up;
|
||||
float result = Mathf.Atan2(up.x, up.z) * Mathf.Rad2Deg;
|
||||
return Mathf.Clamp(result, -180f, 180f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns yaw angle (-180 - 180) of 'forward' vector relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetYaw(Vector3 spaceForward, Vector3 spaceUp, Vector3 forward)
|
||||
{
|
||||
Quaternion space = Quaternion.Inverse(Quaternion.LookRotation(spaceForward, spaceUp));
|
||||
Vector3 dirLocal = space * forward;
|
||||
if (dirLocal.x == 0f && dirLocal.z == 0f) return 0f;
|
||||
if (float.IsInfinity(dirLocal.x) || float.IsInfinity(dirLocal.z)) return 0;
|
||||
return Mathf.Atan2(dirLocal.x, dirLocal.z) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns pitch angle (-90 - 90) of 'forward' vector relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetPitch(Vector3 spaceForward, Vector3 spaceUp, Vector3 forward)
|
||||
{
|
||||
Quaternion space = Quaternion.Inverse(Quaternion.LookRotation(spaceForward, spaceUp));
|
||||
Vector3 dirLocal = space * forward;
|
||||
forward.Normalize();
|
||||
return -Mathf.Asin(dirLocal.y) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns bank angle (-180 - 180) of 'forward' and 'up' vectors relative to rotation space defined by spaceForward and spaceUp axes.
|
||||
/// </summary>
|
||||
public static float GetBank(Vector3 spaceForward, Vector3 spaceUp, Vector3 forward, Vector3 up)
|
||||
{
|
||||
Quaternion space = Quaternion.Inverse(Quaternion.LookRotation(spaceForward, spaceUp));
|
||||
forward = space * forward;
|
||||
up = space * up;
|
||||
|
||||
Quaternion q = Quaternion.Inverse(Quaternion.LookRotation(spaceUp, forward));
|
||||
up = q * up;
|
||||
float result = Mathf.Atan2(up.x, up.z) * Mathf.Rad2Deg;
|
||||
return Mathf.Clamp(result, -180f, 180f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized Vector3.Lerp
|
||||
/// </summary>
|
||||
public static Vector3 Lerp(Vector3 fromVector, Vector3 toVector, float weight) {
|
||||
if (weight <= 0f) return fromVector;
|
||||
if (weight >= 1f) return toVector;
|
||||
|
||||
return Vector3.Lerp(fromVector, toVector, weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized Vector3.Slerp
|
||||
/// </summary>
|
||||
public static Vector3 Slerp(Vector3 fromVector, Vector3 toVector, float weight) {
|
||||
if (weight <= 0f) return fromVector;
|
||||
if (weight >= 1f) return toVector;
|
||||
|
||||
return Vector3.Slerp(fromVector, toVector, weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns vector projection on axis multiplied by weight.
|
||||
/// </summary>
|
||||
public static Vector3 ExtractVertical(Vector3 v, Vector3 verticalAxis, float weight)
|
||||
{
|
||||
if (weight <= 0f) return Vector3.zero;
|
||||
if (verticalAxis == Vector3.up) return Vector3.up * v.y * weight;
|
||||
return Vector3.Project(v, verticalAxis) * weight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns vector projected to a plane and multiplied by weight.
|
||||
/// </summary>
|
||||
public static Vector3 ExtractHorizontal(Vector3 v, Vector3 normal, float weight)
|
||||
{
|
||||
if (weight <= 0f) return Vector3.zero;
|
||||
if (normal == Vector3.up) return new Vector3(v.x, 0f, v.z) * weight;
|
||||
Vector3 tangent = v;
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
return Vector3.Project(v, tangent) * weight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flattens a vector on a plane defined by 'normal'.
|
||||
/// </summary>
|
||||
public static Vector3 Flatten(Vector3 v, Vector3 normal)
|
||||
{
|
||||
if (normal == Vector3.up) return new Vector3(v.x, 0f, v.z);
|
||||
return v - Vector3.Project(v, normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the direction to clampWeight from normalDirection, clampSmoothing is the number of sine smoothing iterations applied on the result.
|
||||
/// </summary>
|
||||
public static Vector3 ClampDirection(Vector3 direction, Vector3 normalDirection, float clampWeight, int clampSmoothing)
|
||||
{
|
||||
if (clampWeight <= 0) return direction;
|
||||
|
||||
if (clampWeight >= 1f) return normalDirection;
|
||||
|
||||
// Getting the angle between direction and normalDirection
|
||||
float angle = Vector3.Angle(normalDirection, direction);
|
||||
float dot = 1f - (angle / 180f);
|
||||
|
||||
if (dot > clampWeight) return direction;
|
||||
|
||||
// Clamping the target
|
||||
float targetClampMlp = clampWeight > 0 ? Mathf.Clamp(1f - ((clampWeight - dot) / (1f - dot)), 0f, 1f) : 1f;
|
||||
|
||||
// Calculating the clamp multiplier
|
||||
float clampMlp = clampWeight > 0 ? Mathf.Clamp(dot / clampWeight, 0f, 1f) : 1f;
|
||||
|
||||
// Sine smoothing iterations
|
||||
for (int i = 0; i < clampSmoothing; i++)
|
||||
{
|
||||
float sinF = clampMlp * Mathf.PI * 0.5f;
|
||||
clampMlp = Mathf.Sin(sinF);
|
||||
}
|
||||
|
||||
// Slerping the direction (don't use Lerp here, it breaks it)
|
||||
return Vector3.Slerp(normalDirection, direction, clampMlp * targetClampMlp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the direction to clampWeight from normalDirection, clampSmoothing is the number of sine smoothing iterations applied on the result.
|
||||
/// </summary>
|
||||
public static Vector3 ClampDirection(Vector3 direction, Vector3 normalDirection, float clampWeight, int clampSmoothing, out bool changed) {
|
||||
changed = false;
|
||||
|
||||
if (clampWeight <= 0) return direction;
|
||||
|
||||
if (clampWeight >= 1f) {
|
||||
changed = true;
|
||||
return normalDirection;
|
||||
}
|
||||
|
||||
// Getting the angle between direction and normalDirection
|
||||
float angle = Vector3.Angle(normalDirection, direction);
|
||||
float dot = 1f - (angle / 180f);
|
||||
|
||||
if (dot > clampWeight) return direction;
|
||||
changed = true;
|
||||
|
||||
// Clamping the target
|
||||
float targetClampMlp = clampWeight > 0? Mathf.Clamp(1f - ((clampWeight - dot) / (1f - dot)), 0f, 1f): 1f;
|
||||
|
||||
// Calculating the clamp multiplier
|
||||
float clampMlp = clampWeight > 0? Mathf.Clamp(dot / clampWeight, 0f, 1f): 1f;
|
||||
|
||||
// Sine smoothing iterations
|
||||
for (int i = 0; i < clampSmoothing; i++) {
|
||||
float sinF = clampMlp * Mathf.PI * 0.5f;
|
||||
clampMlp = Mathf.Sin(sinF);
|
||||
}
|
||||
|
||||
// Slerping the direction (don't use Lerp here, it breaks it)
|
||||
return Vector3.Slerp(normalDirection, direction, clampMlp * targetClampMlp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the direction to clampWeight from normalDirection, clampSmoothing is the number of sine smoothing iterations applied on the result.
|
||||
/// </summary>
|
||||
public static Vector3 ClampDirection(Vector3 direction, Vector3 normalDirection, float clampWeight, int clampSmoothing, out float clampValue) {
|
||||
clampValue = 1f;
|
||||
|
||||
if (clampWeight <= 0) return direction;
|
||||
|
||||
if (clampWeight >= 1f) {
|
||||
return normalDirection;
|
||||
}
|
||||
|
||||
// Getting the angle between direction and normalDirection
|
||||
float angle = Vector3.Angle(normalDirection, direction);
|
||||
float dot = 1f - (angle / 180f);
|
||||
|
||||
if (dot > clampWeight) {
|
||||
clampValue = 0f;
|
||||
return direction;
|
||||
}
|
||||
|
||||
// Clamping the target
|
||||
float targetClampMlp = clampWeight > 0? Mathf.Clamp(1f - ((clampWeight - dot) / (1f - dot)), 0f, 1f): 1f;
|
||||
|
||||
// Calculating the clamp multiplier
|
||||
float clampMlp = clampWeight > 0? Mathf.Clamp(dot / clampWeight, 0f, 1f): 1f;
|
||||
|
||||
// Sine smoothing iterations
|
||||
for (int i = 0; i < clampSmoothing; i++) {
|
||||
float sinF = clampMlp * Mathf.PI * 0.5f;
|
||||
clampMlp = Mathf.Sin(sinF);
|
||||
}
|
||||
|
||||
// Slerping the direction (don't use Lerp here, it breaks it)
|
||||
float slerp = clampMlp * targetClampMlp;
|
||||
clampValue = 1f - slerp;
|
||||
return Vector3.Slerp(normalDirection, direction, slerp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the intersection point of line and plane
|
||||
/// </summary>
|
||||
public static Vector3 LineToPlane(Vector3 origin, Vector3 direction, Vector3 planeNormal, Vector3 planePoint) {
|
||||
float dot = Vector3.Dot(planePoint - origin, planeNormal);
|
||||
float normalDot = Vector3.Dot(direction, planeNormal);
|
||||
|
||||
if (normalDot == 0.0f) return Vector3.zero;
|
||||
|
||||
float dist = dot / normalDot;
|
||||
return origin + direction.normalized * dist;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects a point to a plane.
|
||||
/// </summary>
|
||||
public static Vector3 PointToPlane(Vector3 point, Vector3 planePosition, Vector3 planeNormal) {
|
||||
if (planeNormal == Vector3.up) {
|
||||
return new Vector3(point.x, planePosition.y, point.z);
|
||||
}
|
||||
|
||||
Vector3 tangent = point - planePosition;
|
||||
Vector3 normal = planeNormal;
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
|
||||
return planePosition + Vector3.Project(point - planePosition, tangent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as Transform.TransformPoint(), but not using scale.
|
||||
/// </summary>
|
||||
public static Vector3 TransformPointUnscaled(Transform t, Vector3 point)
|
||||
{
|
||||
return t.position + t.rotation * point;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as Transform.InverseTransformPoint(), but not using scale.
|
||||
/// </summary>
|
||||
public static Vector3 InverseTransformPointUnscaled(Transform t, Vector3 point)
|
||||
{
|
||||
return Quaternion.Inverse(t.rotation) * (point - t.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as Transform.InverseTransformPoint();
|
||||
/// </summary>
|
||||
public static Vector3 InverseTransformPoint(Vector3 tPos, Quaternion tRot, Vector3 tScale, Vector3 point)
|
||||
{
|
||||
return Div(Quaternion.Inverse(tRot) * (point - tPos), tScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as Transform.TransformPoint()
|
||||
/// </summary>
|
||||
public static Vector3 TransformPoint(Vector3 tPos, Quaternion tRot, Vector3 tScale, Vector3 point)
|
||||
{
|
||||
return tPos + Vector3.Scale(tRot * point, tScale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides the values of v1 by the values of v2.
|
||||
/// </summary>
|
||||
public static Vector3 Div(Vector3 v1, Vector3 v2)
|
||||
{
|
||||
return new Vector3(v1.x / v2.x, v1.y / v2.y, v1.z / v2.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a422d0570aa54fb39eb5ace0e12a4ed
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion {
|
||||
|
||||
/// <summary>
|
||||
/// Manages warning messages.
|
||||
/// </summary>
|
||||
public static class Warning {
|
||||
|
||||
public static bool logged;
|
||||
|
||||
public delegate void Logger(string message);
|
||||
|
||||
public static void Log(string message, Logger logger, bool logInEditMode = false) {
|
||||
if (!logInEditMode && !Application.isPlaying) return;
|
||||
if (logged) return;
|
||||
if (logger != null) logger(message);
|
||||
logged = true;
|
||||
}
|
||||
|
||||
public static void Log(string message, Transform context, bool logInEditMode = false) {
|
||||
if (!logInEditMode && !Application.isPlaying) return;
|
||||
if (logged) return;
|
||||
Debug.LogWarning(message, context);
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1a06ee7b207c4743876a042752f325b
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user