#| Ray tracing for the Adafruit CLUE - 21st February 2020 See http://www.ulisp.com/show?2NWA To run it: (init) (ray-trace) |# ; TFT colour display interface ; Pins used for CLUE display (defvar dc 32) (defvar cs 31) ; Display commands (defvar CASET #x2A) (defvar RASET #x2B) (defvar RAMWR #x2C) ; Display offsets and dimensions (defvar yoff 0) (defvar xoff 80) (defvar xsize 240) (defvar ysize 240) (defvar invert 1) (defvar rot 6) ; Convert red, green, blue components 0 to 1 to a 16-bit 565 RGB value (defun rgb (r g b) (logior (ash (logand (truncate (* 255 r)) #xf8) 8) (ash (logand (truncate (* 255 g)) #xfc) 3) (ash (truncate (* 255 b)) -3))) ; Write a command with optional data byte (defun cmd (c &optional d) (with-spi (str cs 4000 1 0 1) (digitalwrite dc 0) (write-byte c str) (digitalwrite dc 1) (when d (write-byte d str)))) ; Write a command with one or two data words (defun cmd2 (c d1 &optional d2) (with-spi (str cs 4000 1 0 1) (digitalwrite dc 0) (write-byte c str) (digitalwrite dc 1) (write-byte (ash d1 -8) str) (write-byte d1 str) (when d2 (write-byte (ash d2 -8) str) (write-byte d2 str)))) ; Initialize the display (defun init () (pinmode dc t) (cmd #x01) ; Software reset (delay 150) ; delay 150 ms (cmd #x11) ; Out of sleep mode (delay 10) ; delay 10 ms (cmd #x3A #x55) ; Set color mode - 16-bit color (cmd (+ #x20 invert)) ; Invert? (cmd #x36 (ash rot 5)); Set orientation (cmd #x29) ; Display on (delay 10)) ; Clear the display (defun clear () (cmd2 CASET yoff (+ yoff ysize -1)) (cmd2 RASET xoff (+ xoff xsize -1)) (cmd #x3A #x03) (cmd RAMWR) (with-spi (str cs 4000 1 0 1) (dotimes (p (* (/ xsize 2) ysize 3)) (write-byte 0 str))) (cmd #x3A #x05)) ; Plot colour c at coordinates x, y (defun plotpoint (x y c) (cmd2 CASET (+ yoff y) (+ yoff y)) (cmd2 RASET (+ xoff x) (+ xoff x)) (cmd2 RAMWR c)) ; Ray trace program ; Vector routines (defun colour (r g b) (list r g b)) (defun point (x y z) (list x y z)) (defun vect (x y z) (list x y z)) (defun dot (v w) (apply + (mapcar * v w))) (defun mul (k v) (mapcar (lambda (z) (* k z)) v)) (defun add (v w) (mapcar + v w)) (defun sub (v w) (mapcar - v w)) (defun sq (x) (* x x)) (defun mag (v) (sqrt (apply + (mapcar sq v)))) (defun unit-vector (v) (let ((d (mag v))) (mapcar (lambda (j) (/ j d)) v))) (defun distance (p1 p2) (mag (mapcar - p1 p2))) (defvar *world* nil) (defvar *eye* nil) (defvar *light* nil) ; Objects (defun sphere-center (s) (second s)) (defun sphere-radius (s) (third s)) (defun sphere-colour (s) (nth 3 s)) (defun plane-point (s) (second s)) (defun plane-normal (s) (third s)) (defun plane-colour (s) (nth 3 s)) (defun make (&rest list) (push list *world*)) ; Methods ; Get the colour of the object s (defun object-colour (s) (case (first s) (sphere (sphere-colour s)) (plane (plane-colour s)))) ; Get the normal to the surface of object s at the point pt (defun object-normal (s pt) (case (first s) (sphere (sphere-normal s pt)) (plane (plane-normal s)))) (defun sphere-normal (s pt) (unit-vector (sub (sphere-center s) pt))) ; Find where the ray defined by pt and pr hits object s and return distance (defun object-hit (s pt pr) (case (first s) (sphere (sphere-hit s pt pr)) (plane (plane-hit s pt pr)))) (defun sphere-hit (s pt pr) (let* ((c (sphere-center s)) (oc (mapcar - pt c))) (minroot (apply + (mapcar sq pr)) (* 2 (dot oc pr)) (- (dot oc oc) (sq (sphere-radius s)))))) (defun minroot (a b c) (if (zerop a) (/ (- c) b) (let ((disc (- (sq b) (* 4 a c)))) (unless (minusp disc) (min (/ (+ (- b) (sqrt disc)) (* 2 a)) (/ (- (- b) (sqrt disc)) (* 2 a))))))) (defun plane-hit (s pt pr) (let ((denom (dot (plane-normal s) pr))) (unless (zerop denom) (let ((n (/ (dot (sub (plane-point s) pt) (plane-normal s)) denom))) (when (>= n 0) n))))) (defun background (x y) (colour 0.5 0.7 1)) (defun tracer () (dotimes (x 240) (dotimes (y 240) (plotpoint x y (apply rgb (colour-at (- x 120) (- y 120))))))) (defun colour-at (x y) (let ((c (send-ray *eye* (unit-vector (sub (list x y 0) *eye*))))) (or c (background x y)))) ; Return colour where ray hits first object, or nil if no hit (defun send-ray (pt pr) (let* ((f (first-hit pt pr)) (s (first f)) (hit (second f))) (when s (mul (lambert s hit pr) (object-colour s))))) (defun send-ray (pt pr) (let* ((f (first-hit pt pr)) (s (first f)) (hit (second f))) (when s (let* ((c (mul (lambert s hit pr) (object-colour s))) (f2 (first-hit *light* (unit-vector (sub hit *light*)))) (h2 (second f2))) (cond ((< (distance hit h2) 1) c) (t (mul .75 c))))))) ; Return nearest surface in world, and hit point (defun first-hit (pt pr) (let (surface hit dist) (dolist (s *world*) (let ((d (object-hit s pt pr))) (when d (let ((h (add pt (mul d pr)))) (when (or (null dist) (< d dist)) (setq surface s) (setq hit h) (setq dist d)))))) (list surface hit))) (defun lambert (s hit pr) (max 0 (dot pr (object-normal s hit)))) (defun ray-trace () (setq *world* nil) (setq *eye* (point 0.0 10.0 300.0)) (setq *light* (point -5000 10000 -1200)) (make 'plane (point 0 -200 0) (vect 0 -1 0) (colour 2 2 2)) (make 'sphere (point -250 0 -1000) 200 (colour 0 1 .5)) (make 'sphere (point 50 0 -1200) 200 (colour 1 .5 0)) (make 'sphere (point 400 0 -1400) 200 (colour 0 .5 1)) (make 'sphere (point -50 -150 -600) 50 (colour 0 0 1)) (make 'sphere (point 200 -150 -800) 50 (colour 1 0 0)) (tracer))