initial upload

This commit is contained in:
tom.hempel
2025-09-21 22:42:26 +02:00
commit d03bcd4ba5
6231 changed files with 351582 additions and 0 deletions

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 24b4e6f1f9c204bc5bd16676aecf434d
folderAsset: yes
timeCreated: 1434626794
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,317 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// %IK system for standard biped characters that is designed to replicate and enhance the behaviour of the Unity's built-in character %IK setup.
/// </summary>
[HelpURL("http://www.root-motion.com/finalikdox/html/page4.html")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/IK/Biped IK")]
public class BipedIK : SolverManager {
// Open the User Manual URL
[ContextMenu("User Manual")]
private void OpenUserManual() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page4.html");
}
// Open the Script Reference URL
[ContextMenu("Scrpt Reference")]
private void OpenScriptReference() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_biped_i_k.html");
}
// Link to the Final IK Google Group
[ContextMenu("Support Group")]
void SupportGroup() {
Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
}
// Link to the Final IK Asset Store thread in the Unity Community
[ContextMenu("Asset Store Thread")]
void ASThread() {
Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
}
#region Main Interface
/// <summary>
/// References to character bones.
/// </summary>
public BipedReferences references = new BipedReferences();
/// <summary>
/// The %IK solvers.
/// </summary>
public BipedIKSolvers solvers = new BipedIKSolvers();
/// <summary>
/// Gets the %IK position weight.
/// </summary>
/// <param name='goal'>
/// %IK Goal.
/// </param>
public float GetIKPositionWeight(AvatarIKGoal goal) {
return GetGoalIK(goal).GetIKPositionWeight();
}
/// <summary>
/// Gets the %IK rotation weight.
/// </summary>
/// <param name='goal'>
/// IK Goal.
/// </param>
public float GetIKRotationWeight(AvatarIKGoal goal) {
return GetGoalIK(goal).GetIKRotationWeight();
}
/// <summary>
/// Sets the %IK position weight.
/// </summary>
/// <param name='goal'>
/// %IK Goal.
/// </param>
/// <param name='weight'>
/// Weight.
/// </param>
public void SetIKPositionWeight(AvatarIKGoal goal, float weight) {
GetGoalIK(goal).SetIKPositionWeight(weight);
}
/// <summary>
/// Sets the %IK rotation weight.
/// </summary>
/// <param name='goal'>
/// %IK Goal.
/// </param>
/// <param name='weight'>
/// Weight.
/// </param>
public void SetIKRotationWeight(AvatarIKGoal goal, float weight) {
GetGoalIK(goal).SetIKRotationWeight(weight);
}
/// <summary>
/// Sets the %IK position.
/// </summary>
/// <param name='goal'>
/// %IK Goal.
/// </param>
/// <param name='IKPosition'>
/// Position.
/// </param>
public void SetIKPosition(AvatarIKGoal goal, Vector3 IKPosition) {
GetGoalIK(goal).SetIKPosition(IKPosition);
}
/// <summary>
/// Sets the %IK rotation.
/// </summary>
/// <param name='goal'>
/// %IK Goal.
/// </param>
/// <param name='IKRotation'>
/// Rotation.
/// </param>
public void SetIKRotation(AvatarIKGoal goal, Quaternion IKRotation) {
GetGoalIK(goal).SetIKRotation(IKRotation);
}
/// <summary>
/// Gets the %IK position.
/// </summary>
/// <param name='goal'>
/// %IK Goal.
/// </param>
public Vector3 GetIKPosition(AvatarIKGoal goal) {
return GetGoalIK(goal).GetIKPosition();
}
/// <summary>
/// Gets the %IK rotation.
/// </summary>
/// <param name='goal'>
/// %IK Goal.
/// </param>
public Quaternion GetIKRotation(AvatarIKGoal goal) {
return GetGoalIK(goal).GetIKRotation();
}
/// <summary>
/// Sets the look at weight.
/// </summary>
/// <param name='weight'>
/// Master Weight.
/// </param>
/// <param name='bodyWeight'>
/// Body weight.
/// </param>
/// <param name='headWeight'>
/// Head weight.
/// </param>
/// <param name='eyesWeight'>
/// Eyes weight.
/// </param>
/// <param name='clampWeight'>
/// Clamp weight for body and head.
/// </param>
/// <param name='clampWeightEyes'>
/// Clamp weight for eyes.
/// </param>
public void SetLookAtWeight(float weight, float bodyWeight , float headWeight, float eyesWeight, float clampWeight, float clampWeightHead, float clampWeightEyes) {
solvers.lookAt.SetLookAtWeight(weight, bodyWeight, headWeight, eyesWeight, clampWeight, clampWeightHead, clampWeightEyes);
}
/// <summary>
/// Sets the look at target.
/// </summary>
/// <param name='lookAtPosition'>
/// Look at position.
/// </param>
public void SetLookAtPosition(Vector3 lookAtPosition) {
solvers.lookAt.SetIKPosition(lookAtPosition);
}
/// <summary>
/// Sets the spine %IK position.
/// </summary>
/// <param name='spinePosition'>
/// Spine %IK position.
/// </param>
public void SetSpinePosition(Vector3 spinePosition) {
solvers.spine.SetIKPosition(spinePosition);
}
/// <summary>
/// Sets the spine weight.
/// </summary>
/// <param name='weight'>
/// Weight.
/// </param>
public void SetSpineWeight(float weight) {
solvers.spine.SetIKPositionWeight(weight);
}
/// <summary>
/// Gets the limb solver for the %IK Goal.
/// </summary>
/// <returns>
/// The solver.
/// </returns>
/// <param name='goal'>
/// %IK Goal.
/// </param>
public IKSolverLimb GetGoalIK(AvatarIKGoal goal) {
switch(goal) {
case AvatarIKGoal.LeftFoot: return solvers.leftFoot;
case AvatarIKGoal.RightFoot: return solvers.rightFoot;
case AvatarIKGoal.LeftHand: return solvers.leftHand;
case AvatarIKGoal.RightHand: return solvers.rightHand;
}
return null;
}
/// <summary>
/// (Re)Initiates the biped IK solvers.
/// </summary>
public void InitiateBipedIK() {
InitiateSolver();
}
/// <summary>
/// Updating BipedIK
/// </summary>
public void UpdateBipedIK() {
UpdateSolver();
}
/*
* Set default solver values.
* */
public void SetToDefaults() {
// Limbs
foreach (IKSolverLimb limb in solvers.limbs) {
limb.SetIKPositionWeight(0f);
limb.SetIKRotationWeight(0f);
limb.bendModifier = IKSolverLimb.BendModifier.Animation;
limb.bendModifierWeight = 1f;
}
solvers.leftHand.maintainRotationWeight = 0f;
solvers.rightHand.maintainRotationWeight = 0f;
// Spine
solvers.spine.SetIKPositionWeight(0f);
solvers.spine.tolerance = 0f;
solvers.spine.maxIterations = 2;
solvers.spine.useRotationLimits = false;
// Aim
solvers.aim.SetIKPositionWeight(0f);
solvers.aim.tolerance = 0f;
solvers.aim.maxIterations = 2;
// LookAt
SetLookAtWeight(0f, 0.5f, 1f, 1f, 0.5f, 0.7f, 0.5f);
}
#endregion Main Interface
/*
* Fixes all the Transforms used by the solver to their default local states.
* */
protected override void FixTransforms() {
solvers.pelvis.FixTransforms();
solvers.lookAt.FixTransforms();
for (int i = 0; i < solvers.limbs.Length; i++) solvers.limbs[i].FixTransforms();
}
/*
* Initiates the %IK solver
* */
protected override void InitiateSolver() {
string message = "";
if (BipedReferences.SetupError(references, ref message)) {
Warning.Log(message, references.root, false);
return;
}
solvers.AssignReferences(references);
// Initiating solvers
if (solvers.spine.bones.Length > 1) solvers.spine.Initiate(transform);
solvers.lookAt.Initiate(transform);
solvers.aim.Initiate(transform);
foreach (IKSolverLimb limb in solvers.limbs) limb.Initiate(transform);
// Initiating constraints
solvers.pelvis.Initiate(references.pelvis);
}
/*
* Updates the solvers. If you need full control of the execution order of your IK solvers, disable this script and call UpdateSolver() instead.
* */
protected override void UpdateSolver() {
// Storing Limb bend and rotation before %IK
for (int i = 0; i < solvers.limbs.Length; i++) {
solvers.limbs[i].MaintainBend();
solvers.limbs[i].MaintainRotation();
}
// Updating constraints
solvers.pelvis.Update();
// Updating %IK solvers
if (solvers.spine.bones.Length > 1) solvers.spine.Update();
solvers.aim.Update();
solvers.lookAt.Update();
for (int i = 0; i < solvers.limbs.Length; i++) solvers.limbs[i].Update();
}
/// <summary>
/// Logs the warning if no other warning has beed logged in this session.
/// </summary>
public void LogWarning(string message) {
Warning.Log(message, transform);
}
}
}

View File

