git.fiddlerwoaroof.com
raytracing_in_one_weekend/1.lisp
8add369e
 (in-package :fwoar.lisp-sandbox.1)
 
96842cff
 (defclass hittable ()
   ())
8add369e
 
96842cff
 (fw.lu:defclass+ sphere (hittable)
   ((center :initarg :center)
    (radius :initarg :radius)
    (material-color :initarg :color
                    :reader material-color
                    :initform (vec3 (random 1.0d0)
                                    (random 1.0d0)
                                    (random 1.0d0)))
    (material :initarg :material
              :reader .material
              :initform (lambertian-material (random 0.8)))))
 
 (fw.lu:defclass+ ray ()
   ((origin :initarg :origin)
    (direction :initarg :direction)))
 
 (fw.lu:defclass+ hit-record ()
   ((p :initarg :p :reader .p)
    (time :initarg :time :reader .time)
    (thing :initarg :thing :accessor .thing)
    (material :initarg :material :accessor .material)
    (normal :initarg :normal :accessor .normal :initform ())
    (front-face :initarg :front-face :accessor .front-face :initform ())))
8bf41476
 
96842cff
 (defclass camera ()
   ((origin :initarg :origin :reader origin)
    (lower-left-corner :initarg :lower-left-corner :reader lower-left-corner)
    (horizontal :initarg :horizontal :reader horizontal)
    (vertical :initarg :vertical :reader vertical)))
8bf41476
 
8add369e
 (defstruct (vec3 (:type vector)
                  (:constructor vec3 (x y z))
                  (:conc-name v3-))
   x y z)
 
 (defun call-with-ppm-header (stream size callback &optional (colors 255))
   (format stream "P3~%~d ~d~%~d~%"
           (size-width size)
           (size-height size)
           colors)
   (funcall callback stream))
 
 #.(progn (defmacro ig (&rest syms) `'(declare (ignore ,@syms)))
          nil)
 
38ff817c
 (defun format-color (s v _ __)
   #.(ig _ __)
   (fw.lu:vector-destructuring-bind (r g b) (scale-to-8bit v)
     (format s "~4d ~4d ~4d" r g b)))
8add369e
 
 (defun write-colors (stream colors columns)
38ff817c
   (funcall colors
            (lambda (color pos)
              (fwoar.lisp-sandbox.canvas-server:send-update
               (scale-to-8bit color) pos)))
   #+(or)
8add369e
   (let ((intermediate ())
         (idx 0))
     (funcall colors
              (lambda (color)
                (push color intermediate)
                (when (= (1- columns)
                         (mod idx columns))
                  (format stream "~{~/fwoar.lisp-sandbox.1::format-color/~^  ~}~&" intermediate)
                  (setf intermediate ()))
                (incf idx)))
     (when intermediate
       (format stream "~{~/fwoar.lisp-sandbox.1::format-color/~^  ~}~&" intermediate))))
 
 (defgeneric at (self it)
   (:method ((ray ray) (it number))
     (with-slots (origin direction) ray
       (vec+ origin
             (vec* it direction)))))
 (defun set-face-normal (hit-record r outward-normal)
   (prog1 hit-record
     (with-slots (direction) r
       (let ((front-face (< (dot direction outward-normal)
                            0)))
         (setf (.front-face hit-record) front-face
               (.normal hit-record) (if front-face
                                        outward-normal
                                        (vec* -1 outward-normal)))))))
 
 (defgeneric hit (thing ray t-min t-max)
   (:method ((things list) (r ray) (t-min real) (t-max real))
     (let (temp-rec
           (hit-anything nil)
           (closest-so-far t-max))
       (loop for thing in things
             for (hit-p hit-rec) = (multiple-value-list
                                    (hit thing r t-min closest-so-far))
             when hit-p do
               (setf hit-anything t
                     closest-so-far (.time hit-rec)
                     temp-rec hit-rec))
       (when hit-anything
         (values hit-anything
                 temp-rec))))
   (:method ((sphere sphere) (r ray) (t-min real) (t-max real))
38ff817c
     (declare (optimize (speed 3)))
8add369e
     (uiop:nest (with-slots ((%center center) (%radius radius)) sphere)
                (let ((center %center) (radius %radius)))
                (with-slots ((%origin origin) (%direction direction)) r)
                (let ((origin %origin) (direction %direction)))
                (let* ((oc (vec- origin center))
                       (a (length-squared direction))
                       (half-b (dot oc direction))
                       (c (- (length-squared oc)
                             (* radius radius)))
                       (discriminant (- (* half-b half-b)
                                        (* a c))))
                  (if (< discriminant 0)
                      (return-from hit nil)
                      (let* ((sqrtd (sqrt discriminant))
                             (root (/ (- (- half-b)
                                         sqrtd)
                                      a)))
                        (when (or (< root t-min)
                                  (< t-max root))
                          (setf root (/ (- sqrtd half-b)
                                        a))
                          (when (or (< root t-min)
                                    (< t-max root))
                            (return-from hit nil)))
                        (let* ((p (at r root))
                               (outward-normal (vec/ (vec- p center)
                                                     radius)))
 
                          (values t
38ff817c
                                  (set-face-normal (hit-record p root sphere (.material sphere))
8add369e
                                                   r
                                                   outward-normal)))))))))
 
 (defun hit-sphere (center radius r)
   (with-slots (origin direction) r
     (let* ((oc (vec- origin center))
            (a (length-squared direction))
            (half-b (dot oc direction))
            (c (- (length-squared oc)
                  (expt radius 2)))
            (discriminant (- (* half-b half-b)
                             (* a c))))
       (if (< discriminant 0)
           -1.0d0
           (/ (- (- half-b) (sqrt discriminant))
              a)))))
 
