initial upload
This commit is contained in:
@ -0,0 +1,283 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Handles smooth aim target switching, weight blending, target interpolation and root rotation.
|
||||
/// </summary>
|
||||
public class AimController : MonoBehaviour {
|
||||
|
||||
[Tooltip("Reference to the AimIK component.")]
|
||||
/// <summary>
|
||||
/// Reference to the AimIK component.
|
||||
/// </summary>
|
||||
public AimIK ik;
|
||||
|
||||
[Tooltip("Master weight of the IK solver.")]
|
||||
/// <summary>
|
||||
/// Master weight of the IK solver.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float weight = 1f;
|
||||
|
||||
[Header("Target Smoothing")]
|
||||
|
||||
[Tooltip("The target to aim at. Do not use the Target transform that is assigned to AimIK. Set to null if you wish to stop aiming.")]
|
||||
/// <summary>
|
||||
/// The target to aim at. Do not use the Target transform that is assigned to AimIK. Set to null if you wish to stop aiming.
|
||||
/// </summary>
|
||||
public Transform target;
|
||||
|
||||
[Tooltip("The time it takes to switch targets.")]
|
||||
/// <summary>
|
||||
/// The time it takes to switch targets.
|
||||
/// </summary>
|
||||
public float targetSwitchSmoothTime = 0.3f;
|
||||
|
||||
[Tooltip("The time it takes to blend in/out of AimIK weight.")]
|
||||
/// <summary>
|
||||
/// The time it takes to blend in/out of AimIK weight.
|
||||
/// </summary>
|
||||
public float weightSmoothTime = 0.3f;
|
||||
|
||||
[Header("Turning Towards The Target")]
|
||||
|
||||
[Tooltip("Enables smooth turning towards the target according to the parameters under this header.")]
|
||||
/// <summary>
|
||||
/// Enables smooth turning towards the target according to the parameters under this header.
|
||||
/// </summary>
|
||||
public bool smoothTurnTowardsTarget = true;
|
||||
|
||||
[Tooltip("Speed of turning towards the target using Vector3.RotateTowards.")]
|
||||
/// <summary>
|
||||
/// Speed of turning towards the target using Vector3.RotateTowards.
|
||||
/// </summary>
|
||||
public float maxRadiansDelta = 3f;
|
||||
|
||||
[Tooltip("Speed of moving towards the target using Vector3.RotateTowards.")]
|
||||
/// <summary>
|
||||
/// Speed of moving towards the target using Vector3.RotateTowards.
|
||||
/// </summary>
|
||||
public float maxMagnitudeDelta = 3f;
|
||||
|
||||
[Tooltip("Speed of slerping towards the target.")]
|
||||
/// <summary>
|
||||
/// Speed of slerping towards the target.
|
||||
/// </summary>
|
||||
public float slerpSpeed = 3f;
|
||||
|
||||
[Tooltip("Smoothing time for turning towards the yaw and pitch of the target using Mathf.SmoothDampAngle. Value of 0 means smooth damping is disabled." )]
|
||||
/// <summary>
|
||||
/// Smoothing time for turning towards the yaw and pitch of the target using Mathf.SmoothDampAngle. Value of 0 means smooth damping is disabled.
|
||||
/// </summary>
|
||||
public float smoothDampTime = 0f;
|
||||
|
||||
[Tooltip("The position of the pivot that the aim target is rotated around relative to the root of the character.")]
|
||||
/// <summary>
|
||||
/// The position of the pivot that the aim target is rotated around relative to the root of the character.
|
||||
/// </summary>
|
||||
public Vector3 pivotOffsetFromRoot = Vector3.up;
|
||||
|
||||
[Tooltip("Minimum distance of aiming from the first bone. Keeps the solver from failing if the target is too close.")]
|
||||
/// <summary>
|
||||
/// Minimum distance of aiming from the first bone. Keeps the solver from failing if the target is too close.
|
||||
/// </summary>
|
||||
public float minDistance = 1f;
|
||||
|
||||
[Tooltip("Offset applied to the target in world space. Convenient for scripting aiming inaccuracy.")]
|
||||
/// <summary>
|
||||
/// Offset applied to the target in world space. Convenient for scripting aiming inaccuracy.
|
||||
/// </summary>
|
||||
public Vector3 offset;
|
||||
|
||||
[Header("RootRotation")]
|
||||
|
||||
[Tooltip("Character root will be rotate around the Y axis to keep root forward within this angle from the aiming direction.")]
|
||||
/// <summary>
|
||||
///Character root will be rotate around the Y axis to keep root forward within this angle from the aiming direction.
|
||||
/// </summary>
|
||||
[Range(0f, 180f)] public float maxRootAngle = 45f;
|
||||
|
||||
[Tooltip("If enabled, aligns the root forward to target direction after 'Max Root Angle' has been exceeded.")]
|
||||
/// <summary>
|
||||
/// If enabled, aligns the root forward to target direction after 'Max Root Angle' has been exceeded.
|
||||
/// </summary>
|
||||
public bool turnToTarget;
|
||||
|
||||
[Tooltip("The time of turning towards the target direction if 'Max Root Angle has been exceeded and 'Turn To Target' is enabled.")]
|
||||
/// <summary>
|
||||
/// The time of turning towards the target direction if 'Max Root Angle has been exceeded and 'Turn To Target' is enabled.
|
||||
/// </summary>
|
||||
public float turnToTargetTime = 0.2f;
|
||||
|
||||
[Header("Mode")]
|
||||
|
||||
[Tooltip("If true, AimIK will consider whatever the current direction of the weapon to be the forward aiming direction and work additively on top of that. This enables you to use recoil and reloading animations seamlessly with AimIK. Adjust the Vector3 value below if the weapon is not aiming perfectly forward in the aiming animation clip.")]
|
||||
/// <summary>
|
||||
/// If true, AimIK will consider whatever the current direction of the weapon to be the forward aiming direction and work additively on top of that. This enables you to use recoil and reloading animations seamlessly with AimIK. Adjust the Vector3 value below if the weapon is not aiming perfectly forward in the aiming animation clip.
|
||||
/// </summary>
|
||||
public bool useAnimatedAimDirection;
|
||||
|
||||
[Tooltip("The direction of the animated weapon aiming in character space. Tweak this value to adjust the aiming. 'Use Animated Aim Direction' must be enabled for this property to work.")]
|
||||
/// <summary>
|
||||
/// The direction of the animated weapon aiming in character space. Tweak this value to adjust the aiming. 'Use Animated Aim Direction' must be enabled for this property to work.
|
||||
/// </summary>
|
||||
public Vector3 animatedAimDirection = Vector3.forward;
|
||||
|
||||
private Transform lastTarget;
|
||||
private float switchWeight, switchWeightV;
|
||||
private float weightV;
|
||||
private Vector3 lastPosition;
|
||||
private Vector3 dir;
|
||||
private bool lastSmoothTowardsTarget;
|
||||
private bool turningToTarget;
|
||||
private float turnToTargetMlp = 1f;
|
||||
private float turnToTargetMlpV;
|
||||
|
||||
void Start() {
|
||||
lastPosition = ik.solver.IKPosition;
|
||||
dir = ik.solver.IKPosition - pivot;
|
||||
|
||||
ik.solver.target = null;
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
// If target has changed...
|
||||
if (target != lastTarget) {
|
||||
if (lastTarget == null && target != null && ik.solver.IKPositionWeight <= 0f)
|
||||
{
|
||||
lastPosition = target.position;
|
||||
dir = target.position - pivot;
|
||||
ik.solver.IKPosition = target.position + offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastPosition = ik.solver.IKPosition;
|
||||
dir = ik.solver.IKPosition - pivot;
|
||||
}
|
||||
|
||||
switchWeight = 0f;
|
||||
lastTarget = target;
|
||||
}
|
||||
|
||||
// Smooth weight
|
||||
float targetWeight = target != null ? weight : 0f;
|
||||
ik.solver.IKPositionWeight = Mathf.SmoothDamp(ik.solver.IKPositionWeight, targetWeight, ref weightV, weightSmoothTime);
|
||||
if (ik.solver.IKPositionWeight >= 0.999f && targetWeight > ik.solver.IKPositionWeight) ik.solver.IKPositionWeight = 1f;
|
||||
if (ik.solver.IKPositionWeight <= 0.001f && targetWeight < ik.solver.IKPositionWeight) ik.solver.IKPositionWeight = 0f;
|
||||
|
||||
if (ik.solver.IKPositionWeight <= 0f) return;
|
||||
|
||||
// Smooth target switching
|
||||
switchWeight = Mathf.SmoothDamp(switchWeight, 1f, ref switchWeightV, targetSwitchSmoothTime);
|
||||
if (switchWeight >= 0.999f) switchWeight = 1f;
|
||||
|
||||
if (target != null) {
|
||||
ik.solver.IKPosition = Vector3.Lerp(lastPosition, target.position + offset, switchWeight);
|
||||
}
|
||||
|
||||
// Smooth turn towards target
|
||||
if (smoothTurnTowardsTarget != lastSmoothTowardsTarget) {
|
||||
dir = ik.solver.IKPosition - pivot;
|
||||
lastSmoothTowardsTarget = smoothTurnTowardsTarget;
|
||||
}
|
||||
|
||||
if (smoothTurnTowardsTarget) {
|
||||
Vector3 targetDir = ik.solver.IKPosition - pivot;
|
||||
|
||||
// Slerp
|
||||
if (slerpSpeed > 0f) dir = Vector3.Slerp(dir, targetDir, Time.deltaTime * slerpSpeed);
|
||||
|
||||
// RotateTowards
|
||||
if (maxRadiansDelta > 0 || maxMagnitudeDelta > 0f) dir = Vector3.RotateTowards(dir, targetDir, Time.deltaTime * maxRadiansDelta, maxMagnitudeDelta);
|
||||
|
||||
// SmoothDamp
|
||||
if (smoothDampTime > 0f)
|
||||
{
|
||||
float yaw = V3Tools.GetYaw(dir);
|
||||
float targetYaw = V3Tools.GetYaw(targetDir);
|
||||
float y = Mathf.SmoothDampAngle(yaw, targetYaw, ref yawV, smoothDampTime);
|
||||
|
||||
float pitch = V3Tools.GetPitch(dir);
|
||||
float targetPitch = V3Tools.GetPitch(targetDir);
|
||||
float p = Mathf.SmoothDampAngle(pitch, targetPitch, ref pitchV, smoothDampTime);
|
||||
|
||||
float dirMag = Mathf.SmoothDamp(dir.magnitude, targetDir.magnitude, ref dirMagV, smoothDampTime);
|
||||
|
||||
dir = Quaternion.Euler(p, y, 0f) * Vector3.forward * dirMag;
|
||||
}
|
||||
|
||||
ik.solver.IKPosition = pivot + dir;
|
||||
}
|
||||
|
||||
// Min distance from the pivot
|
||||
ApplyMinDistance();
|
||||
|
||||
// Root rotation
|
||||
RootRotation();
|
||||
|
||||
// Offset mode
|
||||
if (useAnimatedAimDirection) {
|
||||
ik.solver.axis = ik.solver.transform.InverseTransformVector(ik.transform.rotation * animatedAimDirection);
|
||||
}
|
||||
}
|
||||
|
||||
private float yawV, pitchV, dirMagV;
|
||||
|
||||
// Pivot of rotating the aiming direction.
|
||||
private Vector3 pivot {
|
||||
get {
|
||||
return ik.transform.position + ik.transform.rotation * pivotOffsetFromRoot;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure aiming target is not too close (might make the solver instable when the target is closer to the first bone than the last bone is).
|
||||
void ApplyMinDistance() {
|
||||
Vector3 aimFrom = pivot;
|
||||
Vector3 direction = (ik.solver.IKPosition - aimFrom);
|
||||
direction = direction.normalized * Mathf.Max(direction.magnitude, minDistance);
|
||||
|
||||
ik.solver.IKPosition = aimFrom + direction;
|
||||
}
|
||||
|
||||
// Character root will be rotate around the Y axis to keep root forward within this angle from the aiming direction.
|
||||
private void RootRotation() {
|
||||
float max = Mathf.Lerp(180f, maxRootAngle * turnToTargetMlp, ik.solver.IKPositionWeight);
|
||||
|
||||
if (max < 180f) {
|
||||
Vector3 faceDirLocal = Quaternion.Inverse(ik.transform.rotation) * (ik.solver.IKPosition - pivot);
|
||||
float angle = Mathf.Atan2(faceDirLocal.x, faceDirLocal.z) * Mathf.Rad2Deg;
|
||||
|
||||
float rotation = 0f;
|
||||
|
||||
if (angle > max) {
|
||||
rotation = angle - max;
|
||||
if (!turningToTarget && turnToTarget) StartCoroutine(TurnToTarget());
|
||||
}
|
||||
if (angle < -max) {
|
||||
rotation = angle + max;
|
||||
if (!turningToTarget && turnToTarget) StartCoroutine(TurnToTarget());
|
||||
}
|
||||
|
||||
ik.transform.rotation = Quaternion.AngleAxis(rotation, ik.transform.up) * ik.transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
// Aligns the root forward to target direction after "Max Root Angle" has been exceeded.
|
||||
private IEnumerator TurnToTarget()
|
||||
{
|
||||
turningToTarget = true;
|
||||
|
||||
while (turnToTargetMlp > 0f)
|
||||
{
|
||||
turnToTargetMlp = Mathf.SmoothDamp(turnToTargetMlp, 0f, ref turnToTargetMlpV, turnToTargetTime);
|
||||
if (turnToTargetMlp < 0.01f) turnToTargetMlp = 0f;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
turnToTargetMlp = 1f;
|
||||
turningToTarget = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9df7d42e7cb1a54a8eb68e9e9f0dc68
|
||||
timeCreated: 1512472681
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,78 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using RootMotion;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Aim Poser returns a reference by direction.
|
||||
/// </summary>
|
||||
public class AimPoser : MonoBehaviour {
|
||||
|
||||
/// <summary>
|
||||
/// the pose definition
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Pose {
|
||||
|
||||
public bool visualize = true; // Show the direction and range of this pose in the scene view
|
||||
public string name; // the reference
|
||||
public Vector3 direction; // the direction of the pose
|
||||
public float yaw = 75f; // the yaw range
|
||||
public float pitch = 45f; // the pitch range
|
||||
|
||||
private float angleBuffer;
|
||||
|
||||
// Determines whether this Pose is in the specified direction.
|
||||
public bool IsInDirection(Vector3 d) {
|
||||
if (direction == Vector3.zero) return false;
|
||||
if (yaw <= 0 || pitch <= 0) return false;
|
||||
|
||||
// Yaw
|
||||
if (yaw < 180f) {
|
||||
Vector3 directionYaw = new Vector3(direction.x, 0f, direction.z);
|
||||
if (directionYaw == Vector3.zero) directionYaw = Vector3.forward;
|
||||
|
||||
Vector3 dYaw = new Vector3(d.x, 0f, d.z);
|
||||
float yawAngle = Vector3.Angle(dYaw, directionYaw);
|
||||
|
||||
if (yawAngle > yaw + angleBuffer) return false;
|
||||
}
|
||||
|
||||
// Pitch
|
||||
if (pitch >= 180f) return true;
|
||||
|
||||
float directionPitch = Vector3.Angle(Vector3.up, direction);
|
||||
float dPitch = Vector3.Angle(Vector3.up, d);
|
||||
return Mathf.Abs(dPitch - directionPitch) < pitch + angleBuffer;
|
||||
}
|
||||
|
||||
// Sets the angle buffer to prevent immediatelly switching back to the last pose if the angle should change a bit.
|
||||
public void SetAngleBuffer(float value) {
|
||||
angleBuffer = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float angleBuffer = 5f; // The angle buffer
|
||||
public Pose[] poses = new Pose[0]; // The array of poses.
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pose by direction. GetPose will go through the poses array and return the first pose that has the direction in range.
|
||||
/// </summary>
|
||||
public Pose GetPose(Vector3 localDirection) {
|
||||
if (poses.Length == 0) return null;
|
||||
|
||||
for (int i = 0; i < poses.Length - 1; i++) if (poses[i].IsInDirection(localDirection)) return poses[i];
|
||||
return poses[poses.Length - 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the pose active, increasing its angle buffer.
|
||||
/// </summary>
|
||||
public void SetPoseActive(Pose pose) {
|
||||
for (int i = 0; i < poses.Length; i++) {
|
||||
poses[i].SetAngleBuffer(poses[i] == pose? angleBuffer: 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4267160fb35f54254be87414e585c1a7
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,101 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Demo script that amplifies the motion of a body part relative to the root of the character or another body part.
|
||||
/// </summary>
|
||||
public class Amplifier: OffsetModifier {
|
||||
|
||||
/// <summary>
|
||||
/// Body is amplifying the motion of "transform" relative to the "relativeTo".
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Body {
|
||||
|
||||
/// <summary>
|
||||
/// Linking this to an effector
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class EffectorLink {
|
||||
[Tooltip("Type of the FBBIK effector to use")]
|
||||
public FullBodyBipedEffector effector;
|
||||
[Tooltip("Weight of using this effector")]
|
||||
public float weight;
|
||||
}
|
||||
|
||||
[Tooltip("The Transform that's motion we are reading.")]
|
||||
public Transform transform;
|
||||
[Tooltip("Amplify the 'transform's' position relative to this Transform.")]
|
||||
public Transform relativeTo;
|
||||
[Tooltip("Linking the body to effectors. One Body can be used to offset more than one effector.")]
|
||||
public EffectorLink[] effectorLinks;
|
||||
[Tooltip("Amplification magnitude along the up axis of the character.")]
|
||||
public float verticalWeight = 1f;
|
||||
[Tooltip("Amplification magnitude along the horizontal axes of the character.")]
|
||||
public float horizontalWeight = 1f;
|
||||
[Tooltip("Speed of the amplifier. 0 means instant.")]
|
||||
public float speed = 3f;
|
||||
|
||||
private Vector3 lastRelativePos;
|
||||
private Vector3 smoothDelta;
|
||||
private bool firstUpdate;
|
||||
|
||||
// Update the Body
|
||||
public void Update(IKSolverFullBodyBiped solver, float w, float deltaTime) {
|
||||
if (transform == null || relativeTo == null) return;
|
||||
|
||||
// Find the relative position of the transform
|
||||
Vector3 relativePos = relativeTo.InverseTransformDirection(transform.position - relativeTo.position);
|
||||
|
||||
// Initiating
|
||||
if (firstUpdate) {
|
||||
lastRelativePos = relativePos;
|
||||
firstUpdate = false;
|
||||
}
|
||||
|
||||
// Find how much the relative position has changed
|
||||
Vector3 delta = (relativePos - lastRelativePos) / deltaTime;
|
||||
|
||||
// Smooth the change
|
||||
smoothDelta = speed <= 0f? delta: Vector3.Lerp(smoothDelta, delta, deltaTime * speed);
|
||||
|
||||
// Convert to world space
|
||||
Vector3 worldDelta = relativeTo.TransformDirection(smoothDelta);
|
||||
|
||||
// Extract horizontal and vertical offset
|
||||
Vector3 offset = V3Tools.ExtractVertical(worldDelta, solver.GetRoot().up, verticalWeight) + V3Tools.ExtractHorizontal(worldDelta, solver.GetRoot().up, horizontalWeight);
|
||||
|
||||
// Apply the amplitude to the effector links
|
||||
for (int i = 0; i < effectorLinks.Length; i++) {
|
||||
solver.GetEffector(effectorLinks[i].effector).positionOffset += offset * w * effectorLinks[i].weight;
|
||||
}
|
||||
|
||||
lastRelativePos = relativePos;
|
||||
}
|
||||
|
||||
// Multiply 2 vectors
|
||||
private static Vector3 Multiply(Vector3 v1, Vector3 v2) {
|
||||
v1.x *= v2.x;
|
||||
v1.y *= v2.y;
|
||||
v1.z *= v2.z;
|
||||
return v1;
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("The amplified bodies.")]
|
||||
public Body[] bodies;
|
||||
|
||||
// Called by IKSolverFullBody before updating
|
||||
protected override void OnModifyOffset() {
|
||||
if (!ik.fixTransforms) {
|
||||
if (!Warning.logged) Warning.Log("Amplifier needs the Fix Transforms option of the FBBIK to be set to true. Otherwise it might amplify to infinity, should the animator of the character stop because of culling.", transform);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Bodies
|
||||
foreach (Body body in bodies) body.Update(ik.solver, weight, deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e45ea437e115a4fb6be25b8464e6de7a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,52 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Procedural body tilting with FBBIK.
|
||||
/// </summary>
|
||||
public class BodyTilt: OffsetModifier {
|
||||
|
||||
[Tooltip("Speed of tilting")]
|
||||
public float tiltSpeed = 6f;
|
||||
[Tooltip("Sensitivity of tilting")]
|
||||
public float tiltSensitivity = 0.07f;
|
||||
[Tooltip("The OffsetPose components")]
|
||||
public OffsetPose poseLeft, poseRight;
|
||||
|
||||
private float tiltAngle;
|
||||
private Vector3 lastForward;
|
||||
|
||||
protected override void Start() {
|
||||
base.Start();
|
||||
|
||||
// Store current character forward axis and Time
|
||||
lastForward = transform.forward;
|
||||
}
|
||||
|
||||
// Called by IKSolverFullBody before updating
|
||||
protected override void OnModifyOffset() {
|
||||
// Calculate the angular delta in character rotation
|
||||
Quaternion change = Quaternion.FromToRotation(lastForward, transform.forward);
|
||||
float deltaAngle = 0;
|
||||
Vector3 axis = Vector3.zero;
|
||||
change.ToAngleAxis(out deltaAngle, out axis);
|
||||
if (axis.y > 0) deltaAngle = -deltaAngle;
|
||||
|
||||
deltaAngle *= tiltSensitivity * 0.01f;
|
||||
deltaAngle /= deltaTime;
|
||||
deltaAngle = Mathf.Clamp(deltaAngle, -1f, 1f);
|
||||
|
||||
tiltAngle = Mathf.Lerp(tiltAngle, deltaAngle, deltaTime * tiltSpeed);
|
||||
|
||||
// Applying positionOffsets
|
||||
float tiltF = Mathf.Abs(tiltAngle) / 1f;
|
||||
if (tiltAngle < 0) poseRight.Apply(ik.solver, tiltF);
|
||||
else poseLeft.Apply(ik.solver, tiltF);
|
||||
|
||||
// Store current character forward axis and Time
|
||||
lastForward = transform.forward;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e1cd8d7247544f61bad689b61e39770
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,44 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Bend goal object for CCDIK. Add this to a GameObject you wish CCD to bend towards.
|
||||
/// </summary>
|
||||
public class CCDBendGoal : MonoBehaviour
|
||||
{
|
||||
|
||||
public CCDIK ik;
|
||||
[Range(0f, 1f)] public float weight = 1f;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ik.solver.OnPreUpdate += BeforeIK;
|
||||
}
|
||||
|
||||
private void BeforeIK()
|
||||
{
|
||||
if (!enabled) return;
|
||||
float w = ik.solver.IKPositionWeight * weight;
|
||||
if (w <= 0f) return;
|
||||
|
||||
Vector3 firstBonePos = ik.solver.bones[0].transform.position;
|
||||
Vector3 lastBonePos = ik.solver.bones[ik.solver.bones.Length - 1].transform.position;
|
||||
|
||||
// Rotating the CCD chain towards this gameobject before it solves so it rolls in from that direction
|
||||
Quaternion f = Quaternion.FromToRotation(lastBonePos - firstBonePos, transform.position - firstBonePos);
|
||||
|
||||
if (w < 1f) f = Quaternion.Slerp(Quaternion.identity, f, w);
|
||||
|
||||
ik.solver.bones[0].transform.rotation = f * ik.solver.bones[0].transform.rotation;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (ik != null) ik.solver.OnPreUpdate -= BeforeIK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 285f2e66b1fe97b4797ef0cb776a5682
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,59 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Automatic stretch and squash for CCDIK.
|
||||
/// </summary>
|
||||
public class CCDStretch : MonoBehaviour
|
||||
{
|
||||
|
||||
public CCDIK ik;
|
||||
[Range(0f, 0.999f)] public float maxSquash = 0f;
|
||||
public float maxStretch = 2f;
|
||||
|
||||
private Vector3[] defaultLocalPositions = new Vector3[0];
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Store default localPositions
|
||||
defaultLocalPositions = new Vector3[ik.solver.bones.Length - 1];
|
||||
for (int i = 1; i < ik.solver.bones.Length; i++)
|
||||
{
|
||||
defaultLocalPositions[i - 1] = ik.solver.bones[i].transform.localPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// Reset to default localPositions
|
||||
for (int i = 1; i < ik.solver.bones.Length; i++)
|
||||
{
|
||||
ik.solver.bones[i].transform.localPosition = defaultLocalPositions[i - 1];
|
||||
}
|
||||
|
||||
// Get distance from first bone to target
|
||||
float targetDist = Vector3.Magnitude((ik.solver.target != null ? ik.solver.target.position : ik.solver.IKPosition) - ik.solver.bones[0].transform.position);
|
||||
|
||||
// Get bone chain length
|
||||
float chainLength = 0f;
|
||||
for (int i = 1; i < ik.solver.bones.Length; i++)
|
||||
{
|
||||
chainLength += Vector3.Magnitude(ik.solver.bones[i].transform.position - ik.solver.bones[i - 1].transform.position);
|
||||
}
|
||||
|
||||
// Compare target distance to chain length...
|
||||
maxStretch = Mathf.Max(maxStretch, 1f);
|
||||
float mlp = Mathf.Clamp(targetDist / chainLength, 1f - maxSquash, maxStretch);
|
||||
|
||||
// Stretch
|
||||
for (int i = 1; i < ik.solver.bones.Length; i++)
|
||||
{
|
||||
ik.solver.bones[i].transform.localPosition *= mlp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b2826fa4f637534ebedd76eb2cf6565
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
104
Unity-Master/Assets/Plugins/RootMotion/FinalIK/Tools/EditorIK.cs
Normal file
104
Unity-Master/Assets/Plugins/RootMotion/FinalIK/Tools/EditorIK.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates any Final IK component in Editor mode
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class EditorIK : MonoBehaviour
|
||||
{
|
||||
[Tooltip("If slot assigned, will evaluate PlayableDirector before solving the IK.")] public PlayableDirector playableDirector;
|
||||
[Tooltip("If slot assigned, will update Animator before solving the IK.")] public Animator animator;
|
||||
[Tooltip("Create/Final IK/Editor IK Pose")] public EditorIKPose defaultPose;
|
||||
|
||||
[HideInInspector] public Transform[] bones = new Transform[0];
|
||||
|
||||
public IK ik { get; private set; }
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
if (ik == null) ik = GetComponent<IK>();
|
||||
if (ik == null)
|
||||
{
|
||||
Debug.LogError("EditorIK needs to have an IK component on the same GameObject.", transform);
|
||||
return;
|
||||
}
|
||||
if (bones.Length == 0) bones = ik.transform.GetComponentsInChildren<Transform>();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
if (defaultPose != null && defaultPose.poseStored) defaultPose.Restore(bones);
|
||||
if (ik != null) ik.GetIKSolver().executedInEditor = false;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
if (ik == null) return;
|
||||
if (bones.Length == 0) bones = ik.transform.GetComponentsInChildren<Transform>();
|
||||
if (defaultPose != null && defaultPose.poseStored && bones.Length != 0) defaultPose.Restore(bones);
|
||||
ik.GetIKSolver().executedInEditor = false;
|
||||
}
|
||||
|
||||
public void StoreDefaultPose()
|
||||
{
|
||||
bones = ik.transform.GetComponentsInChildren<Transform>();
|
||||
defaultPose.Store(bones);
|
||||
}
|
||||
|
||||
public bool Initiate()
|
||||
{
|
||||
if (defaultPose == null) return false;
|
||||
if (!defaultPose.poseStored) return false;
|
||||
if (bones.Length == 0) return false;
|
||||
|
||||
if (ik == null) ik = GetComponent<IK>();
|
||||
if (ik == null)
|
||||
{
|
||||
Debug.LogError("EditorIK can not find an IK component.", transform);
|
||||
return false;
|
||||
}
|
||||
|
||||
defaultPose.Restore(bones);
|
||||
|
||||
ik.GetIKSolver().executedInEditor = false;
|
||||
ik.GetIKSolver().Initiate(ik.transform);
|
||||
ik.GetIKSolver().executedInEditor = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
if (ik == null) return;
|
||||
if (!ik.enabled) return;
|
||||
if (!ik.GetIKSolver().executedInEditor) return;
|
||||
if (bones.Length == 0) bones = ik.transform.GetComponentsInChildren<Transform>();
|
||||
if (bones.Length == 0) return;
|
||||
|
||||
if (!defaultPose.Restore(bones)) return;
|
||||
|
||||
ik.GetIKSolver().executedInEditor = false;
|
||||
if (!ik.GetIKSolver().initiated) ik.GetIKSolver().Initiate(ik.transform);
|
||||
if (!ik.GetIKSolver().initiated) return;
|
||||
ik.GetIKSolver().executedInEditor = true;
|
||||
|
||||
if (playableDirector != null)
|
||||
{
|
||||
playableDirector.Evaluate();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animator != null && animator.runtimeAnimatorController != null) animator.Update(Time.deltaTime);
|
||||
}
|
||||
|
||||
ik.GetIKSolver().Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c52e6f5c6ccaca0479ac0f3748e75992
|
||||
timeCreated: 1520429340
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,51 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Editor IK Pose", menuName = "Final IK/Editor IK Pose", order = 1)]
|
||||
public class EditorIKPose : ScriptableObject
|
||||
{
|
||||
|
||||
public Vector3[] localPositions = new Vector3[0];
|
||||
public Quaternion[] localRotations = new Quaternion[0];
|
||||
|
||||
public bool poseStored
|
||||
{
|
||||
get
|
||||
{
|
||||
return localPositions.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Store(Transform[] T)
|
||||
{
|
||||
localPositions = new Vector3[T.Length];
|
||||
localRotations = new Quaternion[T.Length];
|
||||
|
||||
for (int i = 1; i < T.Length; i++)
|
||||
{
|
||||
localPositions[i] = T[i].localPosition;
|
||||
localRotations[i] = T[i].localRotation;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Restore(Transform[] T)
|
||||
{
|
||||
if (localPositions.Length != T.Length)
|
||||
{
|
||||
Debug.LogError("Can not restore pose (unmatched bone count). Please stop the solver and click on 'Store Default Pose' if you have made changes to character hierarchy.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 1; i < T.Length; i++)
|
||||
{
|
||||
T[i].localPosition = localPositions[i];
|
||||
T[i].localRotation = localRotations[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8598cb8df1b85a4e8e2bd3c92a6ba62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,41 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using RootMotion.FinalIK;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
/// <summary>
|
||||
/// Bend goal object for FABRIK. Add this to a GameObject you wish FABRIK to bend towards.
|
||||
/// </summary>
|
||||
public class FABRIKBendGoal : MonoBehaviour
|
||||
{
|
||||
|
||||
public FABRIK ik;
|
||||
[Range(0f, 1f)] public float weight = 1f;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ik.solver.OnPreIteration += OnPreIteration;
|
||||
}
|
||||
|
||||
void OnPreIteration(int it)
|
||||
{
|
||||
if (it != 0) return;
|
||||
if (weight <= 0f) return;
|
||||
|
||||
Vector3 bendDirection = transform.position - ik.solver.bones[0].transform.position;
|
||||
bendDirection *= weight;
|
||||
|
||||
foreach (IKSolverFABRIK.Bone bone in ik.solver.bones)
|
||||
{
|
||||
bone.solverPosition += bendDirection;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (ik != null) ik.solver.OnPreIteration -= OnPreIteration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e00635d593e21414082963c80e4a4327
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,59 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Automatic stretch and squash for FABRIK.
|
||||
/// </summary>
|
||||
public class FABRIKStretch : MonoBehaviour
|
||||
{
|
||||
|
||||
public FABRIK ik;
|
||||
[Range(0f, 0.999f)] public float maxSquash = 0f;
|
||||
public float maxStretch = 2f;
|
||||
|
||||
private Vector3[] defaultLocalPositions = new Vector3[0];
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Store default localPositions
|
||||
defaultLocalPositions = new Vector3[ik.solver.bones.Length - 1];
|
||||
for (int i = 1; i < ik.solver.bones.Length; i++)
|
||||
{
|
||||
defaultLocalPositions[i - 1] = ik.solver.bones[i].transform.localPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// Reset to default localPositions
|
||||
for (int i = 1; i < ik.solver.bones.Length; i++)
|
||||
{
|
||||
ik.solver.bones[i].transform.localPosition = defaultLocalPositions[i - 1];
|
||||
}
|
||||
|
||||
// Get distance from first bone to target
|
||||
float targetDist = Vector3.Magnitude((ik.solver.target != null ? ik.solver.target.position : ik.solver.IKPosition) - ik.solver.bones[0].transform.position);
|
||||
|
||||
// Get bone chain length
|
||||
float chainLength = 0f;
|
||||
for (int i = 1; i < ik.solver.bones.Length; i++)
|
||||
{
|
||||
chainLength += Vector3.Magnitude(ik.solver.bones[i].transform.position - ik.solver.bones[i - 1].transform.position);
|
||||
}
|
||||
|
||||
// Compare target distance to chain length...
|
||||
maxStretch = Mathf.Max(maxStretch, 1f);
|
||||
float mlp = Mathf.Clamp(targetDist / chainLength, 1f - maxSquash, maxStretch);
|
||||
|
||||
// Stretch
|
||||
for (int i = 1; i < ik.solver.bones.Length; i++)
|
||||
{
|
||||
ik.solver.bones[i].transform.localPosition *= mlp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 977397d3381a5a2438c10b308b9d43ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,253 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Class for creating procedural FBBIK hit reactions.
|
||||
/// </summary>
|
||||
public class HitReaction : OffsetModifier {
|
||||
|
||||
/// <summary>
|
||||
/// Hit point definition
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract class HitPoint {
|
||||
|
||||
[Tooltip("Just for visual clarity, not used at all")]
|
||||
public string name;
|
||||
[Tooltip("Linking this hit point to a collider")]
|
||||
public Collider collider;
|
||||
[Tooltip("Only used if this hit point gets hit when already processing another hit")]
|
||||
[SerializeField] float crossFadeTime = 0.1f;
|
||||
|
||||
public bool inProgress { get { return timer < length; } }
|
||||
|
||||
protected float crossFader { get; private set; }
|
||||
protected float timer { get; private set; }
|
||||
protected Vector3 force { get; private set; }
|
||||
|
||||
private float length;
|
||||
private float crossFadeSpeed;
|
||||
private float lastTime;
|
||||
|
||||
// Start processing the hit
|
||||
public virtual void Hit(Vector3 force, Vector3 point) {
|
||||
if (length == 0f) length = GetLength();
|
||||
if (length <= 0f) {
|
||||
Debug.LogError("Hit Point WeightCurve length is zero.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start crossfading if the last hit has not completed yet
|
||||
if (timer < 1f) crossFader = 0f;
|
||||
crossFadeSpeed = crossFadeTime > 0f? 1f / crossFadeTime: 0f;
|
||||
CrossFadeStart();
|
||||
|
||||
// Reset timer
|
||||
timer = 0f;
|
||||
|
||||
// Remember hit direction and point
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
// Apply to IKSolverFullBodyBiped
|
||||
public void Apply(IKSolverFullBodyBiped solver, float weight) {
|
||||
float deltaTime = Time.time - lastTime;
|
||||
lastTime = Time.time;
|
||||
|
||||
if (timer >= length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance the timer
|
||||
timer = Mathf.Clamp(timer + deltaTime, 0f, length);
|
||||
|
||||
// Advance the crossFader
|
||||
if (crossFadeSpeed > 0f) crossFader = Mathf.Clamp(crossFader + (deltaTime * crossFadeSpeed), 0f, 1f);
|
||||
else crossFader = 1f;
|
||||
|
||||
// Pass this on to the hit points
|
||||
OnApply(solver, weight);
|
||||
}
|
||||
|
||||
protected abstract float GetLength();
|
||||
protected abstract void CrossFadeStart();
|
||||
protected abstract void OnApply(IKSolverFullBodyBiped solver, float weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hit Point for FBBIK effectors
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class HitPointEffector: HitPoint {
|
||||
|
||||
/// <summary>
|
||||
/// Linking a FBBIK effector to this effector hit point
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class EffectorLink {
|
||||
[Tooltip("The FBBIK effector type")]
|
||||
public FullBodyBipedEffector effector;
|
||||
[Tooltip("The weight of this effector (could also be negative)")]
|
||||
public float weight;
|
||||
|
||||
private Vector3 lastValue;
|
||||
private Vector3 current;
|
||||
|
||||
// Apply an offset to this effector
|
||||
public void Apply(IKSolverFullBodyBiped solver, Vector3 offset, float crossFader) {
|
||||
current = Vector3.Lerp(lastValue, offset * weight, crossFader);
|
||||
|
||||
solver.GetEffector(effector).positionOffset += current;
|
||||
}
|
||||
|
||||
// Remember the current offset value, so we can smoothly crossfade from it
|
||||
public void CrossFadeStart() {
|
||||
lastValue = current;
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("Offset magnitude in the direction of the hit force")]
|
||||
public AnimationCurve offsetInForceDirection; //
|
||||
[Tooltip("Offset magnitude in the direction of character.up")]
|
||||
public AnimationCurve offsetInUpDirection; //
|
||||
[Tooltip("Linking this offset to the FBBIK effectors")]
|
||||
public EffectorLink[] effectorLinks;
|
||||
|
||||
// Returns the length of this hit (last key in the AnimationCurves)
|
||||
protected override float GetLength() {
|
||||
float time1 = offsetInForceDirection.keys.Length > 0? offsetInForceDirection.keys[offsetInForceDirection.length - 1].time: 0f;
|
||||
float time2 = offsetInUpDirection.keys.Length > 0? offsetInUpDirection.keys[offsetInUpDirection.length - 1].time: 0f;
|
||||
return Mathf.Clamp(time1, time2, time1);
|
||||
}
|
||||
|
||||
// Remember the current offset values for each effector, so we can smoothly crossfade from it
|
||||
protected override void CrossFadeStart() {
|
||||
foreach (EffectorLink e in effectorLinks) e.CrossFadeStart();
|
||||
}
|
||||
|
||||
// Calculate offset, apply to FBBIK effectors
|
||||
protected override void OnApply(IKSolverFullBodyBiped solver, float weight) {
|
||||
Vector3 up = solver.GetRoot().up * force.magnitude;
|
||||
|
||||
Vector3 offset = (offsetInForceDirection.Evaluate(timer) * force) + (offsetInUpDirection.Evaluate(timer) * up);
|
||||
offset *= weight;
|
||||
|
||||
foreach (EffectorLink e in effectorLinks) e.Apply(solver, offset, crossFader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hit Point for simple bone Transforms that don't have a FBBIK effector
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class HitPointBone: HitPoint {
|
||||
|
||||
/// <summary>
|
||||
/// Linking a bone Transform to this bone hit point
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class BoneLink {
|
||||
[Tooltip("Reference to the bone that this hit point rotates")]
|
||||
public Transform bone;
|
||||
[Tooltip("Weight of rotating the bone")]
|
||||
[Range(0f, 1f)] public float weight;
|
||||
|
||||
private Quaternion lastValue = Quaternion.identity;
|
||||
private Quaternion current = Quaternion.identity;
|
||||
|
||||
// Apply a rotational offset to this effector
|
||||
public void Apply(IKSolverFullBodyBiped solver, Quaternion offset, float crossFader) {
|
||||
current = Quaternion.Lerp(lastValue, Quaternion.Lerp(Quaternion.identity, offset, weight), crossFader);
|
||||
|
||||
bone.rotation = current * bone.rotation;
|
||||
}
|
||||
|
||||
// Remember the current offset value, so we can smoothly crossfade from it
|
||||
public void CrossFadeStart() {
|
||||
lastValue = current;
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("The angle to rotate the bone around its rigidbody's world center of mass")]
|
||||
public AnimationCurve aroundCenterOfMass;
|
||||
[Tooltip("Linking this hit point to bone(s)")]
|
||||
public BoneLink[] boneLinks;
|
||||
|
||||
private Rigidbody rigidbody;
|
||||
private Vector3 comAxis;
|
||||
|
||||
public override void Hit(Vector3 force, Vector3 point)
|
||||
{
|
||||
base.Hit(force, point);
|
||||
|
||||
if (rigidbody == null) rigidbody = collider.GetComponent<Rigidbody>();
|
||||
|
||||
Vector3 com = rigidbody != null ? rigidbody.worldCenterOfMass : collider.transform.position;
|
||||
comAxis = Vector3.Cross(force, point - com);
|
||||
}
|
||||
|
||||
// Returns the length of this hit (last key in the AnimationCurves)
|
||||
protected override float GetLength() {
|
||||
return aroundCenterOfMass.keys.Length > 0? aroundCenterOfMass.keys[aroundCenterOfMass.length - 1].time: 0f;
|
||||
}
|
||||
|
||||
// Remember the current offset values for each bone, so we can smoothly crossfade from it
|
||||
protected override void CrossFadeStart() {
|
||||
foreach (BoneLink b in boneLinks) b.CrossFadeStart();
|
||||
}
|
||||
|
||||
// Calculate offset, apply to the bones
|
||||
protected override void OnApply(IKSolverFullBodyBiped solver, float weight) {
|
||||
float comValue = aroundCenterOfMass.Evaluate(timer) * weight;
|
||||
Quaternion offset = Quaternion.AngleAxis(comValue, comAxis);
|
||||
|
||||
foreach (BoneLink b in boneLinks) b.Apply(solver, offset, crossFader);
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("Hit points for the FBBIK effectors")]
|
||||
public HitPointEffector[] effectorHitPoints;
|
||||
[Tooltip(" Hit points for bones without an effector, such as the head")]
|
||||
public HitPointBone[] boneHitPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any of the hits are being processed.
|
||||
/// </summary>
|
||||
public bool inProgress {
|
||||
get {
|
||||
foreach (HitPointEffector h in effectorHitPoints) {
|
||||
if (h.inProgress) return true;
|
||||
}
|
||||
foreach (HitPointBone h in boneHitPoints) {
|
||||
if (h.inProgress) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Called by IKSolverFullBody before updating
|
||||
protected override void OnModifyOffset() {
|
||||
foreach (HitPointEffector e in effectorHitPoints) e.Apply(ik.solver, weight);
|
||||
foreach (HitPointBone b in boneHitPoints) b.Apply(ik.solver, weight);
|
||||
}
|
||||
|
||||
// Hit one of the hit points (defined by hit.collider)
|
||||
public void Hit(Collider collider, Vector3 force, Vector3 point) {
|
||||
if (ik == null) {
|
||||
Debug.LogError("No IK assigned in HitReaction");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (HitPointEffector e in effectorHitPoints) {
|
||||
if (e.collider == collider) e.Hit(force, point);
|
||||
}
|
||||
|
||||
foreach (HitPointBone b in boneHitPoints) {
|
||||
if (b.collider == collider) b.Hit(force, point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6acf54e73e0704cd79c7669317e0cd14
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,247 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Class for creating procedural FBBIK hit reactions.
|
||||
/// </summary>
|
||||
public class HitReactionVRIK : OffsetModifierVRIK {
|
||||
|
||||
public AnimationCurve[] offsetCurves;
|
||||
|
||||
/// <summary>
|
||||
/// Hit point definition
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract class Offset {
|
||||
|
||||
[Tooltip("Just for visual clarity, not used at all")]
|
||||
public string name;
|
||||
[Tooltip("Linking this hit point to a collider")]
|
||||
public Collider collider;
|
||||
[Tooltip("Only used if this hit point gets hit when already processing another hit")]
|
||||
[SerializeField] float crossFadeTime = 0.1f;
|
||||
|
||||
protected float crossFader { get; private set; }
|
||||
protected float timer { get; private set; }
|
||||
protected Vector3 force { get; private set; }
|
||||
|
||||
private float length;
|
||||
private float crossFadeSpeed;
|
||||
private float lastTime;
|
||||
|
||||
// Start processing the hit
|
||||
public virtual void Hit(Vector3 force, AnimationCurve[] curves, Vector3 point) {
|
||||
if (length == 0f) length = GetLength(curves);
|
||||
if (length <= 0f) {
|
||||
Debug.LogError("Hit Point WeightCurve length is zero.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start crossfading if the last hit has not completed yet
|
||||
if (timer < 1f) crossFader = 0f;
|
||||
crossFadeSpeed = crossFadeTime > 0f? 1f / crossFadeTime: 0f;
|
||||
CrossFadeStart();
|
||||
|
||||
// Reset timer
|
||||
timer = 0f;
|
||||
|
||||
// Remember hit direction and point
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
// Apply to IKSolverFullBodyBiped
|
||||
public void Apply(VRIK ik, AnimationCurve[] curves, float weight) {
|
||||
float deltaTime = Time.time - lastTime;
|
||||
lastTime = Time.time;
|
||||
|
||||
if (timer >= length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance the timer
|
||||
timer = Mathf.Clamp(timer + deltaTime, 0f, length);
|
||||
|
||||
// Advance the crossFader
|
||||
if (crossFadeSpeed > 0f) crossFader = Mathf.Clamp(crossFader + (deltaTime * crossFadeSpeed), 0f, 1f);
|
||||
else crossFader = 1f;
|
||||
|
||||
// Pass this on to the hit points
|
||||
OnApply(ik, curves, weight);
|
||||
}
|
||||
|
||||
protected abstract float GetLength(AnimationCurve[] curves);
|
||||
protected abstract void CrossFadeStart();
|
||||
protected abstract void OnApply(VRIK ik, AnimationCurve[] curves, float weight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hit Point for FBBIK effectors
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class PositionOffset: Offset {
|
||||
|
||||
/// <summary>
|
||||
/// Linking a FBBIK effector to this effector hit point
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class PositionOffsetLink {
|
||||
[Tooltip("The FBBIK effector type")]
|
||||
public IKSolverVR.PositionOffset positionOffset;
|
||||
[Tooltip("The weight of this effector (could also be negative)")]
|
||||
public float weight;
|
||||
|
||||
private Vector3 lastValue;
|
||||
private Vector3 current;
|
||||
|
||||
// Apply an offset to this effector
|
||||
public void Apply(VRIK ik, Vector3 offset, float crossFader) {
|
||||
current = Vector3.Lerp(lastValue, offset * weight, crossFader);
|
||||
|
||||
ik.solver.AddPositionOffset(positionOffset, current);
|
||||
}
|
||||
|
||||
// Remember the current offset value, so we can smoothly crossfade from it
|
||||
public void CrossFadeStart() {
|
||||
lastValue = current;
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("Offset magnitude in the direction of the hit force")]
|
||||
public int forceDirCurveIndex;
|
||||
[Tooltip("Offset magnitude in the direction of character.up")]
|
||||
public int upDirCurveIndex = 1;
|
||||
[Tooltip("Linking this offset to the VRIK position offsets")]
|
||||
public PositionOffsetLink[] offsetLinks;
|
||||
|
||||
// Returns the length of this hit (last key in the AnimationCurves)
|
||||
protected override float GetLength(AnimationCurve[] curves) {
|
||||
float time1 = curves[forceDirCurveIndex].keys.Length > 0? curves[forceDirCurveIndex].keys[curves[forceDirCurveIndex].length - 1].time: 0f;
|
||||
float time2 = curves[upDirCurveIndex].keys.Length > 0? curves[upDirCurveIndex].keys[curves[upDirCurveIndex].length - 1].time: 0f;
|
||||
return Mathf.Clamp(time1, time2, time1);
|
||||
}
|
||||
|
||||
// Remember the current offset values for each effector, so we can smoothly crossfade from it
|
||||
protected override void CrossFadeStart() {
|
||||
foreach (PositionOffsetLink l in offsetLinks) l.CrossFadeStart();
|
||||
}
|
||||
|
||||
// Calculate offset, apply to FBBIK effectors
|
||||
protected override void OnApply(VRIK ik, AnimationCurve[] curves, float weight) {
|
||||
Vector3 up = ik.transform.up * force.magnitude;
|
||||
|
||||
Vector3 offset = (curves[forceDirCurveIndex].Evaluate(timer) * force) + (curves[upDirCurveIndex].Evaluate(timer) * up);
|
||||
offset *= weight;
|
||||
|
||||
foreach (PositionOffsetLink l in offsetLinks) l.Apply(ik, offset, crossFader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hit Point for simple bone Transforms that don't have a FBBIK effector
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class RotationOffset: Offset {
|
||||
|
||||
/// <summary>
|
||||
/// Linking a bone Transform to this bone hit point
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class RotationOffsetLink {
|
||||
[Tooltip("Reference to the bone that this hit point rotates")]
|
||||
public IKSolverVR.RotationOffset rotationOffset;
|
||||
[Tooltip("Weight of rotating the bone")]
|
||||
[Range(0f, 1f)] public float weight;
|
||||
|
||||
private Quaternion lastValue = Quaternion.identity;
|
||||
private Quaternion current = Quaternion.identity;
|
||||
|
||||
// Apply a rotational offset to this effector
|
||||
public void Apply(VRIK ik, Quaternion offset, float crossFader) {
|
||||
current = Quaternion.Lerp(lastValue, Quaternion.Lerp(Quaternion.identity, offset, weight), crossFader);
|
||||
|
||||
ik.solver.AddRotationOffset(rotationOffset, current);
|
||||
}
|
||||
|
||||
// Remember the current offset value, so we can smoothly crossfade from it
|
||||
public void CrossFadeStart() {
|
||||
lastValue = current;
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("The angle to rotate the bone around its rigidbody's world center of mass")]
|
||||
public int curveIndex;
|
||||
[Tooltip("Linking this hit point to bone(s)")]
|
||||
public RotationOffsetLink[] offsetLinks;
|
||||
|
||||
private Rigidbody rigidbody;
|
||||
private Vector3 comAxis;
|
||||
|
||||
public override void Hit(Vector3 force, AnimationCurve[] curves, Vector3 point)
|
||||
{
|
||||
base.Hit(force, curves, point);
|
||||
|
||||
if (rigidbody == null) rigidbody = collider.GetComponent<Rigidbody>();
|
||||
|
||||
Vector3 com = rigidbody != null ? rigidbody.worldCenterOfMass : collider.transform.position;
|
||||
comAxis = Vector3.Cross(force, point - com);
|
||||
}
|
||||
|
||||
// Returns the length of this hit (last key in the AnimationCurves)
|
||||
protected override float GetLength(AnimationCurve[] curves) {
|
||||
return curves[curveIndex].keys.Length > 0? curves[curveIndex].keys[ curves[curveIndex].length - 1].time: 0f;
|
||||
}
|
||||
|
||||
// Remember the current offset values for each bone, so we can smoothly crossfade from it
|
||||
protected override void CrossFadeStart() {
|
||||
foreach (RotationOffsetLink l in offsetLinks) l.CrossFadeStart();
|
||||
}
|
||||
|
||||
// Calculate offset, apply to the bones
|
||||
protected override void OnApply(VRIK ik, AnimationCurve[] curves, float weight) {
|
||||
if (collider == null) {
|
||||
Debug.LogError ("No collider assigned for a HitPointBone in the HitReaction component.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (rigidbody == null) rigidbody = collider.GetComponent<Rigidbody>();
|
||||
if (rigidbody != null) {
|
||||
float comValue = curves[curveIndex].Evaluate(timer) * weight;
|
||||
Quaternion offset = Quaternion.AngleAxis(comValue, comAxis);
|
||||
|
||||
foreach (RotationOffsetLink l in offsetLinks) l.Apply(ik, offset, crossFader);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Tooltip("Hit points for the FBBIK effectors")]
|
||||
public PositionOffset[] positionOffsets;
|
||||
[Tooltip(" Hit points for bones without an effector, such as the head")]
|
||||
public RotationOffset[] rotationOffsets;
|
||||
|
||||
// Called by IKSolverFullBody before updating
|
||||
protected override void OnModifyOffset() {
|
||||
foreach (PositionOffset p in positionOffsets) p.Apply(ik, offsetCurves, weight);
|
||||
foreach (RotationOffset r in rotationOffsets) r.Apply(ik, offsetCurves, weight);
|
||||
}
|
||||
|
||||
// Hit one of the hit points (defined by hit.collider)
|
||||
public void Hit(Collider collider, Vector3 force, Vector3 point) {
|
||||
if (ik == null) {
|
||||
Debug.LogError("No IK assigned in HitReaction");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (PositionOffset p in positionOffsets) {
|
||||
if (p.collider == collider) p.Hit(force, offsetCurves, point);
|
||||
}
|
||||
|
||||
foreach (RotationOffset r in rotationOffsets) {
|
||||
if (r.collider == collider) r.Hit(force, offsetCurves, point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01147feb37a92684a9c362eb43225c67
|
||||
timeCreated: 1460978425
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
108
Unity-Master/Assets/Plugins/RootMotion/FinalIK/Tools/Inertia.cs
Normal file
108
Unity-Master/Assets/Plugins/RootMotion/FinalIK/Tools/Inertia.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Demo script that adds the illusion of mass to your character using FullBodyBipedIK.
|
||||
/// </summary>
|
||||
public class Inertia : OffsetModifier {
|
||||
|
||||
/// <summary>
|
||||
/// Body is just following its transform in a lazy and bouncy way.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Body {
|
||||
|
||||
/// <summary>
|
||||
/// Linking this to an effector
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class EffectorLink {
|
||||
[Tooltip("Type of the FBBIK effector to use")]
|
||||
public FullBodyBipedEffector effector;
|
||||
[Tooltip("Weight of using this effector")]
|
||||
public float weight;
|
||||
}
|
||||
|
||||
[Tooltip("The Transform to follow, can be any bone of the character")]
|
||||
public Transform transform;
|
||||
[Tooltip("Linking the body to effectors. One Body can be used to offset more than one effector")]
|
||||
public EffectorLink[] effectorLinks;
|
||||
[Tooltip("The speed to follow the Transform")]
|
||||
public float speed = 10f;
|
||||
[Tooltip("The acceleration, smaller values means lazyer following")]
|
||||
public float acceleration = 3f;
|
||||
[Tooltip("Matching target velocity")]
|
||||
[Range(0f, 1f)] public float matchVelocity;
|
||||
[Tooltip("gravity applied to the Body")]
|
||||
public float gravity;
|
||||
|
||||
private Vector3 delta;
|
||||
private Vector3 lazyPoint;
|
||||
private Vector3 direction;
|
||||
private Vector3 lastPosition;
|
||||
private bool firstUpdate = true;
|
||||
|
||||
// Reset to Transform
|
||||
public void Reset() {
|
||||
if (transform == null) return;
|
||||
lazyPoint = transform.position;
|
||||
lastPosition = transform.position;
|
||||
direction = Vector3.zero;
|
||||
}
|
||||
|
||||
// Update this body, apply the offset to the effector
|
||||
public void Update(IKSolverFullBodyBiped solver, float weight, float deltaTime) {
|
||||
if (transform == null) return;
|
||||
|
||||
// If first update, set this body to Transform
|
||||
if (firstUpdate) {
|
||||
Reset();
|
||||
firstUpdate = false;
|
||||
}
|
||||
|
||||
// Acceleration
|
||||
direction = Vector3.Lerp(direction, ((transform.position - lazyPoint) / deltaTime) * 0.01f, deltaTime * acceleration);
|
||||
|
||||
// Lazy follow
|
||||
lazyPoint += direction * deltaTime * speed;
|
||||
|
||||
// Match velocity
|
||||
delta = transform.position - lastPosition;
|
||||
lazyPoint += delta * matchVelocity;
|
||||
|
||||
// Gravity
|
||||
lazyPoint.y += gravity * deltaTime;
|
||||
|
||||
// Apply position offset to the effector
|
||||
foreach (EffectorLink effectorLink in effectorLinks) {
|
||||
|
||||
solver.GetEffector(effectorLink.effector).positionOffset += (lazyPoint - transform.position) * effectorLink.weight * weight;
|
||||
}
|
||||
|
||||
lastPosition = transform.position;
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("The array of Bodies")]
|
||||
public Body[] bodies;
|
||||
[Tooltip("The array of OffsetLimits")]
|
||||
public OffsetLimits[] limits;
|
||||
|
||||
// Reset all Bodies
|
||||
public void ResetBodies() {
|
||||
lastTime = Time.time;
|
||||
foreach (Body body in bodies) body.Reset();
|
||||
}
|
||||
|
||||
// Called by IKSolverFullBody before updating
|
||||
protected override void OnModifyOffset() {
|
||||
// Update the Bodies
|
||||
foreach (Body body in bodies) body.Update(ik.solver, weight, deltaTime);
|
||||
|
||||
// Apply the offset limits
|
||||
ApplyLimits(limits);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22a01f59b6ac94f1b95f51f60eff7eeb
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,151 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
public class LookAtController : MonoBehaviour {
|
||||
|
||||
public LookAtIK ik;
|
||||
|
||||
[Header("Target Smoothing")]
|
||||
|
||||
[Tooltip("The target to look at. Do not use the Target transform that is assigned to LookAtIK. Set to null if you wish to stop looking.")]
|
||||
public Transform target;
|
||||
|
||||
[Range(0f, 1f)] public float weight = 1f;
|
||||
|
||||
public Vector3 offset;
|
||||
|
||||
[Tooltip("The time it takes to switch targets.")]
|
||||
public float targetSwitchSmoothTime = 0.3f;
|
||||
|
||||
[Tooltip("The time it takes to blend in/out of LookAtIK weight.")]
|
||||
public float weightSmoothTime = 0.3f;
|
||||
|
||||
[Header("Turning Towards The Target")]
|
||||
|
||||
[Tooltip("Enables smooth turning towards the target according to the parameters under this header.")]
|
||||
public bool smoothTurnTowardsTarget = true;
|
||||
|
||||
[Tooltip("Speed of turning towards the target using Vector3.RotateTowards.")]
|
||||
public float maxRadiansDelta = 3f;
|
||||
|
||||
[Tooltip("Speed of moving towards the target using Vector3.RotateTowards.")]
|
||||
public float maxMagnitudeDelta = 3f;
|
||||
|
||||
[Tooltip("Speed of slerping towards the target.")]
|
||||
public float slerpSpeed = 3f;
|
||||
|
||||
[Tooltip("The position of the pivot that the look at target is rotated around relative to the root of the character.")]
|
||||
public Vector3 pivotOffsetFromRoot = Vector3.up;
|
||||
|
||||
[Tooltip("Minimum distance of looking from the first bone. Keeps the solver from failing if the target is too close.")]
|
||||
public float minDistance = 1f;
|
||||
|
||||
[Header("RootRotation")]
|
||||
[Tooltip("Character root will be rotate around the Y axis to keep root forward within this angle from the look direction.")]
|
||||
[Range(0f, 180f)]
|
||||
public float maxRootAngle = 45f;
|
||||
|
||||
private Transform lastTarget;
|
||||
private float switchWeight, switchWeightV;
|
||||
private float weightV;
|
||||
private Vector3 lastPosition;
|
||||
private Vector3 dir;
|
||||
private bool lastSmoothTowardsTarget;
|
||||
|
||||
void Start() {
|
||||
lastPosition = ik.solver.IKPosition;
|
||||
dir = ik.solver.IKPosition - pivot;
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
// If target has changed...
|
||||
if (target != lastTarget) {
|
||||
if (lastTarget == null && target != null && ik.solver.IKPositionWeight <= 0f) {
|
||||
lastPosition = target.position;
|
||||
dir = target.position - pivot;
|
||||
ik.solver.IKPosition = target.position + offset;
|
||||
} else {
|
||||
lastPosition = ik.solver.IKPosition;
|
||||
dir = ik.solver.IKPosition - pivot;
|
||||
}
|
||||
|
||||
switchWeight = 0f;
|
||||
lastTarget = target;
|
||||
}
|
||||
|
||||
// Smooth weight
|
||||
float targetWeight = target != null ? weight : 0f;
|
||||
ik.solver.IKPositionWeight = Mathf.SmoothDamp(ik.solver.IKPositionWeight, targetWeight, ref weightV, weightSmoothTime);
|
||||
if (ik.solver.IKPositionWeight >= 0.999f && targetWeight > ik.solver.IKPositionWeight) ik.solver.IKPositionWeight = 1f;
|
||||
if (ik.solver.IKPositionWeight <= 0.001f && targetWeight < ik.solver.IKPositionWeight) ik.solver.IKPositionWeight = 0f;
|
||||
|
||||
if (ik.solver.IKPositionWeight <= 0f) return;
|
||||
|
||||
// Smooth target switching
|
||||
switchWeight = Mathf.SmoothDamp(switchWeight, 1f, ref switchWeightV, targetSwitchSmoothTime);
|
||||
if (switchWeight >= 0.999f) switchWeight = 1f;
|
||||
|
||||
if (target != null) {
|
||||
ik.solver.IKPosition = Vector3.Lerp(lastPosition, target.position + offset, switchWeight);
|
||||
}
|
||||
|
||||
// Smooth turn towards target
|
||||
if (smoothTurnTowardsTarget != lastSmoothTowardsTarget) {
|
||||
dir = ik.solver.IKPosition - pivot;
|
||||
lastSmoothTowardsTarget = smoothTurnTowardsTarget;
|
||||
}
|
||||
|
||||
if (smoothTurnTowardsTarget) {
|
||||
Vector3 targetDir = ik.solver.IKPosition - pivot;
|
||||
dir = Vector3.Slerp(dir, targetDir, Time.deltaTime * slerpSpeed);
|
||||
dir = Vector3.RotateTowards(dir, targetDir, Time.deltaTime * maxRadiansDelta, maxMagnitudeDelta);
|
||||
ik.solver.IKPosition = pivot + dir;
|
||||
}
|
||||
|
||||
// Min distance from the pivot
|
||||
ApplyMinDistance();
|
||||
|
||||
// Root rotation
|
||||
RootRotation();
|
||||
}
|
||||
|
||||
// Pivot of rotating the aiming direction.
|
||||
private Vector3 pivot {
|
||||
get {
|
||||
return ik.transform.position + ik.transform.rotation * pivotOffsetFromRoot;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure aiming target is not too close (might make the solver instable when the target is closer to the first bone than the last bone is).
|
||||
void ApplyMinDistance() {
|
||||
Vector3 aimFrom = pivot;
|
||||
Vector3 direction = (ik.solver.IKPosition - aimFrom);
|
||||
direction = direction.normalized * Mathf.Max(direction.magnitude, minDistance);
|
||||
|
||||
ik.solver.IKPosition = aimFrom + direction;
|
||||
}
|
||||
|
||||
// Character root will be rotate around the Y axis to keep root forward within this angle from the looking direction.
|
||||
private void RootRotation() {
|
||||
float max = Mathf.Lerp(180f, maxRootAngle, ik.solver.IKPositionWeight);
|
||||
|
||||
if (max < 180f) {
|
||||
Vector3 faceDirLocal = Quaternion.Inverse(ik.transform.rotation) * (ik.solver.IKPosition - pivot);
|
||||
float angle = Mathf.Atan2(faceDirLocal.x, faceDirLocal.z) * Mathf.Rad2Deg;
|
||||
|
||||
float rotation = 0f;
|
||||
|
||||
if (angle > max) {
|
||||
rotation = angle - max;
|
||||
}
|
||||
if (angle < -max) {
|
||||
rotation = angle + max;
|
||||
}
|
||||
|
||||
ik.transform.rotation = Quaternion.AngleAxis(rotation, ik.transform.up) * ik.transform.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8362d7c66e966dd498d59b31392d5148
|
||||
timeCreated: 1517046310
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,113 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all FBBIK effector positionOffset modifiers. Works with animatePhysics, safe delegates, offset limits.
|
||||
/// </summary>
|
||||
public abstract class OffsetModifier: MonoBehaviour {
|
||||
|
||||
/// <summary>
|
||||
/// Limiting effector position offsets
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class OffsetLimits {
|
||||
|
||||
[Tooltip("The effector type (this is just an enum)")]
|
||||
public FullBodyBipedEffector effector;
|
||||
[Tooltip("Spring force, if zero then this is a hard limit, if not, offset can exceed the limit.")]
|
||||
public float spring = 0f;
|
||||
[Tooltip("Which axes to limit the offset on?")]
|
||||
public bool x, y, z;
|
||||
[Tooltip("The limits")]
|
||||
public float minX, maxX, minY, maxY, minZ, maxZ;
|
||||
|
||||
// Apply the limit to the effector
|
||||
public void Apply(IKEffector e, Quaternion rootRotation) {
|
||||
Vector3 offset = Quaternion.Inverse(rootRotation) * e.positionOffset;
|
||||
|
||||
if (spring <= 0f) {
|
||||
// Hard limits
|
||||
if (x) offset.x = Mathf.Clamp(offset.x, minX, maxX);
|
||||
if (y) offset.y = Mathf.Clamp(offset.y, minY, maxY);
|
||||
if (z) offset.z = Mathf.Clamp(offset.z, minZ, maxZ);
|
||||
} else {
|
||||
// Soft limits
|
||||
if (x) offset.x = SpringAxis(offset.x, minX, maxX);
|
||||
if (y) offset.y = SpringAxis(offset.y, minY, maxY);
|
||||
if (z) offset.z = SpringAxis(offset.z, minZ, maxZ);
|
||||
}
|
||||
|
||||
// Apply to the effector
|
||||
e.positionOffset = rootRotation * offset;
|
||||
}
|
||||
|
||||
// Just math for limiting floats
|
||||
private float SpringAxis(float value, float min, float max) {
|
||||
if (value > min && value < max) return value;
|
||||
if (value < min) return Spring(value, min, true);
|
||||
return Spring(value, max, false);
|
||||
}
|
||||
|
||||
// Spring math
|
||||
private float Spring(float value, float limit, bool negative) {
|
||||
float illegal = value - limit;
|
||||
float s = illegal * spring;
|
||||
|
||||
if (negative) return value + Mathf.Clamp(-s, 0, -illegal);
|
||||
return value - Mathf.Clamp(s, 0, illegal);
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("The master weight")]
|
||||
public float weight = 1f;
|
||||
|
||||
[Tooltip("Reference to the FBBIK component")]
|
||||
public FullBodyBipedIK ik;
|
||||
|
||||
// not using Time.deltaTime or Time.fixedDeltaTime here, because we don't know if animatePhysics is true or not on the character, so we have to keep track of time ourselves.
|
||||
protected float deltaTime { get { return Time.time - lastTime; }}
|
||||
protected abstract void OnModifyOffset();
|
||||
|
||||
protected float lastTime;
|
||||
|
||||
protected virtual void Start() {
|
||||
StartCoroutine(Initiate());
|
||||
}
|
||||
|
||||
private IEnumerator Initiate() {
|
||||
while (ik == null) yield return null;
|
||||
|
||||
// You can use just LateUpdate, but note that it doesn't work when you have animatePhysics turned on for the character.
|
||||
ik.solver.OnPreUpdate += ModifyOffset;
|
||||
lastTime = Time.time;
|
||||
}
|
||||
|
||||
// The main function that checks for all conditions and calls OnModifyOffset if they are met
|
||||
private void ModifyOffset() {
|
||||
if (!enabled) return;
|
||||
if (weight <= 0f) return;
|
||||
if (ik == null) return;
|
||||
weight = Mathf.Clamp(weight, 0f, 1f);
|
||||
if (deltaTime <= 0f) return;
|
||||
|
||||
OnModifyOffset();
|
||||
|
||||
lastTime = Time.time;
|
||||
}
|
||||
|
||||
protected void ApplyLimits(OffsetLimits[] limits) {
|
||||
// Apply the OffsetLimits
|
||||
foreach (OffsetLimits limit in limits) {
|
||||
limit.Apply(ik.solver.GetEffector(limit.effector), transform.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the delegate when destroyed
|
||||
protected virtual void OnDestroy() {
|
||||
if (ik != null) ik.solver.OnPreUpdate -= ModifyOffset;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7953027cc122548b0861e027c35af21c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all FBBIK effector positionOffset modifiers. Works with animatePhysics, safe delegates, offset limits.
|
||||
/// </summary>
|
||||
public abstract class OffsetModifierVRIK: MonoBehaviour {
|
||||
|
||||
[Tooltip("The master weight")]
|
||||
public float weight = 1f;
|
||||
|
||||
[Tooltip("Reference to the VRIK component")]
|
||||
public VRIK ik;
|
||||
|
||||
// not using Time.deltaTime or Time.fixedDeltaTime here, because we don't know if animatePhysics is true or not on the character, so we have to keep track of time ourselves.
|
||||
protected float deltaTime { get { return Time.time - lastTime; }}
|
||||
protected abstract void OnModifyOffset();
|
||||
|
||||
private float lastTime;
|
||||
|
||||
protected virtual void Start() {
|
||||
StartCoroutine(Initiate());
|
||||
}
|
||||
|
||||
private IEnumerator Initiate() {
|
||||
while (ik == null) yield return null;
|
||||
|
||||
// You can use just LateUpdate, but note that it doesn't work when you have animatePhysics turned on for the character.
|
||||
ik.solver.OnPreUpdate += ModifyOffset;
|
||||
lastTime = Time.time;
|
||||
}
|
||||
|
||||
// The main function that checks for all conditions and calls OnModifyOffset if they are met
|
||||
private void ModifyOffset() {
|
||||
if (!enabled) return;
|
||||
if (weight <= 0f) return;
|
||||
if (deltaTime <= 0f) return;
|
||||
if (ik == null) return;
|
||||
weight = Mathf.Clamp(weight, 0f, 1f);
|
||||
|
||||
OnModifyOffset();
|
||||
|
||||
lastTime = Time.time;
|
||||
}
|
||||
|
||||
// Remove the delegate when destroyed
|
||||
protected virtual void OnDestroy() {
|
||||
if (ik != null) ik.solver.OnPreUpdate -= ModifyOffset;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e5a797878a3ba546bb923162b51a8e6
|
||||
timeCreated: 1460978461
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Definition of FBBIK Offset pose.
|
||||
/// </summary>
|
||||
public class OffsetPose: MonoBehaviour {
|
||||
|
||||
/// <summary>
|
||||
/// State of an effector in this pose
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class EffectorLink {
|
||||
|
||||
[Tooltip("The effector type (this is just an enum)")] public FullBodyBipedEffector effector;
|
||||
[Tooltip("Offset of the effector in this pose")] public Vector3 offset;
|
||||
[Tooltip("Pin position relative to the solver root Transform")] public Vector3 pin;
|
||||
[Tooltip("Pin weight vector")] public Vector3 pinWeight;
|
||||
[Tooltip("Only applies for end effectors (hands, feet)")] public Vector3 rotationOffset;
|
||||
|
||||
// Apply positionOffset to the effector
|
||||
public void Apply(IKSolverFullBodyBiped solver, float weight, Quaternion rotation) {
|
||||
var e = solver.GetEffector(effector);
|
||||
|
||||
// Offset
|
||||
e.positionOffset += rotation * offset * weight;
|
||||
|
||||
// Calculating pinned position
|
||||
Vector3 pinPosition = solver.GetRoot().position + rotation * pin;
|
||||
Vector3 pinPositionOffset = pinPosition - e.bone.position;
|
||||
|
||||
Vector3 pinWeightVector = pinWeight * Mathf.Abs(weight);
|
||||
|
||||
// Lerping to pinned position
|
||||
e.positionOffset = new Vector3(
|
||||
Mathf.Lerp(e.positionOffset.x, pinPositionOffset.x, pinWeightVector.x),
|
||||
Mathf.Lerp(e.positionOffset.y, pinPositionOffset.y, pinWeightVector.y),
|
||||
Mathf.Lerp(e.positionOffset.z, pinPositionOffset.z, pinWeightVector.z)
|
||||
);
|
||||
|
||||
if (e.isEndEffector) e.bone.localRotation *= Quaternion.Euler(rotationOffset * weight);
|
||||
}
|
||||
}
|
||||
|
||||
public EffectorLink[] effectorLinks = new EffectorLink[0];
|
||||
|
||||
// Apply positionOffsets of all the EffectorLinks
|
||||
public void Apply(IKSolverFullBodyBiped solver, float weight) {
|
||||
for (int i = 0; i < effectorLinks.Length; i++) effectorLinks[i].Apply(solver, weight, solver.GetRoot().rotation);
|
||||
}
|
||||
|
||||
public void Apply(IKSolverFullBodyBiped solver, float weight, Quaternion rotation) {
|
||||
for (int i = 0; i < effectorLinks.Length; i++) effectorLinks[i].Apply(solver, weight, rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5405d3fc16a5b4a0bada53e2da651d3a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,89 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using RootMotion.FinalIK;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Prevents body parts from penetrating scene geometry by offsetting effectors away from the colliders.
|
||||
/// </summary>
|
||||
public class PenetrationAvoidance : OffsetModifier {
|
||||
|
||||
/// <summary>
|
||||
/// Definition of avoidance and raycasting info.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Avoider {
|
||||
|
||||
/// <summary>
|
||||
/// Linking this to an effector
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class EffectorLink {
|
||||
[Tooltip("Effector to apply the offset to.")] public FullBodyBipedEffector effector;
|
||||
[Tooltip("Multiplier of the offset value, can be negative.")] public float weight;
|
||||
}
|
||||
|
||||
[Tooltip("Bones to start the raycast from. Multiple raycasts can be used by assigning more than 1 bone.")] public Transform[] raycastFrom;
|
||||
[Tooltip("The Transform to raycast towards. Usually the body part that you want to keep from penetrating.")] public Transform raycastTo;
|
||||
[Tooltip("If 0, will use simple raycasting, if > 0, will use sphere casting (better, but slower).")] [Range(0f, 1f)] public float raycastRadius;
|
||||
[Tooltip("Linking this to FBBIK effectors.")] public EffectorLink[] effectors;
|
||||
[Tooltip("The time of smooth interpolation of the offset value to avoid penetration.")] public float smoothTimeIn = 0.1f;
|
||||
[Tooltip("The time of smooth interpolation of the offset value blending out of penetration avoidance.")] public float smoothTimeOut = 0.3f;
|
||||
[Tooltip("Layers to keep penetrating from.")] public LayerMask layers;
|
||||
|
||||
private Vector3 offset;
|
||||
private Vector3 offsetTarget;
|
||||
private Vector3 offsetV;
|
||||
|
||||
public void Solve(IKSolverFullBodyBiped solver, float weight) {
|
||||
// Get the offset to interpolate to
|
||||
offsetTarget = GetOffsetTarget(solver);
|
||||
|
||||
// Interpolating the offset value
|
||||
float smoothDampTime = offsetTarget.sqrMagnitude > offset.sqrMagnitude? smoothTimeIn: smoothTimeOut;
|
||||
offset = Vector3.SmoothDamp(offset, offsetTarget, ref offsetV, smoothDampTime);
|
||||
|
||||
// Apply offset to the FBBIK effectors
|
||||
foreach (EffectorLink link in effectors) {
|
||||
solver.GetEffector(link.effector).positionOffset += offset * weight * link.weight;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple raycasting to accumulate the offset
|
||||
private Vector3 GetOffsetTarget(IKSolverFullBodyBiped solver) {
|
||||
Vector3 t = Vector3.zero;
|
||||
|
||||
foreach (Transform from in raycastFrom) {
|
||||
t += Raycast(from.position, raycastTo.position + t);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
// Raycast, return the offset that would not penetrate any colliders
|
||||
private Vector3 Raycast(Vector3 from, Vector3 to) {
|
||||
Vector3 direction = to - from;
|
||||
float distance = direction.magnitude;
|
||||
RaycastHit hit;
|
||||
|
||||
if (raycastRadius <= 0f) {
|
||||
Physics.Raycast(from, direction, out hit, distance, layers);
|
||||
} else {
|
||||
Physics.SphereCast(from, raycastRadius, direction, out hit, distance, layers);
|
||||
}
|
||||
|
||||
if (hit.collider == null) return Vector3.zero;
|
||||
|
||||
return Vector3.Project(-direction.normalized * (distance - hit.distance), hit.normal);
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("Definitions of penetration avoidances.")] public Avoider[] avoiders;
|
||||
|
||||
// Called by IKSolverFullBody before updating
|
||||
protected override void OnModifyOffset() {
|
||||
foreach (Avoider avoider in avoiders) avoider.Solve(ik.solver, weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0095dcee0a4564ca993da4df9dfb070e
|
||||
timeCreated: 1436511903
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
287
Unity-Master/Assets/Plugins/RootMotion/FinalIK/Tools/Recoil.cs
Normal file
287
Unity-Master/Assets/Plugins/RootMotion/FinalIK/Tools/Recoil.cs
Normal file
@ -0,0 +1,287 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Procedural recoil using FBBIK.
|
||||
/// </summary>
|
||||
public class Recoil : OffsetModifier
|
||||
{
|
||||
|
||||
[System.Serializable]
|
||||
public class RecoilOffset
|
||||
{
|
||||
|
||||
[Tooltip("Offset vector for the associated effector when doing recoil.")]
|
||||
public Vector3 offset;
|
||||
[Tooltip("When firing before the last recoil has faded, how much of the current recoil offset will be maintained?")]
|
||||
[Range(0f, 1f)] public float additivity = 1f;
|
||||
[Tooltip("Max additive recoil for automatic fire.")]
|
||||
public float maxAdditiveOffsetMag = 0.2f;
|
||||
|
||||
// Linking this to an effector
|
||||
[System.Serializable]
|
||||
public class EffectorLink
|
||||
{
|
||||
[Tooltip("Type of the FBBIK effector to use")]
|
||||
public FullBodyBipedEffector effector;
|
||||
[Tooltip("Weight of using this effector")]
|
||||
public float weight;
|
||||
}
|
||||
|
||||
[Tooltip("Linking this recoil offset to FBBIK effectors.")]
|
||||
public EffectorLink[] effectorLinks;
|
||||
|
||||
private Vector3 additiveOffset;
|
||||
private Vector3 lastOffset;
|
||||
|
||||
// Start recoil
|
||||
public void Start()
|
||||
{
|
||||
if (additivity <= 0f) return;
|
||||
|
||||
additiveOffset = Vector3.ClampMagnitude(lastOffset * additivity, maxAdditiveOffsetMag);
|
||||
}
|
||||
|
||||
// Apply offset to FBBIK effectors
|
||||
public void Apply(IKSolverFullBodyBiped solver, Quaternion rotation, float masterWeight, float length, float timeLeft)
|
||||
{
|
||||
additiveOffset = Vector3.Lerp(Vector3.zero, additiveOffset, timeLeft / length);
|
||||
lastOffset = (rotation * (offset * masterWeight)) + (rotation * additiveOffset);
|
||||
|
||||
foreach (EffectorLink e in effectorLinks)
|
||||
{
|
||||
solver.GetEffector(e.effector).positionOffset += lastOffset * e.weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public enum Handedness
|
||||
{
|
||||
Right,
|
||||
Left
|
||||
}
|
||||
|
||||
[Tooltip("Reference to the AimIK component. Optional, only used to getting the aiming direction.")]
|
||||
public AimIK aimIK;
|
||||
[Tooltip("Optional head AimIK solver. This solver should only use neck and head bones and have the head as Aim Transform")]
|
||||
public AimIK headIK;
|
||||
[Tooltip("Set this true if you are using IKExecutionOrder.cs or a custom script to force AimIK solve after FBBIK.")]
|
||||
public bool aimIKSolvedLast;
|
||||
[Tooltip("Which hand is holding the weapon?")]
|
||||
public Handedness handedness;
|
||||
[Tooltip("Check for 2-handed weapons.")]
|
||||
public bool twoHanded = true;
|
||||
[Tooltip("Weight curve for the recoil offsets. Recoil procedure is as long as this curve.")]
|
||||
public AnimationCurve recoilWeight;
|
||||
[Tooltip("How much is the magnitude randomized each time Recoil is called?")]
|
||||
public float magnitudeRandom = 0.1f;
|
||||
[Tooltip("How much is the rotation randomized each time Recoil is called?")]
|
||||
public Vector3 rotationRandom;
|
||||
[Tooltip("Rotating the primary hand bone for the recoil (in local space).")]
|
||||
public Vector3 handRotationOffset;
|
||||
[Tooltip("Time of blending in another recoil when doing automatic fire.")]
|
||||
public float blendTime;
|
||||
|
||||
[Space(10)]
|
||||
|
||||
[Tooltip("FBBIK effector position offsets for the recoil (in aiming direction space).")]
|
||||
public RecoilOffset[] offsets;
|
||||
|
||||
[HideInInspector] public Quaternion rotationOffset = Quaternion.identity;
|
||||
|
||||
private float magnitudeMlp = 1f;
|
||||
private float endTime = -1f;
|
||||
private Quaternion handRotation, secondaryHandRelativeRotation, randomRotation;
|
||||
private float length = 1f;
|
||||
private bool initiated;
|
||||
private float blendWeight;
|
||||
private float w;
|
||||
private Quaternion primaryHandRotation = Quaternion.identity;
|
||||
//private Quaternion secondaryHandRotation = Quaternion.identity;
|
||||
private bool handRotationsSet;
|
||||
private Vector3 aimIKAxis;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if recoil has finished or has not been called at all.
|
||||
/// </summary>
|
||||
public bool isFinished
|
||||
{
|
||||
get
|
||||
{
|
||||
return Time.time > endTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the starting rotations for the hands for 1 frame. Use this if the final rotation of the hands will not be the same as before FBBIK solves.
|
||||
/// </summary>
|
||||
public void SetHandRotations(Quaternion leftHandRotation, Quaternion rightHandRotation)
|
||||
{
|
||||
if (handedness == Handedness.Left)
|
||||
{
|
||||
primaryHandRotation = leftHandRotation;
|
||||
//secondaryHandRotation = rightHandRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
primaryHandRotation = rightHandRotation;
|
||||
//secondaryHandRotation = leftHandRotation;
|
||||
}
|
||||
|
||||
handRotationsSet = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the recoil procedure.
|
||||
/// </summary>
|
||||
public void Fire(float magnitude)
|
||||
{
|
||||
float rnd = magnitude * UnityEngine.Random.value * magnitudeRandom;
|
||||
magnitudeMlp = magnitude + rnd;
|
||||
|
||||
randomRotation = Quaternion.Euler(rotationRandom * UnityEngine.Random.value);
|
||||
|
||||
foreach (RecoilOffset offset in offsets)
|
||||
{
|
||||
offset.Start();
|
||||
}
|
||||
|
||||
if (Time.time < endTime) blendWeight = 0f;
|
||||
else blendWeight = 1f;
|
||||
|
||||
Keyframe[] keys = recoilWeight.keys;
|
||||
length = keys[keys.Length - 1].time;
|
||||
endTime = Time.time + length;
|
||||
}
|
||||
|
||||
protected override void OnModifyOffset()
|
||||
{
|
||||
if (aimIK != null) aimIKAxis = aimIK.solver.axis;
|
||||
|
||||
if (!initiated && ik != null)
|
||||
{
|
||||
initiated = true;
|
||||
if (headIK != null) headIK.enabled = false;
|
||||
ik.solver.OnPostUpdate += AfterFBBIK;
|
||||
if (aimIK != null) aimIK.solver.OnPostUpdate += AfterAimIK;
|
||||
}
|
||||
|
||||
if (Time.time >= endTime)
|
||||
{
|
||||
rotationOffset = Quaternion.identity;
|
||||
return;
|
||||
}
|
||||
|
||||
blendTime = Mathf.Max(blendTime, 0f);
|
||||
if (blendTime > 0f) blendWeight = Mathf.Min(blendWeight + Time.deltaTime * (1f / blendTime), 1f);
|
||||
else blendWeight = 1f;
|
||||
|
||||
// Current weight of offset
|
||||
float wTarget = recoilWeight.Evaluate(length - (endTime - Time.time)) * magnitudeMlp;
|
||||
w = Mathf.Lerp(w, wTarget, blendWeight);
|
||||
|
||||
// Find the rotation space of the recoil
|
||||
Quaternion lookRotation = aimIK != null && aimIK.solver.transform != null && !aimIKSolvedLast ? Quaternion.LookRotation(aimIK.solver.IKPosition - aimIK.solver.transform.position, ik.references.root.up) : ik.references.root.rotation;
|
||||
lookRotation = randomRotation * lookRotation;
|
||||
|
||||
// Apply FBBIK effector positionOffsets
|
||||
foreach (RecoilOffset offset in offsets)
|
||||
{
|
||||
offset.Apply(ik.solver, lookRotation, w, length, endTime - Time.time);
|
||||
}
|
||||
|
||||
if (!handRotationsSet)
|
||||
{
|
||||
primaryHandRotation = primaryHand.rotation;
|
||||
//if (twoHanded) secondaryHandRotation = secondaryHand.rotation;
|
||||
}
|
||||
handRotationsSet = false;
|
||||
|
||||
// Rotation offset of the primary hand
|
||||
rotationOffset = Quaternion.Lerp(Quaternion.identity, Quaternion.Euler(randomRotation * primaryHandRotation * handRotationOffset), w);
|
||||
handRotation = rotationOffset * primaryHandRotation;
|
||||
|
||||
// Fix the secondary hand relative to the primary hand
|
||||
if (twoHanded)
|
||||
{
|
||||
Vector3 secondaryHandRelativePosition = Quaternion.Inverse(primaryHand.rotation) * (secondaryHand.position - primaryHand.position);
|
||||
secondaryHandRelativeRotation = Quaternion.Inverse(primaryHand.rotation) * secondaryHand.rotation;
|
||||
|
||||
Vector3 primaryHandPosition = primaryHand.position + primaryHandEffector.positionOffset;
|
||||
Vector3 secondaryHandPosition = primaryHandPosition + handRotation * secondaryHandRelativePosition;
|
||||
|
||||
secondaryHandEffector.positionOffset += secondaryHandPosition - (secondaryHand.position + secondaryHandEffector.positionOffset);
|
||||
}
|
||||
|
||||
if (aimIK != null && aimIKSolvedLast) aimIK.solver.axis = Quaternion.Inverse(ik.references.root.rotation) * Quaternion.Inverse(rotationOffset) * aimIKAxis;
|
||||
}
|
||||
|
||||
private void AfterFBBIK()
|
||||
{
|
||||
if (Time.time < endTime)
|
||||
{
|
||||
// Rotate the hand bones
|
||||
primaryHand.rotation = handRotation;
|
||||
if (twoHanded) secondaryHand.rotation = primaryHand.rotation * secondaryHandRelativeRotation;
|
||||
}
|
||||
|
||||
if (!aimIKSolvedLast && headIK != null) headIK.solver.Update();
|
||||
}
|
||||
|
||||
private void AfterAimIK()
|
||||
{
|
||||
if (aimIKSolvedLast) aimIK.solver.axis = aimIKAxis;
|
||||
if (aimIKSolvedLast && headIK != null) headIK.solver.Update();
|
||||
}
|
||||
|
||||
// Shortcuts
|
||||
private IKEffector primaryHandEffector
|
||||
{
|
||||
get
|
||||
{
|
||||
if (handedness == Handedness.Right) return ik.solver.rightHandEffector;
|
||||
return ik.solver.leftHandEffector;
|
||||
}
|
||||
}
|
||||
|
||||
private IKEffector secondaryHandEffector
|
||||
{
|
||||
get
|
||||
{
|
||||
if (handedness == Handedness.Right) return ik.solver.leftHandEffector;
|
||||
return ik.solver.rightHandEffector;
|
||||
}
|
||||
}
|
||||
|
||||
private Transform primaryHand
|
||||
{
|
||||
get
|
||||
{
|
||||
return primaryHandEffector.bone;
|
||||
}
|
||||
}
|
||||
|
||||
private Transform secondaryHand
|
||||
{
|
||||
get
|
||||
{
|
||||
return secondaryHandEffector.bone;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
if (ik != null && initiated)
|
||||
{
|
||||
ik.solver.OnPostUpdate -= AfterFBBIK;
|
||||
if (aimIK != null) aimIK.solver.OnPostUpdate -= AfterAimIK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c613189034e145e5abecee53af1ce0e
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,77 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using RootMotion.FinalIK;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Shoulder rotator is a workaround for FBBIK not rotating the shoulder bones when pulled by hands.
|
||||
/// It get's the job done if you need it, but will take 2 solving iterations.
|
||||
/// </summary>
|
||||
public class ShoulderRotator : MonoBehaviour {
|
||||
|
||||
[Tooltip("Weight of shoulder rotation")]
|
||||
public float weight = 1.5f;
|
||||
[Tooltip("The greater the offset, the sooner the shoulder will start rotating")]
|
||||
public float offset = 0.2f;
|
||||
|
||||
private FullBodyBipedIK ik;
|
||||
private bool skip;
|
||||
|
||||
void Start() {
|
||||
ik = GetComponent<FullBodyBipedIK>();
|
||||
|
||||
// You can use just LateUpdate, but note that it doesn't work when you have animatePhysics turned on for the character.
|
||||
ik.solver.OnPostUpdate += RotateShoulders;
|
||||
}
|
||||
|
||||
private void RotateShoulders () {
|
||||
if (ik == null) return;
|
||||
if (ik.solver.IKPositionWeight <= 0f) return;
|
||||
|
||||
// Skipping the second update cycle
|
||||
if (skip) {
|
||||
skip = false;
|
||||
return;
|
||||
}
|
||||
|
||||
RotateShoulder(FullBodyBipedChain.LeftArm, weight, offset); // Rotate the left shoulder
|
||||
RotateShoulder(FullBodyBipedChain.RightArm, weight, offset); // Rotate the right shoulder
|
||||
|
||||
skip = true;
|
||||
ik.solver.Update(); // Update FBBIK again with the rotated shoulders
|
||||
}
|
||||
|
||||
// Rotates a shoulder of a FBBIK character
|
||||
private void RotateShoulder(FullBodyBipedChain chain, float weight, float offset) {
|
||||
// Get FromToRotation from the current swing direction of the shoulder to the IK target direction
|
||||
Quaternion fromTo = Quaternion.FromToRotation(GetParentBoneMap(chain).swingDirection, ik.solver.GetEndEffector(chain).position - GetParentBoneMap(chain).transform.position);
|
||||
|
||||
// Direction to the IK target
|
||||
Vector3 toTarget = ik.solver.GetEndEffector(chain).position - ik.solver.GetLimbMapping(chain).bone1.position;
|
||||
|
||||
// Length of the limb
|
||||
float limbLength = ik.solver.GetChain(chain).nodes[0].length + ik.solver.GetChain(chain).nodes[1].length;
|
||||
|
||||
// Divide IK Target direction magnitude by limb length to know how much the limb is being pulled
|
||||
float delta = (toTarget.magnitude / limbLength) - 1f + offset;
|
||||
delta = Mathf.Clamp(delta * weight, 0f, 1f);
|
||||
|
||||
// Calculate the rotation offset for the shoulder
|
||||
Quaternion rotationOffset = Quaternion.Lerp(Quaternion.identity, fromTo, delta * ik.solver.GetEndEffector(chain).positionWeight * ik.solver.IKPositionWeight);
|
||||
|
||||
// Rotate the shoulder
|
||||
ik.solver.GetLimbMapping(chain).parentBone.rotation = rotationOffset * ik.solver.GetLimbMapping(chain).parentBone.rotation;
|
||||
}
|
||||
|
||||
// Get the shoulder BoneMap
|
||||
private IKMapping.BoneMap GetParentBoneMap(FullBodyBipedChain chain) {
|
||||
return ik.solver.GetLimbMapping(chain).GetBoneMap(IKMappingLimb.BoneMapType.Parent);
|
||||
}
|
||||
|
||||
// Remove the delegate when destroyed
|
||||
void OnDestroy() {
|
||||
if (ik != null) ik.solver.OnPostUpdate -= RotateShoulders;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a48af8ef2fd147446af3c65186a1cd9e
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Unity-Master/Assets/Plugins/RootMotion/FinalIK/Tools/VRIK Animated Locomotion.controller
(Stored with Git LFS)
Normal file
BIN
Unity-Master/Assets/Plugins/RootMotion/FinalIK/Tools/VRIK Animated Locomotion.controller
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78f02360f31c1a547b937559d34b03b1
|
||||
timeCreated: 1512733284
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,636 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Calibrates VRIK for the HMD and up to 5 additional trackers.
|
||||
/// </summary>
|
||||
public static class VRIKCalibrator
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The settings for VRIK tracker calibration.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Settings
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies character scale.
|
||||
/// </summary>
|
||||
[Tooltip("Multiplies character scale")]
|
||||
public float scaleMlp = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Local axis of the HMD facing forward.
|
||||
/// </summary>
|
||||
[Tooltip("Local axis of the HMD facing forward.")]
|
||||
public Vector3 headTrackerForward = Vector3.forward;
|
||||
|
||||
/// <summary>
|
||||
/// Local axis of the HMD facing up.
|
||||
/// </summary>
|
||||
[Tooltip("Local axis of the HMD facing up.")]
|
||||
public Vector3 headTrackerUp = Vector3.up;
|
||||
|
||||
/// <summary>
|
||||
/// Local axis of the hand trackers pointing from the wrist towards the palm.
|
||||
/// </summary>
|
||||
[Tooltip("Local axis of the hand trackers pointing from the wrist towards the palm.")]
|
||||
public Vector3 handTrackerForward = Vector3.forward;
|
||||
|
||||
/// <summary>
|
||||
/// Local axis of the hand trackers pointing in the direction of the surface normal of the back of the hand.
|
||||
/// </summary>
|
||||
[Tooltip("Local axis of the hand trackers pointing in the direction of the surface normal of the back of the hand.")]
|
||||
public Vector3 handTrackerUp = Vector3.up;
|
||||
|
||||
/// <summary>
|
||||
/// Local axis of the foot trackers towards the player's forward direction.
|
||||
/// </summary>
|
||||
[Tooltip("Local axis of the foot trackers towards the player's forward direction.")]
|
||||
public Vector3 footTrackerForward = Vector3.forward;
|
||||
|
||||
/// <summary>
|
||||
/// Local axis of the foot tracker towards the up direction.
|
||||
/// </summary>
|
||||
[Tooltip("Local axis of the foot tracker towards the up direction.")]
|
||||
public Vector3 footTrackerUp = Vector3.up;
|
||||
|
||||
[Space(10f)]
|
||||
/// <summary>
|
||||
/// Offset of the head bone from the HMD in (headTrackerForward, headTrackerUp) space relative to the head tracker.
|
||||
/// </summary>
|
||||
[Tooltip("Offset of the head bone from the HMD in (headTrackerForward, headTrackerUp) space relative to the head tracker.")]
|
||||
public Vector3 headOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the hand bones from the hand trackers in (handTrackerForward, handTrackerUp) space relative to the hand trackers.
|
||||
/// </summary>
|
||||
[Tooltip("Offset of the hand bones from the hand trackers in (handTrackerForward, handTrackerUp) space relative to the hand trackers.")]
|
||||
public Vector3 handOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Forward offset of the foot bones from the foot trackers.
|
||||
/// </summary>
|
||||
[Tooltip("Forward offset of the foot bones from the foot trackers.")]
|
||||
public float footForwardOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Inward offset of the foot bones from the foot trackers.
|
||||
/// </summary>
|
||||
[Tooltip("Inward offset of the foot bones from the foot trackers.")]
|
||||
public float footInwardOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Used for adjusting foot heading relative to the foot trackers.
|
||||
/// </summary>
|
||||
[Tooltip("Used for adjusting foot heading relative to the foot trackers.")]
|
||||
[Range(-180f, 180f)]
|
||||
public float footHeadingOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Pelvis target position weight. If the body tracker is on the backpack or somewhere else not very close to the pelvis of the player, position weight needs to be reduced to allow some bending for the spine.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float pelvisPositionWeight = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Pelvis target rotation weight. If the body tracker is on the backpack or somewhere else not very close to the pelvis of the player, rotation weight needs to be reduced to allow some bending for the spine.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float pelvisRotationWeight = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalibrates only the avatar scale, updates CalibrationData to the new scale value
|
||||
/// </summary>
|
||||
public static void RecalibrateScale(VRIK ik, CalibrationData data, Settings settings)
|
||||
{
|
||||
RecalibrateScale(ik, data, settings.scaleMlp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalibrates only the avatar scale, updates CalibrationData to the new scale value
|
||||
/// </summary>
|
||||
public static void RecalibrateScale(VRIK ik, CalibrationData data, float scaleMlp)
|
||||
{
|
||||
CalibrateScale(ik, scaleMlp);
|
||||
data.scale = ik.references.root.localScale.y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calibrates only the avatar scale.
|
||||
/// </summary>
|
||||
private static void CalibrateScale(VRIK ik, Settings settings)
|
||||
{
|
||||
CalibrateScale(ik, settings.scaleMlp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calibrates only the avatar scale.
|
||||
/// </summary>
|
||||
private static void CalibrateScale(VRIK ik, float scaleMlp = 1f)
|
||||
{
|
||||
float sizeF = (ik.solver.spine.headTarget.position.y - ik.references.root.position.y) / (ik.references.head.position.y - ik.references.root.position.y);
|
||||
ik.references.root.localScale *= sizeF * scaleMlp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calibrates VRIK to the specified trackers using the VRIKTrackerCalibrator.Settings.
|
||||
/// </summary>
|
||||
/// <param name="ik">Reference to the VRIK component.</param>
|
||||
/// <param name="settings">Calibration settings.</param>
|
||||
/// <param name="headTracker">The HMD.</param>
|
||||
/// <param name="bodyTracker">(Optional) A tracker placed anywhere on the body of the player, preferrably close to the pelvis, on the belt area.</param>
|
||||
/// <param name="leftHandTracker">(Optional) A tracker or hand controller device placed anywhere on or in the player's left hand.</param>
|
||||
/// <param name="rightHandTracker">(Optional) A tracker or hand controller device placed anywhere on or in the player's right hand.</param>
|
||||
/// <param name="leftFootTracker">(Optional) A tracker placed anywhere on the ankle or toes of the player's left leg.</param>
|
||||
/// <param name="rightFootTracker">(Optional) A tracker placed anywhere on the ankle or toes of the player's right leg.</param>
|
||||
public static CalibrationData Calibrate(VRIK ik, Settings settings, Transform headTracker, Transform bodyTracker = null, Transform leftHandTracker = null, Transform rightHandTracker = null, Transform leftFootTracker = null, Transform rightFootTracker = null)
|
||||
{
|
||||
if (!ik.solver.initiated)
|
||||
{
|
||||
Debug.LogError("Can not calibrate before VRIK has initiated.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (headTracker == null)
|
||||
{
|
||||
Debug.LogError("Can not calibrate VRIK without the head tracker.");
|
||||
return null;
|
||||
}
|
||||
|
||||
CalibrationData data = new CalibrationData();
|
||||
|
||||
ik.solver.FixTransforms();
|
||||
|
||||
// Root position and rotation
|
||||
Vector3 headPos = headTracker.position + headTracker.rotation * Quaternion.LookRotation(settings.headTrackerForward, settings.headTrackerUp) * settings.headOffset;
|
||||
ik.references.root.position = new Vector3(headPos.x, ik.references.root.position.y, headPos.z);
|
||||
Vector3 headForward = headTracker.rotation * settings.headTrackerForward;
|
||||
headForward.y = 0f;
|
||||
ik.references.root.rotation = Quaternion.LookRotation(headForward);
|
||||
|
||||
// Head
|
||||
Transform headTarget = ik.solver.spine.headTarget == null ? (new GameObject("Head Target")).transform : ik.solver.spine.headTarget;
|
||||
headTarget.position = headPos;
|
||||
headTarget.rotation = ik.references.head.rotation;
|
||||
headTarget.parent = headTracker;
|
||||
ik.solver.spine.headTarget = headTarget;
|
||||
|
||||
// Size
|
||||
float sizeF = (headTarget.position.y - ik.references.root.position.y) / (ik.references.head.position.y - ik.references.root.position.y);
|
||||
ik.references.root.localScale *= sizeF * settings.scaleMlp;
|
||||
|
||||
// Body
|
||||
if (bodyTracker != null)
|
||||
{
|
||||
Transform pelvisTarget = ik.solver.spine.pelvisTarget == null ? (new GameObject("Pelvis Target")).transform : ik.solver.spine.pelvisTarget;
|
||||
pelvisTarget.position = ik.references.pelvis.position;
|
||||
pelvisTarget.rotation = ik.references.pelvis.rotation;
|
||||
pelvisTarget.parent = bodyTracker;
|
||||
ik.solver.spine.pelvisTarget = pelvisTarget;
|
||||
|
||||
ik.solver.spine.pelvisPositionWeight = settings.pelvisPositionWeight;
|
||||
ik.solver.spine.pelvisRotationWeight = settings.pelvisRotationWeight;
|
||||
|
||||
ik.solver.plantFeet = false;
|
||||
ik.solver.spine.maxRootAngle = 180f;
|
||||
}
|
||||
else if (leftFootTracker != null && rightFootTracker != null)
|
||||
{
|
||||
ik.solver.spine.maxRootAngle = 0f;
|
||||
}
|
||||
|
||||
// Left Hand
|
||||
if (leftHandTracker != null)
|
||||
{
|
||||
Transform leftHandTarget = ik.solver.leftArm.target == null ? (new GameObject("Left Hand Target")).transform : ik.solver.leftArm.target;
|
||||
leftHandTarget.position = leftHandTracker.position + leftHandTracker.rotation * Quaternion.LookRotation(settings.handTrackerForward, settings.handTrackerUp) * settings.handOffset;
|
||||
Vector3 leftHandUp = Vector3.Cross(ik.solver.leftArm.wristToPalmAxis, ik.solver.leftArm.palmToThumbAxis);
|
||||
leftHandTarget.rotation = QuaTools.MatchRotation(leftHandTracker.rotation * Quaternion.LookRotation(settings.handTrackerForward, settings.handTrackerUp), settings.handTrackerForward, settings.handTrackerUp, ik.solver.leftArm.wristToPalmAxis, leftHandUp);
|
||||
leftHandTarget.parent = leftHandTracker;
|
||||
ik.solver.leftArm.target = leftHandTarget;
|
||||
ik.solver.leftArm.positionWeight = 1f;
|
||||
ik.solver.leftArm.rotationWeight = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
ik.solver.leftArm.positionWeight = 0f;
|
||||
ik.solver.leftArm.rotationWeight = 0f;
|
||||
}
|
||||
|
||||
// Right Hand
|
||||
if (rightHandTracker != null)
|
||||
{
|
||||
Transform rightHandTarget = ik.solver.rightArm.target == null ? (new GameObject("Right Hand Target")).transform : ik.solver.rightArm.target;
|
||||
rightHandTarget.position = rightHandTracker.position + rightHandTracker.rotation * Quaternion.LookRotation(settings.handTrackerForward, settings.handTrackerUp) * settings.handOffset;
|
||||
Vector3 rightHandUp = -Vector3.Cross(ik.solver.rightArm.wristToPalmAxis, ik.solver.rightArm.palmToThumbAxis);
|
||||
rightHandTarget.rotation = QuaTools.MatchRotation(rightHandTracker.rotation * Quaternion.LookRotation(settings.handTrackerForward, settings.handTrackerUp), settings.handTrackerForward, settings.handTrackerUp, ik.solver.rightArm.wristToPalmAxis, rightHandUp);
|
||||
rightHandTarget.parent = rightHandTracker;
|
||||
ik.solver.rightArm.target = rightHandTarget;
|
||||
ik.solver.rightArm.positionWeight = 1f;
|
||||
ik.solver.rightArm.rotationWeight = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
ik.solver.rightArm.positionWeight = 0f;
|
||||
ik.solver.rightArm.rotationWeight = 0f;
|
||||
}
|
||||
|
||||
// Legs
|
||||
if (leftFootTracker != null) CalibrateLeg(settings, leftFootTracker, ik.solver.leftLeg, (ik.references.leftToes != null ? ik.references.leftToes : ik.references.leftFoot), ik.references.root.forward, true);
|
||||
if (rightFootTracker != null) CalibrateLeg(settings, rightFootTracker, ik.solver.rightLeg, (ik.references.rightToes != null ? ik.references.rightToes : ik.references.rightFoot), ik.references.root.forward, false);
|
||||
|
||||
// Root controller
|
||||
bool addRootController = bodyTracker != null || (leftFootTracker != null && rightFootTracker != null);
|
||||
var rootController = ik.references.root.GetComponent<VRIKRootController>();
|
||||
|
||||
if (addRootController)
|
||||
{
|
||||
if (rootController == null) rootController = ik.references.root.gameObject.AddComponent<VRIKRootController>();
|
||||
rootController.Calibrate();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rootController != null) GameObject.Destroy(rootController);
|
||||
}
|
||||
|
||||
// Additional solver settings
|
||||
ik.solver.spine.minHeadHeight = 0f;
|
||||
ik.solver.locomotion.weight = bodyTracker == null && leftFootTracker == null && rightFootTracker == null ? 1f : 0f;
|
||||
|
||||
// Fill in Calibration Data
|
||||
data.scale = ik.references.root.localScale.y;
|
||||
data.head = new CalibrationData.Target(ik.solver.spine.headTarget);
|
||||
data.pelvis = new CalibrationData.Target(ik.solver.spine.pelvisTarget);
|
||||
data.leftHand = new CalibrationData.Target(ik.solver.leftArm.target);
|
||||
data.rightHand = new CalibrationData.Target(ik.solver.rightArm.target);
|
||||
data.leftFoot = new CalibrationData.Target(ik.solver.leftLeg.target);
|
||||
data.rightFoot = new CalibrationData.Target(ik.solver.rightLeg.target);
|
||||
data.leftLegGoal = new CalibrationData.Target(ik.solver.leftLeg.bendGoal);
|
||||
data.rightLegGoal = new CalibrationData.Target(ik.solver.rightLeg.bendGoal);
|
||||
data.pelvisTargetRight = rootController != null ? rootController.pelvisTargetRight : Vector3.zero;
|
||||
data.pelvisPositionWeight = ik.solver.spine.pelvisPositionWeight;
|
||||
data.pelvisRotationWeight = ik.solver.spine.pelvisRotationWeight;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void CalibrateLeg(Settings settings, Transform tracker, IKSolverVR.Leg leg, Transform lastBone, Vector3 rootForward, bool isLeft)
|
||||
{
|
||||
string name = isLeft ? "Left" : "Right";
|
||||
Transform target = leg.target == null ? (new GameObject(name + " Foot Target")).transform : leg.target;
|
||||
|
||||
// Space of the tracker heading
|
||||
Quaternion trackerSpace = tracker.rotation * Quaternion.LookRotation(settings.footTrackerForward, settings.footTrackerUp);
|
||||
Vector3 f = trackerSpace * Vector3.forward;
|
||||
f.y = 0f;
|
||||
trackerSpace = Quaternion.LookRotation(f);
|
||||
|
||||
// Target position
|
||||
float inwardOffset = isLeft ? settings.footInwardOffset : -settings.footInwardOffset;
|
||||
target.position = tracker.position + trackerSpace * new Vector3(inwardOffset, 0f, settings.footForwardOffset);
|
||||
target.position = new Vector3(target.position.x, lastBone.position.y, target.position.z);
|
||||
|
||||
// Target rotation
|
||||
target.rotation = lastBone.rotation;
|
||||
|
||||
// Rotate target forward towards tracker forward
|
||||
Vector3 footForward = AxisTools.GetAxisVectorToDirection(lastBone, rootForward);
|
||||
if (Vector3.Dot(lastBone.rotation * footForward, rootForward) < 0f) footForward = -footForward;
|
||||
Vector3 fLocal = Quaternion.Inverse(Quaternion.LookRotation(target.rotation * footForward)) * f;
|
||||
float angle = Mathf.Atan2(fLocal.x, fLocal.z) * Mathf.Rad2Deg;
|
||||
float headingOffset = isLeft ? settings.footHeadingOffset : -settings.footHeadingOffset;
|
||||
target.rotation = Quaternion.AngleAxis(angle + headingOffset, Vector3.up) * target.rotation;
|
||||
|
||||
target.parent = tracker;
|
||||
leg.target = target;
|
||||
|
||||
leg.positionWeight = 1f;
|
||||
leg.rotationWeight = 1f;
|
||||
|
||||
// Bend goal
|
||||
Transform bendGoal = leg.bendGoal == null ? (new GameObject(name + " Leg Bend Goal")).transform : leg.bendGoal;
|
||||
bendGoal.position = lastBone.position + trackerSpace * Vector3.forward + trackerSpace * Vector3.up;// * 0.5f;
|
||||
bendGoal.parent = tracker;
|
||||
leg.bendGoal = bendGoal;
|
||||
leg.bendGoalWeight = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When VRIK is calibrated by calibration settings, will store CalibrationData that can be used to set up another character with the exact same calibration.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class CalibrationData
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Target
|
||||
{
|
||||
public bool used;
|
||||
public Vector3 localPosition;
|
||||
public Quaternion localRotation;
|
||||
|
||||
public Target(Transform t)
|
||||
{
|
||||
this.used = t != null;
|
||||
if (!this.used) return;
|
||||
|
||||
this.localPosition = t.localPosition;
|
||||
this.localRotation = t.localRotation;
|
||||
}
|
||||
|
||||
public void SetTo(Transform t)
|
||||
{
|
||||
if (!used) return;
|
||||
t.localPosition = localPosition;
|
||||
t.localRotation = localRotation;
|
||||
}
|
||||
}
|
||||
|
||||
public float scale;
|
||||
public Target head, leftHand, rightHand, pelvis, leftFoot, rightFoot, leftLegGoal, rightLegGoal;
|
||||
public Vector3 pelvisTargetRight;
|
||||
public float pelvisPositionWeight;
|
||||
public float pelvisRotationWeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calibrates VRIK to the specified trackers using CalibrationData from a previous calibration. Requires this character's bone orientations to match with the character's that was used in the previous calibration.
|
||||
/// </summary>
|
||||
/// <param name="ik">Reference to the VRIK component.</param>
|
||||
/// <param name="data">Use calibration data from a previous calibration.</param>
|
||||
/// <param name="headTracker">The HMD.</param>
|
||||
/// <param name="bodyTracker">(Optional) A tracker placed anywhere on the body of the player, preferrably close to the pelvis, on the belt area.</param>
|
||||
/// <param name="leftHandTracker">(Optional) A tracker or hand controller device placed anywhere on or in the player's left hand.</param>
|
||||
/// <param name="rightHandTracker">(Optional) A tracker or hand controller device placed anywhere on or in the player's right hand.</param>
|
||||
/// <param name="leftFootTracker">(Optional) A tracker placed anywhere on the ankle or toes of the player's left leg.</param>
|
||||
/// <param name="rightFootTracker">(Optional) A tracker placed anywhere on the ankle or toes of the player's right leg.</param>
|
||||
public static void Calibrate(VRIK ik, CalibrationData data, Transform headTracker, Transform bodyTracker = null, Transform leftHandTracker = null, Transform rightHandTracker = null, Transform leftFootTracker = null, Transform rightFootTracker = null)
|
||||
{
|
||||
if (!ik.solver.initiated)
|
||||
{
|
||||
Debug.LogError("Can not calibrate before VRIK has initiated.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (headTracker == null)
|
||||
{
|
||||
Debug.LogError("Can not calibrate VRIK without the head tracker.");
|
||||
return;
|
||||
}
|
||||
|
||||
ik.solver.FixTransforms();
|
||||
|
||||
// Head
|
||||
Transform headTarget = ik.solver.spine.headTarget == null ? (new GameObject("Head Target")).transform : ik.solver.spine.headTarget;
|
||||
headTarget.parent = headTracker;
|
||||
data.head.SetTo(headTarget);
|
||||
ik.solver.spine.headTarget = headTarget;
|
||||
|
||||
// Size
|
||||
ik.references.root.localScale = data.scale * Vector3.one;
|
||||
|
||||
// Body
|
||||
if (bodyTracker != null && data.pelvis != null)
|
||||
{
|
||||
Transform pelvisTarget = ik.solver.spine.pelvisTarget == null ? (new GameObject("Pelvis Target")).transform : ik.solver.spine.pelvisTarget;
|
||||
pelvisTarget.parent = bodyTracker;
|
||||
data.pelvis.SetTo(pelvisTarget);
|
||||
ik.solver.spine.pelvisTarget = pelvisTarget;
|
||||
|
||||
ik.solver.spine.pelvisPositionWeight = data.pelvisPositionWeight;
|
||||
ik.solver.spine.pelvisRotationWeight = data.pelvisRotationWeight;
|
||||
|
||||
ik.solver.plantFeet = false;
|
||||
ik.solver.spine.maxRootAngle = 180f;
|
||||
}
|
||||
else if (leftFootTracker != null && rightFootTracker != null)
|
||||
{
|
||||
ik.solver.spine.maxRootAngle = 0f;
|
||||
}
|
||||
|
||||
// Left Hand
|
||||
if (leftHandTracker != null)
|
||||
{
|
||||
Transform leftHandTarget = ik.solver.leftArm.target == null ? (new GameObject("Left Hand Target")).transform : ik.solver.leftArm.target;
|
||||
leftHandTarget.parent = leftHandTracker;
|
||||
data.leftHand.SetTo(leftHandTarget);
|
||||
ik.solver.leftArm.target = leftHandTarget;
|
||||
ik.solver.leftArm.positionWeight = 1f;
|
||||
ik.solver.leftArm.rotationWeight = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
ik.solver.leftArm.positionWeight = 0f;
|
||||
ik.solver.leftArm.rotationWeight = 0f;
|
||||
}
|
||||
|
||||
// Right Hand
|
||||
if (rightHandTracker != null)
|
||||
{
|
||||
Transform rightHandTarget = ik.solver.rightArm.target == null ? (new GameObject("Right Hand Target")).transform : ik.solver.rightArm.target;
|
||||
rightHandTarget.parent = rightHandTracker;
|
||||
data.rightHand.SetTo(rightHandTarget);
|
||||
ik.solver.rightArm.target = rightHandTarget;
|
||||
ik.solver.rightArm.positionWeight = 1f;
|
||||
ik.solver.rightArm.rotationWeight = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
ik.solver.rightArm.positionWeight = 0f;
|
||||
ik.solver.rightArm.rotationWeight = 0f;
|
||||
}
|
||||
|
||||
// Legs
|
||||
if (leftFootTracker != null) CalibrateLeg(data, leftFootTracker, ik.solver.leftLeg, (ik.references.leftToes != null ? ik.references.leftToes : ik.references.leftFoot), ik.references.root.forward, true);
|
||||
if (rightFootTracker != null) CalibrateLeg(data, rightFootTracker, ik.solver.rightLeg, (ik.references.rightToes != null ? ik.references.rightToes : ik.references.rightFoot), ik.references.root.forward, false);
|
||||
|
||||
// Root controller
|
||||
bool addRootController = bodyTracker != null || (leftFootTracker != null && rightFootTracker != null);
|
||||
var rootController = ik.references.root.GetComponent<VRIKRootController>();
|
||||
|
||||
if (addRootController)
|
||||
{
|
||||
if (rootController == null) rootController = ik.references.root.gameObject.AddComponent<VRIKRootController>();
|
||||
rootController.Calibrate(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rootController != null) GameObject.Destroy(rootController);
|
||||
}
|
||||
|
||||
// Additional solver settings
|
||||
ik.solver.spine.minHeadHeight = 0f;
|
||||
ik.solver.locomotion.weight = bodyTracker == null && leftFootTracker == null && rightFootTracker == null ? 1f : 0f;
|
||||
}
|
||||
|
||||
private static void CalibrateLeg(CalibrationData data, Transform tracker, IKSolverVR.Leg leg, Transform lastBone, Vector3 rootForward, bool isLeft)
|
||||
{
|
||||
if (isLeft && data.leftFoot == null) return;
|
||||
if (!isLeft && data.rightFoot == null) return;
|
||||
|
||||
string name = isLeft ? "Left" : "Right";
|
||||
Transform target = leg.target == null ? (new GameObject(name + " Foot Target")).transform : leg.target;
|
||||
|
||||
target.parent = tracker;
|
||||
|
||||
if (isLeft) data.leftFoot.SetTo(target);
|
||||
else data.rightFoot.SetTo(target);
|
||||
|
||||
leg.target = target;
|
||||
|
||||
leg.positionWeight = 1f;
|
||||
leg.rotationWeight = 1f;
|
||||
|
||||
// Bend goal
|
||||
Transform bendGoal = leg.bendGoal == null ? (new GameObject(name + " Leg Bend Goal")).transform : leg.bendGoal;
|
||||
bendGoal.parent = tracker;
|
||||
|
||||
if (isLeft) data.leftLegGoal.SetTo(bendGoal);
|
||||
else data.rightLegGoal.SetTo(bendGoal);
|
||||
|
||||
leg.bendGoal = bendGoal;
|
||||
leg.bendGoalWeight = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple calibration to head and hands using predefined anchor position and rotation offsets.
|
||||
/// </summary>
|
||||
/// <param name="ik">The VRIK component.</param>
|
||||
/// <param name="centerEyeAnchor">HMD.</param>
|
||||
/// <param name="leftHandAnchor">Left hand controller.</param>
|
||||
/// <param name="rightHandAnchor">Right hand controller.</param>
|
||||
/// <param name="centerEyePositionOffset">Position offset of the camera from the head bone (root space).</param>
|
||||
/// <param name="centerEyeRotationOffset">Rotation offset of the camera from the head bone (root space).</param>
|
||||
/// <param name="handPositionOffset">Position offset of the hand controller from the hand bone (controller space).</param>
|
||||
/// <param name="handRotationOffset">Rotation offset of the hand controller from the hand bone (controller space).</param>
|
||||
/// <param name="scaleMlp">Multiplies the scale of the root.</param>
|
||||
/// <returns></returns>
|
||||
public static CalibrationData Calibrate(VRIK ik, Transform centerEyeAnchor, Transform leftHandAnchor, Transform rightHandAnchor, Vector3 centerEyePositionOffset, Vector3 centerEyeRotationOffset, Vector3 handPositionOffset, Vector3 handRotationOffset, float scaleMlp = 1f)
|
||||
{
|
||||
CalibrateHead(ik, centerEyeAnchor, centerEyePositionOffset, centerEyeRotationOffset);
|
||||
CalibrateHands(ik, leftHandAnchor, rightHandAnchor, handPositionOffset, handRotationOffset);
|
||||
CalibrateScale(ik, scaleMlp);
|
||||
|
||||
// Fill in Calibration Data
|
||||
CalibrationData data = new CalibrationData();
|
||||
data.scale = ik.references.root.localScale.y;
|
||||
data.head = new CalibrationData.Target(ik.solver.spine.headTarget);
|
||||
data.leftHand = new CalibrationData.Target(ik.solver.leftArm.target);
|
||||
data.rightHand = new CalibrationData.Target(ik.solver.rightArm.target);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calibrates head IK target to specified anchor position and rotation offset independent of avatar bone orientations.
|
||||
/// </summary>
|
||||
public static void CalibrateHead(VRIK ik, Transform centerEyeAnchor, Vector3 anchorPositionOffset, Vector3 anchorRotationOffset)
|
||||
{
|
||||
if (ik.solver.spine.headTarget == null) ik.solver.spine.headTarget = new GameObject("Head IK Target").transform;
|
||||
|
||||
Vector3 forward = Quaternion.Inverse(ik.references.head.rotation) * ik.references.root.forward;
|
||||
Vector3 up = Quaternion.Inverse(ik.references.head.rotation) * ik.references.root.up;
|
||||
Quaternion headSpace = Quaternion.LookRotation(forward, up);
|
||||
|
||||
Vector3 anchorPos = ik.references.head.position + ik.references.head.rotation * headSpace * anchorPositionOffset;
|
||||
Quaternion anchorRot = ik.references.head.rotation * headSpace * Quaternion.Euler(anchorRotationOffset);
|
||||
Quaternion anchorRotInverse = Quaternion.Inverse(anchorRot);
|
||||
|
||||
ik.solver.spine.headTarget.parent = centerEyeAnchor;
|
||||
ik.solver.spine.headTarget.localPosition = anchorRotInverse * (ik.references.head.position - anchorPos);
|
||||
ik.solver.spine.headTarget.localRotation = anchorRotInverse * ik.references.head.rotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calibrates body target to avatar pelvis position and position/rotation offsets in character root space.
|
||||
/// </summary>
|
||||
public static void CalibrateBody(VRIK ik, Transform pelvisTracker, Vector3 trackerPositionOffset, Vector3 trackerRotationOffset)
|
||||
{
|
||||
if (ik.solver.spine.pelvisTarget == null) ik.solver.spine.pelvisTarget = new GameObject("Pelvis IK Target").transform;
|
||||
|
||||
ik.solver.spine.pelvisTarget.position = ik.references.pelvis.position + ik.references.root.rotation * trackerPositionOffset;
|
||||
ik.solver.spine.pelvisTarget.rotation = ik.references.root.rotation * Quaternion.Euler(trackerRotationOffset);
|
||||
ik.solver.spine.pelvisTarget.parent = pelvisTracker;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calibrates hand IK targets to specified anchor position and rotation offsets independent of avatar bone orientations.
|
||||
/// </summary>
|
||||
public static void CalibrateHands(VRIK ik, Transform leftHandAnchor, Transform rightHandAnchor, Vector3 anchorPositionOffset, Vector3 anchorRotationOffset)
|
||||
{
|
||||
if (ik.solver.leftArm.target == null) ik.solver.leftArm.target = new GameObject("Left Hand IK Target").transform;
|
||||
if (ik.solver.rightArm.target == null) ik.solver.rightArm.target = new GameObject("Right Hand IK Target").transform;
|
||||
|
||||
CalibrateHand(ik, leftHandAnchor, anchorPositionOffset, anchorRotationOffset, true);
|
||||
CalibrateHand(ik, rightHandAnchor, anchorPositionOffset, anchorRotationOffset, false);
|
||||
}
|
||||
|
||||
private static void CalibrateHand(VRIK ik, Transform anchor, Vector3 positionOffset, Vector3 rotationOffset, bool isLeft)
|
||||
{
|
||||
if (isLeft)
|
||||
{
|
||||
positionOffset.x = -positionOffset.x;
|
||||
rotationOffset.y = -rotationOffset.y;
|
||||
rotationOffset.z = -rotationOffset.z;
|
||||
}
|
||||
|
||||
var hand = isLeft ? ik.references.leftHand : ik.references.rightHand;
|
||||
var forearm = isLeft ? ik.references.leftForearm : ik.references.rightForearm;
|
||||
var target = isLeft ? ik.solver.leftArm.target : ik.solver.rightArm.target;
|
||||
|
||||
Vector3 forward = isLeft ? ik.solver.leftArm.wristToPalmAxis : ik.solver.rightArm.wristToPalmAxis;
|
||||
if (forward == Vector3.zero) forward = VRIKCalibrator.GuessWristToPalmAxis(hand, forearm);
|
||||
|
||||
Vector3 up = isLeft ? ik.solver.leftArm.palmToThumbAxis : ik.solver.rightArm.palmToThumbAxis;
|
||||
if (up == Vector3.zero) up = VRIKCalibrator.GuessPalmToThumbAxis(hand, forearm);
|
||||
|
||||
Quaternion handSpace = Quaternion.LookRotation(forward, up);
|
||||
Vector3 anchorPos = hand.position + hand.rotation * handSpace * positionOffset;
|
||||
Quaternion anchorRot = hand.rotation * handSpace * Quaternion.Euler(rotationOffset);
|
||||
Quaternion anchorRotInverse = Quaternion.Inverse(anchorRot);
|
||||
|
||||
target.parent = anchor;
|
||||
target.localPosition = anchorRotInverse * (hand.position - anchorPos);
|
||||
target.localRotation = anchorRotInverse * hand.rotation;
|
||||
}
|
||||
|
||||
public static Vector3 GuessWristToPalmAxis(Transform hand, Transform forearm)
|
||||
{
|
||||
Vector3 toForearm = forearm.position - hand.position;
|
||||
Vector3 axis = AxisTools.ToVector3(AxisTools.GetAxisToDirection(hand, toForearm));
|
||||
if (Vector3.Dot(toForearm, hand.rotation * axis) > 0f) axis = -axis;
|
||||
return axis;
|
||||
}
|
||||
|
||||
public static Vector3 GuessPalmToThumbAxis(Transform hand, Transform forearm)
|
||||
{
|
||||
if (hand.childCount == 0)
|
||||
{
|
||||
Debug.LogWarning("Hand " + hand.name + " does not have any fingers, VRIK can not guess the hand bone's orientation. Please assign 'Wrist To Palm Axis' and 'Palm To Thumb Axis' manually for both arms in VRIK settings.", hand);
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
float closestSqrMag = Mathf.Infinity;
|
||||
int thumbIndex = 0;
|
||||
|
||||
for (int i = 0; i < hand.childCount; i++)
|
||||
{
|
||||
float sqrMag = Vector3.SqrMagnitude(hand.GetChild(i).position - hand.position);
|
||||
if (sqrMag < closestSqrMag)
|
||||
{
|
||||
closestSqrMag = sqrMag;
|
||||
thumbIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 handNormal = Vector3.Cross(hand.position - forearm.position, hand.GetChild(thumbIndex).position - hand.position);
|
||||
Vector3 toThumb = Vector3.Cross(handNormal, hand.position - forearm.position);
|
||||
Vector3 axis = AxisTools.ToVector3(AxisTools.GetAxisToDirection(hand, toThumb));
|
||||
if (Vector3.Dot(toThumb, hand.rotation * axis) < 0f) axis = -axis;
|
||||
return axis;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a19b811591057074d98d5ab94aaf496a
|
||||
timeCreated: 1499778189
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using RootMotion.FinalIK;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Simple VRIK LOD level controller based on renderer.isVisible and camera distance.
|
||||
/// </summary>
|
||||
public class VRIKLODController : MonoBehaviour
|
||||
{
|
||||
|
||||
public Renderer LODRenderer;
|
||||
public float LODDistance = 15f;
|
||||
public bool allowCulled = true;
|
||||
|
||||
private VRIK ik;
|
||||
|
||||
void Start()
|
||||
{
|
||||
ik = GetComponent<VRIK>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
ik.solver.LOD = GetLODLevel();
|
||||
}
|
||||
|
||||
// Setting LOD level to 1 saves approximately 20% of solving time. LOD level 2 means IK is culled, with only root position and rotation updated if locomotion enabled.
|
||||
private int GetLODLevel()
|
||||
{
|
||||
if (allowCulled)
|
||||
{
|
||||
if (LODRenderer == null) return 0;
|
||||
if (!LODRenderer.isVisible) return 2;
|
||||
}
|
||||
|
||||
// Set LOD to 1 if too far from camera
|
||||
float sqrMag = (ik.transform.position - Camera.main.transform.position).sqrMagnitude;
|
||||
if (sqrMag > LODDistance * LODDistance) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1158802e47cf684439400e5c8e03cba9
|
||||
timeCreated: 1549982641
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,80 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
public class VRIKRootController : MonoBehaviour
|
||||
{
|
||||
|
||||
public Vector3 pelvisTargetRight { get; private set; }
|
||||
|
||||
private Transform pelvisTarget;
|
||||
private Transform leftFootTarget;
|
||||
private Transform rightFootTarget;
|
||||
private VRIK ik;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
ik = GetComponent<VRIK>();
|
||||
ik.solver.OnPreUpdate += OnPreUpdate;
|
||||
Calibrate();
|
||||
}
|
||||
|
||||
public void Calibrate()
|
||||
{
|
||||
if (ik == null)
|
||||
{
|
||||
Debug.LogError("No VRIK found on VRIKRootController's GameObject.", transform);
|
||||
return;
|
||||
}
|
||||
pelvisTarget = ik.solver.spine.pelvisTarget;
|
||||
leftFootTarget = ik.solver.leftLeg.target;
|
||||
rightFootTarget = ik.solver.rightLeg.target;
|
||||
if (pelvisTarget != null) pelvisTargetRight = Quaternion.Inverse(pelvisTarget.rotation) * ik.references.root.right;
|
||||
}
|
||||
|
||||
public void Calibrate(VRIKCalibrator.CalibrationData data)
|
||||
{
|
||||
if (ik == null)
|
||||
{
|
||||
Debug.LogError("No VRIK found on VRIKRootController's GameObject.", transform);
|
||||
return;
|
||||
}
|
||||
pelvisTarget = ik.solver.spine.pelvisTarget;
|
||||
leftFootTarget = ik.solver.leftLeg.target;
|
||||
rightFootTarget = ik.solver.rightLeg.target;
|
||||
if (pelvisTarget != null)
|
||||
{
|
||||
pelvisTargetRight = data.pelvisTargetRight;
|
||||
}
|
||||
}
|
||||
|
||||
void OnPreUpdate()
|
||||
{
|
||||
if (!enabled) return;
|
||||
|
||||
if (pelvisTarget != null)
|
||||
{
|
||||
ik.references.root.position = new Vector3(pelvisTarget.position.x, ik.references.root.position.y, pelvisTarget.position.z);
|
||||
|
||||
Vector3 f = Vector3.Cross(pelvisTarget.rotation * pelvisTargetRight, ik.references.root.up);
|
||||
f.y = 0f;
|
||||
ik.references.root.rotation = Quaternion.LookRotation(f);
|
||||
|
||||
ik.references.pelvis.position = Vector3.Lerp(ik.references.pelvis.position, pelvisTarget.position, ik.solver.spine.pelvisPositionWeight);
|
||||
ik.references.pelvis.rotation = Quaternion.Slerp(ik.references.pelvis.rotation, pelvisTarget.rotation, ik.solver.spine.pelvisRotationWeight);
|
||||
}
|
||||
else if (leftFootTarget != null && rightFootTarget != null)
|
||||
{
|
||||
ik.references.root.position = Vector3.Lerp(leftFootTarget.position, rightFootTarget.position, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (ik != null) ik.solver.OnPreUpdate -= OnPreUpdate;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d0d2eb6d6e88294dbaf39641e718d71
|
||||
timeCreated: 1500635579
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user