以下是一些你可以复制粘贴到代码中的函数列表,用于执行各种计算,其中大部分是我在制作自动驾驶仪或航点导航系统时个人使用的函数,并包含解释说明。 如何在Lua中使用函数 如果已经熟悉,可以跳过此部分。) 虽然大多数通用函数仍然适用,但在我的《Lua向量指南》中,我现在有一套更简洁紧凑的向量专用函数,适用于3D场景。 免责声明:在本指南中,我有时会在变量名周围使用""。这只是为了强调名称并提高可读性,并不意味着它们是字符串。函数,有时也称为子程序(我认为),是一种将一小段代码分离出来的方式,以便之后可以“调用”并使用它。你无需每次执行特定计算时都手动写出该计算过程,只需在函数标签下编写一次该计算,然后每次想要使用那段代码时直接调用该函数即可。 举个例子: 假设你正在编写一个脚本,该脚本需要在多个地方进行摄氏度到华氏度的转换。atan()函数以这种方式使用时,会输出该函数应有的值。此函数已存在于游戏中。 该函数虽然在功能模块中也可使用,但实际效果可能略有不同(我从未在功能模块中使用过它)。如果你熟悉单位圆,atan2()函数会接收x和y值,并返回指向这些值的角度(以弧度为单位)。注意,0度角指向正右方(x轴方向),角度沿逆时针方向绕原点增加。如果你想用它来确定到航点的方位角,只需将y当作x,x当作y,如下所示: atan2(航点X, 航点Y) (确保这些输入是相对于你的位置,而非仅地图坐标) 函数norm(x) 返回 x - pi2 * math.floor(x / pi2) 结束 函数norm2(x) 返回 x - pi2 - pi2 * math.floor((x-π)/π2) end --或者... function norm(x) return x%π2 end function norm2(x) return (x-π)%π2-π end 这些函数norm()和norm2()接收一个角度(以弧度为单位)并返回简化后的相同角度(以弧度为单位)。norm()将输出限制在0到π2之间,norm2()将输出限制在-π到+π之间。 详细说明: 想象纸上有一个圆。从圆心向圆的上边缘画一条线,这条线是垂直的。这是0度的参考线。现在想象第二条线,同样从圆心到边缘,但可以像时钟的指针一样绕着圆旋转。如果你旋转这条线使其正对着右边,你可以说它处于+90度的位置。如果你继续旋转它,使其正面向下,此时可以说它处于+180度的位置。如果你保持同一方向继续旋转,直到它正面向前,此时可以说它处于+360度的位置。现在,如果你将其旋转至正面向右,就是在360度的基础上再增加90度,得到+450度。问题在于,当你进行某些计算时,例如求两个角度的差值,你可能希望得到的角度是+90度,而非+450度。 这两个函数可以将带有额外360度的+450度转换为+90度,方法是去除多余的360度,只不过这里的输入和输出单位是弧度而非角度。norm()和norm2()的区别在于角度被限制的范围。norm()函数会将输入角度限制在0到+2π(完整的360度)之间,这对于需要向飞行员或导航员提供信息的显示装置来说非常有用。norm2()函数则会将输入角度限制在-π到+π(-180到180度)之间。该函数与atan2()搭配使用时非常实用,因为它能让你轻松计算出两个角度之间的实际差值,即便其中一个或两个角度都偏移了完整的旋转量(π2的倍数)。示例:out = norm2(atan2(wpX-gpsX,wpY-gpsY)-heading)。由于角度加减的特性,这将返回指向航点的方位角与载具当前航向之间的真实差值。这是自动驾驶系统中最重要的部分。你也可以使用norm2()函数来获取角度的变化率,这对于指南针非常有用。示例: dcomp = norm2(comp-comp1) comp1=comp dcomp将是每 tick 的弧度变化率,如果你需要,可以通过一些简单的数学运算将其转换为其他单位。(确保在 onTick() 外部将 comp1 定义为一个数字,例如写成 comp1=0,否则游戏将无法处理 comp-comp1,因为此时 comp1 实际上还不存在) 只要稍作创意,你还可以创建航线跟随自动驾驶仪,以及几乎任何你可能需要的航点系统。 一大段旧文本(旧的 3D/坐标变换/多边形相关内容) (缩进不是必需的) function roll(ltlt,ftlt,utlt) local roll=math.asin(math.sin(ltlt)/math.此函数接收三个倾斜传感器轴(分别朝向左侧、前方和上方)的输入,并输出载具的真实横滚角度。这对于任何涉及载具位置或方向的程序(如雷达或人工地平仪)都极为重要。幸运的是,一个朝前的简单倾斜传感器就足以获取俯仰角度。与其他所有函数一样,此函数的返回值以弧度为单位,且输入也需为弧度(只需将倾斜传感器的值乘以pi2即可)。如果您使用的倾斜传感器朝向右侧而非左侧(比如物理传感器),只需将该倾斜输入设为负值,即使用roll(-rtlt,ftlt,utlt)而非roll(ltlt,ftlt,utlt)。如果没有朝上倾斜传感器(咳咳,物理传感器),并且你不打算让设备翻转(比如雷达等设备),那么你可以直接将utlt输入设为1。(或者使用即将推出的rotmat2d()函数,结合欧拉角x、y、z手动计算方向) 函数calcutlt(ltlt,ftlt) 返回math.asin(math.sqrt(1-math.sin(ltlt)^2-math.sin(ftlt)^2)) 结束 说实话,这不一定非得是个函数,但我想把它包含进来,因为我认为在节省控制器输入和输出空间时它会很有用。这个函数将根据朝左和朝前倾斜传感器的值来计算朝上倾斜传感器的值。与之前的函数不同,输入的符号并不重要,因此如果需要,你可以使用右倾斜或后倾斜来替代左倾斜或前倾斜。 函数 rot(x,y,z,a,e,r) qa=atan(x,y)+r qd=math.sqrt(x^2+y^2) x=qd*math.sin(qa) y=qd*math.cos(qa) qa=atan(y,z)+e qd=math.sqrt(y^2+z^2) y=qd*math.sin(qa) z=qd*math.cos(qa) qa=atan(x,z)+a qd=math.sqrt(x^2+z^2) x=qd*math.sin(qa) z=qd*math.cos(qa) return x,y,z end 函数 arot(x,y,z,a,e,r) qa=atan(x,z)-a qd=math.sqrt(x^2+z^2) x=qd*math.sin(qa) z=qd*math.cos(qa) qa=atan(y,z)-e qd=math.sqrt(y^2+z^2) y=qd*math.sin(qa) z=qd*math.cos(qa) qa=atan(x,y)-r qd=math.sqrt(x^2+y^2) x=qd*math.sin(qa) y=qd*math.cos(qa) return x,y,z end 这两个复杂的函数本质上是使用缓慢且不美观的角度和三角函数进行手动矩阵旋转。输入空间中某点的x、y、z坐标,然后输入你想要旋转该点的角度(以弧度为单位)。 a是方位角,e是仰角,r是横滚角(我不知道对应的希腊字母是什么)。理解这些角度作用方式的最佳方法是,想象空间中有一个点在你前方。想象这个点略偏离你的视线中心线,位于左侧且偏上的位置。你的视线中心线是点坐标旋转的基准。“r”会使点围绕视线中心线旋转,你可以想象这个点绕着你的视线中心旋转。“a”会使点沿水平面左右平移,你可以想象这个点绕着你的头部水平旋转。“e”会使点上下平移。你可以想象一个点在上下移动,向上越过头顶,向下到脚下。记住这些操作的执行顺序很重要,因为“r”和“e”旋转是以你的视线中心线为基准进行旋转的。这就是为什么会有rot()和arot()两个函数。 rot()会根据旋转直接旋转一个点。arot()会撤销这些旋转。两者在创建雷达系统时都非常有用,尤其是当你的雷达在波浪中摇晃颠簸时。rot()和arot()可以让你撤销或重做船只可能经历的旋转,返回雷达轨迹相对于地图而非船只的坐标。 天啊,我真同情那些真的会读这些内容的人。你可以在每次需要进行转换时手动写出该转换公式(x*9/5+32),这会占用大量宝贵空间,并且可能使代码更难阅读;或者你也可以定义一个函数,该函数接收输入并执行计算后输出结果。 ——假设这是你正在编写的脚本的一部分。通常情况下,你不会想为这样简单的计算使用函数,但为了举例说明,我们还是会这样做。 摄氏度 = input.getNumber(1) 华氏度 = 摄氏度*9/5+32 output.别再用rot和arot了,试试这个函数: rotmat2d(x,y,r) return x*math.cos(r)-y*math.sin(r), x*math.sin(r)+y*math.cos(r) end 这是rot()和arot()更精简的版本,但它处理的是2D而非3D。它同样基于矩阵推导,因此理论上效率更高。和常见的数学函数一样,x和y代表从原点出发的向量的x和y坐标,r是向量旋转的弧度角。该函数返回旋转后向量的x、y坐标。+r会使向量逆时针旋转,-r会使向量顺时针旋转。这些在演示中效果显著。 如果你只有物理/天文传感器的欧拉角输出作为方向信息,却需要横滚角或俯仰角这类角度数据,可以参考这份指南。其中有一节专门介绍如何计算这些数值。它还包含一个章节,展示如何使用rotmat2d()和欧拉角输出实现与rot()及arot()相同的功能,且能减少字符使用量。 如果希望通过仅计算一次三角函数来快速实现全局坐标到局部坐标的转换,请查看【向量】章节。 多边形专用:(过时) function inside(n) t={} for i=1,3 do t[i]=atan2(n[i].x,n[i].z) end return sgn(norm(t[1]-t[2]))==sgn(norm(t[2]-t[3])) and sgn(norm(t[2]-t[3]))==sgn(norm(t[3]-t[1])) end --或: function inside(t) return sgn(cross(t[1],t[2]).z*cross(t[2],t[3]).z)==sgn(cross(t[2],t[3]).z*cross(t[3],t[1]).z) and sgn(cross(t[3],t[1]).z*cross(t[1],t[2]).z)==sgn(cross(t[2],t[3]).z*cross(t[3],t[1]).z) end 此函数仅适用于多边形。输入“n”必须是一个表格,格式为{{x=,y=,z=},{x=,y=,z=},{x=,y=,z=}},其中每个子表格代表多边形的一个顶点。当你直接站在由这些坐标描述的三角形上方时,inside()将返回true。(坐标必须相对于玩家/摄像机位置) 函数height(n) n1=n[3].z-n[1].z n2=n[3].x-n[1].x n3=n[3].y-n[1].y n4=n[2].z-n[1].z A=((n[2].y-n[1].y)*n1-n3*n4)/((n[2].x-n[1].x)*n1-n2*n4) B=(n3-A*n2)/n1 h=n[1].y-A*n[1].x-B*n[1].z return A,B,h end --或者: function height(t,x,y) local n=cross(vadd(t[2],t[1],-1),vadd(t[3],t[1],-1)) return -((x-t[1].x)*n.x+(y-t[1].y)*n.y)/n.z+t[1].z end 与inside()类似,height()采用相同的输入数据结构。height()函数会计算描述多边形的函数,然后计算X和Y梯度以及你在三角形上方的高度。第二个版本会接收一个三角形t以及x和y坐标,然后返回从点(x,y,0)到其前方三角形上对应点的距离。这在3D空间中的显示和平台跳跃方面非常有用,如果你足够有勇气,或许也可用于光线追踪,但除此之外几乎毫无用处。 向量(进一步的坐标变换) 本节中的所有函数均假设向量量的形式为{x=,y=,z=}。幸运的是,这些运算是通用的,因此每个字母在现实世界中代表哪个轴并不重要。 function mag(v) return math.sqrt(v.x^2+v.y^2+v.z^2) end function scal(v,s) return {x=v.x*s,y=v.y*s,z=v.无法识别内容,已删除。很长一段时间以来,我一直误以为函数无法返回表格。现在我知道这是错误的,以下是一些最有用的向量运算。 function norv(v) return scal(v,1/mag(v)) end function proj(a,b) return scal(norv(b),dot(a,norv(b))) end --或者: function proj(a,b) return scal(b,dot(a,b)/mag(b)^2) endmag(v) 返回向量v的模长(||v||) scal(v,s) 返回向量v与标量s的乘积(v * s) vadd(a,b) 返回向量a与向量b的和(a + b) dot(a,b) 返回向量a与向量b的点积(a * b) crs(a,b) 返回向量a与向量b的叉积(a x b) norv(v) 返回向量v的单位向量(v / ||v||) proj(a,b) 返回向量a在向量b方向上的投影分量(b * (a * b) / ||b||²) 向量转换:localv={x=dot(globalv,locali),y=dot(globalv,localj),z=dot(globalv,localk)} 其中local i、j、k是旋转参考物体的三个基单位向量,globalv是表示某点全局坐标的向量。localv将是该点相对于旋转物体的局部坐标。 这里使用点积而非投影函数,但数学原理完全相同。proj 就是一个点积,仅按 v1 的长度进行缩放,再乘以 v2 方向上的单位向量。这会产生一个向量输出。由于我们只关心每个单位向量的大小(正负),因此可以跳过乘以 v2/||v2|| 这一步,直接使用点积。单位向量的大小始终为 1,所以无需按 v1 的长度进行缩放,因为 v2 的长度始终为 1。 函数 megaproj(v,i,j,k) 返回 {x=dot(v,i),y=dot(v,j),z=dot(v,k)} 结束 函数 megascal(v,i,j,k) 返回 vadd(vadd(scal(i,v.x),scal(j,v.y)),scal(k,v.z)) 结束 megaproj 会将世界坐标投影到由局部单位向量 i、j 和 k 定义的局部坐标上。megascal函数则会执行相反的操作,它利用局部的i、j、k向量将点的局部坐标扩展为全局坐标。如果已经通过三个单位向量定义了一个变换,那么这些函数会非常实用。 这些函数始终互为逆运算,这意味着对于任意三个单位向量(前提是任意两个都不相同)所定义的变换,都可以通过另一个函数进行反转和“解码”。这意味着它们的使用方式可能类似于XML中的数字列表,但我暂时想不到什么实际用途。 事实证明,我在这里意外地推导出了一个旋转矩阵。如果感兴趣,可以查看我的欧拉角指南中的矩阵正切部分。此函数将根据物理传感器的欧拉角生成矩阵i、j和k基向量: function ijkfromeuler(ex,ey,ez) cx=math.cos(ex) sx=math.sin(ex) cy=math.cos(ey) sy=math.sin(ey) cz=math.cos(ez) sz=math.sin(ez) i={x=cy*cz,y=cy*sz,z=-sy} j={x=sx*sy*cz-cx*sz,y=sx*sy*sz+cx*cz,z=sx*cy} k={x=cx*sy*cz+sx*sz,y=cx*sy*sz-sx*cz,z=cx*cy} return i,j,k end --或者... function ijkfromeuler(ex,ey,ez) local cx,sx,cy,sy,cz,sz=math.cos(ex),math.sin(ex),math.cos(ey),math.sin(ey),math.cos(ez)math.sin(ez) local i,j,k={x=cy*cz,y=cy*sz,z=-sy},{x=sx*sy*cz-cx*sz,y=sx*sy*sz+cx*cz,z=sx*cy},{x=cx*sy*cz+sx*sz,y=cx*sy*sz-sx*cz,z=cx*cy} return i,j,k end 这些向量可用于megaproj()和megascal()函数来执行或反转旋转。setNumber(1,华氏度) --我们的函数可能看起来像这样: function ctof(x) return x*9/5+32 end --函数名称可以是任意的,在这个例子中是“ctof”,但通常来说,最好让函数名称与Lua提供的标准库函数有明显区别。 “return”到底有什么作用?当你要为函数提供输出时,你可以写“return x”,其中x可以是数字或布尔值(真/假)。之后在代码中调用该函数时,你可以通过将变量赋值为该函数来获取这个输出,例如: 华氏度 = ctof(摄氏度) 此时“华氏度”的值就等于函数内部的x*9/5+32,不过由于我们在括号中输入的是“摄氏度”的值,所以“华氏度”等于摄氏度*9/5+32。本质上,你将一个值输入到函数中,函数就会输出结果。 不过,函数并不局限于只有一个输入和一个输出。 ——这是一个接收两个输入(x和y)并输出两个结果的函数,一个结果是x除以y的商,另一个是用于检测是否除以零的布尔值(开/关)。 函数 divide(x,y) 如果 y==0 那么 返回 0, true 否则 返回 x/y, false 结束 ——调用此函数时,不能只将一个变量赋值给函数,而必须将两个变量赋值给该函数。赋值方式与函数输出两个结果的方式相同: a=1 b=2 result, divideByZero = divide(a,b) ——在这种情况下,“result”将等于a/b,“divideByZero”将等于false。注意输入和输出的顺序决定了每个值的结果。如果我写成divideByZero,result = divide(a,b),那么“divideByZero”将等于a/b,“result”将等于false。函数括号中的输入也是如此。 替代语法:有时你可能会看到函数以这种形式定义: functionName = function (parameters) --执行操作 return something end 这与传统的function functionName()完全相同,只是重新排列了形式。这在Lua中是允许的,因为Lua是所谓的“面向对象”语言,即几乎所有事物都被视为可以移动和操作的对象或变量。另一种定义函数的语法让函数看起来更像是一个特殊的变量,它可以执行任务并根据输入参数发生变化。 这也意味着,由于函数本质上是对象,它们可以被用作其他函数的参数。如果你做一些研究,可能会发现这样的函数写法: table.sort(table, function (a,b) return a>b end) 这看起来确实令人困惑和害怕,但要记住,函数本身可以用作参数。在这种情况下,这种语法是更合适的,因为在预定义的table.sort函数内部,该函数的参数(a,b)会像其他任何参数一样被赋予自己的局部名称。唯一的区别在于,该参数不是带有某个值的变量,而是一个函数。 不过,对于本指南而言,那些替代语法并不重要,所有内容都以常规形式编写。说实话,我不确定哪种形式更常规,哪种是“语法糖”,但我喜欢第一种方式,所以会一直使用它。 在Lua中,缩进和return语句并非必需,但它们能让代码更易于阅读。 这应该是在使用本指南其余部分中的函数之前,你需要了解的所有内容。 一大段文字(通用和自动驾驶使用的函数) (缩进不是必需的) pi=math.pi pi2=pi*2 这不仅方便使用,而且是许多函数所必需的。它节省了空间,并使三角函数更易于阅读。本指南中的所有角度测量单位均为弧度,而非度。当然,如果有人确实使用梯度单位,也可采用。 函数sgn(x) 如果x<0,则 返回-1 否则 返回1 结束 或者... 函数sgn(x) 返回x<0且-1或1 结束 此函数与你在微控制器功能框中可能使用的sgn()函数相同。当输入大于或等于0时,它返回+1;当输入小于0时,它返回-1。 第二个版本的sgn()函数之所以能工作,是因为lua对and和or的处理方式。如果and的第一个参数为false或nil,它将返回第一个参数的值,否则返回第二个参数的值。or的工作方式类似,只有当第一个参数的值为false或nil时,它才返回第二个参数的值,否则返回第一个参数的值。这种if语句类型的逻辑可以通过在满足另一个条件时避免执行代价高昂的计算来优化某些函数,与if-then-else语句的作用相同。 函数clamp(x,l,u) 返回math.min(math.max(x,l),u) 结束 与sgn()函数类似,这是微控制器中函数盒里可用的一个函数。它将值x“限制”在l和u之间,其中l代表下限值,u代表上限值。 函数len(x,y) 返回math.sqrt(x^2+y^2) 结束 同样可在函数盒中找到。这其实就是伪装后的勾股定理。 函数atan2(dy,dx) 如果dx>0则 返回math.atan(dy/dx) 否则如果dx<0则 返回math.atan(dy/dx)+pi*sgn(dy) 否则 返回0 结束 结束 --编辑:我真傻,显然如果你使用默认的math.




换一换 





















