CCD IK(循环坐标下降逆动态学)

本文介绍了CCDIK策略,通过骨骼层级结构进行子节点旋转,逐步将每个骨骼调整到目标位置,迭代次数影响最终效果。关键概念包括箭头追踪、角度限制和CCD Solver的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CCDIK(循环坐标下降逆动态学)

策略思路
1.骨骼间的结构为子父层次链接(父节点的变换会影响子节点的)
2.每个骨骼都以【自身轴点到尾叶子节点的方向】旋转到【自身轴点到目标点方向】,开始趋近
3.最终效果与迭代次数成正比
在这里插入图片描述
更多细节查看 IK

Arrow类

    [Serializable]
    public class Arrow
    {
        public float len;		//箭头长度
        public float angle;		//相对父箭头方向的角度
        public Vector2 angleLimt = new Vector2(-180f,180f);		//角度限制

        public Color color;			//Gizmo Color

        public Vector2 a { get; private set; }  //起点
        public Vector2 b { get; private set; }  //终点
        public Vector2 forward { get { return (b - a).normalized; } }

        /// <summary>
        /// 箭头看向目标位置
        /// </summary>
        /// <param name="target">目标位置</param>
        /// <param name="end">评估节点位置</param>
        /// <param name="limt">角度限制</param>
        public void Follow(Vector2 target, Vector2 end, bool limt)
        {
            angle = (angle + Vector2.SignedAngle(end - a, target - a)) % 360f;

            if (limt && (angleLimt.x != -180 || angleLimt.y != 180))
            {
                angle = Mathf.Clamp(angle, angleLimt.x, angleLimt.y);
            }
        }
        /// <summary>
        /// 计算箭头 A、B 点位置
        /// </summary>
        /// <param name="origin">起点</param>
        /// <param name="right">父箭头方向</param>
        public void Calculate_AB(Vector2 origin, Vector2 right)
        {
            a = origin;
            b = a + Rotate(right, angle) * len;
        }

        /// <summary>
        /// 返回旋转后的角度
        /// </summary>
        /// <param name="v">normal</param>
        /// <param name="a">rad</param>
        private Vector2 Rotate(Vector2 v, float a)
        {
            a = a * Mathf.Deg2Rad + Mathf.Atan2(v.y, v.x);
            return new Vector2(Mathf.Cos(a), Mathf.Sin(a));
        }
    }

IKSolverCCD 类

    [Serializable]
    public class IKSolverCCD {

        public Arrow[] arrows = new Arrow[] { };    //箭头集合

        public Vector2 target;  //目标位置

        public bool useLimt = false;        //是否启用角度限制

        public int iterations = 4;      //迭代次数

        /// <summary>
        /// 箭头线段跟踪目标位置
        /// </summary>
        /// <param name="maxIterations">迭代次数</param>
        public void FollowTarget()
        {
            for (int n = 0; n < iterations; n++)
            {
                for (int i = arrows.Length - 1; i >= 0; i--)
                {
                    arrows[i].Follow(target, arrows[arrows.Length - 1].b, useLimt);

                    UpdatePosition(i == 0 ? Vector2.right : arrows[i - 1].forward, i);
                }
            }
        }

        /// <summary>
        /// 集合箭头从指定顺序索引开始更新箭头 A、B 点位置
        /// </summary>
        /// <param name="arrows">箭头集合</param>
        /// <param name="right">父箭头方向</param>
        /// <param name="n">指定顺序索引</param>
        public void UpdatePosition(Vector2 right, int n = 0)
        {
            Vector2 origin = arrows[n].a;
            for (; n < arrows.Length; n++)
            {
                arrows[n].Calculate_AB(origin, right);
                origin = arrows[n].b;
                right = arrows[n].forward;
            }
        }
    }

CCDTest类

    public class CCDTest : MonoBehaviour
    {
        public IKSolverCCD iKSolverCCD;     //CCD 解算器
        public Transform targetP;       //目标位置
        public Transform anchorP;       //固定位置

        public bool useLimt = true;     //使用限制
        public bool update = false;     //实时更新
        [Range(1,10)]
        public int iterations = 1;      //迭代次数

        [Button("UpdateAnchor")]
        void UpdateAnchor()
        {
            iKSolverCCD.useLimt = useLimt;
            iKSolverCCD.iterations = iterations;
            iKSolverCCD.arrows[0].Calculate_AB(anchorP.position, Vector2.right);
            iKSolverCCD.UpdatePosition(Vector2.right);
            iKSolverCCD.FollowTarget();
        }
        [Button("Calculate")]
        void Calculate()
        {
            iKSolverCCD.useLimt = useLimt;
            iKSolverCCD.iterations = iterations;
            iKSolverCCD.target = targetP.position;
            iKSolverCCD.FollowTarget();
        }
        private void OnDrawGizmos()
        {
            if (update) {
                Calculate();
            }

            Arrow last = null;
            foreach (var item in iKSolverCCD.arrows)
            {
                Gizmos.color = item.color;
                Mov.GizmeDrawArrow(item.a, item.b);

                if (useLimt)
                {
                    Mov.GizmeDrawCircleLimt(item.a, item.forward, item.angleLimt, 0, item.len / 4f, last == null ? (item.a-Vector2.right): last.a);
                }
                last = item;
            }
        }
    }

测试

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值