/// <summary> /// 현재 호출한 Bone Unit을 시작으로 Tail 방향으로 World를 갱신한다. /// Parent의 PosW, AngleWorld가 갱신되었어야 한다. /// IK의 핵심이 되는 _angleLocal_Next가 계산된 상태여야 한다. /// </summary> public void CalculateWorldRecursive() { if (_parentChainUnit != null) { //Parent 기준으로 Pos를 갱신한다. _parentPosW = _parentChainUnit._bonePosW; _angleWorld_Parent = _parentChainUnit._angleWorld_Next; _bonePosW.x = _parentPosW.x + _parentChainUnit._lengthBoneToTarget * Mathf.Cos(_angleWorld_Parent * Mathf.Deg2Rad); _bonePosW.y = _parentPosW.y + _parentChainUnit._lengthBoneToTarget * Mathf.Sin(_angleWorld_Parent * Mathf.Deg2Rad); } //Local Angle에 따라 World Angle을 갱신한다. _angleWorld_Next = _angleLocal_Next + _angleWorld_Parent; //Child Unit도 같이 갱신해주자 if (_childChainUnit != null) { _childChainUnit.CalculateWorldRecursive(); } else { //엥 여기가 Tail인가염 _targetPosW.x = _bonePosW.x + _lengthBoneToTarget * Mathf.Cos(_angleWorld_Next * Mathf.Deg2Rad); _targetPosW.y = _bonePosW.y + _lengthBoneToTarget * Mathf.Sin(_angleWorld_Next * Mathf.Deg2Rad); } }
/// <summary> /// IK를 시뮬레이션한다. /// 요청한 Bone을 Tail로 하여 Head까지 처리한다. /// 결과값은 Delta Angle로 나오며, 이 값을 참조하여 결정한다. (Matrix 중 어디에 쓸지는 외부에서 결정) /// </summary> /// <param name="targetPosW"></param> public bool SimulateIK(Vector2 targetPosW, bool isContinuous) { if (!_bone._isIKTail) { //Debug.LogError("Failed 1 - _bone._isIKTail : " + _bone._isIKTail); return(false); } if (_chainUnits.Count == 0) { //Debug.LogError("Failed 2 - _chainUnits.Count : " + _chainUnits.Count); return(false); } apOptBoneIKChainUnit chainUnit = null; //[Tail] .....[] .... [Head] //Tail에 가까운(인덱스가 가장 작은) Constraint가 적용된 Bone을 구한다. //Head에 가까운(인덱스가 가장 큰) Constraint가 적용된 Bone을 구한다. float lengthTotal = 0.0f; //1. Simulate 준비 //Debug.Log("ReadyToSimulate : " + _chainUnits.Count); for (int i = 0; i < _chainUnits.Count; i++) { chainUnit = _chainUnits[i]; chainUnit.ReadyToSimulate(); lengthTotal += chainUnit._lengthBoneToTarget; } if (_tailChainUnit == null) { _tailChainUnit = _chainUnits[0]; } if (_headChainUnit == null) { _headChainUnit = _chainUnits[_chainUnits.Count - 1]; } //1. 길이 확인 후 압축을 해야하는지 적용 float length2Target = (targetPosW - _headChainUnit._bonePosW).magnitude; //Debug.Log("1) Length to Target : " + length2Target); //Debug.Log("Target PosW : " + targetPosW + " <- Head : " + _headChainUnit._bonePosW + " [" + _headChainUnit._baseBone._name + "]"); float length2Tail = (_tailChainUnit._targetPosW - _headChainUnit._bonePosW).magnitude; //Debug.Log("2) Length to Tail : " + length2Tail); //Debug.Log("Tail : " + _tailChainUnit._targetPosW + " [" + _tailChainUnit._baseBone._name + "] <- Head : " + _headChainUnit._bonePosW + " [" + _headChainUnit._baseBone._name + "]"); if (length2Tail == 0.0f) { //Debug.LogError("Failed 3 - length2Tail : " + length2Tail); //Debug.LogError("_tailChainUnit._targetPosW : " + _tailChainUnit._targetPosW); //Debug.LogError("_headChainUnit._bonePosW : " + _headChainUnit._bonePosW); return(false); } float beforSqrDist = (targetPosW - _tailChainUnit._bonePosW).sqrMagnitude; apOptBoneIKChainUnit curBoneUnit = null; if (length2Target < lengthTotal) { //압축을 해야한다. float compressRatio = Mathf.Clamp01(length2Target / lengthTotal); //float compressAngle = Mathf.Acos(compressRatio) * Mathf.Rad2Deg; //Vector2 dirHeadToTarget = targetPosW - _headChainUnit._bonePosW; for (int i = 0; i < _chainUnits.Count; i++) { curBoneUnit = _chainUnits[i]; if (curBoneUnit._isAngleContraint) { curBoneUnit._angleLocal_Next = curBoneUnit._angleDir_Preferred * (1.0f - compressRatio) + curBoneUnit._angleLocal_Next + compressRatio; //Preferred를 적용했다는 것을 알려주자 curBoneUnit._isPreferredAngleAdapted = true; } } _headChainUnit.CalculateWorldRecursive(); } else if (length2Target > lengthTotal + 1.0f) //Bias 추가해서 플래그 리셋 { for (int i = 0; i < _chainUnits.Count; i++) { _chainUnits[i]._isPreferredAngleAdapted = false; } } curBoneUnit = null; int nCalculate = 1; for (int i = 0; i < _nLoop; i++) { curBoneUnit = _tailChainUnit; while (true) { //루프를 돕시다. curBoneUnit.RequestIK(targetPosW, isContinuous); curBoneUnit.CalculateWorldRecursive(); //Debug.Log(" curBoneUnit._angleWorld_Next if (curBoneUnit._parentChainUnit != null) { curBoneUnit = curBoneUnit._parentChainUnit; } else { break; } } //마지막으로 Tail에서 처리 한번더 curBoneUnit = _tailChainUnit; //curBoneUnit.RequestIK(targetPosW, i, _nLoop); curBoneUnit.RequestIK(targetPosW, isContinuous); curBoneUnit.CalculateWorldRecursive(); nCalculate++; } //만약 Continuous 모드에서 각도가 너무 많이 차이가 나면 실패한 처리다. //이전 요청 좌표와 거리가 적은 경우 유효 if (isContinuous) { if (_isContinuousPrevPos) { float distTargetDelta = Vector2.Distance(_prevTargetPosW, targetPosW); if (distTargetDelta < CONTINUOUS_TARGET_POS_BIAS) { //연속된 위치 입력인 경우 //전체의 각도 크기를 구하자 float totalDeltaAngle = 0.0f; for (int i = 0; i < _chainUnits.Count; i++) { totalDeltaAngle += Mathf.Abs(_chainUnits[i]._angleLocal_Delta); } //Debug.Log("Cont Move : " + distTargetDelta + " / Delta Angle : " + totalDeltaAngle); if (totalDeltaAngle > CONTINUOUS_ANGLE_JUMP_LIMIT) { //너무 많이 움직였다. _isContinuousPrevPos = true; _prevTargetPosW = targetPosW; //Debug.LogError("Angle Jump Error : Total Angle : " + totalDeltaAngle + " / Delta Target : " + distTargetDelta); return(false); } } } _isContinuousPrevPos = true; _prevTargetPosW = targetPosW; } else { _isContinuousPrevPos = false; } if (isContinuous && length2Target < lengthTotal) { float afterSqrdist = (_tailChainUnit._targetPosW - targetPosW).sqrMagnitude; if (beforSqrDist * 1.2f < afterSqrdist) { //오히려 더 멀어졌다. //Debug.LogError("다시 멀어졌다"); //Debug.LogError("Failed 4 - length2Target < lengthTotal : " + length2Target + " < " + lengthTotal); return(false); } } _requestedTargetPosW = _tailChainUnit._targetPosW; _requestedBonePosW = _tailChainUnit._bonePosW; return(true); }