/*************************************************************************/ void Start() { m_gameObjectScale = gameObject.transform.localScale; m_BlendedRotations = new Quaternion[m_LookAtBones.Length]; m_LastFrameRotations = new Quaternion[m_LookAtBones.Length]; m_IsValid = CheckValidity(); for (int i = 0; i < m_LookAtBones.Length; i++) { PerfectLookAtData lookAtBoneData = m_LookAtBones[i]; PerfecLookAtLinkedBones[] currentLinkedBones = lookAtBoneData.m_LinkedBones; lookAtBoneData.SetDefaultRotation(lookAtBoneData.m_Bone.localRotation); m_LastFrameRotations[i] = lookAtBoneData.m_Bone.localRotation; lookAtBoneData.CheckJointRotation(); for (int j = 0; j < currentLinkedBones.Length; j++) { currentLinkedBones[j].SetDefaultRotation(currentLinkedBones[j].m_Bone.localRotation); currentLinkedBones[j].SetLastFrameRotation(currentLinkedBones[j].m_Bone.rotation); } } //Initialize Leg Bones for Leg IK. for (int i = 0; i < m_legStabilizers.Length; i++) { m_legStabilizers[i].Initialize(); } }
/*************************************************************************/ private bool CheckValidity() { bool ret = true; for (int i = 0; i < m_LookAtBones.Length; i++) { PerfectLookAtData lookAtData = m_LookAtBones[i]; if (lookAtData.m_Bone == null) { ret = false; break; } for (int j = 0; j < lookAtData.m_LinkedBones.Length; j++) { if (lookAtData.m_LinkedBones[j].m_Bone == null) { ret = false; break; } } } return(ret); }
/*************************************************************************/ private void CheckForForceDefaultRotation() { for (int i = 0; i < m_LookAtBones.Length; i++) { PerfectLookAtData lookAtData = m_LookAtBones[i]; if (lookAtData.m_ResetToDefaultRotation) { lookAtData.m_Bone.transform.localRotation = lookAtData.GetDefaultRotation(); } for (int j = 0; j < lookAtData.m_LinkedBones.Length; j++) { PerfecLookAtLinkedBones linkedBoneData = lookAtData.m_LinkedBones[j]; if (linkedBoneData.m_ResetToDefaultRotation) { linkedBoneData.m_Bone.transform.localRotation = linkedBoneData.GetDefaultRotation(); } } } }
/*************************************************************************/ void Start() { m_IsValid = CheckValidity(); m_BlendedRotations = new Quaternion[m_LookAtBones.Length]; m_LastFrameRotations = new Quaternion[m_LookAtBones.Length]; for (int i = 0; i < m_LookAtBones.Length; i++) { PerfectLookAtData lookAtBoneData = m_LookAtBones[i]; PerfecLookAtLinkedBones[] currentLinkedBones = lookAtBoneData.m_LinkedBones; lookAtBoneData.SetDefaultRotation(lookAtBoneData.m_Bone.localRotation); m_LastFrameRotations[i] = lookAtBoneData.m_Bone.localRotation; lookAtBoneData.CheckJointRotation(); for (int j = 0; j < currentLinkedBones.Length; j++) { currentLinkedBones[j].SetDefaultRotation(currentLinkedBones[j].m_Bone.localRotation); currentLinkedBones[j].SetLastFrameRotation(currentLinkedBones[j].m_Bone.rotation); } } }
/*************************************************************************/ void LateUpdate() { if (m_TargetObject == null) { Debug.LogWarning("No target object set for the component. Component won't work without a target object", this); return; } if (!m_IsValid) { Debug.LogWarning("Missing bones in PerfectlookAt component. Component won't work unless all bones are set", this); return; } if (m_Weight < Mathf.Epsilon) { //updating last frame rotation even if the weight is zero for (int i = 0; i < m_LookAtBones.Length; i++) { PerfectLookAtData lookAtBoneData = m_LookAtBones[i]; m_LastFrameRotations[i] = lookAtBoneData.m_Bone.localRotation; for (int j = 0; j < lookAtBoneData.m_LinkedBones.Length; j++) { lookAtBoneData.m_LinkedBones[j].SetLastFrameRotation(lookAtBoneData.m_LinkedBones[j].m_Bone.rotation); } } return; } //Updating the bones rotations if (m_LookAtBones.Length > 0) { //if scale has changed the leg stabilizers get reinitialized CheckIfCharacterScaleChanged(); CheckForForceDefaultRotation(); //Cache Leg Bones for IK Leg Fix for (int i = 0; i < m_legStabilizers.Length; i++) { m_legStabilizers[i].CacheBones(); } Dictionary <int, Quaternion> linkedBonesRotations = new Dictionary <int, Quaternion>(); Vector3 rotatedInitFwdVec = GetForwardVector(ref m_LookAtBones[0].m_Bone, m_LookAtBones[0].m_ForwardAxis); Vector3 currentFwdVec; Vector3 parentFwdVec; Vector3 targetVector = m_TargetObject.transform.position - m_LookAtBones[0].m_Bone.position; //current vector is being updated in UpdateCurrentTargetVector Vector3 firstBoneRotatedInitFwdVec = rotatedInitFwdVec; byte numBonesToRotate = 1; for (int i = 0; i < m_LookAtBones.Length; i++) { Transform currentBone = m_LookAtBones[i].m_Bone; Transform parentBone = currentBone.parent;; float rotationLimit = m_LookAtBones[i].m_RotationLimit; bool parentExists = true; currentFwdVec = GetForwardVector(ref currentBone, m_LookAtBones[i].m_ForwardAxis); parentFwdVec = GetForwardVector(ref parentBone, m_LookAtBones[i].m_ParentBoneForwardAxis); if (parentBone != null) { currentFwdVec = GetForwardVector(ref currentBone, m_LookAtBones[i].m_ForwardAxis); parentFwdVec = GetForwardVector(ref parentBone, m_LookAtBones[i].m_ParentBoneForwardAxis); float diffAngleFromAnim = Vector3.Angle(currentFwdVec, parentFwdVec); //in case animation already has a bone with relative rotation higher than the limit specified by user if (diffAngleFromAnim > rotationLimit) { rotationLimit = diffAngleFromAnim; } } else { parentExists = false; } Quaternion lookAtRot = GetWorldLookAtRotation(targetVector, rotatedInitFwdVec); if (m_LookAtBones[i].m_RotateAroundUpVectorWeight > 0.0f) { Vector3 currentRotationAxis; currentRotationAxis.x = lookAtRot.x; currentRotationAxis.y = lookAtRot.y; currentRotationAxis.z = lookAtRot.z; //checking the up vector direction for rotation float rotationSign = Mathf.Sign(Vector3.Cross(firstBoneRotatedInitFwdVec, targetVector).y); Vector3 finalUpVector = rotationSign * m_UpVector; finalUpVector = Vector3.Lerp(currentRotationAxis, finalUpVector, m_LookAtBones[i].m_RotateAroundUpVectorWeight); lookAtRot = QuaternionFromAngleAxis(ref finalUpVector, GetAngleFromQuaternionRad(lookAtRot)); } Quaternion childRotation = lookAtRot * currentBone.rotation; if (parentExists) { //checking the angle difference float diffAngle = Mathf.Abs(Vector3.Angle(parentFwdVec, lookAtRot * currentFwdVec)) - rotationLimit; if (diffAngle > 0) { { Vector3 rotationAxis = new Vector3(lookAtRot.x, lookAtRot.y, lookAtRot.z); float limitAngleRad = GetAngleFromQuaternionRad(ref lookAtRot) + Mathf.Deg2Rad * (-diffAngle); lookAtRot = QuaternionFromAngleAxis(ref rotationAxis, limitAngleRad); childRotation = lookAtRot * currentBone.rotation; // blending with the rotation before assigning it to bone so it can have no effect on rotaiton limit checking // and we can have updated bone position for calculating the rotated fwd vec and target vector Quaternion currentBoneRotationLS = currentBone.localRotation; m_LookAtBones[i].m_Bone.rotation = childRotation; m_BlendedRotations[i] = PerfectLookAtSlerp(currentBoneRotationLS, m_LookAtBones[i].m_Bone.localRotation, m_Weight); if (m_LookAtBones[i].m_LinkedBones.Length > 0) { linkedBonesRotations.Add(i, PerfectLookAtSlerp(Quaternion.identity, lookAtRot, m_Weight)); } } // if (i != m_LookAtBones.Length - 1) { Vector3 currentBoneToParentPosDiff = m_LookAtBones[0].m_Bone.position - m_LookAtBones[i + 1].m_Bone.position; Vector3 firstBoneToTargetDiff = m_TargetObject.transform.position - m_LookAtBones[0].m_Bone.position; rotatedInitFwdVec = GetForwardVector(ref m_LookAtBones[0].m_Bone, m_LookAtBones[0].m_ForwardAxis); rotatedInitFwdVec.Normalize(); rotatedInitFwdVec = firstBoneToTargetDiff.magnitude * rotatedInitFwdVec; rotatedInitFwdVec = currentBoneToParentPosDiff + rotatedInitFwdVec; targetVector = currentBoneToParentPosDiff + firstBoneToTargetDiff; numBonesToRotate++; if (m_DrawDebugLookAtLines) { Debug.DrawLine(m_LookAtBones[i + 1].m_Bone.position, m_LookAtBones[i + 1].m_Bone.position + rotatedInitFwdVec, Color.green); Debug.DrawLine(m_LookAtBones[i + 1].m_Bone.position, m_LookAtBones[i + 1].m_Bone.position + targetVector, Color.red); } } } else { Quaternion currentBoneRotationLS = currentBone.localRotation; m_LookAtBones[i].m_Bone.rotation = childRotation; m_BlendedRotations[i] = PerfectLookAtSlerp(currentBoneRotationLS, m_LookAtBones[i].m_Bone.localRotation, m_Weight); if (m_LookAtBones[i].m_LinkedBones.Length > 0) { linkedBonesRotations.Add(i, PerfectLookAtSlerp(Quaternion.identity, lookAtRot, m_Weight)); } break; } } else { Quaternion currentBoneRotationLS = currentBone.localRotation; m_LookAtBones[i].m_Bone.rotation = childRotation; m_BlendedRotations[i] = PerfectLookAtSlerp(currentBoneRotationLS, m_LookAtBones[i].m_Bone.localRotation, m_Weight); if (m_LookAtBones[i].m_LinkedBones.Length > 0) { linkedBonesRotations.Add(i, PerfectLookAtSlerp(Quaternion.identity, lookAtRot, m_Weight)); } if (i < m_LookAtBones.Length - 1) { Debug.LogWarning("Warning Bone name doesn't have a parent. The rest of the PerfectLookAt bone chain won't work after this bone!", this); break; } } } //Setting bones rotations final pass. The initial bone rotations are set during the last loop. //These two loops are used for blending and smooth movement. //Updating last frame rotations. Two loops are separated to have less jumps in memory and be more cache friendly. bool isBlending = Mathf.Abs(m_Weight - 1.0f) > Mathf.Epsilon; // Calculating the blend weight to blend between the current frame and last frame to achieve smooth rotations in dynamic animations with large range of movements. float smoothingWeight = Mathf.Clamp(m_LookAtBlendSpeed * Time.deltaTime, 0.0f, 1.0f); //Linearly blend the smoothingWeight to zero and its current frame value to be sure when perfect look at blend weight is zero- //the rotation is exactly the same as animation and no last frame is getting blended in. smoothingWeight = (smoothingWeight - 1.0f) * m_Weight + 1.0f; for (int k = 0; k < m_LookAtBones.Length; k++) { PerfectLookAtData lookAtBoneData = m_LookAtBones[k]; Quaternion boneLocalRotation; if (isBlending && k < numBonesToRotate) { boneLocalRotation = m_BlendedRotations[k]; } else { boneLocalRotation = lookAtBoneData.m_Bone.localRotation; } Quaternion localRotationFromAnim = lookAtBoneData.m_Bone.localRotation; lookAtBoneData.m_Bone.localRotation = PerfectLookAtSlerp(m_LastFrameRotations[k], boneLocalRotation, smoothingWeight); m_LastFrameRotations[k] = lookAtBoneData.m_Bone.localRotation; } // updating linked bones for (int m = 0; m < m_LookAtBones.Length; m++) { PerfectLookAtData lookAtBoneData = m_LookAtBones[m]; if (lookAtBoneData.m_LinkedBones.Length > 0) { if (m < numBonesToRotate) { Quaternion linkedBoneLookAtQuat = linkedBonesRotations[m]; for (int n = 0; n < lookAtBoneData.m_LinkedBones.Length; n++) { Transform linkedBone = lookAtBoneData.m_LinkedBones[n].m_Bone; linkedBone.rotation = PerfectLookAtSlerp(lookAtBoneData.m_LinkedBones[n].GetLastFrameRotation(), linkedBoneLookAtQuat * linkedBone.rotation, smoothingWeight); lookAtBoneData.m_LinkedBones[n].SetLastFrameRotation(linkedBone.rotation); } } else { for (int n = 0; n < lookAtBoneData.m_LinkedBones.Length; n++) { lookAtBoneData.m_LinkedBones[n].SetLastFrameRotation(lookAtBoneData.m_LinkedBones[n].m_Bone.rotation); } } } } //Fix Leg Bones for IK Leg Fix for (int i = 0; i < m_legStabilizers.Length; i++) { m_legStabilizers[i].FixLeg(LegStabilizerMaxIterations, m_LegStabilizerMinDistanceToStartSolving); } } }