Mind the following code:
-- A n-dimensional axis aligned bounding box.
data AABB v a = AABB {
aabbMin :: !(v a),
aabbMax :: !(v a)
} deriving (Show)
-- A n-dimensional ray.
data Ray v a = Ray {
rayPos :: !(v a),
rayDir :: !(v a)
} deriving (Show)
-- Traces a ray through a box, returns the
-- intersection indexes (tmin, tmax).
crossAABB
:: (Foldable f,
Metric f,
Ord a,
Num (f a),
Fractional (f a),
Floating a)
=> Ray f a
-> AABB f a
-> [a]
crossAABB (Ray rPos rDir) (AABB aabbMin aabbMax) = [tmin, tmax] where
t1 = (aabbMin - rPos)/rDir
t2 = (aabbMax - rPos)/rDir
tmin = foldr1 max $ liftI2 min t1 t2
tmax = foldr1 min $ liftI2 max t1 t2
-- Returns the first point of intersection
-- between a ray and a box.
hitAABB
:: (Foldable f,
Metric f,
Ord a,
Num (f a),
Fractional (f a),
Floating a)
=> Ray f a
-> AABB f a
-> Maybe (f a)
hitAABB ray@(Ray pos dir) aabb
| tmin >= 0 && tmin < tmax = Just $ pos + tmin *^ dir
| otherwise = Nothing
where [tmin,tmax] = crossAABB ray aabb
That is an usual Ray→AABB intersection function, and it is pretty simple. The types used are parameterized on the container, v, because it is multidimensional.
type Box2DFloat = AABB V2 Float
type Box4DFloat = AABB V4 Float
Those represent rectangles (2d) and hypercuboids (4d). The hitAABB function is generic enough to work for arbitrary dimensions, so it can check if a point is inside a rectangle, a cube, a cuboid, etc. All good, expect for the fact type signature is almost bigger than the function itself! That signature is terrible and makes the code much scarier than it should be. Trying to find a good solution, someone suggested I could use kind constraints that "encapsule my needs" to make it less verbose, but 1. I can't find a kind constraint that "encapsules my needs" properly, and 2. that feels like just hiding the dirty under the couch. "My needs", on this case, are basically "the type acts like a number should". So, on my mind, the following would make sense:
class Real a where
... anything I want a real number to do ...
instance Real Float where
...
instance (Real a) => Real (V2 a) where
...
instance (Real a) => Real (V3 a) where
...
type AABB a = V2 a
type Ray a = V2 a
type Box2DFloat = AABB (V2 Float)
type Box4DFloat = AABB (V4 Float)
type Ray2D a = Ray (V2 a)
type Ray3DRatio = Ray (V3 Ratio)
... etc ...
That way, my signatures would become, simply:
crossAABB :: (Real r, Real s) => Ray r -> AABB r -> [s]
hitAABB :: (Real r) => Ray r -> AABB r -> Maybe r
Which looks much, much better. The only problem is: if nobody using Haskell has bothered to define such class, then there should be a reason. My question is: what is the reason there is no "Real" class, and, if defining such a class would be a bad idea (I think), what is the proper solution to my issue?
Aucun commentaire:
Enregistrer un commentaire