四元数旋转有一种奇怪的行为(Haskell OpenGL)
我一直在跟踪调查。 三维空间中的旋转吸引了我,所以我开始学习欧拉角,最后是四元数 我想用四元数实现我自己的函数来执行旋转(在立方体上),我基于这两篇论文:和 当我只在一个轴上执行旋转时,我的函数工作正常,但当我在X和Y轴上执行旋转时,例如,立方体开始随机向前移动,并且在旋转时被“阻止” 当我设置三个轴(X,Y,Z)时,它会放大更多(但没有那种奇怪的阻挡效果): 以下是我的程序代码: 这是一个主文件,它创建一个窗口,设置空闲功能,并在屏幕上输出旋转角度a的结果,其中a在每帧上增加0.05 最后,如果它能帮助人们了解我的算法中是否存在问题,以下是使用我的函数的一些旋转示例: 围绕点(0,0,0)(原点)在X轴上点(1,2,3)的90°旋转给出:四元数旋转有一种奇怪的行为(Haskell OpenGL),haskell,opengl,quaternions,Haskell,Opengl,Quaternions,我一直在跟踪调查。 三维空间中的旋转吸引了我,所以我开始学习欧拉角,最后是四元数 我想用四元数实现我自己的函数来执行旋转(在立方体上),我基于这两篇论文:和 当我只在一个轴上执行旋转时,我的函数工作正常,但当我在X和Y轴上执行旋转时,例如,立方体开始随机向前移动,并且在旋转时被“阻止” 当我设置三个轴(X,Y,Z)时,它会放大更多(但没有那种奇怪的阻挡效果): 以下是我的程序代码: 这是一个主文件,它创建一个窗口,设置空闲功能,并在屏幕上输出旋转角度a的结果,其中a在每帧上增加0.05 最后,
(0.99999994,-3.0,2.0)
相同的旋转,但在X&Y轴上给出:(5.4999995,-0.99999994,-0.49999888)
同样的旋转,但在X、Y和Z轴上给出:
(5.9999999 5,1.9999999,3.9999999 5)
关于四元数旋转的第二篇文章,您所指的是这样一句话:
“(x̂,ŷ,ẑ)是定义旋转轴的单位向量。”
所以四元数必须归一化,分量的平方和等于1
例如,如果你有3个轴,它必须是(cosθ/2,r3sinθ/2,r3sinθ/2,r3*sinθ/2),其中r3是3的平方根的倒数。这就是我将如何解释,当涉及多个轴时,您在文章末尾提到的旋转结果无法保持向量的长度
因此,关键部分是函数旋转3f中的这一行:
q = cos a' : ((\x -> if x `elem` axes then sin a' else 0) <$> [X,Y,Z])
请注意,定义特殊类型的想法不仅允许减少代码宽度,而且还提供了额外的灵活性。如果有一天您决定用比普通列表更有效的其他数据结构来表示四元数,那么可以在保持客户端代码不变的情况下完成
接下来,旋转代码正确。函数rotQuat0
是您的初始算法,它再现问题末尾提到的数值结果。FunctionrotQuat1
是一个修改版本,给出1-规范化四元数
-- original code:
rotQuat0 :: [Axes] -> GLfloat -> QuatFloat
rotQuat0 axes angle = let fn x = if (x `elem` axes) then (sin angle) else 0
in (cos angle) : (map fn [X,Y,Z])
-- modified code:
rotQuat1 :: [Axes] -> GLfloat -> QuatFloat
rotQuat1 axes angle = let corr = 1.0 / sqrt (fromIntegral (length axes))
fn x = if (x `elem` axes) then corr*(sin angle) else 0
in (cos angle) : (map fn [X,Y,Z])
使用rotQuat1
编码:
rotate3f :: GLfloatV3 -> [Axes] -> GLfloat -> [GLfloatV3] -> [GLfloatV3]
rotate3f _ _ _ [] = []
rotate3f _ [] _ _ = []
rotate3f org axes degθ pts =
let -- convert to radians and divide by 2, as all q components take θ/2
a' = (degθ * (pi / 180)) / 2
u :: [GLfloatV3]
u = [org, (0,0,0)]
-- translation if I don't want to rotate it by the origin
p' = translate3f pts u
-- if the axis is set, then its related component is
-- equal to sin θ/2, otherwise it will be zero
---- q = cos a' : ((\x -> if x `elem` axes then sin a' else 0) <$> [X,Y,Z])
q = rotQuat1 axes a' -- modified version
q' = qconj q
-- rotate and translate again to put the object where it belongs
in translate3f ((rotate q q') <$> p') [(0,0,0), org]
translate3f :: [GLfloatV3] -> [GLfloatV3] -> [GLfloatV3]
translate3f pts [(ax,ay,az), (bx,by,bz)] =
let dx = bx - ax
dy = by - ay
dz = bz - az
in map (\(x,y,z) -> (x + dx, y + dy, z + dz)) pts
让我们检查一下,对于函数rotQuat1
,初始(1,2,3)输入向量(即1+4+9=13)的平方范数保持不变,这与适当的旋转是一致的:
$ ghc opengl00.hs -o ./opengl00.x && ./opengl00.x
[1 of 1] Compiling Main ( opengl00.hs, opengl00.o )
Linking ./opengl00.x ...
(0.99999994,-3.0,2.0)
(3.6213198,-0.62132025,0.70710695)
(2.5773501,0.84529924,2.5773501)
14.0
13.999995
13.999998
$
不幸的是,我没有足够的时间安装OpenGL基础设施并复制动画。请让我们知道这是否解决了整个问题。您提到的关于四元数旋转的第二篇文章有这样一句话:“(x̂,ŷ,ẑ)是定义旋转轴的单位向量。”。所以四元数必须归一化,分量的平方和等于1。例如,如果你有3个轴,它必须是(cosθ/2,r3*sinθ/2,r3*sinθ/2,r3*sinθ/2),其中r3是3的平方根的倒数。这就是我如何解释的,当涉及多个轴时,你在文章末尾提到的旋转结果无法保持向量的长度。@jpmarinier感谢你的详细回答。例如,如果我涉及到2轴(例如y和x),那么Z分量应该归零?如果你只启用了x和y轴,那么它应该是(cosθ/2,r2*sinθ/2,r2*sinθ/2,0),其中r2是2的平方根的倒数。请不要教初学者类型同义词提供了一个他们没有的抽象障碍。这就是newtypes的作用。您的解决方案工作得很好,谢谢:),当我在X、Y和Z上旋转时,没有bug,并且工作正常。然而,使用X&Y时,立方体不再向前移动,但仍像上一个视频中那样“阻塞”自身。这是正常的行为吗?
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ExplicitForAll #-}
type GLfloat = Float
type GLfloatV3 = (GLfloat, GLfloat, GLfloat)
type QuatFloat = [GLfloat]
data Axes = X | Y | Z deriving Eq
qmul :: QuatFloat -> QuatFloat -> QuatFloat
qmul [qa0, qa1, qa2, qa3] [qb0, qb1, qb2, qb3] =
[
qa0*qb0 - qa1*qb1 - qa2*qb2 - qa3*qb3 ,
qa0*qb1 + qa1*qb0 + qa2*qb3 - qa3*qb2 ,
qa0*qb2 - qa1*qb3 + qa2*qb0 + qa3*qb1 ,
qa0*qb3 + qa1*qb2 - qa2*qb1 + qa3*qb0
]
qmul _ _ = error "Quaternion length differs from 4"
qconj :: QuatFloat -> QuatFloat
qconj q = (head q) : (map negate (tail q)) -- q-conjugation
rotate :: [GLfloat] -> [GLfloat] -> GLfloatV3 -> GLfloatV3
rotate q q' (x,y,z) = let p = [0, x,y,z]
[q0,q1,q2,q3] = qmul (qmul q p) q'
in (q1, q2, q3)
-- original code:
rotQuat0 :: [Axes] -> GLfloat -> QuatFloat
rotQuat0 axes angle = let fn x = if (x `elem` axes) then (sin angle) else 0
in (cos angle) : (map fn [X,Y,Z])
-- modified code:
rotQuat1 :: [Axes] -> GLfloat -> QuatFloat
rotQuat1 axes angle = let corr = 1.0 / sqrt (fromIntegral (length axes))
fn x = if (x `elem` axes) then corr*(sin angle) else 0
in (cos angle) : (map fn [X,Y,Z])
rotate3f :: GLfloatV3 -> [Axes] -> GLfloat -> [GLfloatV3] -> [GLfloatV3]
rotate3f _ _ _ [] = []
rotate3f _ [] _ _ = []
rotate3f org axes degθ pts =
let -- convert to radians and divide by 2, as all q components take θ/2
a' = (degθ * (pi / 180)) / 2
u :: [GLfloatV3]
u = [org, (0,0,0)]
-- translation if I don't want to rotate it by the origin
p' = translate3f pts u
-- if the axis is set, then its related component is
-- equal to sin θ/2, otherwise it will be zero
---- q = cos a' : ((\x -> if x `elem` axes then sin a' else 0) <$> [X,Y,Z])
q = rotQuat1 axes a' -- modified version
q' = qconj q
-- rotate and translate again to put the object where it belongs
in translate3f ((rotate q q') <$> p') [(0,0,0), org]
translate3f :: [GLfloatV3] -> [GLfloatV3] -> [GLfloatV3]
translate3f pts [(ax,ay,az), (bx,by,bz)] =
let dx = bx - ax
dy = by - ay
dz = bz - az
in map (\(x,y,z) -> (x + dx, y + dy, z + dz)) pts
sqNorm3 :: GLfloatV3 -> GLfloat
sqNorm3 (x,y,z) = x*x + y*y +z*z
printAsLines :: Show α => [α] -> IO ()
printAsLines xs = mapM_ (putStrLn . show) xs
main = do
let pt = (1,2,3) :: GLfloatV3
pt1 = rotate3f (0,0,0) [X] 90 [pt]
pt2 = rotate3f (0,0,0) [X,Y] 90 [pt]
pt3 = rotate3f (0,0,0) [X,Y,Z] 90 [pt]
pts = map head [pt1, pt2, pt3]
ptN = map sqNorm3 pts
printAsLines pts
putStrLn " "
printAsLines ptN
$ ghc opengl00.hs -o ./opengl00.x && ./opengl00.x
[1 of 1] Compiling Main ( opengl00.hs, opengl00.o )
Linking ./opengl00.x ...
(0.99999994,-3.0,2.0)
(3.6213198,-0.62132025,0.70710695)
(2.5773501,0.84529924,2.5773501)
14.0
13.999995
13.999998
$