packages feed

Box2D-0.1.0.0: src/Box2D/Mover.hs

{-| Friendly surface over the engine's mover plane solver.

The raw bindings ("Box2D.Collision".@solvePlanes@ / @clipVector@) work on a
mutable @b2CollisionPlane@ array: @b2SolvePlanes@ accumulates each plane's
@push@ in place and @b2ClipVector@ reads those accumulators back. The
wrappers here own that mutation, so a kinematic character controller can
stay in vector land:

@
(delta, planes', _iters) <- solvePlanes targetDelta planes
velocity' <- clipVector velocity planes'
@

Planes are normally assembled from the 'Box2D.Collision.PlaneResult's gathered
by 'Box2D.World.collideMover'.
-}
module Box2D.Mover
  ( solvePlanes
  , clipVector
  ) where

import Data.Vector.Storable qualified as VS
import Data.Vector.Storable.Mutable qualified as VSM

import Box2D.Collision (CollisionPlane, PlaneSolverResult (..))
import Box2D.Collision qualified as Collision
import Box2D.MathTypes (Vec2)

{- | Solve the position of a mover that satisfies the given collision
planes. Returns the resolved translation, the planes with their final
@push@ accumulators (feed these to 'clipVector'), and the solver
iteration count (for diagnostics).
-}
solvePlanes :: Vec2 -> VS.Vector CollisionPlane -> IO (Vec2, VS.Vector CollisionPlane, Int)
solvePlanes targetDelta planes = do
  mplanes <- VS.thaw planes
  result <- VSM.unsafeWith mplanes $ \p ->
    Collision.solvePlanes targetDelta p (VSM.length mplanes)
  planes' <- VS.unsafeFreeze mplanes
  pure
    ( result.delta
    , planes'
    , fromIntegral result.iterationCount
    )

{- | Clip a velocity against the given collision planes; planes with zero
@push@ or with @clipVelocity@ unset are skipped. Call it on the planes
returned by 'solvePlanes'.
-}
clipVector :: Vec2 -> VS.Vector CollisionPlane -> IO Vec2
clipVector vector planes =
  VS.unsafeWith planes $ \p ->
    Collision.clipVector vector p (VS.length planes)