(define (make-plane p1 p2 p3 shader)
  (letrec
    ((x1 ((p1 'get-x)))
     (y1 ((p1 'get-y)))
     (z1 ((p1 'get-z)))
     (x2 ((p2 'get-x)))
     (y2 ((p2 'get-y)))
     (z2 ((p2 'get-z)))
     (x3 ((p3 'get-x)))
     (y3 ((p3 'get-y)))
     (z3 ((p3 'get-z)))
     (u1 (- x2 x1))
     (u2 (- y2 y1))
     (u3 (- z2 z1))
     (v1 (- x3 x1))
     (v2 (- y3 y1))
     (v3 (- z3 z1))
     (a (+ (* y1 (- z2 z3))
           (* y2 (- z3 z1))
           (* y3 (- z1 z2))))
     (b (+ (* z1 (- x2 x3))
           (* z2 (- x3 x1))
           (* z3 (- x1 x2))))
     (c (+ (* x1 (- y2 y3))
           (* x2 (- y3 y1))
           (* x3 (- y1 y2))))
     (d (- (+ (* x1 (- (* y2 z3) (* y3 z2)))
              (* x2 (- (* y3 z1) (* y1 z3)))
              (* x3 (- (* y1 z2) (* y2 z1))))))
     (normal (normalize
       (make-point
         (- (* u2 v3) (* u3 v2))
         (- (- (* u1 v3) (* u3 v1)))
         (- (* u1 v2) (* u2 v1))))))
    (lambda (selector)
      (case selector
        ((get-bounding-box)
         (lambda () '())) ; degenerate
        ((get-shader)
         (lambda ()
           shader))
        ((get-normal)
         (lambda ()
           normal))
        ((get-a)
         (lambda ()
           a))
        ((get-b)
         (lambda ()
           b))
        ((get-c)
         (lambda ()
           c))
        ((get-d)
         (lambda ()
           d))
        ((intersect-ray)
         (lambda (ray-source dir-x dir-y dir-z shadow-test)
           (let*
             ((x1 ((ray-source 'get-x)))
              (y1 ((ray-source 'get-y)))
              (z1 ((ray-source 'get-z)))
              (x2 (+ x1 dir-x))
              (y2 (+ y1 dir-y))
              (z2 (+ z1 dir-z))
              (denom
                (+ (* a (- x1 x2))
                   (* b (- y1 y2))
                   (* c (- z1 z2)))))
             (if (= denom 0.0)
               '() ; no intersection
               (let
                 ((u (/ (+ (* a x1)
                           (* b y1)
                           (* c z1)
                        d)
                        denom)))
                 (make-point
                   (+ x1 (* u (- x2 x1)))
                   (+ y1 (* u (- y2 y1)))
                   (+ z1 (* u (- z2 z1)))))))))
        ((display)
         (lambda ()
           (display "plane: ")
             (display a) (display " ")
             (display b) (display " ")
             (display c) (display " ")
             (display d) (newline)
           (display "normal: ")
             ((normal 'display))))
        (else
         (render-error "make-plane" "Invalid selector: " selector))))))

(define (make-plane-given-normal p1 normal shader)
  (letrec
    ((a ((normal 'get-x)))
     (b ((normal 'get-y)))
     (c ((normal 'get-z)))
     (d (- (dot p1 normal))))
    (lambda (selector)
      (case selector
        ((get-bounding-box)
         (lambda () '())) ; degenerate
        ((get-shader)
         (lambda ()
           shader))
        ((get-normal)
         (lambda ()
           (v-scale normal -1.0)))
        ((get-a)
         (lambda ()
           a))
        ((get-b)
         (lambda ()
           b))
        ((get-c)
         (lambda ()
           c))
        ((get-d)
         (lambda ()
           d))
        ((intersect-ray)
         (lambda (ray-source dir-x dir-y dir-z)
           (let*
             ((x1 ((ray-source 'get-x)))
              (y1 ((ray-source 'get-y)))
              (z1 ((ray-source 'get-z)))
              (x2 (+ x1 dir-x))
              (y2 (+ y1 dir-y))
              (z2 (+ z1 dir-z))
              (denom
                (+ (* a (- x1 x2))
                   (* b (- y1 y2))
                   (* c (- z1 z2)))))
             (if (= denom 0.0)
               '() ; no intersection
               (let
                 ((u (/ (+ (* a x1)
                           (* b y1)
                           (* c z1)
                        d)
                        denom)))
                 (make-point
                   (+ x1 (* u (- x2 x1)))
                   (+ y1 (* u (- y2 y1)))
                   (+ z1 (* u (- z2 z1)))))))))
        ((display)
         (lambda ()
           (display "plane: ")
             (display a) (display " ")
             (display b) (display " ")
             (display c) (display " ")
             (display d) (newline)
           (display "normal: ")
             ((normal 'display))))
        (else
         (render-error "make-plane" "Invalid selector: " selector))))))
