公式中的矩阵操作如何应用于CocosCreator

前言

在使用CocosCreator开发的过程中,少不了进行一些数学运算。有一些是通用的图形学公式,去各种地方百度到的,但以一种数学论述的方式存在。猛然碰见,不是太好实现在程序之中。下载就以3次B样条曲线的公式为例来说明如何将矩阵操作“翻译成”CocosCreator中的JavaScript/TypeScript代码。

需求

这次我们发现需要画出3次B样条曲线,也就是输入n个点,来确定一条空间曲线路径,比如在游戏中需要飞行轨迹,弹道轨迹之类的需求。一般情况下,我们都去搜索引擎里面找相关的数学框架,其实就是数学结论。这次,我就找到了这些东西:
B样条: P ( u ) = ∑ i = 0 n P i B i , k ( u ) u ∈ [ u k − 1 , u n + 1 ] \displaystyle P(u)=\sum_{i=0}^nP_iB_{i,k}(u) \quad u\in[u_{k-1},u_{n+1}] P(u)=i=0nPiBi,k(u)u[uk1,un+1]
B 0 , 3 ( t ) = 1 6 ( 1 − t ) 3 B 1 , 3 ( t ) = 1 6 ( 3 t 3 − 6 t 2 + 4 ) B 2 , 3 ( t ) = 1 6 ( − 3 t 3 + 3 t 2 + 3 t + 1 ) B 3 , 3 ( t ) = 1 6 t 3 B_{0,3}(t)=\dfrac1 6 (1-t)^3 \\B_{1,3}(t)=\dfrac1 6 (3t^3-6t^2+4)\\B_{2,3}(t)=\dfrac1 6 (-3t^3+3t^2+3t+1)\\B_{3,3}(t)=\dfrac1 6 t^3 B0,3(t)=61(1t)3B1,3(t)=61(3t36t2+4)B2,3(t)=61(3t3+3t2+3t+1)B3,3(t)=61t3
以及它的矩阵形式
Q ( t ) = ∑ i = 0 3 P i B i , 3 ( t ) = 1 6 [ 1 t t 2 t 3 ] ⋅ [ 1 4 1 0 − 3 0 3 0 3 − 6 3 0 − 1 3 − 3 1 ] ⋅ [ P i P i + 1 P i + 2 P i + 3 ] t ∈ [ 0 , 1 ] \displaystyle Q(t)=\sum_{i=0}^3P_iB_{i,3}(t) =\dfrac1 6\begin{bmatrix}1&t&t^2&t^3\end{bmatrix}\cdot\begin{bmatrix}1&4&1&0\\-3&0&3&0\\3&-6&3&0\\-1&3&-3&1\end{bmatrix}\cdot\begin{bmatrix}P_i\\P_{i+1}\\P_{i+2}\\P_{i+3}\end{bmatrix}\quad t\in[0,1] Q(t)=i=03PiBi,3(t)=61[1tt2t3]1331406313330001PiPi+1Pi+2Pi+3t[0,1]

代数形式

其中代数形式不是本文想要解释的内容,这里随便实现了一下。其中P为cc.Vec3,N表示上面公式中的B函数组,循环就是连加,order是阶数,这里固定是3,addSelf是自身的每一个分量加上后面对应的分量,mul是向量每一个分量乘以某个值。

let P = cc.v3(0, 0, 0);
let N = [
    (t) => Math.pow(1 - t, 3) / 6,
    (t) => (3 * t * t * t - 6 * t * t + 4) / 6,
    (t) => (-3 * t * t * t + 3 * t * t + 3 * t + 1) / 6,
    (t) => t * t * t / 6
];
for (let i = 0; i < this.order; i++) {
    P.addSelf(this.pList[i + n].mul(N[i](t)));
}

矩阵形式

这里是文章的核心,直接用这里结论即可。
首先CocosCreator内置类型有相对完整的cc.Mat4,也有cc.Mat3,但是没有相关运算,不完整。我这里就都全部使用cc.Mat4作为矩阵的基本数据类型了。我发现的几点套路:

  1. mat4当然是4阶矩阵,如果要算的矩阵行或列超过4阶,就不能用这个了。
  2. 如果本身公式中的矩阵行列少于4个,那就空着,创建矩阵时自动保持单位矩阵中原来的值。一行都不够,那就补0。
    比如这个1x2矩阵 [ 1 t t 2 t 3 ] \begin{bmatrix}1&t&t^2&t^3\end{bmatrix} [1tt2t3],表示为:
cc.mat4(1, t, t * t, t * t * t)
  1. cc.mat4(…)创建的矩阵默认是个单位矩阵,其它矩阵与它相乘都不发生变化。
  2. 点组成的列矩阵: [ P i P i + 1 P i + 2 P i + 3 ] \begin{bmatrix}P_i\\P_{i+1}\\P_{i+2}\\P_{i+3}\end{bmatrix} PiPi+1Pi+2Pi+3应该把每个点扩展为一个行比如:
