module Hittable (Shape (..), Circle (..), Hit (..), isFrontFace) where

import Linear.V3
import Linear.Vector
import Material
import Maths
import Ray

class Shape s where
  testHit :: Ray Double -> s -> Maybe Hit

data Circle = Circle (V3 Double) Double Material

data Hit = Hit {root :: Double, p :: V3 Double, n :: V3 Double, m :: Material}

getFaceNormal :: Ray Double -> V3 Double -> V3 Double
getFaceNormal (Ray o d) n
  | (d `dotP` n) < 0.0 = n
  | otherwise = (-1.0) *^ n

isFrontFace :: Ray Double -> V3 Double -> Bool
isFrontFace (Ray o d) n
  | (d `dotP` n) < 0.0 = True
  | otherwise = False

-- t_min 0 t_max infinity... need closest so far
instance Shape Circle where
  testHit (Ray o d) (Circle center r m) =
    if discriminant < 0.0
      then Nothing
      else
        if root > 0.001
          then Just (Hit root p (getFaceNormal (Ray o d) ((p - center) ^/ r)) m)
          else
            if rootP > 0.001
              then Just (Hit rootP pp (getFaceNormal (Ray o d) ((pp - center) ^/ r)) m)
              else Nothing
    where
      co = o - center
      a = lengthSquared d -- a vector dotted with itself is = lengthSquared
      b = co `dotP` d
      c = lengthSquared co - r ^ 2
      discriminant = b ^ 2 - a * c
      root = (b * (-1.0) - sqrt discriminant) / a
      rootP = (b * (-1.0) + sqrt discriminant) / a
      p = rayAt (Ray o d) root
      pp = rayAt (Ray o d) rootP

-- would make sense to build a Functor for shapes