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;
}
}
大功告成
预览影响力图的功能在文章中没有提及,但包含在完整代码中。
点此下载完整代码
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。