@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 6d406d8b892f54ccaa5b0ef4de59944a
labels:
- InverseKinematics
- IK
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 9998
icon: {fileID: 2800000, guid: 2631a18e2dd7844718dea7db8ecf6c03, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
using UnityEngine;
using System.Collections;
using System;
namespace RootMotion.FinalIK {
/// <summary>
/// BipedIK solver collection.
/// </summary>
[System.Serializable]
public class BipedIKSolvers {
/// <summary>
/// The left foot
/// </summary>
public IKSolverLimb leftFoot = new IKSolverLimb(AvatarIKGoal.LeftFoot);
/// <summary>
/// The right foot.
/// </summary>
public IKSolverLimb rightFoot = new IKSolverLimb(AvatarIKGoal.RightFoot);
/// <summary>
/// The left hand.
/// </summary>
public IKSolverLimb leftHand = new IKSolverLimb(AvatarIKGoal.LeftHand);
/// <summary>
/// The right hand.
/// </summary>
public IKSolverLimb rightHand = new IKSolverLimb(AvatarIKGoal.RightHand);
/// <summary>
/// The spine.
/// </summary>
public IKSolverFABRIK spine = new IKSolverFABRIK();
/// <summary>
/// The Look At %IK.
/// </summary>
public IKSolverLookAt lookAt = new IKSolverLookAt();
/// <summary>
/// The Aim %IK. Rotates the spine to aim a transform's forward towards the target.
/// </summary>
public IKSolverAim aim = new IKSolverAim();
/// <summary>
/// %Constraints for manipulating the character's pelvis.
/// </summary>
public Constraints pelvis = new Constraints();
/// <summary>
/// Gets the array containing all the limbs.
/// </summary>
public IKSolverLimb[] limbs {
get {
if (_limbs == null || (_limbs != null && _limbs.Length != 4)) _limbs = new IKSolverLimb[4] { leftFoot, rightFoot, leftHand, rightHand };
return _limbs;
}
}
private IKSolverLimb[] _limbs;
/// <summary>
/// Gets the array containing all %IK solvers.
/// </summary>
public IKSolver[] ikSolvers {
get {
if (_ikSolvers == null || (_ikSolvers != null && _ikSolvers.Length != 7)) _ikSolvers = new IKSolver[7] { leftFoot, rightFoot, leftHand, rightHand, spine, lookAt, aim };
return _ikSolvers;
}
}
private IKSolver[] _ikSolvers;
public void AssignReferences(BipedReferences references) {
// Assigning limbs from references
leftHand.SetChain(references.leftUpperArm, references.leftForearm, references.leftHand, references.root);
rightHand.SetChain(references.rightUpperArm, references.rightForearm, references.rightHand, references.root);
leftFoot.SetChain(references.leftThigh, references.leftCalf, references.leftFoot, references.root);
rightFoot.SetChain(references.rightThigh, references.rightCalf, references.rightFoot, references.root);
// Assigning spine bones from references
spine.SetChain(references.spine, references.root);
// Assigning lookAt bones from references
lookAt.SetChain(references.spine, references.head, references.eyes, references.root);
// Assigning Aim bones from references
aim.SetChain(references.spine, references.root);
leftFoot.goal = AvatarIKGoal.LeftFoot;
rightFoot.goal = AvatarIKGoal.RightFoot;
leftHand.goal = AvatarIKGoal.LeftHand;
rightHand.goal = AvatarIKGoal.RightHand;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 552b6be99351d42019784667c84a7d5a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 230c114522f924447be695b657911302
folderAsset: yes
timeCreated: 1434626794
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,42 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// The base abstract class for all Transform constraints.
/// </summary>
[System.Serializable]
public abstract class Constraint {
#region Main Interface
/// <summary>
/// The transform to constrain.
/// </summary>
public Transform transform;
/// <summary>
/// %Constraint weight.
/// </summary>
public float weight;
/// <summary>
/// Gets a value indicating whether this <see cref="Constraint"/> is valid.
/// </summary>
/// <value>
/// <c>true</c> if is valid; otherwise, <c>false</c>.
/// </value>
public bool isValid {
get {
return transform != null;
}
}
/// <summary>
/// Updates the constraint.
/// </summary>
public abstract void UpdateConstraint();
#endregion Main Interface
}
}

View File

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

View File

@ -0,0 +1,34 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// %Constraints to position in world space.
/// </summary>
[System.Serializable]
public class ConstraintPosition : Constraint {
#region Main Interface
/// <summary>
/// The target position.
/// </summary>
public Vector3 position;
public override void UpdateConstraint() {
if (weight <= 0) return;
if (!isValid) return;
// Lerping to position
transform.position = Vector3.Lerp(transform.position, position, weight);
}
#endregion Main Interface
public ConstraintPosition() {}
public ConstraintPosition(Transform transform) {
this.transform = transform;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 071793b8887c44ddda2d9dc3a0525167
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,64 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Offsets the transform from its (animated) position.
/// </summary>
[System.Serializable]
public class ConstraintPositionOffset : Constraint {
#region Main Interface
/// <summary>
/// The position offset in world space.
/// </summary>
public Vector3 offset;
public override void UpdateConstraint() {
if (weight <= 0) return;
if (!isValid) return;
// Initiating
if (!initiated) {
// Storing default values
defaultLocalPosition = transform.localPosition;
lastLocalPosition = transform.localPosition;
initiated = true;
}
// Check if position has changed. If true, set default local position to current.
if (positionChanged) defaultLocalPosition = transform.localPosition;
// Offsetting the position
transform.localPosition = defaultLocalPosition;
transform.position += offset * weight;
// Store the current local position to check if it has changed in the next update.
lastLocalPosition = transform.localPosition;
}
#endregion Main Interface
public ConstraintPositionOffset() {}
public ConstraintPositionOffset(Transform transform) {
this.transform = transform;
}
private Vector3 defaultLocalPosition, lastLocalPosition;
private bool initiated;
/*
* Check if position has been changed by animation or any other external script.
* If not, consider the object to be static and offset only from the default rotation.
* */
private bool positionChanged {
get {
return transform.localPosition != lastLocalPosition;
}
}
}
}

View File

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

View File

@ -0,0 +1,34 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// %Constraints to rotation in world space
/// </summary>
[System.Serializable]
public class ConstraintRotation : Constraint {
#region Main Interface
/// <summary>
/// The target rotation.
/// </summary>
public Quaternion rotation;
public override void UpdateConstraint() {
if (weight <= 0) return;
if (!isValid) return;
// Slerping to target rotation
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, weight);
}
#endregion Main Interface
public ConstraintRotation() {}
public ConstraintRotation(Transform transform) {
this.transform = transform;
}
}
}

View File

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

View File

@ -0,0 +1,64 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Offsets the transform from its (animated) rotation
/// </summary>
[System.Serializable]
public class ConstraintRotationOffset: Constraint {
#region Main Interface
/// <summary>
/// The rotation offset in world space.
/// </summary>
public Quaternion offset;
public override void UpdateConstraint() {
if (weight <= 0) return;
if (!isValid) return;
// Initiating
if (!initiated) {
// Storing default rotations.
defaultLocalRotation = transform.localRotation;
lastLocalRotation = transform.localRotation;
initiated = true;
}
// Check if rotation has changed. If true, set default local rotation to current.
if (rotationChanged) defaultLocalRotation = transform.localRotation;
// Offsetting the rotation
transform.localRotation = defaultLocalRotation;
transform.rotation = Quaternion.Slerp(transform.rotation, offset, weight);
// Store the current local rotation to check if it has changed in the next update.
lastLocalRotation = transform.localRotation;
}
#endregion Main Interface
public ConstraintRotationOffset() {}
public ConstraintRotationOffset(Transform transform) {
this.transform = transform;
}
private Quaternion defaultRotation, defaultLocalRotation, lastLocalRotation, defaultTargetLocalRotation;
private bool initiated;
/*
* Check if rotation has been changed by animation or any other external script.
* If not, consider the object to be static and offset only from the default rotation.
* */
private bool rotationChanged {
get {
return transform.localRotation != lastLocalRotation;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 530e73d7a55ff47029c43502e289273f
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,95 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Contains and manages a set of constraints.
/// </summary>
[System.Serializable]
public class Constraints {
#region Main Interface
/// <summary>
/// The transform.
/// </summary>
public Transform transform;
/// <summary>
/// The target.
/// </summary>
public Transform target;
/// <summary>
/// The position offset.
/// </summary>
public Vector3 positionOffset;
/// <summary>
/// The position to lerp to by positionWeight
/// </summary>
public Vector3 position;
/// <summary>
/// The weight of lerping to position
/// </summary>
[Range(0f, 1f)]
public float positionWeight;
/// <summary>
/// The rotation offset.
/// </summary>
public Vector3 rotationOffset;
/// <summary>
/// The rotation to slerp to by rotationWeight
/// </summary>
public Vector3 rotation;
/// <summary>
/// The weight of slerping to rotation
/// </summary>
[Range(0f, 1f)]
public float rotationWeight;
private Vector3 defaultLocalPosition;
private Quaternion defaultLocalRotation;
/// <summary>
/// Determines whether this instance is valid.
/// </summary>
public bool IsValid() {
return transform != null;
}
/// <summary>
/// Initiate to the specified transform.
/// </summary>
public void Initiate(Transform transform) {
this.transform = transform;
this.position = transform.position;
this.rotation = transform.eulerAngles;
defaultLocalPosition = transform.localPosition;
defaultLocalRotation = transform.localRotation;
}
public void FixTransforms()
{
transform.localPosition = defaultLocalPosition;
transform.localRotation = defaultLocalRotation;
}
/// <summary>
/// Updates the constraints.
/// </summary>
public void Update() {
if (!IsValid()) return;
// Position
if (target != null) position = target.position;
transform.position += positionOffset;
if (positionWeight > 0f) transform.position = Vector3.Lerp(transform.position, position, positionWeight);
// Rotation
if (target != null) rotation = target.eulerAngles;
transform.rotation = Quaternion.Euler(rotationOffset) * transform.rotation;
if (rotationWeight > 0f) transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(rotation), rotationWeight);
}
#endregion Main Interface
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 11679c68b0e4448338c1e21138a3596a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

View File

@ -0,0 +1,6 @@
fileFormatVersion: 2
guid: e7b02201a6e3c416cad2e367877dacc7
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,6 @@
fileFormatVersion: 2
guid: 365bd3269a5454635af24dc6962f7d1d
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,6 @@
fileFormatVersion: 2
guid: 7ef58c8cd22c3461180e62ac068acdc4
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 44d91317b400b4b9ab842fa07822acc2
folderAsset: yes
timeCreated: 1434626794
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,387 @@
using UnityEngine;
using System.Collections;
using RootMotion;
using System;
namespace RootMotion.FinalIK {
/// <summary>
/// Contains a LimbIK solver and some additional logic to handle the finger.
/// </summary>
[System.Serializable]
public class Finger {
[System.Serializable]
public enum DOF {
One,
Three
}
/// <summary>
/// Master Weight for the finger.
/// </summary>
[Tooltip("Master Weight for the finger.")]
[Range(0f, 1f)] public float weight = 1f;
/// <summary>
/// The weight of rotating the finger tip and bending the finger to the target.
/// </summary>
[Tooltip("The weight of rotating the finger tip and bending the finger to the target.")]
[Range(0f, 1f)]
public float rotationWeight = 1f;
[Tooltip("Rotational degrees of freedom. When set to 'One' the fingers will be able to be rotated only around a single axis. When 3, all 3 axes are free to rotate around.")]
/// <summary>
/// Rotational degrees of freedom. When set to 'One' the fingers will be able to be rotated only around a single axis. When 3, all 3 axes are free to rotate around.
/// </summary>
public DOF rotationDOF;
[Tooltip("If enabled, keeps bone1 twist angle fixed relative to bone2.")]
/// <summary>
/// If enabled, keeps bone1 twist angle fixed relative to bone2.
/// </summary>
public bool fixBone1Twist;
/// <summary>
/// The first bone of the finger.
/// </summary>
[Tooltip("The first bone of the finger.")]
public Transform bone1;
/// <summary>
/// The second bone of the finger.
/// </summary>
[Tooltip("The second bone of the finger.")]
public Transform bone2;
/// <summary>
/// The (optional) third bone of the finger. This can be ignored for thumbs.
/// </summary>
[Tooltip("The (optional) third bone of the finger. This can be ignored for thumbs.")]
public Transform bone3;
/// <summary>
/// The fingertip object. If your character doesn't have tip bones, you can create an empty GameObject and parent it to the last bone in the finger. Place it to the tip of the finger.
/// </summary>
[Tooltip("The fingertip object. If your character doesn't have tip bones, you can create an empty GameObject and parent it to the last bone in the finger. Place it to the tip of the finger.")]
public Transform tip;
/// <summary>
/// The IK target (optional, can use IKPosition and IKRotation directly).
/// </summary>
[Tooltip("The IK target (optional, can use IKPosition and IKRotation directly).")]
public Transform target;
/// <summary>
/// Has the finger properly initiated (in play mode only)?
/// </summary>
public bool initiated { get; private set; }
/// <summary>
/// Gets or sets the IK target position if target is not used.
/// </summary>
public Vector3 IKPosition {
get {
return solver.IKPosition;
}
set {
solver.IKPosition = value;
}
}
/// <summary>
/// Gets or sets the IK target rotation if target is not used.
/// </summary>
public Quaternion IKRotation {
get {
return solver.IKRotation;
}
set {
solver.IKRotation = value;
}
}
/// <summary>
/// Is this finger setup valid?
/// </summary>
public bool IsValid(ref string errorMessage) {
if (bone1 == null || bone2 == null || tip == null) {
errorMessage = "One of the bones in the Finger Rig is null, can not initiate solvers.";
return false;
}
return true;
}
private IKSolverLimb solver;
private Quaternion bone3RelativeToTarget;
private Vector3 bone3DefaultLocalPosition;
private Quaternion bone3DefaultLocalRotation;
private Vector3 bone1Axis;
private Vector3 tipAxis;
private Vector3 bone1TwistAxis;
private Vector3 defaultBendNormal;
// Initiates the LimbIK solver
public void Initiate(Transform hand, int index) {
initiated = false;
string errorMessage = string.Empty;
if (!IsValid(ref errorMessage)) {
Warning.Log(errorMessage, hand, false);
return;
}
solver = new IKSolverLimb();
solver.IKPositionWeight = weight;
solver.bendModifier = IKSolverLimb.BendModifier.Target;
solver.bendModifierWeight = 1f;
defaultBendNormal = -Vector3.Cross(tip.position - bone1.position, bone2.position - bone1.position).normalized;
solver.bendNormal = defaultBendNormal;
Vector3 axisWorld = Vector3.Cross(bone2.position - bone1.position, tip.position - bone1.position);
bone1Axis = Quaternion.Inverse(bone1.rotation) * axisWorld;
tipAxis = Quaternion.Inverse(tip.rotation) * axisWorld;
Vector3 normal = bone2.position - bone1.position;
Vector3 tangent = -Vector3.Cross(tip.position - bone1.position, bone2.position - bone1.position);
Vector3.OrthoNormalize(ref normal, ref tangent);
bone1TwistAxis = Quaternion.Inverse(bone1.rotation) * tangent;
IKPosition = tip.position;
IKRotation = tip.rotation;
if (bone3 != null) {
bone3RelativeToTarget = Quaternion.Inverse(IKRotation) * bone3.rotation;
bone3DefaultLocalPosition = bone3.localPosition;
bone3DefaultLocalRotation = bone3.localRotation;
}
solver.SetChain(bone1, bone2, tip, hand);
solver.Initiate(hand);
initiated = true;
}
// Fix bones to their initial local position and rotation
public void FixTransforms() {
if (!initiated) return;
if (weight <= 0f) return;
solver.FixTransforms();
if (bone3 != null) {
bone3.localPosition = bone3DefaultLocalPosition;
bone3.localRotation = bone3DefaultLocalRotation;
}
}
// Stores the default localPosition/Rotation of the finger bones used by FixTransforms()
public void StoreDefaultLocalState() {
if (!initiated) return;
solver.StoreDefaultLocalState();
if (bone3 != null) {
bone3DefaultLocalPosition = bone3.localPosition;
bone3DefaultLocalRotation = bone3.localRotation;
}
}
// Update the LimbIK solver and rotate the optional 3rd bone
public void Update(float masterWeight) {
if (!initiated) return;
float w = weight * masterWeight;
if (w <= 0f) return;
solver.target = target;
if (target != null) {
IKPosition = target.position;
IKRotation = target.rotation;
}
if (rotationDOF == DOF.One) {
Quaternion q = Quaternion.FromToRotation(IKRotation * tipAxis, bone1.rotation * bone1Axis);
IKRotation = q * IKRotation;
}
// Rotate the 3rd bone
if (bone3 != null) {
if (w * rotationWeight >= 1f) {
bone3.rotation = IKRotation * bone3RelativeToTarget;
} else {
bone3.rotation = Quaternion.Lerp(bone3.rotation, IKRotation * bone3RelativeToTarget, w * rotationWeight);
}
}
solver.IKPositionWeight = w;
solver.IKRotationWeight = rotationWeight;
solver.Update();
if (fixBone1Twist)
{
Quaternion bone2Rotation = bone2.rotation;
Quaternion space = Quaternion.LookRotation(bone1.rotation * bone1TwistAxis, bone2.position - bone1.position);
Vector3 bone1Twist = Quaternion.Inverse(space) * solver.bendNormal;
float angle = Mathf.Atan2(bone1Twist.x, bone1Twist.z) * Mathf.Rad2Deg;
bone1.rotation = Quaternion.AngleAxis(angle, bone2.position - bone1.position) * bone1.rotation;
bone2.rotation = bone2Rotation;
}
}
}
/// <summary>
/// Handles IK for a number of Fingers with 3-4 joints.
/// </summary>
public class FingerRig : SolverManager {
/// <summary>
/// The master weight for all fingers.
/// </summary>
[Tooltip("The master weight for all fingers.")]
[Range(0f, 1f)]
public float weight = 1f;
/// <summary>
/// The array of Fingers.
/// </summary>
public Finger[] fingers = new Finger[0];
/// <summary>
/// Has the rig properly initiated (in play mode only)?
/// </summary>
public bool initiated { get; private set; }
/// <summary>
/// Is this rig valid?
/// </summary>
public bool IsValid(ref string errorMessage) {
foreach (Finger finger in fingers) {
if (!finger.IsValid(ref errorMessage)) return false;
}
return true;
}
/// <summary>
/// Attempts to automatically fill in the Finger bones.
/// </summary>
[ContextMenu("Auto-detect")]
public void AutoDetect() {
fingers = new Finger[0];
for (int i = 0; i < transform.childCount; i++) {
Transform[] potentialFinger = new Transform[0];
AddChildrenRecursive(transform.GetChild(i), ref potentialFinger);
if (potentialFinger.Length == 3 || potentialFinger.Length == 4) {
Finger finger = new Finger();
finger.bone1 = potentialFinger[0];
finger.bone2 = potentialFinger[1];
if (potentialFinger.Length == 3) {
finger.tip = potentialFinger[2];
} else {
finger.bone3 = potentialFinger[2];
finger.tip = potentialFinger[3];
}
finger.weight = 1f;
Array.Resize(ref fingers, fingers.Length + 1);
fingers[fingers.Length - 1] = finger;
}
}
}
/// <summary>
/// Adds a finger in run-time.
/// </summary>
public void AddFinger(Transform bone1, Transform bone2, Transform bone3, Transform tip, Transform target = null) {
Finger finger = new Finger();
finger.bone1 = bone1;
finger.bone2 = bone2;
finger.bone3 = bone3;
finger.tip = tip;
finger.target = target;
Array.Resize(ref fingers, fingers.Length + 1);
fingers[fingers.Length - 1] = finger;
initiated = false;
finger.Initiate(transform, fingers.Length - 1);
if (fingers[fingers.Length - 1].initiated) initiated = true;
}
/// <summary>
/// Removes a finger in runtime.
/// </summary>
public void RemoveFinger(int index) {
if (index < 0f || index >= fingers.Length) {
Warning.Log("RemoveFinger index out of bounds.", transform);
return;
}
if (fingers.Length == 1) {
fingers = new Finger[0];
return;
}
Finger[] newFingers = new Finger[fingers.Length - 1];
int added = 0;
for (int i = 0; i < fingers.Length; i++) {
if (i != index) {
newFingers[added] = fingers[i];
added ++;
}
}
fingers = newFingers;
}
// Adds child Transforms of the 'parent' to the 'array' recursively only if each Transform has a single child.
private void AddChildrenRecursive(Transform parent, ref Transform[] array) {
Array.Resize(ref array, array.Length + 1);
array[array.Length - 1] = parent;
if (parent.childCount != 1) return;
AddChildrenRecursive(parent.GetChild(0), ref array);
}
protected override void InitiateSolver() {
initiated = true;
for (int i = 0; i < fingers.Length; i++) {
fingers[i].Initiate(transform, i);
if (!fingers[i].initiated) initiated = false;
}
}
public void UpdateFingerSolvers() {
foreach (Finger finger in fingers) {
finger.Update(weight);
}
}
public void FixFingerTransforms() {
if (weight <= 0f) return;
foreach (Finger finger in fingers) {
finger.FixTransforms();
}
}
public void StoreDefaultLocalState() {
foreach (Finger finger in fingers) {
finger.StoreDefaultLocalState();
}
}
protected override void UpdateSolver() {
UpdateFingerSolvers();
}
protected override void FixTransforms() {
if (weight <= 0f) return;
FixFingerTransforms();
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: ee545149746b44547897f32030465e57
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 10000
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 1bdc8112837ae4a76b936df3038e4b2c
folderAsset: yes
timeCreated: 1434626794
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 14a4d69b319a04b75bb03f351a174cec
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 2631a18e2dd7844718dea7db8ecf6c03
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 79450243724744b44820e5468e418048
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: e394bb73c6c534de1a49e2eff63b4f6d
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 0ee227cd5021b40b2a18d383ba9dadff
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 1ccc9218a62cd44dda0ddb5fa15c62cc
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 55f7720fb239042f08656d4ab294f5c5
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 89e0c79ada10847639ab1754e12413a7
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: c0b08042695f548e79a4e5c84112fe52
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 36056811931e14f20adcc2767d6262e7
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 7c84c5721b6d74a15ab63fe46effb886
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: c92822fd107844dccaf6c30c024e5806
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 9e5cf919254ee4d6bb9ccd7dc1b4bebd
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 5e7cd1ffcb3304ceba342d37416ceb2c
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 706b88054f06c4e83ad5f34217bedea3
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 2a1323dcf174944a69736d167afe96e0
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: e747b4712fd43444ab8213b7ff4c089b
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: dc3f34dec09f54c4d8a48bf1a653b179
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 8e949b7451d544077b75cb3246f9f739
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: c829e174cb3c74f869cd9949be8d8814
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: -1
mipBias: -1
wrapMode: 1
nPOTScale: 1
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 7e0729a4bf8e24db994fb1390c510a4e
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: ac7ab65192f5c4348bdd26925b0bb42d
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: 03a93cc74a1364909b1ddd071896ad75
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: f6c289ca5c243470198f39f6517123fd
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,53 @@
fileFormatVersion: 2
guid: ff34b0ecb22e7466fbd0a856b7a00621
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 2
mipmaps:
mipMapMode: 0
enableMipMap: 0
linearTexture: 0
correctGamma: 0
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: .25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 0
cubemapConvolution: 0
cubemapConvolutionSteps: 8
cubemapConvolutionExponent: 1.5
seamlessCubemap: 0
textureFormat: -2
maxTextureSize: 128
textureSettings:
filterMode: -1
aniso: 0
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
rGBM: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: .5, y: .5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaIsTransparency: 1
textureType: 5
buildTargetSettings: []
spriteSheet:
sprites: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: bed935aadc9ff4217bc309bf058d2fdb
folderAsset: yes
timeCreated: 1434626794
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,92 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Dedicated abstrac base component for the Grounding solver.
/// </summary>
public abstract class Grounder: MonoBehaviour {
#region Main Interface
/// <summary>
/// The master weight. Use this to fade in/out the grounding effect.
/// </summary>
[Tooltip("The master weight. Use this to fade in/out the grounding effect.")]
[Range(0f, 1f)] public float weight = 1f;
/// <summary>
/// The %Grounding solver. Not to confuse with IK solvers.
/// </summary>
[Tooltip("The Grounding solver. Not to confuse with IK solvers.")]
public Grounding solver = new Grounding();
/// <summary>
/// Delegate for Grounder events.
/// </summary>
public delegate void GrounderDelegate();
/// <summary>
/// Called before the Grounder updates its solver.
/// </summary>
public GrounderDelegate OnPreGrounder;
/// <summary>
/// Called after the Grounder has updated its solver and before the IK is applied.
/// </summary>
public GrounderDelegate OnPostGrounder;
/// <summary>
/// Called after the IK has updated.
/// </summary>
public GrounderDelegate OnPostIK;
/// <summary>
/// Resets this Grounder so characters can be teleported instananeously.
/// </summary>
public abstract void ResetPosition();
#endregion Main Interface
public bool initiated { get; protected set; }
// Gets the spine bend direction
protected Vector3 GetSpineOffsetTarget() {
Vector3 sum = Vector3.zero;
for (int i = 0; i < solver.legs.Length; i++) {
sum += GetLegSpineBendVector(solver.legs[i]);
}
return sum;
}
// Logs the warning if no other warning has beed logged in this session.
protected void LogWarning(string message) {
Warning.Log(message, transform);
}
// Gets the bend direction for a foot
private Vector3 GetLegSpineBendVector(Grounding.Leg leg) {
Vector3 spineTangent = GetLegSpineTangent(leg);
float dotF = (Vector3.Dot(solver.root.forward, spineTangent.normalized) + 1) * 0.5f; // Default behaviour, not bending spine when going downhill
//float dotF = Mathf.Abs(Vector3.Dot(solver.root.forward, spineTangent.normalized)); // Bending spine backwards when going downhill
//float dotF = Vector3.Dot(solver.root.forward, spineTangent.normalized); // Bending spine forward when going downhill
float w = (leg.IKPosition - leg.transform.position).magnitude;
return spineTangent * w * dotF;
}
// Gets the direction from the root to the foot (ortho-normalized to root.up)
private Vector3 GetLegSpineTangent(Grounding.Leg leg) {
Vector3 tangent = leg.transform.position - solver.root.position;
if (!solver.rotateSolver || solver.root.up == Vector3.up) return new Vector3(tangent.x, 0f, tangent.z);
Vector3 normal = solver.root.up;
Vector3.OrthoNormalize(ref normal, ref tangent);
return tangent;
}
// Open the User Manual url
protected abstract void OpenUserManual();
// Open the Script Reference url
protected abstract void OpenScriptReference();
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 20d3b31ccf0b543f5b659a1c3292d5c5
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,196 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Grounding for BipedIK characters.
/// </summary>
[HelpURL("http://www.root-motion.com/finalikdox/html/page9.html")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder Biped")]
public class GrounderBipedIK: Grounder {
// Open the User Manual URL
[ContextMenu("User Manual")]
protected override void OpenUserManual() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
}
// Open the Script Reference URL
[ContextMenu("Scrpt Reference")]
protected override void OpenScriptReference() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_biped_i_k.html");
}
#region Main Interface
/// <summary>
/// The BipedIK componet.
/// </summary>
[Tooltip("The BipedIK componet.")]
public BipedIK ik;
/// <summary>
/// The amount of spine bending towards upward slopes.
/// </summary>
[Tooltip("The amount of spine bending towards upward slopes.")]
public float spineBend = 7f;
/// <summary>
/// The interpolation speed of spine bending.
/// </summary>
[Tooltip("The interpolation speed of spine bending.")]
public float spineSpeed = 3f;
#endregion Main Interface
public override void ResetPosition() {
solver.Reset();
spineOffset = Vector3.zero;
}
private Transform[] feet = new Transform[2];
private Quaternion[] footRotations = new Quaternion[2];
private Vector3 animatedPelvisLocalPosition, solvedPelvisLocalPosition;
private Vector3 spineOffset;
private float lastWeight;
// Can we initiate the Grounding?
private bool IsReadyToInitiate() {
if (ik == null) return false;
if (!ik.solvers.leftFoot.initiated) return false;
if (!ik.solvers.rightFoot.initiated) return false;
return true;
}
// Initiate once we have a BipedIK component
void Update() {
weight = Mathf.Clamp(weight, 0f, 1f);
if (weight <= 0f) return;
if (initiated) return;
if (!IsReadyToInitiate()) return;
Initiate();
}
private void Initiate() {
// Gathering both foot bones from the BipedIK
feet = new Transform[2];
footRotations = new Quaternion[2];
feet[0] = ik.references.leftFoot;
feet[1] = ik.references.rightFoot;
footRotations[0] = Quaternion.identity;
footRotations[1] = Quaternion.identity;
// Adding to the delegates to get call at certain points in the solving process
ik.solvers.spine.OnPreUpdate += OnSolverUpdate;
ik.solvers.rightFoot.OnPostUpdate += OnPostSolverUpdate;
// Store the default localPosition of the pelvis
animatedPelvisLocalPosition = ik.references.pelvis.localPosition;
// Initiate the Grounding
solver.Initiate(ik.references.root, feet);
initiated = true;
}
// Weigh out the limb solvers properly when the component is disabled
void OnDisable() {
if (!initiated) return;
ik.solvers.leftFoot.IKPositionWeight = 0f;
ik.solvers.rightFoot.IKPositionWeight = 0f;
}
// Called before updating the spine IK solver
private void OnSolverUpdate() {
if (!enabled) return;
if (weight <= 0f) {
if (lastWeight <= 0f) return;
// Weigh out the limb solvers properly
OnDisable();
}
lastWeight = weight;
if (OnPreGrounder != null) OnPreGrounder();
// If the pelvis local position has not changed since last solved state, consider it unanimated
if (ik.references.pelvis.localPosition != solvedPelvisLocalPosition) animatedPelvisLocalPosition = ik.references.pelvis.localPosition;
else ik.references.pelvis.localPosition = animatedPelvisLocalPosition;
// Update the Grounding
solver.Update();
// Move the pelvis
ik.references.pelvis.position += solver.pelvis.IKOffset * weight;
// Update IKPositions and IKPositionWeights of the feet
SetLegIK(ik.solvers.leftFoot, 0);
SetLegIK(ik.solvers.rightFoot, 1);
// Bending the spine
if (spineBend != 0f && ik.references.spine.Length > 0) {
spineSpeed = Mathf.Clamp(spineSpeed, 0f, spineSpeed);
Vector3 spineOffseTarget = GetSpineOffsetTarget() * weight;
spineOffset = Vector3.Lerp(spineOffset, spineOffseTarget * spineBend, Time.deltaTime * spineSpeed);
// Store upper arm rotations to revert them after we rotate the spine
Quaternion leftArmRotation = ik.references.leftUpperArm.rotation;
Quaternion rightArmRotation = ik.references.rightUpperArm.rotation;
// Get the offset rotation for the spine
Vector3 up = solver.up;
Quaternion f = Quaternion.FromToRotation(up, up + spineOffset);
// Rotate the spine
ik.references.spine[0].rotation = f * ik.references.spine[0].rotation;
// Revert the upper arms
ik.references.leftUpperArm.rotation = leftArmRotation;
ik.references.rightUpperArm.rotation = rightArmRotation;
ik.solvers.lookAt.SetDirty();
}
if (OnPostGrounder != null) OnPostGrounder();
}
// Set the IK position and weight for a limb
private void SetLegIK(IKSolverLimb limb, int index) {
footRotations[index] = feet[index].rotation;
limb.IKPosition = solver.legs[index].IKPosition;
limb.IKPositionWeight = weight;
}
// Rotating the feet after IK has finished
private void OnPostSolverUpdate() {
if (weight <= 0f) return;
if (!enabled) return;
for (int i = 0; i < feet.Length; i++) {
feet[i].rotation = Quaternion.Slerp(Quaternion.identity, solver.legs[i].rotationOffset, weight) * footRotations[i];
}
// Store the local position of the pelvis so we know it it changes
solvedPelvisLocalPosition = ik.references.pelvis.localPosition;
if (OnPostIK != null) OnPostIK();
}
// Cleaning up the delegates
void OnDestroy() {
if (initiated && ik != null) {
ik.solvers.spine.OnPreUpdate -= OnSolverUpdate;
ik.solvers.rightFoot.OnPostUpdate -= OnPostSolverUpdate;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 391939450fd8044e3839af08f34c2af8
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: c0b08042695f548e79a4e5c84112fe52, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,215 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Grounding for FBBIK characters.
/// </summary>
[HelpURL("https://www.youtube.com/watch?v=9MiZiaJorws&index=6&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder Full Body Biped")]
public class GrounderFBBIK: Grounder {
// Open a video tutorial video
[ContextMenu("TUTORIAL VIDEO")]
void OpenTutorial() {
Application.OpenURL("https://www.youtube.com/watch?v=9MiZiaJorws&index=6&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6");
}
// Open the User Manual URL
[ContextMenu("User Manual")]
protected override void OpenUserManual() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
}
// Open the Script Reference URL
[ContextMenu("Scrpt Reference")]
protected override void OpenScriptReference() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_f_b_b_i_k.html");
}
#region Main Interface
/// <summary>
/// Contains the bending weights for an effector.
/// </summary>
[System.Serializable]
public class SpineEffector {
/// <summary>
/// The type of the effector.
/// </summary>
[Tooltip("The type of the effector.")]
public FullBodyBipedEffector effectorType;
/// <summary>
/// The weight of horizontal bend offset towards the slope..
/// </summary>
[Tooltip("The weight of horizontal bend offset towards the slope.")]
public float horizontalWeight = 1f;
/// <summary>
/// The vertical bend offset weight.
/// </summary>
[Tooltip("The vertical bend offset weight.")]
public float verticalWeight;
public SpineEffector() {}
/// <summary>
/// Initializes a new instance of the <see cref="RootMotion.FinalIK.GrounderFBBIK+SpineEffector"/> class.
/// </summary>
/// <param name="effectorType">Effector type.</param>
/// <param name="horizontalWeight">Horizontal weight.</param>
/// <param name="verticalWeight">Vertical weight.</param>
public SpineEffector(FullBodyBipedEffector effectorType, float horizontalWeight, float verticalWeight) {
this.effectorType = effectorType;
this.horizontalWeight = horizontalWeight;
this.verticalWeight = verticalWeight;
}
}
/// <summary>
/// Reference to the FBBIK componet.
/// </summary>
[Tooltip("Reference to the FBBIK componet.")]
public FullBodyBipedIK ik;
/// <summary>
/// The amount of spine bending towards upward slopes.
/// </summary>
[Tooltip("The amount of spine bending towards upward slopes.")]
public float spineBend = 2f;
/// <summary>
/// The interpolation speed of spine bending.
/// </summary>
[Tooltip("The interpolation speed of spine bending.")]
public float spineSpeed = 3f;
/// <summary>
/// The spine bending effectors.
/// </summary>
public SpineEffector[] spine = new SpineEffector[0];
#endregion Main Interface
public override void ResetPosition() {
solver.Reset();
spineOffset = Vector3.zero;
}
public void Reinitiate()
{
initiated = false;
firstSolve = false;
}
private Transform[] feet = new Transform[2];
private Vector3 spineOffset;
private bool firstSolve;
// Can we initiate the Grounding?
private bool IsReadyToInitiate() {
if (ik == null) return false;
if (!ik.solver.initiated) return false;
return true;
}
// Initiate once we have a FBBIK component
void Update() {
firstSolve = true;
weight = Mathf.Clamp(weight, 0f, 1f);
if (weight <= 0f) return;
if (initiated) return;
if (!IsReadyToInitiate()) return;
Initiate();
}
void FixedUpdate() {
firstSolve = true;
}
void LateUpdate() {
firstSolve = true;
}
private void Initiate () {
// Set maintainRotationWeight to 1 for both limbs so their rotation will be maintained as animated
ik.solver.leftLegMapping.maintainRotationWeight = 1f;
ik.solver.rightLegMapping.maintainRotationWeight = 1f;
// Gathering both foot bones from the FBBIK
feet = new Transform[2];
feet[0] = ik.solver.leftFootEffector.bone;
feet[1] = ik.solver.rightFootEffector.bone;
// Add to the FBBIK OnPreUpdate delegate to know when it solves
ik.solver.OnPreUpdate += OnSolverUpdate;
ik.solver.OnPostUpdate += OnPostSolverUpdate;
// Initiate Grounding
solver.Initiate(ik.references.root, feet);
initiated = true;
}
// Called before updating the main IK solver
private void OnSolverUpdate() {
if (!firstSolve) return;
firstSolve = false;
if (!enabled) return;
if (weight <= 0f) return;
if (OnPreGrounder != null) OnPreGrounder();
solver.Update();
// Move the pelvis
ik.references.pelvis.position += solver.pelvis.IKOffset * weight;
// Set effector positionOffsets for the feet
SetLegIK(ik.solver.leftFootEffector, solver.legs[0]);
SetLegIK(ik.solver.rightFootEffector, solver.legs[1]);
// Bending the spine
if (spineBend != 0f) {
spineSpeed = Mathf.Clamp(spineSpeed, 0f, spineSpeed);
Vector3 spineOffseTarget = GetSpineOffsetTarget() * weight;
spineOffset = Vector3.Lerp(spineOffset, spineOffseTarget * spineBend, Time.deltaTime * spineSpeed);
Vector3 verticalOffset = ik.references.root.up * spineOffset.magnitude;
for (int i = 0; i < spine.Length; i++) {
ik.solver.GetEffector(spine[i].effectorType).positionOffset += (spineOffset * spine[i].horizontalWeight) + (verticalOffset * spine[i].verticalWeight);
}
}
if (OnPostGrounder != null) OnPostGrounder();
}
// Set the effector positionOffset for the foot
private void SetLegIK(IKEffector effector, Grounding.Leg leg) {
effector.positionOffset += (leg.IKPosition - effector.bone.position) * weight;
effector.bone.rotation = Quaternion.Slerp(Quaternion.identity, leg.rotationOffset, weight) * effector.bone.rotation;
}
// Auto-assign ik
void OnDrawGizmosSelected() {
if (ik == null) ik = GetComponent<FullBodyBipedIK>();
if (ik == null) ik = GetComponentInParent<FullBodyBipedIK>();
if (ik == null) ik = GetComponentInChildren<FullBodyBipedIK>();
}
private void OnPostSolverUpdate()
{
if (OnPostIK != null) OnPostIK();
}
// Cleaning up the delegate
void OnDestroy() {
if (initiated && ik != null)
{
ik.solver.OnPreUpdate -= OnSolverUpdate;
ik.solver.OnPostUpdate -= OnPostSolverUpdate;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6c72e1df647af4c0098866e944a04b01
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 36056811931e14f20adcc2767d6262e7, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,280 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Grounding for LimbIK, CCD and/or FABRIK solvers.
/// </summary>
[HelpURL("http://www.root-motion.com/finalikdox/html/page9.html")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder IK")]
public class GrounderIK: Grounder {
// Open the User Manual URL
[ContextMenu("User Manual")]
protected override void OpenUserManual() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
}
// Open the Script Reference URL
[ContextMenu("Scrpt Reference")]
protected override void OpenScriptReference() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_i_k.html");
}
#region Main Interface
/// <summary>
/// The leg %IK componets (can be any type of IK component).
/// </summary>
public IK[] legs;
/// <summary>
/// The pelvis transform. Common ancestor of all the legs.
/// </summary>
[Tooltip("The pelvis transform. Common ancestor of all the legs.")]
public Transform pelvis;
/// <summary>
/// The root Transform of the character, with the rigidbody and the collider.
/// </summary>
[Tooltip("The root Transform of the character, with the rigidbody and the collider.")]
public Transform characterRoot;
/// <summary>
/// The weight of rotating the character root to the ground normal (range: 0 - 1).
/// </summary>
[Tooltip("The weight of rotating the character root to the ground normal (range: 0 - 1).")]
[Range(0f, 1f)]
public float rootRotationWeight;
/// <summary>
/// The speed of rotating the character root to the ground normal (range: 0 - inf).
/// </summary>
[Tooltip("The speed of rotating the character root to the ground normal (range: 0 - inf).")]
public float rootRotationSpeed = 5f;
/// <summary>
/// The maximum angle of root rotation (range: 0 - 90).
/// </summary>
[Tooltip("The maximum angle of root rotation (range: 0 - 90).")]
public float maxRootRotationAngle = 45f;
#endregion Main Interface
public override void ResetPosition() {
for (int i = 0; i < legs.Length; i++) {
legs[i].GetIKSolver().IKPosition = feet[i].transform.position;
if (legs[i] is LimbIK)
{
var leg = legs[i] as LimbIK;
leg.solver.IKRotation = solver.legs[i].transform.rotation;
}
footRotations[i] = feet[i].rotation;
}
animatedPelvisLocalPosition = pelvis.localPosition;
solvedPelvisLocalPosition = pelvis.localPosition;
solver.Reset();
}
private Transform[] feet = new Transform[0];
private Quaternion[] footRotations = new Quaternion[0];
private Vector3 animatedPelvisLocalPosition, solvedPelvisLocalPosition;
private int solvedFeet;
private bool solved;
private float lastWeight;
private Rigidbody characterRootRigidbody;
// Can we initiate the Grounding?
private bool IsReadyToInitiate() {
if (pelvis == null) return false;
if (legs.Length == 0) return false;
foreach (IK leg in legs) {
if (leg == null) return false;
if (leg is FullBodyBipedIK) {
LogWarning("GrounderIK does not support FullBodyBipedIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead. If you want to use FullBodyBipedIK, use the GrounderFBBIK component.");
return false;
}
if (leg is FABRIKRoot) {
LogWarning("GrounderIK does not support FABRIKRoot, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
return false;
}
if (leg is AimIK) {
LogWarning("GrounderIK does not support AimIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
return false;
}
}
return true;
}
// Weigh out the IK solvers properly when the component is disabled
void OnDisable() {
if (!initiated) return;
for (int i = 0; i < legs.Length; i++) {
if (legs[i] != null) legs[i].GetIKSolver().IKPositionWeight = 0f;
}
}
// Initiate once we have all the required components
void Update() {
weight = Mathf.Clamp(weight, 0f, 1f);
if (weight <= 0f) return;
solved = false;
if (initiated) {
// Clamping values
rootRotationWeight = Mathf.Clamp(rootRotationWeight, 0f, 1f);
rootRotationSpeed = Mathf.Clamp(rootRotationSpeed, 0f, rootRotationSpeed);
// Root rotation
if (characterRoot != null && rootRotationSpeed > 0f && rootRotationWeight > 0f && solver.isGrounded) {
Vector3 normal = solver.GetLegsPlaneNormal();
// Root rotation weight
if (rootRotationWeight < 1f) {
normal = Vector3.Slerp(Vector3.up, normal, rootRotationWeight);
}
// Root rotation limit
Quaternion upRotation = Quaternion.FromToRotation(transform.up, Vector3.up) * characterRoot.rotation;
Quaternion rotationTarget = Quaternion.RotateTowards(upRotation, Quaternion.FromToRotation(transform.up, normal) * characterRoot.rotation, maxRootRotationAngle);
// Rotate the root
if (characterRootRigidbody == null) {
characterRoot.rotation = Quaternion.Lerp(characterRoot.rotation, rotationTarget, Time.deltaTime * rootRotationSpeed);
} else {
characterRootRigidbody.MoveRotation(Quaternion.Lerp(characterRoot.rotation, rotationTarget, Time.deltaTime * rootRotationSpeed));
}
}
return;
}
if (!IsReadyToInitiate()) return;
Initiate();
}
private void Initiate() {
// Building arrays
feet = new Transform[legs.Length];
footRotations = new Quaternion[legs.Length];
for (int i = 0; i < feet.Length; i++) footRotations[i] = Quaternion.identity;
// Gathering the last bones of the IK solvers as feet
for (int i = 0; i < legs.Length; i++) {
IKSolver.Point[] points = legs[i].GetIKSolver().GetPoints();
feet[i] = points[points.Length - 1].transform;
// Add to the update delegates of each ik solver
legs[i].GetIKSolver().OnPreUpdate += OnSolverUpdate;
legs[i].GetIKSolver().OnPostUpdate += OnPostSolverUpdate;
}
// Store the default localPosition of the pelvis
animatedPelvisLocalPosition = pelvis.localPosition;
// Initiate the Grounding
solver.Initiate(transform, feet);
for (int i = 0; i < legs.Length; i++) {
if (legs [i] is LegIK) {
solver.legs[i].invertFootCenter = true;
}
}
characterRootRigidbody = characterRoot.GetComponent<Rigidbody>();
initiated = true;
}
// Called before updating the first IK solver
private void OnSolverUpdate() {
if (!enabled) return;
if (weight <= 0f) {
if (lastWeight <= 0f) return;
// Weigh out the limb solvers properly
OnDisable();
}
lastWeight = weight;
// If another IK has already solved in this frame, do nothing
if (solved) return;
if (OnPreGrounder != null) OnPreGrounder();
// If the pelvis local position has not changed since last solved state, consider it unanimated
if (pelvis.localPosition != solvedPelvisLocalPosition) animatedPelvisLocalPosition = pelvis.localPosition;
else pelvis.localPosition = animatedPelvisLocalPosition;
// Update the Grounding
solver.Update();
// Update the IKPositions and IKPositonWeights of the legs
for (int i = 0; i < legs.Length; i++) SetLegIK(i);
// Move the pelvis
pelvis.position += solver.pelvis.IKOffset * weight;
solved = true;
solvedFeet = 0;
if (OnPostGrounder != null) OnPostGrounder();
}
// Set the IK position and weight for a limb
private void SetLegIK(int index) {
footRotations[index] = feet[index].rotation;
if (legs [index] is LegIK) {
(legs[index].GetIKSolver() as IKSolverLeg).IKRotation = Quaternion.Slerp(Quaternion.identity, solver.legs[index].rotationOffset, weight) * footRotations[index];
(legs[index].GetIKSolver() as IKSolverLeg).IKRotationWeight = 1f;
}
legs[index].GetIKSolver().IKPosition = solver.legs[index].IKPosition;
legs[index].GetIKSolver().IKPositionWeight = weight;
}
// Rotating the feet after IK has finished
private void OnPostSolverUpdate() {
if (weight <= 0f) return;
if (!enabled) return;
// Only do this after the last IK solver has finished
solvedFeet ++;
if (solvedFeet < feet.Length) return;
solved = false;
for (int i = 0; i < feet.Length; i++) {
feet[i].rotation = Quaternion.Slerp(Quaternion.identity, solver.legs[i].rotationOffset, weight) * footRotations[i];
}
// Store the local position of the pelvis so we know it it changes
solvedPelvisLocalPosition = pelvis.localPosition;
if (OnPostIK != null) OnPostIK();
}
// Cleaning up the delegates
void OnDestroy() {
if (initiated) {
foreach (IK leg in legs) {
if (leg != null) {
leg.GetIKSolver().OnPreUpdate -= OnSolverUpdate;
leg.GetIKSolver().OnPostUpdate -= OnPostSolverUpdate;
}
}
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 65ace204533ef4c24ac80f11ef8ee8ea
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7c84c5721b6d74a15ab63fe46effb886, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,449 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Grounding for LimbIK, CCD and/or FABRIK solvers.
/// </summary>
[HelpURL("http://www.root-motion.com/finalikdox/html/page9.html")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder Quadruped")]
public class GrounderQuadruped: Grounder {
// Open the User Manual URL
[ContextMenu("User Manual")]
protected override void OpenUserManual() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
}
// Open the Script Reference URL
[ContextMenu("Scrpt Reference")]
protected override void OpenScriptReference() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_quadruped.html");
}
#region Main Interface
/// <summary>
/// The %Grounding solver for the forelegs.
/// </summary>
[Tooltip("The Grounding solver for the forelegs.")]
public Grounding forelegSolver = new Grounding();
/// <summary>
/// The weight of rotating the character root to the ground angle (range: 0 - 1).
/// </summary>
[Tooltip("The weight of rotating the character root to the ground angle (range: 0 - 1).")]
[Range(0f, 1f)]
public float rootRotationWeight = 0.5f;
/// <summary>
/// The maximum angle of rotating the quadruped downwards (going downhill, range: -90 - 0).
/// </summary>
[Tooltip("The maximum angle of rotating the quadruped downwards (going downhill, range: -90 - 0).")]
[Range(-90f, 0f)]
public float minRootRotation = -25f;
/// <summary>
/// The maximum angle of rotating the quadruped upwards (going uphill, range: 0 - 90).
/// </summary>
[Tooltip("The maximum angle of rotating the quadruped upwards (going uphill, range: 0 - 90).")]
[Range(0f, 90f)]
public float maxRootRotation = 45f;
/// <summary>
/// The speed of interpolating the character root rotation (range: 0 - inf).
/// </summary>
[Tooltip("The speed of interpolating the character root rotation (range: 0 - inf).")]
public float rootRotationSpeed = 5f;
/// <summary>
/// The maximum IK offset for the legs (range: 0 - inf).
/// </summary>
[Tooltip("The maximum IK offset for the legs (range: 0 - inf).")]
public float maxLegOffset = 0.5f;
/// <summary>
/// The maximum IK offset for the forelegs (range: 0 - inf).
/// </summary>
[Tooltip("The maximum IK offset for the forelegs (range: 0 - inf).")]
public float maxForeLegOffset = 0.5f;
/// <summary>
/// The weight of maintaining the head's rotation as it was before solving the Grounding (range: 0 - 1).
/// </summary>
[Tooltip("The weight of maintaining the head's rotation as it was before solving the Grounding (range: 0 - 1).")]
[Range(0f, 1f)]
public float maintainHeadRotationWeight = 0.5f;
/// <summary>
/// The root Transform of the character, with the rigidbody and the collider.
/// </summary>
[Tooltip("The root Transform of the character, with the rigidbody and the collider.")]
public Transform characterRoot;
/// <summary>
/// The pelvis transform. Common ancestor of both legs and the spine.
/// </summary>
[Tooltip("The pelvis transform. Common ancestor of both legs and the spine.")]
public Transform pelvis;
/// <summary>
/// The last bone in the spine that is the common parent for both forelegs.
/// </summary>
[Tooltip("The last bone in the spine that is the common parent for both forelegs.")]
public Transform lastSpineBone;
/// <summary>
/// The head (optional, if you intend to maintain its rotation).
/// </summary>
[Tooltip("The head (optional, if you intend to maintain its rotation).")]
public Transform head;
/// <summary>
/// %IK componets of the hindlegs. Can be any type of IK components.
/// </summary>
public IK[] legs;
/// <summary>
/// %IK components for the forelegs. Can be any type of IK components.
/// </summary>
public IK[] forelegs;
/// <summary>
/// When using GrounderQuadruped on a spherical object, update this vector to always point towards the center of that object.
/// </summary>
[HideInInspector] public Vector3 gravity = Vector3.down;
#endregion Main Interface
public override void ResetPosition() {
for (int i = 0; i < legs.Length; i++)
{
legs[i].GetIKSolver().IKPosition = feet[i].transform.position;
if (legs[i] is LimbIK)
{
var leg = legs[i] as LimbIK;
leg.solver.IKRotation = solver.legs[i].transform.rotation;
}
}
solver.Reset();
forelegSolver.Reset();
}
// Contains all the required information about a foot
public struct Foot {
public IKSolver solver;
public Transform transform;
public Quaternion rotation;
public Grounding.Leg leg;
// The custom constructor
public Foot (IKSolver solver, Transform transform) {
this.solver = solver;
this.transform = transform;
this.leg = null;
rotation = transform.rotation;
}
}
private Foot[] feet = new Foot[0];
private Vector3 animatedPelvisLocalPosition;
private Quaternion animatedPelvisLocalRotation;
private Quaternion animatedHeadLocalRotation;
private Vector3 solvedPelvisLocalPosition;
private Quaternion solvedPelvisLocalRotation;
private Quaternion solvedHeadLocalRotation;
private int solvedFeet;
private bool solved;
private float angle;
private Transform forefeetRoot;
private Quaternion headRotation;
private float lastWeight;
private Rigidbody characterRootRigidbody;
// Can we initiate the Grounding?
private bool IsReadyToInitiate() {
if (pelvis == null) return false;
if (lastSpineBone == null) return false;
if (legs.Length == 0) return false;
if (forelegs.Length == 0) return false;
if (characterRoot == null) return false;
if (!IsReadyToInitiateLegs(legs)) return false;
if (!IsReadyToInitiateLegs(forelegs)) return false;
return true;
}
// Are the leg IK components valid for initiation?
private bool IsReadyToInitiateLegs(IK[] ikComponents) {
foreach (IK leg in ikComponents) {
if (leg == null) return false;
if (leg is FullBodyBipedIK) {
LogWarning("GrounderIK does not support FullBodyBipedIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead. If you want to use FullBodyBipedIK, use the GrounderFBBIK component.");
return false;
}
if (leg is FABRIKRoot) {
LogWarning("GrounderIK does not support FABRIKRoot, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
return false;
}
if (leg is AimIK) {
LogWarning("GrounderIK does not support AimIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
return false;
}
}
return true;
}
// Weigh out the IK solvers properly when the component is disabled
void OnDisable() {
if (!initiated) return;
for (int i = 0; i < feet.Length; i++) {
if (feet[i].solver != null) feet[i].solver.IKPositionWeight = 0f;
}
}
// Initiate once we have all the required components
void Update() {
weight = Mathf.Clamp(weight, 0f, 1f);
if (weight <= 0f) return;
solved = false;
if (initiated) return;
if (!IsReadyToInitiate()) return;
Initiate();
}
// Initiate this Grounder
private void Initiate() {
// Building the feet
feet = new Foot[legs.Length + forelegs.Length];
// Gathering the last bones of the IK solvers as feet
Transform[] footBones = InitiateFeet(legs, ref feet, 0);
Transform[] forefootBones = InitiateFeet(forelegs, ref feet, legs.Length);
// Store the default localPosition and localRotation of the pelvis
animatedPelvisLocalPosition = pelvis.localPosition;
animatedPelvisLocalRotation = pelvis.localRotation;
if (head != null) animatedHeadLocalRotation = head.localRotation;
forefeetRoot = new GameObject().transform;
forefeetRoot.parent = transform;
forefeetRoot.name = "Forefeet Root";
// Initiate the Grounding
solver.Initiate(transform, footBones);
forelegSolver.Initiate(forefeetRoot, forefootBones);
for (int i = 0; i < footBones.Length; i++) feet[i].leg = solver.legs[i];
for (int i = 0; i < forefootBones.Length; i++) feet[i + legs.Length].leg = forelegSolver.legs[i];
characterRootRigidbody = characterRoot.GetComponent<Rigidbody>();
initiated = true;
}
// Initiate the feet
private Transform[] InitiateFeet(IK[] ikComponents, ref Foot[] f, int indexOffset) {
Transform[] bones = new Transform[ikComponents.Length];
for (int i = 0; i < ikComponents.Length; i++) {
IKSolver.Point[] points = ikComponents[i].GetIKSolver().GetPoints();
f[i + indexOffset] = new Foot(ikComponents[i].GetIKSolver(), points[points.Length - 1].transform);
bones[i] = f[i + indexOffset].transform;
// Add to the update delegates of each ik solver
f[i + indexOffset].solver.OnPreUpdate += OnSolverUpdate;
f[i + indexOffset].solver.OnPostUpdate += OnPostSolverUpdate;
}
return bones;
}
void LateUpdate () {
if (weight <= 0f) return;
// Clamping values
rootRotationWeight = Mathf.Clamp(rootRotationWeight, 0f, 1f);
minRootRotation = Mathf.Clamp(minRootRotation, -90f, maxRootRotation);
maxRootRotation = Mathf.Clamp(maxRootRotation, minRootRotation, 90f);
rootRotationSpeed = Mathf.Clamp(rootRotationSpeed, 0f, rootRotationSpeed);
maxLegOffset = Mathf.Clamp(maxLegOffset, 0f, maxLegOffset);
maxForeLegOffset = Mathf.Clamp(maxForeLegOffset, 0f, maxForeLegOffset);
maintainHeadRotationWeight = Mathf.Clamp(maintainHeadRotationWeight, 0f, 1f);
// Rotate the character root
RootRotation();
}
// Rotate the character along with the terrain
private void RootRotation() {
if (rootRotationWeight <= 0f) return;
if (rootRotationSpeed <= 0f) return;
solver.rotateSolver = true;
forelegSolver.rotateSolver = true;
// Get the horizontal rotation of the character
Vector3 tangent = characterRoot.forward;
Vector3 normal = -gravity;
Vector3.OrthoNormalize(ref normal, ref tangent);
Quaternion horizontalRotation = Quaternion.LookRotation(tangent, -gravity);
// Get the direction from root hit to forelegs root hit in the space of the horizontal character rotation
Vector3 hitDirection = forelegSolver.rootHit.point - solver.rootHit.point;
Vector3 hitDirectionLocal = Quaternion.Inverse(horizontalRotation) * hitDirection;
// Get the angle between the horizontal and hit directions
float angleTarget = Mathf.Atan2(hitDirectionLocal.y, hitDirectionLocal.z) * Mathf.Rad2Deg;
angleTarget = Mathf.Clamp(angleTarget * rootRotationWeight, minRootRotation, maxRootRotation);
// Interpolate the angle
angle = Mathf.Lerp(angle, angleTarget, Time.deltaTime * rootRotationSpeed);
if (characterRootRigidbody == null) {
characterRoot.rotation = Quaternion.Slerp(characterRoot.rotation, Quaternion.AngleAxis(-angle, characterRoot.right) * horizontalRotation, weight);
} else {
characterRootRigidbody.MoveRotation(Quaternion.Slerp(characterRoot.rotation, Quaternion.AngleAxis(-angle, characterRoot.right) * horizontalRotation, weight));
}
}
// Called before updating the first IK solver
private void OnSolverUpdate() {
if (!enabled) return;
if (weight <= 0f) {
if (lastWeight <= 0f) return;
// Weigh out the limb solvers properly
OnDisable();
}
lastWeight = weight;
// If another IK has already solved in this frame, do nothing
if (solved) return;
if (OnPreGrounder != null) OnPreGrounder();
// If the bone transforms have not changed since last solved state, consider them unanimated
if (pelvis.localPosition != solvedPelvisLocalPosition) animatedPelvisLocalPosition = pelvis.localPosition;
else pelvis.localPosition = animatedPelvisLocalPosition;
if (pelvis.localRotation != solvedPelvisLocalRotation) animatedPelvisLocalRotation = pelvis.localRotation;
else pelvis.localRotation = animatedPelvisLocalRotation;
if (head != null) {
if (head.localRotation != solvedHeadLocalRotation) animatedHeadLocalRotation = head.localRotation;
else head.localRotation = animatedHeadLocalRotation;
}
for (int i = 0; i < feet.Length; i++) feet[i].rotation = feet[i].transform.rotation;
// Store the head rotation so it could be maintained later
if (head != null) headRotation = head.rotation;
// Position the forefeet root to the center of forefeet
UpdateForefeetRoot();
// Update the Grounding
solver.Update();
forelegSolver.Update();
// Move the pelvis
pelvis.position += solver.pelvis.IKOffset * weight;
// Rotate the pelvis
Vector3 spineDirection = lastSpineBone.position - pelvis.position;
Vector3 newSpinePosition =
lastSpineBone.position +
forelegSolver.root.up * Mathf.Clamp(forelegSolver.pelvis.heightOffset, Mathf.NegativeInfinity, 0f) -
solver.root.up * solver.pelvis.heightOffset;
Vector3 newDirection = newSpinePosition - pelvis.position;
Quaternion f = Quaternion.FromToRotation(spineDirection, newDirection);
pelvis.rotation = Quaternion.Slerp(Quaternion.identity, f, weight) * pelvis.rotation;
// Update the IKPositions and IKPositonWeights of the legs
for (int i = 0; i < feet.Length; i++) SetFootIK(feet[i], (i < 2? maxLegOffset: maxForeLegOffset));
solved = true;
solvedFeet = 0;
if (OnPostGrounder != null) OnPostGrounder();
}
// Position the forefeet root to the center of forefeet
private void UpdateForefeetRoot() {
// Get the centroid
Vector3 foreFeetCenter = Vector3.zero;
for (int i = 0; i < forelegSolver.legs.Length; i++) {
foreFeetCenter += forelegSolver.legs[i].transform.position;
}
foreFeetCenter /= (float)forelegs.Length;
Vector3 dir = foreFeetCenter - transform.position;
// Ortho-normalize to this Transform's rotation
Vector3 normal = transform.up;
Vector3 tangent = dir;
Vector3.OrthoNormalize(ref normal, ref tangent);
// Positioning the forefeet root
forefeetRoot.position = transform.position + tangent.normalized * dir.magnitude;
}
// Set the IK position and weight for a limb
private void SetFootIK(Foot foot, float maxOffset) {
Vector3 direction = foot.leg.IKPosition - foot.transform.position;
foot.solver.IKPosition = foot.transform.position + Vector3.ClampMagnitude(direction, maxOffset);
foot.solver.IKPositionWeight = weight;
}
// Rotating the feet after IK has finished
private void OnPostSolverUpdate() {
if (weight <= 0f) return;
if (!enabled) return;
// Only do this after the last IK solver has finished
solvedFeet ++;
if (solvedFeet < feet.Length) return;
for (int i = 0; i < feet.Length; i++) {
feet[i].transform.rotation = Quaternion.Slerp(Quaternion.identity, feet[i].leg.rotationOffset, weight) * feet[i].rotation;
}
if (head != null) head.rotation = Quaternion.Lerp(head.rotation, headRotation, maintainHeadRotationWeight * weight);
// Store the solved transform's of the bones so we know if they are not animated
solvedPelvisLocalPosition = pelvis.localPosition;
solvedPelvisLocalRotation = pelvis.localRotation;
if (head != null) solvedHeadLocalRotation = head.localRotation;
if (OnPostIK != null) OnPostIK();
}
// Cleaning up the delegates
void OnDestroy() {
if (initiated) {
DestroyLegs(legs);
DestroyLegs(forelegs);
}
}
// Cleaning up the delegates
private void DestroyLegs(IK[] ikComponents) {
foreach (IK leg in ikComponents) {
if (leg != null) {
leg.GetIKSolver().OnPreUpdate -= OnSolverUpdate;
leg.GetIKSolver().OnPostUpdate -= OnPostSolverUpdate;
}
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9823e47edf1dd40c29dfe0ba019f33a6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: c92822fd107844dccaf6c30c024e5806, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,364 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Foot placement system.
/// </summary>
[System.Serializable]
public partial class Grounding {
#region Main Interface
/// <summary>
/// The raycasting quality. Fastest is a single raycast per foot, Simple is three raycasts, Best is one raycast and a capsule cast per foot.
/// </summary>
[System.Serializable]
public enum Quality {
Fastest,
Simple,
Best
}
/// <summary>
/// Layers to ground the character to. Make sure to exclude the layer of the character controller.
/// </summary>
[Tooltip("Layers to ground the character to. Make sure to exclude the layer of the character controller.")]
public LayerMask layers;
/// <summary>
/// Max step height. Maximum vertical distance of Grounding from the root of the character.
/// </summary>
[Tooltip("Max step height. Maximum vertical distance of Grounding from the root of the character.")]
public float maxStep = 0.5f;
/// <summary>
/// The height offset of the root.
/// </summary>
[Tooltip("The height offset of the root.")]
public float heightOffset;
/// <summary>
/// The speed of moving the feet up/down.
/// </summary>
[Tooltip("The speed of moving the feet up/down.")]
public float footSpeed = 2.5f;
/// <summary>
/// CapsuleCast radius. Should match approximately with the size of the feet.
/// </summary>
[Tooltip("CapsuleCast radius. Should match approximately with the size of the feet.")]
public float footRadius = 0.15f;
/// <summary>
/// Offset of the foot center along character forward axis.
/// </summary>
[Tooltip("Offset of the foot center along character forward axis.")]
[HideInInspector] public float footCenterOffset; // TODO make visible in inspector if Grounder Visualization is finished.
/// <summary>
/// Amount of velocity based prediction of the foot positions.
/// </summary>
[Tooltip("Amount of velocity based prediction of the foot positions.")]
public float prediction = 0.05f;
/// <summary>
/// Weight of rotating the feet to the ground normal offset.
/// </summary>
[Tooltip("Weight of rotating the feet to the ground normal offset.")]
[Range(0f, 1f)]
public float footRotationWeight = 1f;
/// <summary>
/// Speed of slerping the feet to their grounded rotations.
/// </summary>
[Tooltip("Speed of slerping the feet to their grounded rotations.")]
public float footRotationSpeed = 7f;
/// <summary>
/// Max Foot Rotation Angle, Max angular offset from the foot's rotation (Reasonable range: 0-90 degrees).
/// </summary>
[Tooltip("Max Foot Rotation Angle. Max angular offset from the foot's rotation.")]
[Range(0f, 90f)]
public float maxFootRotationAngle = 45f;
/// <summary>
/// If true, solver will rotate with the character root so the character can be grounded for example to spherical planets.
/// For performance reasons leave this off unless needed.
/// </summary>
[Tooltip("If true, solver will rotate with the character root so the character can be grounded for example to spherical planets. For performance reasons leave this off unless needed.")]
public bool rotateSolver;
/// <summary>
/// The speed of moving the character up/down.
/// </summary>
[Tooltip("The speed of moving the character up/down.")]
public float pelvisSpeed = 5f;
/// <summary>
/// Used for smoothing out vertical pelvis movement (range 0 - 1).
/// </summary>
[Tooltip("Used for smoothing out vertical pelvis movement (range 0 - 1).")]
[Range(0f, 1f)]
public float pelvisDamper;
/// <summary>
/// The weight of lowering the pelvis to the lowest foot.
/// </summary>
[Tooltip("The weight of lowering the pelvis to the lowest foot.")]
public float lowerPelvisWeight = 1f;
/// <summary>
/// The weight of lifting the pelvis to the highest foot. This is useful when you don't want the feet to go too high relative to the body when crouching.
/// </summary>
[Tooltip("The weight of lifting the pelvis to the highest foot. This is useful when you don't want the feet to go too high relative to the body when crouching.")]
public float liftPelvisWeight;
/// <summary>
/// The radius of the spherecast from the root that determines whether the character root is grounded.
/// </summary>
[Tooltip("The radius of the spherecast from the root that determines whether the character root is grounded.")]
public float rootSphereCastRadius = 0.1f;
/// <summary>
/// If false, keeps the foot that is over a ledge at the root level. If true, lowers the overstepping foot and body by the 'Max Step' value.
/// </summary>
[Tooltip("If false, keeps the foot that is over a ledge at the root level. If true, lowers the overstepping foot and body by the 'Max Step' value.")]
public bool overstepFallsDown = true;
/// <summary>
/// The raycasting quality. Fastest is a single raycast per foot, Simple is three raycasts, Best is one raycast and a capsule cast per foot.
/// </summary>
[Tooltip("The raycasting quality. Fastest is a single raycast per foot, Simple is three raycasts, Best is one raycast and a capsule cast per foot.")]
public Quality quality = Quality.Best;
/// <summary>
/// The %Grounding legs.
/// </summary>
public Leg[] legs { get; private set; }
/// <summary>
/// The %Grounding pelvis.
/// </summary>
public Pelvis pelvis { get; private set; }
/// <summary>
/// Gets a value indicating whether any of the legs are grounded
/// </summary>
public bool isGrounded { get; private set; }
/// <summary>
/// The root Transform
/// </summary>
public Transform root { get; private set; }
/// <summary>
/// Ground height at the root position.
/// </summary>
public RaycastHit rootHit { get; private set; }
/// <summary>
/// Is the RaycastHit from the root grounded?
/// </summary>
public bool rootGrounded {
get {
return rootHit.distance < maxStep * 2f;
}
}
// For overriding ray/capsule/sphere casting functions
public delegate bool OnRaycastDelegate(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
public OnRaycastDelegate Raycast = Physics.Raycast;
public delegate bool OnCapsuleCastDelegate(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
public OnCapsuleCastDelegate CapsuleCast = Physics.CapsuleCast;
public delegate bool OnSphereCastDelegate(Vector3 origin, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
public OnSphereCastDelegate SphereCast = Physics.SphereCast;
/// <summary>
/// Raycasts or sphereCasts to find the root ground point. Distance of the Ray/Sphere cast is maxDistanceMlp x maxStep. Use this instead of rootHit if the Grounder is weighed out/disabled and not updated.
/// </summary>
public RaycastHit GetRootHit(float maxDistanceMlp = 10f) {
RaycastHit h = new RaycastHit();
Vector3 _up = up;
Vector3 legsCenter = Vector3.zero;
foreach (Leg leg in legs) legsCenter += leg.transform.position;
legsCenter /= (float)legs.Length;
h.point = legsCenter - _up * maxStep * 10f;
float distMlp = maxDistanceMlp + 1;
h.distance = maxStep * distMlp;
if (maxStep <= 0f) return h;
if (quality != Quality.Best) Raycast(legsCenter + _up * maxStep, -_up, out h, maxStep * distMlp, layers, QueryTriggerInteraction.Ignore);
else SphereCast(legsCenter + _up * maxStep, rootSphereCastRadius, -up, out h, maxStep * distMlp, layers, QueryTriggerInteraction.Ignore);
return h;
}
/// <summary>
/// Gets a value indicating whether this <see cref="Grounding"/> is valid.
/// </summary>
public bool IsValid(ref string errorMessage) {
if (root == null) {
errorMessage = "Root transform is null. Can't initiate Grounding.";
return false;
}
if (legs == null) {
errorMessage = "Grounding legs is null. Can't initiate Grounding.";
return false;
}
if (pelvis == null) {
errorMessage = "Grounding pelvis is null. Can't initiate Grounding.";
return false;
}
if (legs.Length == 0) {
errorMessage = "Grounding has 0 legs. Can't initiate Grounding.";
return false;
}
return true;
}
/// <summary>
/// Initiate the %Grounding as an integrated solver by providing the root Transform, leg solvers, pelvis Transform and spine solver.
/// </summary>
public void Initiate(Transform root, Transform[] feet) {
this.root = root;
initiated = false;
rootHit = new RaycastHit();
// Constructing Legs
if (legs == null) legs = new Leg[feet.Length];
if (legs.Length != feet.Length) legs = new Leg[feet.Length];
for (int i = 0; i < feet.Length; i++) if (legs[i] == null) legs[i] = new Leg();
// Constructing pelvis
if (pelvis == null) pelvis = new Pelvis();
string errorMessage = string.Empty;
if (!IsValid(ref errorMessage)) {
Warning.Log(errorMessage, root, false);
return;
}
// Initiate solvers only if application is playing
if (Application.isPlaying) {
for (int i = 0; i < feet.Length; i++) legs[i].Initiate(this, feet[i]);
pelvis.Initiate(this);
initiated = true;
}
}
/// <summary>
/// Updates the Grounding.
/// </summary>
public void Update() {
if (!initiated) return;
if (layers == 0) LogWarning("Grounding layers are set to nothing. Please add a ground layer.");
maxStep = Mathf.Clamp(maxStep, 0f, maxStep);
footRadius = Mathf.Clamp(footRadius, 0.0001f, maxStep);
pelvisDamper = Mathf.Clamp(pelvisDamper, 0f, 1f);
rootSphereCastRadius = Mathf.Clamp(rootSphereCastRadius, 0.0001f, rootSphereCastRadius);
maxFootRotationAngle = Mathf.Clamp(maxFootRotationAngle, 0f, 90f);
prediction = Mathf.Clamp(prediction, 0f, prediction);
footSpeed = Mathf.Clamp(footSpeed, 0f, footSpeed);
// Root hit
rootHit = GetRootHit();
float lowestOffset = Mathf.NegativeInfinity;
float highestOffset = Mathf.Infinity;
isGrounded = false;
// Process legs
foreach (Leg leg in legs) {
leg.Process();
if (leg.IKOffset > lowestOffset) lowestOffset = leg.IKOffset;
if (leg.IKOffset < highestOffset) highestOffset = leg.IKOffset;
if (leg.isGrounded) isGrounded = true;
}
// Precess pelvis
lowestOffset = Mathf.Max(lowestOffset, 0f);
highestOffset = Mathf.Min(highestOffset, 0f);
pelvis.Process(-lowestOffset * lowerPelvisWeight, -highestOffset * liftPelvisWeight, isGrounded);
}
// Calculate the normal of the plane defined by leg positions, so we know how to rotate the body
public Vector3 GetLegsPlaneNormal() {
if (!initiated) return Vector3.up;
Vector3 _up = up;
Vector3 normal = _up;
// Go through all the legs, rotate the normal by its offset
for (int i = 0; i < legs.Length; i++) {
// Direction from the root to the leg
Vector3 legDirection = legs[i].IKPosition - root.position;
// Find the tangent
Vector3 legNormal = _up;
Vector3 legTangent = legDirection;
Vector3.OrthoNormalize(ref legNormal, ref legTangent);
// Find the rotation offset from the tangent to the direction
Quaternion fromTo = Quaternion.FromToRotation(legTangent, legDirection);
// Rotate the normal
normal = fromTo * normal;
}
return normal;
}
// Set everything to 0
public void Reset() {
if (!Application.isPlaying) return;
pelvis.Reset();
foreach (Leg leg in legs) leg.Reset();
}
#endregion Main Interface
private bool initiated;
// Logs the warning if no other warning has beed logged in this session.
public void LogWarning(string message) {
Warning.Log(message, root);
}
// The up vector in solver rotation space.
public Vector3 up {
get {
return (useRootRotation? root.up: Vector3.up);
}
}
// Gets the vertical offset between two vectors in solver rotation space
public float GetVerticalOffset(Vector3 p1, Vector3 p2) {
if (useRootRotation) {
Vector3 v = Quaternion.Inverse(root.rotation) * (p1 - p2);
return v.y;
}
return p1.y - p2.y;
}
// Flattens a vector to ground plane in solver rotation space
public Vector3 Flatten(Vector3 v) {
if (useRootRotation) {
Vector3 tangent = v;
Vector3 normal = root.up;
Vector3.OrthoNormalize(ref normal, ref tangent);
return Vector3.Project(v, tangent);
}
v.y = 0;
return v;
}
// Determines whether to use root rotation as solver rotation
private bool useRootRotation {
get {
if (!rotateSolver) return false;
if (root.up == Vector3.up) return false;
return true;
}
}
public Vector3 GetFootCenterOffset() {
return root.forward * footRadius + root.forward * footCenterOffset;
}
}
}

View File

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

View File

@ -0,0 +1,345 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
public partial class Grounding {
/// <summary>
/// The %Grounding %Leg.
/// </summary>
public class Leg {
/// <summary>
/// Returns true distance from foot to ground is less that maxStep
/// </summary>
public bool isGrounded { get; private set; }
/// <summary>
/// Gets the current IK position of the foot.
/// </summary>
public Vector3 IKPosition { get; private set; }
/// <summary>
/// Gets the current rotation offset of the foot.
/// </summary>
public Quaternion rotationOffset = Quaternion.identity;
/// <summary>
/// Returns true, if the leg is valid and initiated
/// </summary>
public bool initiated { get; private set; }
/// <summary>
/// The height of foot from ground.
/// </summary>
public float heightFromGround { get; private set; }
/// <summary>
/// Velocity of the foot
/// </summary>
public Vector3 velocity { get; private set; }
/// <summary>
/// Gets the foot Transform.
/// </summary>
public Transform transform { get; private set; }
/// <summary>
/// Gets the current IK offset.
/// </summary>
public float IKOffset { get; private set; }
public bool invertFootCenter;
public RaycastHit heelHit { get; private set; }
public RaycastHit capsuleHit { get; private set; }
/// <summary>
/// Gets the RaycastHit last used by the Grounder to get ground height at foot position.
/// </summary>
public RaycastHit GetHitPoint {
get
{
if (grounding.quality == Quality.Best) return capsuleHit;
return heelHit;
}
}
/// <summary>
/// Overrides the animated position of the foot.
/// </summary>
public void SetFootPosition(Vector3 position)
{
doOverrideFootPosition = true;
overrideFootPosition = position;
}
private Grounding grounding;
private float lastTime, deltaTime;
private Vector3 lastPosition;
private Quaternion toHitNormal, r;
private Vector3 up = Vector3.up;
private bool doOverrideFootPosition;
private Vector3 overrideFootPosition;
private Vector3 transformPosition;
// Initiates the Leg
public void Initiate(Grounding grounding, Transform transform) {
initiated = false;
this.grounding = grounding;
this.transform = transform;
up = Vector3.up;
IKPosition = transform.position;
rotationOffset = Quaternion.identity;
initiated = true;
OnEnable();
}
// Should be called each time the leg is (re)activated
public void OnEnable() {
if (!initiated) return;
lastPosition = transform.position;
lastTime = Time.deltaTime;
}
// Set everything to 0
public void Reset() {
lastPosition = transform.position;
lastTime = Time.deltaTime;
IKOffset = 0f;
IKPosition = transform.position;
rotationOffset = Quaternion.identity;
}
// Raycasting, processing the leg's position
public void Process() {
if (!initiated) return;
if (grounding.maxStep <= 0) return;
transformPosition = doOverrideFootPosition ? overrideFootPosition : transform.position;
doOverrideFootPosition = false;
deltaTime = Time.time - lastTime;
lastTime = Time.time;
if (deltaTime == 0f) return;
up = grounding.up;
heightFromGround = Mathf.Infinity;
// Calculating velocity
velocity = (transformPosition - lastPosition) / deltaTime;
//velocity = grounding.Flatten(velocity);
lastPosition = transformPosition;
Vector3 prediction = velocity * grounding.prediction;
if (grounding.footRadius <= 0) grounding.quality = Grounding.Quality.Fastest;
isGrounded = false;
// Raycasting
switch (grounding.quality)
{
// The fastest, single raycast
case Grounding.Quality.Fastest:
RaycastHit predictedHit = GetRaycastHit(prediction);
SetFootToPoint(predictedHit.normal, predictedHit.point);
if (predictedHit.collider != null) isGrounded = true;
break;
// Medium, 3 raycasts
case Grounding.Quality.Simple:
heelHit = GetRaycastHit(Vector3.zero);
Vector3 f = grounding.GetFootCenterOffset();
if (invertFootCenter) f = -f;
RaycastHit toeHit = GetRaycastHit(f + prediction);
RaycastHit sideHit = GetRaycastHit(grounding.root.right * grounding.footRadius * 0.5f);
if (heelHit.collider != null || toeHit.collider != null || sideHit.collider != null) isGrounded = true;
Vector3 planeNormal = Vector3.Cross(toeHit.point - heelHit.point, sideHit.point - heelHit.point).normalized;
if (Vector3.Dot(planeNormal, up) < 0) planeNormal = -planeNormal;
SetFootToPlane(planeNormal, heelHit.point, heelHit.point);
break;
// The slowest, raycast and a capsule cast
case Grounding.Quality.Best:
heelHit = GetRaycastHit(invertFootCenter ? -grounding.GetFootCenterOffset() : Vector3.zero);
capsuleHit = GetCapsuleHit(prediction);
if (heelHit.collider != null || capsuleHit.collider != null) isGrounded = true;
SetFootToPlane(capsuleHit.normal, capsuleHit.point, heelHit.point);
break;
}
float offsetTarget = stepHeightFromGround;
if (!grounding.rootGrounded) offsetTarget = 0f;
IKOffset = Interp.LerpValue(IKOffset, offsetTarget, grounding.footSpeed, grounding.footSpeed);
IKOffset = Mathf.Lerp(IKOffset, offsetTarget, deltaTime * grounding.footSpeed);
float legHeight = grounding.GetVerticalOffset(transformPosition, grounding.root.position);
float currentMaxOffset = Mathf.Clamp(grounding.maxStep - legHeight, 0f, grounding.maxStep);
IKOffset = Mathf.Clamp(IKOffset, -currentMaxOffset, IKOffset);
RotateFoot();
// Update IK values
IKPosition = transformPosition - up * IKOffset;
float rW = grounding.footRotationWeight;
rotationOffset = rW >= 1? r: Quaternion.Slerp(Quaternion.identity, r, rW);
}
// Gets the height from ground clamped between min and max step height
public float stepHeightFromGround {
get {
return Mathf.Clamp(heightFromGround, -grounding.maxStep, grounding.maxStep);
}
}
// Get predicted Capsule hit from the middle of the foot
private RaycastHit GetCapsuleHit(Vector3 offsetFromHeel)
{
RaycastHit hit = new RaycastHit();
Vector3 f = grounding.GetFootCenterOffset();
if (invertFootCenter) f = -f;
Vector3 origin = transformPosition + f;
if (grounding.overstepFallsDown)
{
hit.point = origin - up * grounding.maxStep;
}
else
{
hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
}
hit.normal = up;
// Start point of the capsule
Vector3 capsuleStart = origin + grounding.maxStep * up;
// End point of the capsule depending on the foot's velocity.
Vector3 capsuleEnd = capsuleStart + offsetFromHeel;
if (grounding.CapsuleCast(capsuleStart, capsuleEnd, grounding.footRadius, -up, out hit, grounding.maxStep * 2, grounding.layers, QueryTriggerInteraction.Ignore))
{
// Safeguarding from a CapsuleCast bug in Unity that might cause it to return NaN for hit.point when cast against large colliders.
if (float.IsNaN(hit.point.x))
{
hit.point = origin - up * grounding.maxStep * 2f;
hit.normal = up;
}
}
// Since Unity2017 Raycasts will return Vector3.zero when starting from inside a collider
if (hit.point == Vector3.zero && hit.normal == Vector3.zero)
{
if (grounding.overstepFallsDown)
{
hit.point = origin - up * grounding.maxStep;
}
else
{
hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
}
}
return hit;
}
// Get simple Raycast from the heel
private RaycastHit GetRaycastHit(Vector3 offsetFromHeel)
{
RaycastHit hit = new RaycastHit();
Vector3 origin = transformPosition + offsetFromHeel;
if (grounding.overstepFallsDown)
{
hit.point = origin - up * grounding.maxStep;
}
else
{
hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
}
hit.normal = up;
if (grounding.maxStep <= 0f) return hit;
grounding.Raycast(origin + grounding.maxStep * up, -up, out hit, grounding.maxStep * 2, grounding.layers, QueryTriggerInteraction.Ignore);
// Since Unity2017 Raycasts will return Vector3.zero when starting from inside a collider
if (hit.point == Vector3.zero && hit.normal == Vector3.zero)
{
if (grounding.overstepFallsDown)
{
hit.point = origin - up * grounding.maxStep;
}
else
{
hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
}
}
return hit;
}
// Rotates ground normal with respect to maxFootRotationAngle
private Vector3 RotateNormal(Vector3 normal) {
if (grounding.quality == Grounding.Quality.Best) return normal;
return Vector3.RotateTowards(up, normal, grounding.maxFootRotationAngle * Mathf.Deg2Rad, deltaTime);
}
// Set foot height from ground relative to a point
private void SetFootToPoint(Vector3 normal, Vector3 point) {
toHitNormal = Quaternion.FromToRotation(up, RotateNormal(normal));
heightFromGround = GetHeightFromGround(point);
}
// Set foot height from ground relative to a plane
private void SetFootToPlane(Vector3 planeNormal, Vector3 planePoint, Vector3 heelHitPoint) {
planeNormal = RotateNormal(planeNormal);
toHitNormal = Quaternion.FromToRotation(up, planeNormal);
Vector3 pointOnPlane = V3Tools.LineToPlane(transformPosition + up * grounding.maxStep, -up, planeNormal, planePoint);
// Get the height offset of the point on the plane
heightFromGround = GetHeightFromGround(pointOnPlane);
// Making sure the heel doesn't penetrate the ground
float heelHeight = GetHeightFromGround(heelHitPoint);
heightFromGround = Mathf.Clamp(heightFromGround, -Mathf.Infinity, heelHeight);
}
// Calculate height offset of a point
private float GetHeightFromGround(Vector3 hitPoint) {
return grounding.GetVerticalOffset(transformPosition, hitPoint) - rootYOffset;
}
// Adding ground normal offset to the foot's rotation
private void RotateFoot() {
// Getting the full target rotation
Quaternion rotationOffsetTarget = GetRotationOffsetTarget();
// Slerping the rotation offset
r = Quaternion.Slerp(r, rotationOffsetTarget, deltaTime * grounding.footRotationSpeed);
}
// Gets the target hit normal offset as a Quaternion
private Quaternion GetRotationOffsetTarget() {
if (grounding.maxFootRotationAngle <= 0f) return Quaternion.identity;
if (grounding.maxFootRotationAngle >= 180f) return toHitNormal;
return Quaternion.RotateTowards(Quaternion.identity, toHitNormal, grounding.maxFootRotationAngle);
}
// The foot's height from ground in the animation
private float rootYOffset {
get {
return grounding.GetVerticalOffset(transformPosition, grounding.root.position - up * grounding.heightOffset);
}
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0098ae58c812d4f599afc603a2d8b4b0
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,82 @@
using UnityEngine;
using System.Collections;
using RootMotion;
namespace RootMotion.FinalIK {
public partial class Grounding {
/// <summary>
/// The %Grounding %Pelvis.
/// </summary>
public class Pelvis {
/// <summary>
/// Offset of the pelvis as a Vector3.
/// </summary>
public Vector3 IKOffset { get; private set; }
/// <summary>
/// Scalar vertical offset of the pelvis.
/// </summary>
public float heightOffset { get; private set; }
private Grounding grounding;
private Vector3 lastRootPosition;
private float damperF;
private bool initiated;
private float lastTime;
// Initiating the pelvis
public void Initiate(Grounding grounding) {
this.grounding = grounding;
initiated = true;
OnEnable();
}
// Set everything to 0
public void Reset() {
this.lastRootPosition = grounding.root.transform.position;
lastTime = Time.deltaTime;
IKOffset = Vector3.zero;
heightOffset = 0f;
}
// Should be called each time the pelvis is (re)activated
public void OnEnable() {
if (!initiated) return;
this.lastRootPosition = grounding.root.transform.position;
lastTime = Time.time;
}
// Updates the pelvis position offset
public void Process(float lowestOffset, float highestOffset, bool isGrounded) {
if (!initiated) return;
float deltaTime = Time.time - lastTime;
lastTime = Time.time;
if (deltaTime <= 0f) return;
float offsetTarget = lowestOffset + highestOffset;
if (!grounding.rootGrounded) offsetTarget = 0f;
// Interpolating the offset
heightOffset = Mathf.Lerp(heightOffset, offsetTarget, deltaTime * grounding.pelvisSpeed);
// Damper
Vector3 rootDelta = (grounding.root.position - lastRootPosition);
lastRootPosition = grounding.root.position;
// Fading out damper when ungrounded
damperF = Interp.LerpValue(damperF, isGrounded? 1f: 0f, 1f, 10f);
// Calculating the final damper
heightOffset -= grounding.GetVerticalOffset(rootDelta, Vector3.zero) * grounding.pelvisDamper * damperF;
// Update IK value
IKOffset = grounding.up * heightOffset;
}
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: f581c9c6c8e7b49b48ff9a8be3b47df3
folderAsset: yes
timeCreated: 1434626794
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,53 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Aim %IK solver component.
/// </summary>
[HelpURL("https://www.youtube.com/watch?v=wT8fViZpLmQ&index=3&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6")]
[AddComponentMenu("Scripts/RootMotion.FinalIK/IK/Aim IK")]
public class AimIK : IK {
// Open the User Manual URL
[ContextMenu("User Manual")]
protected override void OpenUserManual() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page1.html");
}
// Open the Script Reference URL
[ContextMenu("Scrpt Reference")]
protected override void OpenScriptReference() {
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_aim_i_k.html");
}
// Open a video tutorial about setting up the component
[ContextMenu("TUTORIAL VIDEO")]
void OpenSetupTutorial() {
Application.OpenURL("https://www.youtube.com/watch?v=wT8fViZpLmQ");
}
// Link to the Final IK Google Group
[ContextMenu("Support Group")]
void SupportGroup() {
Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
}
// Link to the Final IK Asset Store thread in the Unity Community
[ContextMenu("Asset Store Thread")]
void ASThread() {
Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
}
/// <summary>
/// The Aim %IK solver.
/// </summary>
public IKSolverAim solver = new IKSolverAim();
public override IKSolver GetIKSolver() {
return solver as IKSolver;
}
}
}

View File

@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: 5013856973b27429d937d256dc082f2e
labels:
- InverseKinematics
- IK
- Aim
- AimingSystem
timeCreated: 1511881277
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 9997
icon: {fileID: 2800000, guid: 14a4d69b319a04b75bb03f351a174cec, type: 3}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More