Я следил за Haskell учебником OpenGL . Вращения в трехмерном пространстве заинтриговали меня, поэтому я начал изучать углы Эйлера и, наконец, кватернионы. эти два документа: в основном этот и этот .
Моя функция работает нормально, когда я выполняю вращение только на одной оси, но когда я это делаю например, по X и Y куб начинает случайным образом go вперед и блокируется при вращении.
Видео куба, выполняющего вращение по XY .
Когда я устанавливаю три оси (X, Y, Z), масштабирование увеличивается еще больше (но без этой странной блокирующей штуки): видео .
Вот код моей программы:
Вот главный файл, который создает окно, устанавливает функцию ожидания и выводит результат поворота на угол A на экране, где A увеличивается на 0,05 для каждого кадра.
module Main (main) where
import Core
import Utils
import Data.IORef
import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL
main :: IO ()
main = do
createAWindow "177013"
mainLoop
createAWindow :: [Char] -> IO ()
createAWindow windowName = do
(procName, _args) <- getArgsAndInitialize
createWindow windowName
initialDisplayMode $= [DoubleBuffered]
angle <- newIORef 0.0
delta <- newIORef 0.05
displayCallback $= (start angle)
reshapeCallback $= Just reshape
keyboardMouseCallback $= Just keyboardMouse
idleCallback $= Just (idle angle delta)
reshape :: ReshapeCallback
reshape size = do
viewport $= (Position 0 0, size)
postRedisplay Nothing
keyboardMouse :: KeyboardMouseCallback
keyboardMouse _ _ _ _ = return ()
idle :: IORef GLfloat -> IORef GLfloat -> IdleCallback
idle angle delta = do
d <- get delta
a <- get angle
angle $~! (+d)
postRedisplay Nothing
start :: IORef GLfloat -> DisplayCallback
start angle = do
clear [ColorBuffer]
loadIdentity
a <- get angle
let c = rotate3f (0, 0, 0) [X,Y,Z] a $ cube3f 0.2 -- here I'm rotating on X, Y and Z axis
draw3f Quads c CCyan
flush
swapBuffers
where
Вот файл ядра, в котором ion функция определена (с некоторыми другими). Я добавил несколько комментариев, так как это, вероятно, код низкого качества haskell.
module Core (draw3f, vertex3f, rotate3f, translate3f, rotate3d, Colors(..), Axes(..)) where
import Control.Lens
import Graphics.Rendering.OpenGL
data Axes = X | Y | Z
deriving Eq
data Colors = CRed | CGreen | CBlue | CYellow | CWhite | CMagenta | CCyan | CBlack | CNone | CPreset
deriving Eq
rotate3f :: (GLfloat, GLfloat, GLfloat) -> [Axes] -> GLfloat -> [(GLfloat, GLfloat, GLfloat)] -> [(GLfloat, GLfloat, GLfloat)]
rotate3f _ _ _ [] = []
rotate3f _ [] _ _ = []
rotate3f o axes a p = let p' = translate3f p u -- translation if I don't want to rotate it by the origin
q = cos a' : ((\x -> if x `elem` axes then sin a' else 0) <$> [X,Y,Z]) -- if the axe is set then its related component is equal to sin theta/2, otherwise it will be 0
q' = q !! 0 : (negate <$> (tail q)) -- quaternion inversion
in translate3f ((rotate q q') <$> p') [(0,0,0),o] -- rotate and translate again to put the object where it belongs
where
a' = (a * (pi / 180)) / 2 -- convert to radians and divide by 2 as all q components takes theta/2
u :: [(GLfloat, GLfloat, GLfloat)]
u = [o,(0,0,0)]
rotate :: [GLfloat] -> [GLfloat] -> (GLfloat, GLfloat, GLfloat) -> (GLfloat, GLfloat, GLfloat)
rotate q q' (x,y,z) = let p = [0,x,y,z]
qmul q1 q2 = [(q1 !! 0) * (q2 !! 0) - (q1 !! 1) * (q2 !! 1) - (q1 !! 2) * (q2 !! 2) - (q1 !! 3) * (q2 !! 3),
(q1 !! 0) * (q2 !! 1) + (q1 !! 1) * (q2 !! 0) + (q1 !! 2) * (q2 !! 3) - (q1 !! 3) * (q2 !! 2),
(q1 !! 0) * (q2 !! 2) - (q1 !! 1) * (q2 !! 3) + (q1 !! 2) * (q2 !! 0) + (q1 !! 3) * (q2 !! 1),
(q1 !! 0) * (q2 !! 3) + (q1 !! 1) * (q2 !! 2) - (q1 !! 2) * (q2 !! 1) + (q1 !! 3) * (q2 !! 0)]
p' = qmul (qmul q p) q'
in (p' !! 1, p' !! 2, p' !! 3)
translate3f :: [(GLfloat, GLfloat, GLfloat)] -> [(GLfloat, GLfloat, GLfloat)] -> [(GLfloat, GLfloat, GLfloat)]
translate3f p [(ax,ay,az),(bx,by,bz)] = map (\(x,y,z) -> (x + (bx - ax), y + (by - ay), z + (bz - az))) p
draw3f :: PrimitiveMode -> [(GLfloat, GLfloat, GLfloat)] -> Colors -> IO()
draw3f shape points color = renderPrimitive shape $ mapM_ (\(x,y,z) -> vertex3f x y z color) points
vertex3f :: GLfloat -> GLfloat -> GLfloat -> Colors -> IO()
vertex3f x y z c = do
if c /= CPreset
then color $ Color3 (c' ^. _1) (c' ^. _2) ((c' ^. _3) :: GLfloat)
else return ()
vertex $ Vertex3 x y z
where
c' :: (GLfloat, GLfloat, GLfloat)
c' = case c of CRed -> (1,0,0)
CGreen -> (0,1,0)
CBlue -> (0,0,1)
CYellow -> (1,1,0)
CMagenta -> (1,0,1)
CCyan -> (0,1,1)
CBlack -> (0,0,0)
_ -> (1,1,1)
А вот файл utils, где есть только определение куба из Haskell Учебник по OpenGL
module Utils (cube3f) where
import Core
import Graphics.UI.GLUT
import Graphics.Rendering.OpenGL
cube3f :: GLfloat -> [(GLfloat, GLfloat, GLfloat)]
cube3f w = [( w, w, w), ( w, w,-w), ( w,-w,-w), ( w,-w, w),
( w, w, w), ( w, w,-w), (-w, w,-w), (-w, w, w),
( w, w, w), ( w,-w, w), (-w,-w, w), (-w, w, w),
(-w, w, w), (-w, w,-w), (-w,-w,-w), (-w,-w, w),
( w,-w, w), ( w,-w,-w), (-w,-w,-w), (-w,-w, w),
( w, w,-w), ( w,-w,-w), (-w,-w,-w), (-w, w,-w)]
Наконец, если это может помочь людям увидеть, есть ли проблема в моих алгоритмах, вот несколько образцов вращения с использованием моей функции:
Вращение на 90 ° точки (1, 2, 3) по оси X вокруг точки (0, 0, 0) (начало координат) дает: (0.99999994,-3.0,2.0)
То же вращение, но по осям X и Y дает: (5.4999995,-0.99999994,-0.49999988)
Снова то же вращение, но по осям X, Y и Z дает: (5.9999995,1.9999999,3.9999995)