第057封“情书”:现实很骨感Essentials- Parallel Transport平行移动<Entagma>Houdini 2018

▉养活一团春意思,撑起两根穷骨头— 每天翻译一篇教程,这就是我写给houdini的情书。【首发于同名公众号:“致houdini的情书”】

钢筋铁骨

前言不搭后语:

   天将降大任于斯人也必先苦其心志,,行拂乱其所为。

今天这节内容:

   如何让沿路径复制物体正切路径跟随路径平滑旋转

这一节要实现的效果

.....

▉今天是42岁第031天周日

这是写给houdini的

第057封“情书”

我是geo流程图

我是pointvop流程图

我是primwrangle的代码

创建“平行传输”

//-- 1 所有点;点的数量

int pnts[] = primpoints(0,@primnum);

int pntcnt = len(pnts);

//-- 2 第一个正切;全局y轴向量;

vector firsttangent = normalize(point(0,"P",pnts[1])-point(0,"P",pnts[0]));

vector firstnormal = {0,1,0};

//-- 3 第一个积乘;第二个积乘 

vector helper = normalize(cross(firstnormal,firsttangent)); 

firstnormal = normalize(cross(firsttangent,helper));

//-- 4 声明变量 稍后用到的所有变量

vector bitangent = {0,0,0}; 

float theta = 0; 

vector tangents[] = {}; 

vectornormals[] = {}; 

//-- 5 填充数组  

    //-- 5a 一组:设置除最后一点的所有点

       for(int i =0; i

    push(tangents,normalize(point(0,"P",pnts[i+1])-point(0,"P",pnts[i])));

    push(normals,firstnormal);

    }

    //-- 5b 二组:设置最后一个点

     push(tangents,tangents[pntcnt-2]);

     push(normals,normals[pntcnt-2]);

//-- 7 平行移动parallel transport

for(int k=0; k

     bitangent = cross(tangents[k],tangents[k+1]); 

    if(length(bitangent) == 0){     

        normals[k+1] = normals[k]; 

        }

    else{

        bitangent = normalize(bitangent);       

        theta = acos(dot(tangents[k],tangents[k+1]));  

        matrix rotmat = ident(); 

        rotate(rotmat,theta,bitangent); 

        normals[k+1] = rotmat*normals[k];

        }

    }

//-- 6 设置所有属性 首先让所有属性视窗可见,看是否设置正确。

for (int j=0; j

    setpointattrib(0, “PT_tangent”, pnts[j],tangents[j],“set”); 

    setpointattrib(0, "PT_normal", pnts[j],normals[j],"set");

    bitangent = normalize(cross(normals[j],tangents[j]));

    setpointattrib(0, "PT_bitangent",pnts[j],bitangent,"set");

    }

本节需要注意的知识点:

复制物沿路径平滑旋转步骤

1

创建平行移动的曲线坐标系 

primitivewrangle

1)所有点pnts[]  点的数量pntcnt=len(pnts)

2)第一个正切向量: firsttangent

=normalize(point(0,"P",pnts[1])-point(0,"P",pnts[0])) 全局y轴向量: {0,1,0}; 

3)得到法向量:firstnormal

    第一个积乘正交向量helper =“第一个正切”与“全局y轴向量”;第二个积乘法向量firstnormal=“正切向量:firsttangent ”积乘“正交向量helper

4)声明稍后用到的所有变量

a)初始切向量与N的正交向量 bitangent

= {0,0,0}

b)切向量夹角theta

= 0; 

c)切向量数组tangents[]

= {}; 

d)法线数组normals[]

= {};  

5)填充初始法线,切向量数组  :两部分

a)设置最除最后一点的所有点:

         1)for循环所有点出最后一个

    for(int i =0; i<pntcnt-1;i++){

          2) 添加切向量到切向量数组中,当前点减下一个点。  

push(tangents,normalize(point(0,"P",pnts[i+1])-point(0,"P",pnts[i])));

         3) 初始法线添加到法线数组中

    push(normals,firstnormal);

b)设置最后一个点         

         1) 添加正切值到数组:push最后一点之前的一个点的正切值复制给最后一点

     push(tangents,tangents[pntcnt-2]);

         2) 添加法线到数组:

     push(normals,normals[pntcnt-2]);

7) 平行移动parallel transport

