我的任务是编写一个基于python的插件,用于生成图形的STL模型的图形绘制程序。图形是由顶点和边缘组成的对象,其中顶点由3D球(曲面细分的二十面体)表示,并且边缘用在任一端与两个球连接的圆柱表示。 3D模型的最终结果是它将被转储到STL文件以进行3D打印。我能够毫无问题地为球和圆柱体生成3D模型,但是我在生成整体模型时遇到了一些问题,并且让球和圆柱体正确连接。
我最初的想法是在原点创建镶嵌的二十面体,然后将它们转换为顶点的位置。这很好用。然后,对于每个边缘,我将在原点创建一个圆柱体,将其旋转到正确的角度,使其指向正确的方向,然后将其转换为两个顶点之间的中点,以便嵌入圆柱体的末端在二十面体中。这是出问题的地方。我在旋转正确方面遇到了一些困难。为了计算旋转,我正在做以下事情:
首先,我发现两点之间的角度如下(其中源和目标都是图中的顶点,属于我正在处理的边缘):
deltaX = source.x - target.x
deltaY = source.y - target.y
deltaZ = source.z - target.z
xyAngle = math.atan2(deltaX, deltaY)
xzAngle = math.atan2(deltaX, deltaZ)
yzAngle = math.atan2(deltaY, deltaZ)
计算的角度似乎是合理的,据我所知,实际上代表顶点之间的角度。例如,如果我在(1,1,0)处有一个顶点而在(3,3,0)处有另一个顶点,则连接它们的角度边缘确实显示为两个顶点之间的45度角。 (那个,或者-135度,取决于哪个顶点是源,哪个是目标)。
一旦我计算了角度,我创建一个圆柱体并按照已计算的角度旋转它,就像这样,使用我创建的其他类: c =圆柱() c.createCylinder(edgeThickness,edgeLength)
c.rotateX(-yzAngle)
c.rotateY(xzAngle)
c.rotateZ(-xyAngle)
c.translate(edgePosition.x, edgePosition.y, edgePosition.z)
(其中edgePosition是图中两个顶点之间的中点,edgeThickness是正在创建的圆柱的半径,edgeLength是两个顶点之间的距离)。
如上所述,它的气缸旋转不能按预期工作。它似乎在x / y平面上进行了正确的旋转,但是只要边缘的顶点在所有三个分量(x,y和z)中都不同,旋转就会失败。下面是一个图表示例,该图表的x和y组件不同,但z组件中没有:
这是生成的STL文件,如Makerware中所示(用于将3D模型发送到3D打印机):
(左下角的额外圆柱位是我目前留给测试目的的东西 - 一个指向z轴方向的圆柱体,位于原点)。
如果我采用相同的图形并在z轴上移出中间顶点,那么现在所有的边都涉及所有三个轴的角度,我得到的结果如下:
如应用中所示:
生成的STL文件,如Makerware中所示:
......和从侧面看的那个模型:
正如你所看到的那样,气缸绝对不会像我想的那样与球相遇。我的问题是:我这样做的方法是否有缺陷,或者是我在轮换中某处做的一些小而重要的错误?我很确定旋转功能本身不是问题,因为我已经能够独立验证它们是否按预期工作。我还尝试创建一个旋转函数,它接受一个偏航,俯仰和滚动,同时完成所有三个,它似乎产生相同的结果,如下所示:
c.rotateYawPitchRoll(xzAngle, -yzAngle, -xyAngle)
所以......任何人对我可能做错了什么都有任何想法?
更新:正如joojaa指出的那样,它是计算正确角度以及它们应用顺序的组合。为了使事情有效,我首先计算x轴上的旋转,如下所示:
zyAngle = math.atan2(deltaVector.z, deltaVector.y)
其中deltaVector是目标和源向量之间的差异。此旋转尚未应用!下一步是计算y轴上的旋转,如下所示:
angle = vector.angleBetweenVectors(vector(target.x - source.x, target.y - source.y, target.z - source.z), vector(target.x - source.x, target.y - source.y, 0.0))
一旦计算出两个旋转,它们就会以相反的顺序应用......首先是x,然后是y:
c.rotateY(angle)
c.rotateX(-zyAngle) #... where c is a cylinder object
似乎仍有一些错误,但这似乎至少适用于一个简单的测试用例。
答案 0 :(得分:1)
旋转以连续顺序发生,因此角度相互影响。无法使用Euler模型一次旋转它们。这就是为什么你不能只根据第一个静态情况计算旋转的原因。想象一下,转动一个立方体,使它直立在角落上。是的,第一次旋转是45,但第二次旋转不是因为那个时间已经转动了立方体(绘制序列的每一步,看看会发生什么)。空间轮换并非无足轻重。
因此您需要旋转一个角度然后重新计算第二个角度,依此类推。这也是你的第一次轮换正常工作的原因。你只需要2次旋转,除非你有兴趣确保轴周围的旋转有一定的方向。
我建议你使用轴角或矩阵代替这样做。主要是因为在轴角度这是微不足道的,角度是沿管开始和结束向量之间的点,轴是那些之间的交叉。然后,如果需要,可以将它们转换为欧拉角。但可能你可以直接使用矩阵。有关如何直接计算转换次数和轮换方式的建议,请参阅:Christoph Gohlke撰写的transformations.py。另见c源。
我想我需要稍微扩展一下这个答案
这个问题有一个非常简单的方法可以回避所有你和许多其他人的问题。答案是不要使用欧拉角度旋转。我已经使用了大量的智力来试图解释欧拉旋转问题,这些问题最终在没有欧拉旋转的情况下更容易解决。为了证明这一点,如果你想要更多考虑更多的答案,我只会留下一个理由。
大多数使用欧拉旋转序列的原因是你可能不了解欧拉角。实际上只有极少数情况下它们是好的。没有自尊的程序员使用欧拉旋转来解决这个问题。你做的是用矢量数学代替。
所以你有从源到目标的方向向量,通常是计算出来的:
along = normalize(target-source)
这只是你的一个矩阵行(或列符号取决于模型制作者),一个对应于你的圆柱体原始方向(行只是x y z w),那么你需要另一个垂直于这个的向量。选择一个像向上的任意向量(如果你的指向近似向上,则选择左侧)。通过你的第二行方向交叉产生这个向上的向量。最后将您的源代码作为最后一行中的最后一行。做完全形成的仿射矩阵描述气缸的prition。因为你可以绘制矢量,所以更容易理解。
有更短的方法,但这个很容易理解。