连接球和圆柱体的 3D 旋转 [英] 3D rotations to connect balls and cylinders

查看:23
本文介绍了连接球和圆柱体的 3D 旋转的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的任务是为生成图形的 STL 模型的图形绘制程序编写一个基于 Python 的插件.图是由顶点和边组成的对象,其中顶点由 3D 球(镶嵌的二十面体)表示,边由圆柱表示,该圆柱与两端的两个球相连.3D 模型的最终结果是它将转储到 STL 文件中以进行 3D 打印.我能够毫无问题地为球和圆柱体生成 3D 模型,但是我在生成整体模型以及让球体和圆柱体正确连接时遇到了一些问题.

I've been tasked with writing a python based plugin for a graph drawing program that generates an STL model of a graph. A graph being an object made up of vertices and edges, where a vertex is represented by a 3D ball (a tessellated icosahedron), and an edge is represented with a cylinder that connects with two balls at either end. The end result of the 3D model is that it will get dumped out to an STL file for 3D printing. I'm able to generate the 3D models for the balls and cylinders without any issues, but I'm having some issues generating the overall model, and getting the balls and cylinders to connect properly.

我最初的想法是在原点创建镶嵌的二十面体,然后将它们转换到顶点的位置.这工作正常.然后,对于每条边,我会在原点创建一个圆柱体,将其旋转到正确的角度,使其指向正确的方向,然后将其平移到两个顶点之间的中点,以便嵌入圆柱体的末端在二十面体中.这就是事情出错的地方.我在使旋转正确时遇到了一些困难.要计算旋转,我正在执行以下操作:

My original idea was to create tessellated icosahedrons at the origin, then translate them out to the positions of the vertices. This works fine. I then, for each edge, I would create a cylinder at the origin, rotate it to the correct angle so that it points in the correct direction, then translate it to the midpoint between the two vertices so that the ends of the cylinders are embedded in the icosahedrons. This is where things are going wrong. I'm having some difficulties getting the rotations correct. To calculate the rotations, I'm doing the following:

首先,我找到两点之间的角度如下(其中源和目标都是图中的顶点,属于我当前正在处理的边):

First, I find the angle between the two points as follows (where source and target are both vertices in the graph, belonging to the edge that I'm currently processing):

        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 度,取决于哪个顶点是源,哪个是目标).

The angles being calculated seem reasonable, and as far as I can tell, do actually represent the angle between the vertices. For example, if I have a vertex at (1, 1, 0) and another vertex at (3, 3, 0), the angle edge connecting them does show up as a 45 degree angle between the two vertices. (That, or -135 degrees, depending which vertex is the source and which is the target).

一旦计算出角度,我就创建了一个圆柱体并按照计算出的角度旋转它,就像这样,使用我创建的其他一些类:c = 气缸()c.createCylinder(edgeThickness, edgeLength)

Once I have the angles calculated, I create a cylinder and rotate it by the angles that have been calculated, like so, using some other classes that I've created: c = cylinder() c.createCylinder(edgeThickness, edgeLength)

        c.rotateX(-yzAngle)
        c.rotateY(xzAngle)
        c.rotateZ(-xyAngle)
        c.translate(edgePosition.x, edgePosition.y, edgePosition.z)

(其中 edgePosition 是图中两个顶点之间的中点,edgeThickness 是正在创建的圆柱体的半径,edgeLength 是两个顶点之间的距离).

(Where edgePosition is the midpoint between the two vertices in the graph, edgeThickness is the radius of the cylinder being created, and edgeLength is the distance between the two vertices).

如前所述,气缸的旋转没有按预期工作.它似乎在 x/y 平面上进行了正确的旋转,但是只要一条边的顶点在所有三个分量(x、y 和 z)中都不同,旋转就会失败.下面是一个在 x 和 y 分量上不同但在 z 分量上不同的图示例:

As mentioned, its the rotating of the cylinders that doesn't work as expected. It seems to do the correct rotation on the x/y plane, but as soon as an edge has vertices that differ in all three components (x, y, and z), the rotation fails. Here's an example of a graph that differs in the x, and y components, but not in the z component:

这是生成的 STL 文件,如 Makerware 中所示(用于将 3D 模型发送到 3D 打印机):

And here's the resulting STL file, as seen in Makerware (which is used to send the 3D models to the 3D printer):

(左下角的额外圆柱体是我目前为了测试目的而留下的东西 - 一个圆柱体指向 z 轴的方向,位于原点).

