從新實現unity3d的Mecanim動畫混合 (2) 2D Freeform Cartesian

2D Freeform的兩個方式在計算開銷上要比Simple大一些。其中Cartesian算法比較簡單,Directional的方法要基於Cartesian,因此本篇先討論這個算法。node

2D Freeform的兩個算法是能夠在網上找到參考資料的,它們基於一篇論文[http://runevision.com/thesis]的章節6.3。算法

基礎程序框架:

咱們依然沿用以前的那個場景,代碼框架稍微有一些變化。框架

[ExecuteInEditMode]
public class SampleNode : MonoBehaviour
{
    public BlendNode[] Nodes;

    private Vector3 mLastPosition;

    void Start ()
    {
        Nodes = FindObjectsOfType<BlendNode>();
    }
    
    void Update ()
    {
        var pos = transform.position;
        if(mLastPosition == pos) return;
        mLastPosition = pos;
        UpdateWeights();
    }

    [ContextMenu("Update Weights")]
    void UpdateWeights()
    {
        for (int i = 0; i < Nodes.Length; ++i) Nodes[i].Weight = 0;

        CalcWeightsFreeformCartesian();
        NormalizeWeights();
    }

    void CalcWeightsFreeformCartesian()
    {
    }
    
    void NormalizeWeights()
    {
        float totalWeight = 0;
        foreach (var node in Nodes)
        {
            totalWeight += node.Weight;
        }
        if(totalWeight == 0) return;
        foreach (var node in Nodes)
        {
            node.Weight = node.Weight / totalWeight;
        }
    }
}

算法思路

這個算法的思考方向是,想象爲任意一個節點生成一個影響力圖,那麼該影響力必然從這個節點的位置向周圍各個方向逐漸遞減。
對任一個方向而言,影響力在這個方向上遇到的第一個其它節點時會降到零。動畫

使用1維來理解這件事,看下圖。
圖片描述
有兩個節點pi和pj,對於採樣點在位置p,那麼個pi的影響值應該是多少呢?
咱們能夠看點p在直線pipj上的投影,這個投影點到pi的距離L0與pi到pj的距離L1,恰好能用來計算這個概念。
使I=1-L0/L1,那麼能夠知足當p接近pi時,I趨近於1,當p接近pj時,I趨近於0。spa

根據向量點乘的特性,能夠很容易的計算出來投影的長度。
$$ L0 = cos \theta |\vec {p_i p}| = {\vec {p_i p} \cdot \vec {p_i p_j} \over |{\vec {p_i p_j}|}} $$
因此
$$ I_{ij} = 1 - {L0 \over |\vec{p_i p_j}|} = 1 - {\vec{p_i p} \cdot \vec{p_i p_j} \over |{\vec{p_i p_j}|^2}} $$
每一個節點的最終影響力取它到全部其它節點的影響力中的最小值,至此咱們獲得了論文中的公式(6.6):
$$ h_i(p) = \min_{j=1}^n (1 - {\vec{p_i p} \cdot \vec{p_i p_j} \over |{\vec{p_i p_j}|^2}}) $$
最後,對全部節點的影響力進行歸一化,就能獲得它們各自的權重。code

代碼實現

void CalcWeightsFreeformCartesian()
{
    var posS = mInputPosition;

    // 爲每個動畫節點計算影響值
    foreach (var node0 in Nodes)
    {
        var influence = float.MaxValue;
        var pos0 = node0.transform.position;

        // 計算當前節點針對每個其它節點的影響值,並取最小的一個
        for (int i = 0; i < Nodes.Length; ++i)
        {
            var node1 = Nodes[i];
            if (node0 == node1) continue;
            var pos1 = node1.transform.position;

            var vec0S = posS - pos0;
            var vec01 = pos1 - pos0;

            // 影響值至關於0S在01上的投影的歸一化
            var h = Mathf.Clamp01(1 - Vector3.Dot(vec0S, vec01) / vec01.sqrMagnitude);
            // 保留影響值中最小的
            influence = Mathf.Min(influence, h);
        }

        node0.Weight = influence;
    }
}

大功告成

圖片描述

預覽影響力圖的功能在文章中沒有說起,但包含在完整代碼中。
點此下載完整代碼orm

相關文章
相關標籤/搜索