▉养活一团春意思,撑起两根穷骨头— 每天翻译一篇教程,这就是我写给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<pntcnt-1;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<pntcnt-1;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<pntcnt; 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<pntcnt-1;k++){ //正交向量只有少一点的数量“pntcnt-1”
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<pntcnt-1;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<pntcnt-1;k++){ //正交向量只有少一点的数量“pntcnt-1”
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<pntcnt; 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
本文图片全部原创,版权归原作者所有