git.fiddlerwoaroof.com
Browse code

feat: add raytracer

Implemented up to: https://raytracing.github.io/books/RayTracingInOneWeekend.html#antialiasing

Edward authored on 20/03/2021 09:09:43
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,322 @@
1
+(defpackage :fwoar.lisp-sandbox.1
2
+  (:use :cl
3
+        )
4
+  (:export ))
5
+(in-package :fwoar.lisp-sandbox.1)
6
+
7
+(define-symbol-macro infinity
8
+  #.sb-ext:double-float-positive-infinity)
9
+(defun deg2rad (deg)
10
+  (/ (* deg pi)
11
+     180.0d0))
12
+
13
+(defstruct (size (:type vector))
14
+  width height)
15
+(defstruct (color (:type vector))
16
+  r g b)
17
+(defstruct (vec3 (:type vector)
18
+                 (:constructor vec3 (x y z))
19
+                 (:conc-name v3-))
20
+  x y z)
21
+
22
+(defun vec+ (vec1 vec2)
23
+  (declare (optimize (speed 3)))
24
+  (fw.lu:vector-destructuring-bind (a b c) vec1
25
+    (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2
26
+      (vec3 (+ a a1)
27
+            (+ b b1)
28
+            (+ c c1)))))
29
+(define-compiler-macro vec+ (&whole whole vec1 vec2)
30
+  (cond ((and (vectorp vec1)
31
+              (vectorp vec2))
32
+         (vec+ vec1 vec2))
33
+        ((vectorp vec1)
34
+         (alexandria:once-only (vec2)
35
+           `(fw.lu:vector-destructuring-bind (a b c) ,vec1
36
+              (vec3
37
+               (+ a (aref ,vec2 0))
38
+               (+ b (aref ,vec2 1))
39
+               (+ c (aref ,vec2 2))))))
40
+        ((vectorp vec2)
41
+         (alexandria:once-only (vec1)
42
+           `(fw.lu:vector-destructuring-bind (a b c) ,vec2
43
+              (vec3
44
+               (+ a (aref ,vec1 0))
45
+               (+ b (aref ,vec1 1))
46
+               (+ c (aref ,vec1 2))))))
47
+        (t whole)))
48
+(defun vec- (vec1 vec2)
49
+  (declare (optimize (speed 3)))
50
+  (fw.lu:vector-destructuring-bind (a b c) vec1
51
+    (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2
52
+      (vec3 (- a a1)
53
+            (- b b1)
54
+            (- c c1)))))
55
+(defun vec* (vec1 vec2)
56
+  (declare (optimize (speed 3)))
57
+  (etypecase vec1
58
+    ((array * (3)) (fw.lu:vector-destructuring-bind (a b c) vec1
59
+                     (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2
60
+                       (vec3 (* a a1)
61
+                             (* b b1)
62
+                             (* c c1)))))
63
+    (double-float (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2
64
+                    (vec3 (* vec1 a1)
65
+                          (* vec1 b1)
66
+                          (* vec1 c1))))
67
+    (single-float (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2
68
+                    (vec3 (* vec1 a1)
69
+                          (* vec1 b1)
70
+                          (* vec1 c1))))
71
+    (number (fw.lu:vector-destructuring-bind (a1 b1 c1) vec2
72
+              (vec3 (* vec1 a1)
73
+                    (* vec1 b1)
74
+                    (* vec1 c1))))))
75
+(defun vec/ (vec it)
76
+  (declare (optimize (speed 3)))
77
+  (vec* (/ 1.0 it)
78
+        vec))
79
+(defun dot (u v)
80
+  (fw.lu:vector-destructuring-bind (a1 b1 c1) u
81
+    (fw.lu:vector-destructuring-bind (a2 b2 c2) v
82
+      (+ (* a1 a2)
83
+         (* b1 b2)
84
+         (* c1 c2)))))
85
+(defun cross (u v)
86
+  (fw.lu:vector-destructuring-bind (a1 b1 c1) u
87
+    (fw.lu:vector-destructuring-bind (a2 b2 c2) v
88
+      (vec3 (- (* b1 c2)
89
+               (* c1 b2))
90
+            (- (* c1 a2)
91
+               (* a1 c2))
92
+            (- (* a1 b2)
93
+               (* b1 a2))))))
94
+
95
+(defun length-squared (v)
96
+  (fw.lu:vector-destructuring-bind (x y z) v
97
+    (+ (* x x)
98
+       (* y y)
99
+       (* z z))))
100
+(defun vec-length (v)
101
+  (sqrt (length-squared v)))
102
+(defun unit-vector (v)
103
+  (vec/ v
104
+        (vec-length v)))
105
+
106
+(defun call-with-ppm-header (stream size callback &optional (colors 255))
107
+  (format stream "P3~%~d ~d~%~d~%"
108
+          (size-width size)
109
+          (size-height size)
110
+          colors)
111
+  (funcall callback stream))
112
+
113
+#.(progn (defmacro ig (&rest syms) `'(declare (ignore ,@syms)))
114
+         nil)
115
+
116
+(defvar *color-depth* 255)
117
+(defun format-color (s v _ __)
118
+  #.(ig _ __)
119
+  (fwoar.lisputils:vector-destructuring-bind (r g b) v
120
+    (format s "~4d ~4d ~4d"
121
+            (round (* *color-depth* r))
122
+            (round (* *color-depth* g))
123
+            (round (* *color-depth* b)))))
124
+
125
+(defun write-colors (stream colors columns)
126
+  (let ((intermediate ())
127
+        (idx 0))
128
+    (funcall colors
129
+             (lambda (color)
130
+               (push color intermediate)
131
+               (when (= (1- columns)
132
+                        (mod idx columns))
133
+                 (format stream "~{~/fwoar.lisp-sandbox.1::format-color/~^  ~}~&" intermediate)
134
+                 (setf intermediate ()))
135
+               (incf idx)))
136
+    (when intermediate
137
+      (format stream "~{~/fwoar.lisp-sandbox.1::format-color/~^  ~}~&" intermediate))))
138
+
139
+(fw.lu:defclass+ ray ()
140
+  ((origin :initarg :origin)
141
+   (direction :initarg :direction)))
142
+
143
+(defgeneric at (self it)
144
+  (:method ((ray ray) (it number))
145
+    (with-slots (origin direction) ray
146
+      (vec+ origin
147
+            (vec* it direction)))))
148
+
149
+(fw.lu:defclass+ hit-record ()
150
+  ((p :initarg :p :reader .p)
151
+   (time :initarg :time :reader .time)
152
+   (normal :initarg :normal :accessor .normal :initform ())
153
+   (front-face :initarg :front-face :accessor .front-face :initform ())))
154
+(defun set-face-normal (hit-record r outward-normal)
155
+  (prog1 hit-record
156
+    (with-slots (direction) r
157
+      (let ((front-face (< (dot direction outward-normal)
158
+                           0)))
159
+        (setf (.front-face hit-record) front-face
160
+              (.normal hit-record) (if front-face
161
+                                       outward-normal
162
+                                       (vec* -1 outward-normal)))))))
163
+
164
+(defclass hittable ()
165
+  ())
166
+(fw.lu:defclass+ sphere (hittable)
167
+  ((center :initarg :center)
168
+   (radius :initarg :radius)))
169
+(defgeneric hit (thing ray t-min t-max)
170
+  (:method ((things list) (r ray) (t-min real) (t-max real))
171
+    (let (temp-rec
172
+          (hit-anything nil)
173
+          (closest-so-far t-max))
174
+      (loop for thing in things
175
+            for (hit-p hit-rec) = (multiple-value-list
176
+                                   (hit thing r t-min closest-so-far))
177
+            when hit-p do
178
+              (setf hit-anything t
179
+                    closest-so-far (.time hit-rec)
180
+                    temp-rec hit-rec))
181
+      (when hit-anything
182
+        (values hit-anything
183
+                temp-rec))))
184
+  (:method ((sphere sphere) (r ray) (t-min real) (t-max real))
185
+    (uiop:nest (with-slots ((%center center) (%radius radius)) sphere)
186
+               (let ((center %center) (radius %radius)))
187
+               (with-slots ((%origin origin) (%direction direction)) r)
188
+               (let ((origin %origin) (direction %direction)))
189
+               (let* ((oc (vec- origin center))
190
+                      (a (length-squared direction))
191
+                      (half-b (dot oc direction))
192
+                      (c (- (length-squared oc)
193
+                            (* radius radius)))
194
+                      (discriminant (- (* half-b half-b)
195
+                                       (* a c))))
196
+                 (if (< discriminant 0)
197
+                     (return-from hit nil)
198
+                     (let* ((sqrtd (sqrt discriminant))
199
+                            (root (/ (- (- half-b)
200
+                                        sqrtd)
201
+                                     a)))
202
+                       (when (or (< root t-min)
203
+                                 (< t-max root))
204
+                         (setf root (/ (- sqrtd half-b)
205
+                                       a))
206
+                         (when (or (< root t-min)
207
+                                   (< t-max root))
208
+                           (return-from hit nil)))
209
+                       (let* ((p (at r root))
210
+                              (outward-normal (vec/ (vec- p center)
211
+                                                    radius)))
212
+
213
+                         (values t
214
+                                 (set-face-normal (hit-record p root)
215
+                                                  r
216
+                                                  outward-normal)))))))))
217
+
218
+(defun hit-sphere (center radius r)
219
+  (with-slots (origin direction) r
220
+    (let* ((oc (vec- origin center))
221
+           (a (length-squared direction))
222
+           (half-b (dot oc direction))
223
+           (c (- (length-squared oc)
224
+                 (expt radius 2)))
225
+           (discriminant (- (* half-b half-b)
226
+                            (* a c))))
227
+      (if (< discriminant 0)
228
+          -1.0d0
229
+          (/ (- (- half-b) (sqrt discriminant))
230
+             a)))))
231
+
232
+(defgeneric ray-color (ray world)
233
+  (:method ((ray ray) world)
234
+    (multiple-value-bind (hit-p rec)
235
+        (hit world ray 0 infinity)
236
+      (when hit-p
237
+        (return-from ray-color
238
+          (vec* 0.5
239
+                (vec+ #(1 1 1)
240
+                      (.normal rec)))))
241
+      (with-slots (direction) ray
242
+        (let* ((unit-direction (unit-vector direction))
243
+               (it (+ (* 0.5 (v3-y unit-direction))
244
+                      1.0d0)))
245
+          (vec+ (vec* (- 1.0d0 it)
246
+                      #(1.0d0 1.0d0 1.0d0))
247
+                (vec* it
248
+                      #(0.5d0 0.7d0 1.0d0))))))))
249
+
250
+(defun raytrace (out)
251
+  (let* ((world (list (sphere #(0 0 -1) 0.5)
252
+                      (sphere #(0 -100.5 -1) 100)))
253
+         (aspect-ratio (/ 16.0d0 9.0d0))
254
+         (image-width 400)
255
+         (image-height (* (floor (/ image-width aspect-ratio))))
256
+
257
+         (viewport-height 2.0d0)
258
+         (viewport-width (* aspect-ratio viewport-height))
259
+         (focal-length 1.0d0)
260
+
261
+         (origin (vec3 0 0 0))
262
+         (horizontal (vec3 viewport-width 0 0))
263
+         (vertical (vec3 0 viewport-height 0))
264
+         (lower-left-corner (vec- (vec- (vec- origin
265
+                                              (vec/ horizontal 2))
266
+                                        (vec/ vertical 2))
267
+                                  (vec3 0 0 focal-length))))
268
+    (alexandria:with-output-to-file (s out :if-exists :supersede)
269
+      (call-with-ppm-header s (make-size :width image-width :height image-height)
270
+                            (lambda (s)
271
+                              (write-colors s
272
+                                            (lambda (c)
273
+                                              (loop for j from (1- image-height) downto 0
274
+                                                    do (format *trace-output*
275
+                                                               "~&Scanlines remaining: ~d ~s~%"
276
+                                                               j
277
+                                                               (local-time:now))
278
+                                                    do
279
+                                                       (loop for i from 0 below image-width
280
+                                                             for u = (/ (* 1.0d0 i)
281
+                                                                        (1- image-width))
282
+                                                             for v = (/ (* 1.0d0 j)
283
+                                                                        (1- image-height))
284
+                                                             for r = (ray origin
285
+                                                                          (vec- (vec+ (vec+ lower-left-corner
286
+                                                                                            (vec* u
287
+                                                                                                  horizontal))
288
+                                                                                      (vec* v
289
+                                                                                            vertical))
290
+                                                                                origin))
291
+                                                             for color = (ray-color r world)
292
+                                                             collect
293
+                                                             (funcall c color))))
294
+                                            image-width))
295
+                            (round *color-depth*)))))
296
+
297
+(defun sample-image (out)
298
+  (let ((image-width 256)
299
+        (image-height 256))
300
+    (alexandria:with-output-to-file (s out :if-exists :supersede)
301
+      (call-with-ppm-header s (make-size :width image-width :height image-height)
302
+                            (lambda (s)
303
+                              (write-colors s
304
+                                            (lambda (c)
305
+                                              (loop for j from (1- image-height) downto 0
306
+                                                    do (format *trace-output*
307
+                                                               "~&Scanlines remaining: ~d ~s~%"
308
+                                                               j
309
+                                                               (local-time:now))
310
+                                                    do
311
+                                                       (loop for i from 0 below image-width
312
+                                                             collect
313
+                                                             (let* ((r (/ (* i 1.0d0)
314
+                                                                          (1- image-width)))
315
+                                                                    (g (/ (* j 1.0d0)
316
+                                                                          (1- image-height)))
317
+                                                                    (b 0.15d0))
318
+                                                               (funcall c (make-color :r r
319
+                                                                                      :g g
320
+                                                                                      :b b))))))
321
+                                            image-width))
322
+                            (1- #.(expt 2 8))))))