imj-base-0.1.0.2: src/Imj/Geo/Continuous.hs
{-# LANGUAGE NoImplicitPrelude #-}
module Imj.Geo.Continuous
(-- * Continuous coordinates
Vec2(..)
, module Imj.Geo.Continuous.Conversion
-- * Sampled continuous geometry
-- ** Circle
, translatedFullCircle
, translatedFullCircleFromQuarterArc
-- ** Parabola
, parabola
-- * Polygon extremities
, polyExtremities
-- * Vec2 utilities
, sumVec2d
, scalarProd
, rotateByQuarters
-- * Reexports
, Pos, Vel, Acc
) where
import Imj.Prelude
import Imj.Geo.Continuous.Types
import Imj.Geo.Continuous.Conversion
import Imj.Iteration
-- | Creates a list of 4 'Vec2' from a single one by rotating it successively by pi/2.
rotateByQuarters :: Vec2 Pos -> [Vec2 Pos]
rotateByQuarters v@(Vec2 x y) =
[v,
Vec2 x $ -y,
Vec2 (-x) $ -y,
Vec2 (-x) y]
-- | Sums two 'Vec2'.
{-# INLINE sumVec2d #-}
sumVec2d :: Vec2 a -> Vec2 a -> Vec2 a
sumVec2d (Vec2 vx vy) (Vec2 wx wy) = Vec2 (vx+wx) (vy+wy)
-- | Multiplies a 'Vec2' by a scalar.
scalarProd :: Float -> Vec2 a -> Vec2 a
scalarProd f (Vec2 x y) = Vec2 (f*x) (f*y)
-- | Integrate twice a constant acceleration over a duration, return a position
{-# INLINE integrateAcceleration2 #-}
integrateAcceleration2 :: Frame -> Vec2 Acc -> Vec2 Pos
integrateAcceleration2 (Frame time) (Vec2 vx vy) =
let factor = 0.5 * fromIntegral (time * time)
in Vec2 (vx * factor) (vy * factor)
-- | Integrate a constant velocity over a duration, return a position
{-# INLINE integrateVelocity #-}
integrateVelocity :: Frame -> Vec2 Vel -> Vec2 Pos
integrateVelocity (Frame time) (Vec2 vx vy) =
let factor = fromIntegral time
in Vec2 (vx * factor) (vy * factor)
gravity :: Vec2 Acc
gravity = Vec2 0 0.032 -- this number was adjusted so that the timing in Hamazed
-- game looks good. Instead, we could have adjusted the scale
-- of the world.
{-| Using
<https://en.wikipedia.org/wiki/Equations_of_motion equation [2] in "Constant linear acceleration in any direction">:
\[ \vec r = \vec r_0 + \vec v_0*t + {1 \over 2}* \vec a*t^2 \]
\[ where \]
\[ \vec r = current\;position \]
\[ \vec r_0 = initial\;position \]
\[ \vec v_0 = initial\;velocity \]
\[ \vec a = gravity\;force \]
\[ t = time \]
-}
parabola :: Vec2 Pos -> Vec2 Vel -> Frame -> Vec2 Pos
parabola r0 v0 time =
let iv = integrateVelocity time v0
ia = integrateAcceleration2 time gravity
in sumVec2d r0 $ sumVec2d iv ia
mkPointOnCircle :: Float -> Float -> Vec2 Pos
mkPointOnCircle radius angle =
let x = radius * sin angle
y = radius * cos angle
in Vec2 x y
discretizeArcOfCircle :: Float -> Float -> Float -> Int -> [Vec2 Pos]
discretizeArcOfCircle radius arcAngle firstAngle resolution =
let angleIncrement = arcAngle / (fromIntegral resolution :: Float)
in map (\i ->
let angle = firstAngle + angleIncrement * (fromIntegral i :: Float)
in mkPointOnCircle radius angle) [0..resolution]
fullCircleFromQuarterArc :: Float -> Float -> Int -> [Vec2 Pos]
fullCircleFromQuarterArc radius firstAngle quarterArcResolution =
let quarterArcAngle = pi/2
quarterCircle = discretizeArcOfCircle radius quarterArcAngle firstAngle quarterArcResolution
in concatMap rotateByQuarters quarterCircle
fullCircle :: Float -> Float -> Int -> [Vec2 Pos]
fullCircle radius firstAngle resolution =
let totalAngle = 2*pi
in discretizeArcOfCircle radius totalAngle firstAngle resolution
-- | Samples a circle in an optimized way, to reduce the number of 'sin' and 'cos'
-- calls.
--
-- The total number of points will always be a multiple of 4.
translatedFullCircleFromQuarterArc :: Vec2 Pos
-- ^ Center
-> Float
-- ^ Radius
-> Float
-- ^ The angle corresponding to the first sampled point
-> Int
-- ^ The total number of sampled points __per quarter arc__.
-> [Vec2 Pos]
translatedFullCircleFromQuarterArc center radius firstAngle resolution =
let circle = fullCircleFromQuarterArc radius firstAngle resolution
in map (sumVec2d center) circle
-- | Samples a circle.
translatedFullCircle :: Vec2 Pos
-- ^ Center
-> Float
-- ^ Radius
-> Float
-- ^ The angle corresponding to the first sampled point
-> Int
-- ^ The total number of sampled points
-> [Vec2 Pos]
translatedFullCircle center radius firstAngle resolution =
let circle = fullCircle radius firstAngle resolution
in map (sumVec2d center) circle
-- | Returns the extremities of a polygon. Note that it is equal to 'translatedFullCircle'
polyExtremities :: Vec2 Pos
-- ^ Center
-> Float
-- ^ Radius
-> Float
-- ^ Rotation angle
-> Int
-- ^ Number of sides of the polygon.
-> [Vec2 Pos]
polyExtremities = translatedFullCircle