for(int

k=0; k

     bitangent

= cross(tangents[k],tangents[k+1]); //正切与下一个正切积乘变量bitangent

if(length(bitangent) == 0){// 积乘=0就是这两个正切线相同

        normals[k+1] = normals[k];  // 所以法线复制给下一个点

        }

    else{

        bitangent = normalize(bitangent);      // 否则标准化

theta =acos(dot(tangents[k],tangents[k+1]));            //求切向量的积乘反余弦,也就是θ旋转角度

        matrix rotmat = ident(); //返回 矩阵id

//对给定矩阵应用旋转; 特定的角度围着指定的向量旋转;

rotate(rotmat,theta,bitangent); //旋转矩阵,该函数:使矩阵以θ的弧度“theta”,绕切线旋转

normals[k+1] = rotmat*normals[k];

6)设置所有属性

首先让所有属性视窗可见 。

b )首先需要循环,因为我们要对所有点设置

for(int j=0; j<pntcnt;j++){

a )设置正切值写回点属性

setpointattrib(0, “PT_tangent”, pnts[j],tangents[j],“set”); //观察矢量效果

8)法线;与法线切线正交的bitangent向量写回point属性里

a )把法向量写回点属性

    setpointattrib(0, "PT_normal", pnts[j],normals[j],"set");

b )最后第三个向量:正交与 切线和法向量 两个向量

bitangent =normalize(cross(normals[j],tangents[j]));

setpointattrib(0, "PT_bitangent",pnts[j],bitangent,"set");

2

向量转orient四元数

1)bind引入三个矢量:PT_bitangent相当于x轴;PT_normal法线相当于y轴;PT_tangent正切值相当于z轴

2)vectomatx1:从向量中提取旋转矩阵

3)matxtoquat1:矩阵转四元数

4)bind expor 输出orient

接下来

理论部分

问题1:curve

frame(曲线坐标系统)

01)曲线坐标系:为曲线的每个点,构造一个矩阵,从而在曲线的每一点上定义一个方向。但构造这样的朝向并不是容易获得,因为在曲线上唯一可知的信息只有“切向量”

02)有几种算法可以解决这个问题:其中一种算法叫“并行传输”:

3)并行传输:就是在一条曲线上,在曲线方向的每个点上,定义一个“矩阵” 或“坐标系”;

    a)首先,这个坐标系应该沿着曲线方向运动,第一个参数“切向量T”切向量总是沿着曲线的走势.

    b)然后,找到第二个向量:法向量N, 定义曲线的扭转,假设曲线的每一点上都放了一个立方体,围绕着切线的旋转就构成了一个沿着切向量旋转的平面,用不同的角度θ决定了stuff如何围着曲线转动。

       绿色表示的法向量来表示曲线平滑变化程度。算法发现 从一个normal到下一个normal的个体“法线” 之间的差别很小的.在“切向量” 的方向或旋转方面没有突然的变化的,就是一个平稳的变化。

c)这两个向量:“切矢量” 和“法向量”使用“叉乘” 来构造第三个向量,

 d)最后三个向量创建了一个矩阵。       

02)“平行传输”具体算法:            

    a)INPUT:

1)首先,标准化的“切向量T”列表。

2)然后,初始化的“法向量N”,正交与切向量T0,这个法线在切线上可以有任何方向

    这是算法的一个技巧:

    (取这个初始法向量,把它从一个点移动到另一个点,我们将它旋转 使它最小的旋转与下一个相切。所以它只会在需要时旋转它,并使它与下一个切线正交,因此 这个“法向量”沿着曲线非常地平稳)

    b)OUTPUT:该算法将输出一个平行移动的正交与切向量的“法向量”列表。

这个算法是如何工作的呢:

1)第一个表述是从0到n-1;

2)第一个计算的变量B,用正切Ti和Ti+1叉乘得来;Ti是当前过程点的切线, Ti+1是下一个切线;把这切线暂时放在ti位置,这两个向量将张成一个平面,这个乘积给我们一个正交于这个平面的向量:临时切线B;

    3)检查:如果B长度=0:当Ti和Ti+1是完全相同的矢量,叉乘B会返回0; 然后把现在的“normal”复制到下一个“normal”,也就是当直线时,“切向量”相同,normal相同。

    4)如果B长度!=0;首先规范化临时切线B,θ=arccos(Ti,Ti+1)反余弦求夹角度数,用点积来计算这两个向量的夹角θ。

   4a) 临时切线B旋转度数θ,把法线放到下一个切线上,乘沿B轴旋转的阵列R(旋转矩阵);V是法线,Vi+1=Vi*R (旋转矩阵)

   4b) 所以最终构造为

    A)旋转矩阵R:1)rotate由θ,2)围绕临时切线B,

    B)这个旋转矩阵R,应用到“初始化的法向量Vi” ;

 用一个矩阵R乘以向量Vi(将由“矩阵编码的转换” 应用到“向量”)

 使用“初始化的法向量Vi”再旋转θ度,移动到下一个点

