rattletrap-12.0.1: src/lib/Rattletrap/Type/Quaternion.hs
module Rattletrap.Type.Quaternion where
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import qualified Data.Ord as Ord
import qualified Rattletrap.BitGet as BitGet
import qualified Rattletrap.BitPut as BitPut
import qualified Rattletrap.Exception.InvalidComponent as InvalidComponent
import qualified Rattletrap.Schema as Schema
import qualified Rattletrap.Type.CompressedWord as CompressedWord
import qualified Rattletrap.Utility.Json as Json
data Quaternion = Quaternion
{ x :: Double,
y :: Double,
z :: Double,
w :: Double
}
deriving (Eq, Show)
instance Json.FromJSON Quaternion where
parseJSON = Json.withObject "Quaternion" $ \object -> do
x <- Json.required object "x"
y <- Json.required object "y"
z <- Json.required object "z"
w <- Json.required object "w"
pure Quaternion {x, y, z, w}
instance Json.ToJSON Quaternion where
toJSON a =
Json.object
[ Json.pair "x" $ x a,
Json.pair "y" $ y a,
Json.pair "z" $ z a,
Json.pair "w" $ w a
]
schema :: Schema.Schema
schema =
Schema.named "quaternion" $
Schema.object
[ (Json.pair "x" $ Schema.ref Schema.number, True),
(Json.pair "y" $ Schema.ref Schema.number, True),
(Json.pair "z" $ Schema.ref Schema.number, True),
(Json.pair "w" $ Schema.ref Schema.number, True)
]
data Component
= X
| Y
| Z
| W
deriving (Eq, Show)
toQuaternion :: Component -> Double -> Double -> Double -> Quaternion
toQuaternion component a b c =
let d = toPart a b c
in case component of
X -> Quaternion d a b c
Y -> Quaternion a d b c
Z -> Quaternion a b d c
W -> Quaternion a b c d
toPart :: Double -> Double -> Double -> Double
toPart a b c = sqrt (1 - (a * a) - (b * b) - (c * c))
compressPart :: Double -> CompressedWord.CompressedWord
compressPart =
CompressedWord.CompressedWord maxCompressedValue
. round
. (* wordToDouble maxCompressedValue)
. (+ 0.5)
. (/ 2.0)
. (/ maxValue)
decompressPart :: CompressedWord.CompressedWord -> Double
decompressPart x_ =
(* maxValue)
. (* 2.0)
. subtract 0.5
. (/ wordToDouble (CompressedWord.limit x_))
. wordToDouble
$ CompressedWord.value x_
maxComponent :: Quaternion -> Component
maxComponent quaternion =
let x_ = x quaternion
y_ = y quaternion
z_ = z quaternion
w_ = w quaternion
parts = [(x_, X), (y_, Y), (z_, Z), (w_, W)]
biggestPart = maximumOn fst parts
roundTrip = decompressPart . compressPart
computedPart =
Maybe.fromMaybe
biggestPart
(List.find (\(value, _) -> value /= roundTrip value) parts)
in snd
( if (biggestPart == computedPart)
|| (abs (fst biggestPart - fst computedPart) > 0.00001)
then biggestPart
else computedPart
)
maximumOn :: (Foldable t, Ord b) => (a -> b) -> t a -> a
maximumOn f = List.maximumBy (Ord.comparing f)
numBits :: Word
numBits = 18
wordToDouble :: Word -> Double
wordToDouble = fromIntegral
maxCompressedValue :: Word
maxCompressedValue = (2 ^ numBits) - 1
maxValue :: Double
maxValue = 1.0 / sqrt 2.0
bitPut :: Quaternion -> BitPut.BitPut
bitPut q =
let c = maxComponent q
in putComponent c <> case c of
X -> putParts (y q) (z q) (w q)
Y -> putParts (x q) (z q) (w q)
Z -> putParts (x q) (y q) (w q)
W -> putParts (x q) (y q) (z q)
putComponent :: Component -> BitPut.BitPut
putComponent component =
CompressedWord.bitPut
( CompressedWord.CompressedWord
3
( case component of
X -> 0
Y -> 1
Z -> 2
W -> 3
)
)
putParts :: Double -> Double -> Double -> BitPut.BitPut
putParts a b c = putPart a <> putPart b <> putPart c
putPart :: Double -> BitPut.BitPut
putPart = CompressedWord.bitPut . compressPart
bitGet :: BitGet.BitGet Quaternion
bitGet = do
component <- decodeComponent
a <- decodePart
b <- decodePart
c <- decodePart
pure $ toQuaternion component a b c
decodeComponent :: BitGet.BitGet Component
decodeComponent = do
x_ <- CompressedWord.bitGet 3
case CompressedWord.value x_ of
0 -> pure X
1 -> pure Y
2 -> pure Z
3 -> pure W
y_ -> BitGet.throw $ InvalidComponent.InvalidComponent y_
decodePart :: BitGet.BitGet Double
decodePart = fmap decompressPart $ CompressedWord.bitGet maxCompressedValue