1

2D Freeform的两个方式在计算开销上要比Simple大一些。其中Cartesian算法比较简单,Directional的方法要基于Cartesian,所以本篇先讨论这个算法。

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。

根据向量点乘的特性,可以很容易的计算出来投影的长度。
$$ 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}}) $$
最后,对所有节点的影响力进行归一化,就能得到它们各自的权重。

代码实现

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;
    }
}

大功告成

图片描述

预览影响力图的功能在文章中没有提及,但包含在完整代码中。
点此下载完整代码


迷途吧
145 声望25 粉丝

看见好文章就翻译一下,发现一些细节就分享一下,也许没什么技术,只希望能被需要的人搜到。