38ff817c
 (defgeneric scatter (material ray-in rec))
 
9c02b840
 (defun reflect (v n)
   (vec- v
         (vec* 2.0d0
               (vec* (dot v n)
                     n))))
 
 (defun refract (uv n eta*/eta)
   (let* ((cos-theta (min 1.0d0
                          (dot (negate uv)
                               n)))
          (out-perp (vec* eta*/eta
                          (vec+ uv
                                (vec* cos-theta
                                      n))))
          (out-parallel (vec* (- (sqrt (abs (- 1.0d0
                                               (length-squared out-perp)))))
                              n)))
     (vec+ out-perp out-parallel)))
 
 (defun reflectance (cosine ref-idx)
   (let* ((r0 (/ (- 1 ref-idx)
                 (+ 1 ref-idx)))
          (r0 (* r0 r0)))
     (+ r0
        (* (- 1 r0)
           (expt (- 1 cosine)
                 5)))))
 
96842cff
 (define-symbol-macro infinity
   #.sb-ext:double-float-positive-infinity)
8bf41476
 (defgeneric ray-color (ray world depth)
   (:method :around (r w (depth integer))
     (if (<= depth 0)
         (vec3 1.0d0 1.0d0 1.0d0)
         (call-next-method)))
   (:method ((ray ray) world (depth integer))
8add369e
     (multiple-value-bind (hit-p rec)
8bf41476
         (hit world ray 0.001d0 infinity)
8add369e
       (when hit-p
38ff817c
         (return-from ray-color
9c02b840
           (funcall (.material rec) rec ray world depth)))
8add369e
       (with-slots (direction) ray
         (let* ((unit-direction (unit-vector direction))
                (it (+ (* 0.5 (v3-y unit-direction))
                       1.0d0)))
           (vec+ (vec* (- 1.0d0 it)
                       #(1.0d0 1.0d0 1.0d0))
                 (vec* it
                       #(0.5d0 0.7d0 1.0d0))))))))
 
8bf41476
 (defun camera (&key
                  (aspect-ratio 16/9)
9c02b840
                  (viewport-height 2.0d0)
8bf41476
                  (viewport-width (* aspect-ratio viewport-height))
9c02b840
                  (focal-length 1.0d0))
8bf41476
   (let ((origin (vec3 0d0 0.0d0 0.0d0))
         (horizontal (vec3 viewport-width 0.0d0 0.0d0))
         (vertical (vec3 0.0d0 viewport-height 0.0d0)))
     (make-instance 'camera
                    :origin origin
                    :horizontal horizontal
                    :vertical vertical
                    :lower-left-corner (vec- (vec- (vec- origin
                                                         (vec/ horizontal 2))
                                                   (vec/ vertical 2))
                                             (vec3 0 0 focal-length)))))
 (defgeneric get-ray (camera u v)
   (:method ((camera camera) (u real) (v real))
     (with-slots (origin horizontal vertical lower-left-corner) camera
       (macrolet ((-> (v &body forms)
                    (if forms
                        `(-> (,(caar forms) ,v ,@(cdar forms))
                             ,@(cdr forms))
                        v)))
 
 
         (ray origin
              (-> lower-left-corner
                  (vec+ (vec* u horizontal))
                  (vec+ (vec* v vertical))
                  (vec- origin)))))))
 
38ff817c
 (declaim (notinline u-loop))
 (defun u-loop (j image-width image-height camera world max-depth c)
   (loop for i from 0 below image-width
         for u = (/ (* 1.0d0 i)
                    (1- image-width))
         for v = (/ (* 1.0d0 j)
                    (1- image-height))
         for r = (get-ray camera u v)
         for color = (loop for s below *samples-per-pixel*
                           for u = (/ i
                                      (1- image-width))
                             then (/ (+ i (random 1.0d0))
                                     (1- image-width))
                           for v = (/ j
                                      (1- image-height))
                             then (/ (+ j (random 1.0d0))
                                     (1- image-height))
                           for r = (get-ray camera u v)
                           for pixel-color = (ray-color r world max-depth)
                             then (vec+ pixel-color
                                        (ray-color r world max-depth))
                           finally (return pixel-color))
         do (funcall c color
                     (list i
                           (- image-height j)))))
 
 (defvar *thread-queues*
   (make-array 8))
 
 (defun start-worker (id)
   (let ((mailbox (sb-concurrency:make-mailbox :name (format nil "mailbox-~d" id))))
     (setf (aref *thread-queues* id) mailbox)
     (bt:make-thread
      (lambda ()
        (loop
          (destructuring-bind (*samples-per-pixel*
                               j image-width image-height camera world max-depth c)
              (sb-concurrency:receive-message mailbox)
            (u-loop j image-width image-height camera world max-depth c))))
      :name (format nil "worker-~d" id))))
 
9c02b840
 (defun start-workers (&optional (n 8))
   (loop for x below n
38ff817c
         collect (start-worker x)))
 
 (defun shuffle (seq)
   (let ((arr (map 'vector 'identity seq)))
     (loop for i from (1- (length arr)) downto 1
           for j = (random i)
           do (rotatef (aref arr i)
                       (aref arr j))
           finally (return (coerce arr (type-of seq))))))
6b17e8ac
 
 #+(or)
 (progn (let ((it (map 'list
                       'sb-concurrency:mailbox-count
                       *thread-queues*)))
          (list it (apply '+ it)))
        )