▉养活一团春意思,撑起两根穷骨头— 每天翻译一篇教程,这就是我写给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 本文图片全部原创,版权归原作者所有