旋转通常使用从起始位置开始的某种偏移量(轴角、四元数、欧拉角等)来定义。您正在寻找的东西将被更准确地描述(在我看来)作为重新定位。幸运的是,这并不难做到。你需要的是一个基差矩阵。
首先,让我们在代码中定义我们正在使用的内容:
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;
// ...
}
// ...
}