(The extra cylinder looking bit in the bottom left is something I've currently left in for testing purposes - a cylinder that points in the direction of the z axis, located at the origin).

如果我采用相同的图形并将中间顶点移出 z 轴,那么现在所有边都涉及所有三个轴上的角度,我会得到如下结果:

If I take that same graph and move the middle vertex out in the z axis, so now all the edges involve angles in all three axis, I get a result something like the following:

如应用中所示:

生成的 STL 文件,如 Makerware 中所示:

The resulting STL file, as show in Makerware:

...从侧面看同一个模型:

...and that same model as viewed from the side:

如您所见,圆柱体绝对不会像我想象的那样与球相遇.我的问题是:我做这件事的方法是有缺陷的,还是我在轮换中犯的一些小但严重的错误?我很确定这不是旋转函数本身的问题,因为我已经能够独立验证它们是否按预期工作.我还尝试创建一个旋转函数,该函数接受偏航、俯仰和滚转,并同时执行所有三个操作,它似乎产生了相同的结果,如下所示:

As you can see, the cylinders definitely aren't meeting up with the balls like I thought they would. My question is this: Is my approach to doing this flawed, or is it some small but critical mistake that I'm making somewhere in my rotations? I'm pretty sure it isn't a problem with the rotation functions themselves, as I've been able to independently verify that they work as expected. I also tried creating a rotate function that takes in a yaw, pitch, and roll and does all three at once, and it seemed to generate the same result, like so:

c.rotateYawPitchRoll(xzAngle, -yzAngle, -xyAngle)

那么......有人对我可能做错了什么有任何想法吗?

So... anyone have any ideas on what I might be doing wrong?

更新:正如joojaa 所指出的,这是计算正确角度以及应用它们的顺序的组合.为了让事情顺利进行,我首先计算 x 轴上的旋转,如下所示:

UPDATE: As joojaa pointed out, it was a combination of calculating the correct angles as well as the order that they were applied. In order to get things working, I first calculate the rotation on the x axis, as follows:

zyAngle = math.atan2(deltaVector.z, deltaVector.y)

其中 deltaVector 是目标向量和源向量之间的差异.不过,这种轮换尚未应用!下一步是计算y轴上的旋转,如下:

where deltaVector is the difference between the target and source vectors. This rotation is not yet applied though! The next step is to calculate the rotation on the y axis, as follows:

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:

Once both rotations are calculated, they are then applied... in the reverse order! First, the x, then the y:

c.rotateY(angle)
c.rotateX(-zyAngle) #... where c is a cylinder object

似乎仍然存在一些错误,但这似乎至少适用于一个简单的测试用例.

There still seems to be a few bugs, but this seems to at least work for a simple test case.

推荐答案

旋转是连续发生的,所以角度会相互影响.不可能使用欧拉模型一次旋转它们.这就是为什么您不能仅根据第一个静态情况计算旋转.想象一下转动一个立方体,使它的角直立.是的,第一次旋转是 45,但第二次不是,因为那时立方体已经转动了(绘制序列的每一步,看看会发生什么).空间旋转并非微不足道.

Rotation happens in successive order, so the angles affect each other. It is not possible to use a Euler model to rotate them at once. This is why you can not just calculate the rotations based on the first static situation. Just imagine turning a cube so that it is standing on its corner upright. Yes the first rotation is 45 but the second is not since the cube is already turned by that time (draw a each step of the sequence and see what happens). Space rotations aren't trivial.

所以你需要旋转一个角度然后重新计算第二个角度等等.这也是为什么您的第一次轮换工作正常的原因.除非您有兴趣确保绕轴的旋转具有特定方向,否则您只需要旋转 2 圈.

So you need to rotate one angle then re calculate the second angle and so forth. This is also why your first rotation works right. You only need 2 rotations unless your interested in making sure the rotation around the shaft has a certain direction.

我建议您改用轴角或矩阵来执行此操作.主要是因为在轴角度中这是微不足道的,角度是沿管起点和终点向量之间的点,轴是这两个向量之间的交叉点.然后,如果需要,您可以将它们转换为欧拉角.但也许你可以直接使用矩阵.有关如何直接计算转换和旋转的想法,请参见:transformations.py 作者:Christoph Gohlke.另请参阅随附的 c 源.

I would suggest you use axis angles or matrices instead to do this. Mainly because in axis angles this is trivial the angle is the dot between the along tube start and end vectors and the axis is the cross between those 2. You can then convert those to Euler angles if you need. But probably you can just use the matrix directly. For ideas on how conversions and how the rotation could directly be calculated see: transformations.py by Christoph Gohlke. Also see the accompanying c source.

我想我需要稍微扩展一下这个答案

对于这个问题,有一个非常简单的方法可以避开您和许多其他人的问题.答案是不要使用欧拉角旋转.我已经使用了大量的脑力来尝试将欧拉旋转解释为最终在没有欧拉旋转的情况下更容易解决的问题.为了证明这一点,如果您想更多地想出更多答案,我将只留下一个原因.

There is a really easy way out for this question that sidesteps all your and many other persons problems. The answer is do not use Euler angle rotation. Ive used a lot of brainpower to try to explain Euler rotations to problems that are ultimately solved more easily without Euler rotations. To justify i will leave just one reason for this if you want more think up of some more answers.

大多数使用欧拉旋转序列的原因是您可能不了解欧拉角.事实上,只有少数情况是好的.没有自尊的程序员使用欧拉旋转来解决这个问题.你所做的是使用向量数学来代替.

The reason most to use Euler rotation sequences is that you probably don't understand Euler angles. There are in fact only a handful of situations where they are good. No self respecting programmer uses Euler rotations to solve this issue. What you do is you use vector math instead.

所以你有从源到目标的方向向量,通常是计算出来的:

So you have the direction vector from the source to target which is usually calculated:

along = normalize(target-source)

这只是您的矩阵行之一(或列符号由模型制造商决定),与您的圆柱体原始方向相对应的行(行只是 x y z w),然后您需要另一个与此垂直的向量.选择一个任意的向量,比如向上(如果你的沿指向接近向上,则向左).沿着第二行方向交叉乘积这个向上向量.最后将您的源作为最后一行,最后一列中为 1.完成完全形成的仿射矩阵描述圆柱体.由于您可以绘制矢量,因此更容易理解.

this is simply one of your matrix rows (or column notation is up to model maker), the one that corresponds to your cylinders original direction (the rows are just x y z w), then you need another vector perpendicular to this one. Choose a arbitrary vector like up (or left if your along is pointing close to up). cross product this up vector by your along for the second row direction. and finally put your source as the last row with 1 in the last column. Done fully formed affine matrix describing the cylinders prition. Much easier to understand since you can draw the vectors.

有更短的方法,但这个很容易理解.

There are shorter ways but this one is easy to understand.

这篇关于连接球和圆柱体的 3D 旋转的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