我正在循环中创建一些随机向量/方向作为圆顶形状,如下所示:
void generateDome(glm::vec3 direction)
{
for(int i=0;i<1000;++i)
{
float xDir = randomByRange(-1.0f, 1.0f);
float yDir = randomByRange(0.0f, 1.0f);
float zDir = randomByRange(-1.0f, 1.0f);
auto vec = glm::vec3(xDir, yDir, zDir);
vec = glm::normalize(vec);
...
//some transformation with direction-vector
}
...
}
这将在
+y
中创建圆顶形状的矢量方向 (0,1,0)
:现在我想旋转
vec
-Vector by a given direction-Vector like (1,0,0)
.这应该像这样将“圆顶”旋转到 x 方向:
我怎样才能做到这一点? (最好用glm)
最佳答案
旋转通常使用与起始位置的某种偏移(轴角、四元数、欧拉角等)来定义。您正在寻找的内容(在我看来)会更准确地描述为重新定位。幸运的是,这并不难做到。你需要的是一个变化的基矩阵。
首先,让我们定义我们在代码中使用的内容:
using glm::vec3;
using glm::mat3;
vec3 direction; // points in the direction of the new Y axis
vec3 vec; // This is a randomly generated point that we will
// eventually transform using our base-change matrix
要计算矩阵,您需要为每个新轴创建单位向量。从上面的示例中可以明显看出,您希望提供的向量成为新的 Y 轴:
vec3 new_y = glm::normalize(direction);
现在,计算 X 轴和 Z 轴会稍微复杂一些。我们知道它们必须彼此正交并且与上面计算的 Y 轴正交。构建 Z 轴最合乎逻辑的方法是假设旋转发生在由旧 Y 轴和新 Y 轴定义的平面中。通过使用叉积,我们可以计算该平面的法向量,并将其用于 Z 轴:
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
从技术上讲,这里不需要归一化,因为两个输入向量都已经归一化了,但为了清楚起见,我已经离开了。另请注意,当输入向量与 Y 轴共线时,存在一种特殊情况,在这种情况下,上面的叉积未定义。解决此问题的最简单方法是将其视为特殊情况。我们将使用:
if (direction.x == 0 && direction.z == 0)
{
if (direction.y < 0) // rotate 180 degrees
vec = vec3(-vec.x, -vec.y, vec.z);
// else if direction.y >= 0, leave `vec` as it is.
}
else
{
vec3 new_y = glm::normalize(direction);
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
// code below will go here.
}
对于 X 轴,我们可以将新的 Y 轴与新的 Z 轴交叉。这会产生一个垂直于其他两个轴的向量:
vec3 new_x = glm::normalize(glm::cross(new_y, new_z));
同样,这种情况下的归一化并不是真正必要的,但如果
y
或 z
不是已经是单位向量,它会是。最后,我们将新的轴向量组合成一个基变化矩阵:
mat3 transform = mat3(new_x, new_y, new_z);
将一个点向量 (
vec3 vec
) 乘以这个会在相同的位置产生一个新点,但相对于新的基向量(轴):vec = transform * vec;
对每个随机生成的点执行最后一步,就完成了!无需计算旋转角度或类似的东西。
作为旁注,您生成随机单位向量的方法将偏向远离轴的方向。这是因为选择特定方向的概率与到给定方向上可能的最远点的距离成正比。对于轴,这是
1.0
.对于像这样的方向。 (1, 1, 1)
,这个距离是sqrt(3)
.这可以通过丢弃位于单位球面之外的任何向量来解决:glm::vec3 vec;
do
{
float xDir = randomByRange(-1.0f, 1.0f);
float yDir = randomByRange(0.0f, 1.0f);
float zDir = randomByRange(-1.0f, 1.0f);
vec = glm::vec3(xDir, yDir, zDir);
} while (glm::length(vec) > 1.0f); // you could also use glm::length2 instead, and avoid a costly sqrt().
vec = glm::normalize(vec);
这将确保所有方向的概率相等,代价是如果您非常不走运,则选择的点可能会一遍又一遍地位于单位球体之外,并且可能需要很长时间才能生成内部点。如果这是一个问题,可以修改它以限制迭代:
while (++i < 4 && ...)
或者通过增加每次迭代接受点的半径。当 >= sqrt(3)
,所有可能的点都将被视为有效,因此循环将结束。这两种方法都会导致远离轴的轻微偏差,但在几乎任何实际情况下,都无法检测到。将上面的所有代码放在一起,结合您的代码,我们得到:
void generateDome(glm::vec3 direction)
{
// Calculate change-of-basis matrix
glm::mat3 transform;
if (direction.x == 0 && direction.z == 0)
{
if (direction.y < 0) // rotate 180 degrees
transform = glm::mat3(glm::vec3(-1.0f, 0.0f 0.0f),
glm::vec3( 0.0f, -1.0f, 0.0f),
glm::vec3( 0.0f, 0.0f, 1.0f));
// else if direction.y >= 0, leave transform as the identity matrix.
}
else
{
vec3 new_y = glm::normalize(direction);
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
vec3 new_x = glm::normalize(glm::cross(new_y, new_z));
transform = mat3(new_x, new_y, new_z);
}
// Use the matrix to transform random direction vectors
vec3 point;
for(int i=0;i<1000;++i)
{
int k = 4; // maximum number of direction vectors to guess when looking for one inside the unit sphere.
do
{
point.x = randomByRange(-1.0f, 1.0f);
point.y = randomByRange(0.0f, 1.0f);
point.z = randomByRange(-1.0f, 1.0f);
} while (--k > 0 && glm::length2(point) > 1.0f);
point = glm::normalize(point);
point = transform * point;
// ...
}
// ...
}
关于opengl - 如何按给定方向旋转矢量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20923232/