cc.mat4(
     this.pList[n].x, this.pList[n].y, this.pList[n].z, 0,
     this.pList[n + 1].x, this.pList[n + 1].y, this.pList[n + 1].z, 0,
     this.pList[n + 2].x, this.pList[n + 2].y, this.pList[n + 2].z, 0,
     this.pList[n + 3].x, this.pList[n + 3].y, this.pList[n + 3].z, 0,
 );
  1. 最后就是矩阵乘法,Mat4有个mul函数,参数是一个矩阵,实际意义就是原矩阵左乘参数中的矩阵,注意是左乘就对了,困惑了我好久。
  2. 乘常量,Mat4里面的multiplyScalar。我发现还有个函数叫mulScalar,乘出来结果为null,不要用就好了。(我用的CocosCreator2.4.6)
  3. 结果值,因为前面的矩阵补过0,最后的结果就只关注第一行,vec3的x,y,z就取结果矩阵的第一行前三个数即可。

公式的代码实现

Q ( t ) = ∑ i = 0 3 P i B i , 3 ( t ) = 1 6 [ 1 t t 2 t 3 ] ⋅ [ 1 4 1 0 − 3 0 3 0 3 − 6 3 0 − 1 3 − 3 1 ] ⋅ [ P i P i + 1 P i + 2 P i + 3 ] t ∈ [ 0 , 1 ] \displaystyle Q(t)=\sum_{i=0}^3P_iB_{i,3}(t) =\dfrac1 6\begin{bmatrix}1&t&t^2&t^3\end{bmatrix}\cdot\begin{bmatrix}1&4&1&0\\-3&0&3&0\\3&-6&3&0\\-1&3&-3&1\end{bmatrix}\cdot\begin{bmatrix}P_i\\P_{i+1}\\P_{i+2}\\P_{i+3}\end{bmatrix}\quad t\in[0,1] Q(t)=i=03PiBi,3(t)=61[1tt2t3]1331406313330001PiPi+1Pi+2Pi+3t[0,1]

let entry1 = cc.mat4(1, t, t * t, t * t * t)
let entry2 = cc.mat4(1, 4, 1, 0, -3, 0, 3, 0, 3, -6, 3, 0, -1, 3, -3, 1);
let entry3 = cc.mat4(
    this.pList[n].x, this.pList[n].y, this.pList[n].z, 0,
    this.pList[n + 1].x, this.pList[n + 1].y, this.pList[n + 1].z, 0,
    this.pList[n + 2].x, this.pList[n + 2].y, this.pList[n + 2].z, 0,
    this.pList[n + 3].x, this.pList[n + 3].y, this.pList[n + 3].z, 0,
);
let result = entry3.mul(entry2).mul(entry1).multiplyScalar(1 / 6);
P = cc.v3(result.m[0], result.m[1], result.m[2]);

注意图中的n就是公式里面的i,由于需求给入的是多个连续点,n就在0~给定点数-阶数范围遍历取值。
由于mul表示左乘,那么就是用的 entry3.mul(entry2).mul(entry1),而不是反过来,因为矩阵乘法不满足交换律。

观察

如果CocosCreator提供从 cc.vec3或者cc.vec4的数组直接创建cc.mat4就好了。代码里面最大的那一块就可以化简一些了。
至于算法复杂度,上面的公式法似乎已经把前两个矩阵乘完了。而矩阵法,第一个矩阵里面的 t 2 , t 3 t^2,t^3 t2,t3则已经算好存进了矩阵,且里面没有循环,顺序流程直接出结果。
当然,B样条曲线有专门的几何方法去生成。一般来说,计算机图形学中,几何方法总是比代数方法效率优化的多。B样条曲线的基础标准生成算方法就叫de Boor-cox。我最后把这个算法的代码贴在下面,这并不是写这篇文章的主要内容,可以感受一下简单明了的递归方法,而且还是任意阶次的。

class deBoorcox {

    n: number;
    pList: cc.Vec3[];
    k: number;
    u: number;

    constructor(pList: cc.Vec3[], k: number) {
        this.pList = pList;
        this.n = pList.length - 1;
        this.k = k;
        this.u = k - 1;
    }

    // u取值 [k-1, n+1)
    B(u, k, i) {
        if (k == 1) {
            if (i <= u && u < i + 1) return 1;
            else return 0;
        } else {
            let coef_0 = 0, coef_1 = 0;
            if (u - i == 0 && k - 1 == 0) {
            } else {
                coef_0 = (u - i) / (k - 1);
            }
            if (i + k - u == 0 && k - 1 == 0) {
            } else {
                coef_1 = (i + k - u) / (k - 1);
            }
            return coef_0 * this.B(u, k - 1, i) + coef_1 * this.B(u, k - 1, i + 1);
        }
    }

    solve(u) {
        let P = cc.v3(0, 0, 0);
        for (let i = 0; i <= this.n; i++) {
            P.addSelf(this.pList[i].mul(this.B(u, this.k, i)));
        }
        return P;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值