“法向量V”总是与“切向量T”正交, ,一步一步沿着这条曲线被平移。因为它总是相同的矢量Vi旋转,这就给了平稳的变化的“法线向量”。

接下来

开始正式制作

使用软件houdini16.5

如何创建平行移动的曲线坐标系

1)Primitivewrangle1命名parallel_transport

//-- 1 所有点;点的数量

int pnts[] = primpoints(0,@primnum);

int pntcnt = len(pnts);

//-- 2 第一个正切;全局y轴向量;

vector firsttangent = normalize(point(0,"P",pnts[1])-point(0,"P",pnts[0]));

vector firstnormal = {0,1,0};

//-- 3 第一个积乘;第二个积乘 首先求正交的向量,然后把它和切线方向相交

vector helper = normalize(cross(firstnormal,firsttangent)); //正交过渡向量

firstnormal = normalize(cross(firsttangent,helper));  //法向量

//-- 4 声明变量 稍后用到的所有变量

vector bitangent = {0,0,0}; //-初始切向量与N的正交向量

float theta = 0; //-切向量夹角

vector tangents[] = {}; //-切向量数组

vectornormals[] = {}; //-法线数组

//-- 5 填充数组  最后一点没有下一个点,所以跳过最后一个点,分成两组并行

    //-- 5a 一组:设置除最后一点的所有点

        //-- 1) for循环所有点,for(int i =0; i

        //-- 2) 添加切向量到切向量数组中,当前点减下一个点。 // push函数:将数值添加到数组中

    push(tangents,normalize(point(0,"P",pnts[i+1])-point(0,"P",pnts[i])));

        //-- 3) 初始法线添加到法线数组中

    push(normals,firstnormal);

    }

    //-- 5b 二组:设置最后一个点

        //-- 1) 添加正切值到数组:push最后一点之前的一个点的正切值复制给最后一点

     push(tangents,tangents[pntcnt-2]);

        //-- 2) 添加法线到数组:

     push(normals,normals[pntcnt-2]);

//-- 7 平行移动parallel transport

for(int k=0; k

     bitangent = cross(tangents[k],tangents[k+1]); //正切与下一个正切积乘变量bitangent

    if(length(bitangent) == 0){     // 积乘=0就是这两个正切线相同

        normals[k+1] = normals[k]; // 所以法线复制给下一个点

        }

    else{

        bitangent = normalize(bitangent);       // 否则标准化

        theta = acos(dot(tangents[k],tangents[k+1]));  // 求切向量的积乘反余弦,也就是θ旋转角度

        matrix rotmat = ident(); //返回 矩阵id

        //-- 对给定矩阵应用旋转; 特定的角度围着指定的向量旋转;

        rotate(rotmat,theta,bitangent); //旋转矩阵,该函数:使矩阵以θ的弧度“theta”,绕切线旋转

        normals[k+1] = rotmat*normals[k];

        }

    }

//-- 6 设置所有属性 首先让所有属性视窗可见,看是否设置正确。

    //-- 6b 首先需要循环,因为我们要对所有点设置

for (int j=0; j

    //-- 6a 设置正切值给点

    setpointattrib(0, “PT_tangent”, pnts[j],tangents[j],“set”); //观察矢量效果

    //-- 8a 把法向量写回几何体

    setpointattrib(0, "PT_normal", pnts[j],normals[j],"set");

    //-- 8b 最后第三个向量:正交与 切线和法向量 两个向量

    bitangent = normalize(cross(normals[j],tangents[j]));

    setpointattrib(0, "PT_bitangent",pnts[j],bitangent,"set");

    }

问题3:如何使复制物沿路径自然转动

//曲线坐标系,要对齐复制物的方向,所以要把曲线坐标系转成orient

 1~3)bind :// 三个引入三个变量(注意按照顺序排列)

    bind1) Name:PT_bitangent;type:vector。

    bind2) Name:PT_normal;type:vector。

    bind3) Name:PT_tangent

2-4)vectomatx1:// 向量转矩阵,从向量中提取旋转矩阵

2-5) matxtoquat1 : //矩阵转四元数

2-6)bind://输出orient 

3)copytopoints1:

4)attribrandomize1://name:Cd

今天就到这儿了,收功

教程翻译自entagma的网络教程

下一节:20180326 Season 3 Is Here! Quicktip- Rayleigh Taylor Instability Using FLIP

     本文图片全部原创,版权归原作者所有

你可能感兴趣的:(第057封“情书”:现实很骨感Essentials- Parallel Transport平行移动<Entagma>Houdini 2018)