git.fiddlerwoaroof.com
Raw Blame History
;;;; 3dr.lisp

(in-package #:3dr)

;;; "3dr" goes here. Hacks and glory await!

(defstruct (point (:type vector)) x y z)

(defclass plane ()
  ((cells :initarg :cells :accessor cells :type (array integer (* *)))
   (distance :initarg :distance :accessor distance :type integer)))

;;(declaim (ftype (function ((vector integer 3) integer) (vector rational 3)) intercept-coordinate))
(defun intercept-coordinate (point distance &optional out-point)
  (declare (inline) (optimize (speed 3)))
  (when (null out-point)
                                        ;(break)
    (setf out-point (vector 0 0 0)))
  (let ((a (elt point 0)) (b (elt point 1)) (c (elt point 2)))
    (setf (elt out-point 0) distance)
    (setf (elt out-point 1) (/ (* b distance) a))
    (setf (elt out-point 2) (/ (* c distance) a))
    out-point))

(defun run-plane (plane point-cb)
  (declare (optimize (speed 3)))
  (let ((a (distance plane))
        (points (cells plane)))
    (destructuring-bind (b-bound c-bound) (array-dimensions points)
      (lparallel:pdotimes (b (1- b-bound))
        (let ((the-point (make-point :x a :y (- b (/ b-bound 2)) :z 0))
              (out-point (make-point :x 0 :y 0 :z 0)))
          (dotimes (c (1- c-bound))
            (setf (point-z the-point) (- c (/ c-bound 2)))
            (setf (aref points b c)
                  (funcall point-cb the-point out-point))))))))

(declaim (ftype (function ((vector rational 3) (vector rational 3)) float) c-distance))
(defun c-distance (point-1 point-2)
  ;(declare (optimize (speed 0) (safety 3) (debug 3)))
  (when (not (and (point-x point-1) (point-x point-2)))
    (break))

  ;(declare (inline) (optimize (speed 3) (safety 1) (debug 0) (space 0)))
  (sqrt (+ (expt (- (aref point-2 0) (aref point-1 0)) 2)
           (expt (- (aref point-2 1) (aref point-1 1)) 2)
           (expt (- (aref point-2 2) (aref point-1 2)) 2))))

(defun extract-projected-point (point obj-dist &optional out-point)
  ;(declare (inline) (optimize (speed 3)))
  (intercept-coordinate point obj-dist out-point))

(defclass shape ()
  ((origin :initarg :origin :accessor origin)
   (radius :initarg :radius :accessor radius)
   (args :initarg :args :accessor args)
   (render-func :initarg :render-func :accessor render-func)))

(defgeneric get-render-cb (shape))
(defmethod get-render-cb ((shape shape))
  (with-slots (origin radius render-func args) shape
    (apply render-func origin radius args)))

(defun make-neg-sphere-cb (origin radius)
  (let ((obj-dist (point-x origin))
        (r_sqr (expt radius 2))) 
    (lambda (point &optional out-point extracted-point)
      ;(declare (inline) (optimize (speed 3)))
      (let* ((point (or extracted-point (extract-projected-point point obj-dist out-point)))
             (d_o (c-distance origin point)))
        (if (<= d_o radius)
          (* -1 (ceiling (sqrt (- r_sqr (expt d_o 2))))) 
          0)))))

;; d_o + r : upright :: upright : r - d_o
;; upright^2 = r^2 - d_o^2
;; upright = sqrt ( r^2 - d_o^2 )
(defun make-sphere-cb (origin radius)
  (let ((obj-dist (point-x origin))
        (r_sqr (expt radius 2)))
    (lambda (point &optional out-point extracted-point)
      (declare (inline) (optimize (speed 3)))
      (let* ((out-point (or out-point (make-point :x 0 :y 0 :z 0)))
             (point (or extracted-point (extract-projected-point point obj-dist out-point))))
        (let ((d_o (c-distance origin point)))
          (values (if (<= d_o radius)
                      (ceiling (* (/ (sqrt (- r_sqr
                                              (expt d_o
                                                    2)))
                                     radius)
                                  #. (expt 2 7))) 
                      0)
                  point))))))

(defun make-interesting-cb (origin radius)
  (let ((obj-dist (point-x origin))
        (r_sqr (expt radius 3)))
    (lambda (point &optional out-point extracted-point)
      (declare (inline) (optimize (speed 3)))
      (let* ((out-point (or out-point (make-point :x 0 :y 0 :z 0)))
             (point (or extracted-point (extract-projected-point point obj-dist out-point))))
        (let ((d_o (c-distance origin point)))
          (values (if (<= d_o radius)
                      (ceiling (* (/ (expt (- r_sqr
                                              (expt d_o
                                                    3))
                                           0.33)
                                     radius)
                                  #. (expt 2 7))) 
                      0)
                  point))))))

(defun make-donut-cb (origin radius width)
  (let ((pos-cb (make-sphere-cb origin radius))
        (neg-cb (make-neg-sphere-cb origin (- radius width)))
        (obj-dist (point-x origin)))
    (lambda (point &optional out-point)
      (declare (inline) (optimize (speed 3)))
      (let* ((out-point (or out-point (make-point :x 0 :y 0 :z 0))) 
             (extracted-point (extract-projected-point point obj-dist out-point)))
        (+ (funcall pos-cb point out-point extracted-point)
           (funcall neg-cb point out-point extracted-point))))))

(defun combine-cbs (&rest cbs)
  (lambda (point &optional out-point)
    (let ((out-point (or out-point (make-point :x 0 :y 0 :z 0))))
      (loop for cb in cbs sum (funcall cb point out-point)))))

(defun combine-shapes (&rest shapes)
  (let* ((shape-cbs (mapcar #'get-render-cb
                            (lparallel:psort shapes #'<
                                             :key (lambda (shape)
                                                    (with-slots (origin radius) shape
                                                      (-
                                                        (c-distance #(0 0 0)
                                                                    origin)
                                                        radius))))))
         (origins (mapcar 'origin shapes))
         (radii (mapcar 'radius shapes))
         (light-scaling-factor (lparallel:pmapcar
                                 (lambda (origin radius)
                                   (abs
                                     (/ (- (c-distance #(0 100 100)
                                                       origin)
                                           radius)
                                        100)))
                                 origins
                                 radii)))
    (lambda (point &optional out-point)
      (let ((out-point (or out-point (make-point :x 0 :y 0 :z 0))))
        (loop for shape-cb in shape-cbs
              for scale-factor in light-scaling-factor
              for val = (ceiling
                          (* scale-factor
                             (funcall shape-cb point out-point)))
              until (< 0 val)
              finally (return val))))))

(defun array-to-pgm (arr)
  ;;(declare (optimize (speed 3)))
  (with-output-to-string (s)
    (destructuring-bind (height width) (array-dimensions arr)
      (format s "P2~%~3d ~3d~%~d~%"
              width height
              (1+ (reduce #'max (make-array (* width height) :displaced-to arr)))))
    (loop with (x-bound y-bound) = (array-dimensions arr)
       for x from 0 to (1- x-bound)
       do (loop for y from 0 to (1- y-bound)
             for val = (aref arr x y)
             do (princ val s)
             do (princ #\space s))
       do (terpri s))))

(defmacro defshape (render-func origin radius &rest args)
  `(make-instance 'shape
                  :render-func (function ,render-func)
                  :origin (copy-seq ,origin)
                  :radius ,radius
                  :args (list ,@args)))

(defun main (&rest args)
  (declare (ignore args))
  ;;(sb-profile:profile intercept-coordinate c-distance)
  (loop for img-num from 0 to 1
     do
       (let ((the-plane (make-instance 'plane
                                       :cells (make-array '(1024 1024))
                                       :distance 50))
             (lparallel:*kernel* (lparallel:make-kernel 7)))

         (labels ((100- (x) (- x 100))
                  (random-coord () (100- (random 200))))
           (time
            (run-plane the-plane
                       (apply #'combine-shapes
                              (loop for x from 0 to 100 by 20
                                 append (loop for y from 0 to 100 by 20
                                           collect
                                             (defshape make-sphere-cb
                                                 (vector 30 x y)
                                               10)))))))

         (with-open-file (s (format nil "/tmp/spheres.~d.pgm" img-num)
                            :direction :output :if-exists :supersede)
           (write-sequence (array-to-pgm (cells the-plane))
                           s))
         nil))
                                        ;(sb-profile:report)
  )

;(loop with (x-bound y-bound) = (array-dimensions (cells the-plane))
;      for x from 0 to (1- x-bound)
;      do (loop for y from 0 to (1- y-bound)
;               for val = (aref (cells the-plane) x y)
;               when (= val 0) do (princ #\space)
;               unless (= val 0) do (princ val)
;               do (princ #\space))
;      do (terpri))