HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Ray→plane and ray→quad intersection

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
rayplaneintersectionandquad

Problem

This checks the intersection between a Ray and a Plane and between a Ray and a Quad (in 3D):

import Linear

data Ray a   = Ray {rayPos :: V3 a, rayDir :: V3 a}
data Plane a = Plane {planePos :: V3 a, planeNorm :: V3 a}
type Quad a  = V4 (V3 a)

hitPlane :: (Num a, Fractional a) => Ray a -> Plane a -> V3 a
hitPlane (Ray rPos rDir) (Plane pPos pNorm) = rPos + dot (pPos - rPos) pNorm / dot rDir pNorm *^ rDir

hitQuad :: (Ord a, Epsilon a, Num a, Floating a) => Ray a -> Quad a -> Maybe (V3 a)
hitQuad ray (quad@(V4 a b c d)) = if hitInsideQuad then Just hitPoint else Nothing 
    where hitInsideQuad = insideQuad hitPoint quad
          planeNormal   = cross (b - a) (d - a)
          hitPoint      = hitPlane ray (Plane a planeNormal)

insideQuad :: (Num a, Ord a) => V3 a -> Quad a -> Bool
insideQuad pos (V4 a b c d) = all inside borders where
    borders      = [(a,b),(b,c),(c,d),(d,a)]
    inside (a,b) = dot (b - a) (pos - a) > 0


Please, analyze if:

-
(Most importantly) I'm using instances correctly (I'm suspect something is wrong with the amount of instances on the types).

-
My design of types is correct and linguistic.

-
My code is comprehensible.

-
If I did something stupid in general.

Solution

I'll preface this by saying that I'm not a Haskell programmer, so take my comments advisedly.

-
I would appreciate a short comment specifying the behaviour of each type and function. For example:

-- The point of intersection between a ray and a plane.
hitPlane :: (Fractional a) => Ray a -> Plane a -> V3 a


This allows a reader to quickly understand the purpose of a function without having to read the code to find out. Also it provides a specification that can be checked against the implementation.

-
In geometry the term ray usually refers to the half-line with a starting point and direction. So a ray does not necessarily hit a plane, and the type of hitPlane ought to be:

hitPlane :: (RealFrac a) => Ray a -> Plane a -> Maybe (V3 a)


If you intend your Ray type to be a full line, then it ought to be called Line instead, to avoid confusion. But even a full line might not have a point of intersection with a plane, because it might be parallel to the plane. In this case there will be a division by zero error: it would be better to avoid this and return Nothing.

-
I don't understand all the details of the class constraints. hitPlane requires both Num a and Fractional a but if I understand the standard Haskell type documentation, the latter implies the former, so the Num a is redundant. Similarly for hitQuad, where Num a is redundant given that you have Floating a.

-
hitQuad requires Epsilon a and Floating a, but I don't see how either of these classes is necessary. I would have expected just Ord a, Fractional a, and this combination is the same as RealFrac a.

-
Some of the helper functions could be usefully made into top-level functions. For example, the implementation of hitQuad finds the plane containing a triangle of points. But this operation is generally useful:

-- The plane containing three points, with a normal chosen so that
-- the points are clockwise when looking in the normal direction.
planeContaining :: (Num a) => (V3 a, V3 a, V3 a) -> Plane a
planeContaining (a, b, c) = Plane a (cross (b - a) (c - a))

Code Snippets

-- The point of intersection between a ray and a plane.
hitPlane :: (Fractional a) => Ray a -> Plane a -> V3 a
hitPlane :: (RealFrac a) => Ray a -> Plane a -> Maybe (V3 a)
-- The plane containing three points, with a normal chosen so that
-- the points are clockwise when looking in the normal direction.
planeContaining :: (Num a) => (V3 a, V3 a, V3 a) -> Plane a
planeContaining (a, b, c) = Plane a (cross (b - a) (c - a))

Context

StackExchange Code Review Q#77336, answer score: 3

Revisions (0)

No revisions yet.