處理數(shù)字是 Common Lisp 的強項之一。Common Lisp 有著豐富的數(shù)值類型,而 Common Lisp 操作數(shù)字的特性與其他語言比起來更受人喜愛。
Common Lisp 提供了四種不同類型的數(shù)字:整數(shù)、浮點數(shù)、比值與復數(shù)。本章所講述的函數(shù)適用于所有類型的數(shù)字。有幾個不能用在復數(shù)的函數(shù)會特別說明。
整數(shù)寫成一串數(shù)字:如?2001
?。浮點數(shù)是可以寫成一串包含小數(shù)點的數(shù)字,如?253.72
?,或是用科學表示法,如?2.5372e2
?。比值是寫成由整數(shù)組成的分數(shù):如?2/3
?。而復數(shù)?a+bi
?寫成?#c(a?b)
?,其中?a
?與?b
?是任兩個類型相同的實數(shù)。
謂詞?integerp
?、?floatp
?以及?complexp
?針對相應的數(shù)字類型返回真。圖 9.1 展示了數(shù)值類型的層級。
朗伯定律?告訴我們,由平面上一點所反射的光的強度,正比于該點的單位法向量 (unit normal vector)?N?(這里是與平面垂直且長度為一的向量)與該點至光源的單位向量?L?的點積 (dot-product):
i=N?L
如果光剛好照到這點,?N?與?L?會重合 (coincident),則點積會是最大值,?1
?。如果將在這時候將平面朝光轉 90 度,則?N?與?L?會垂直,則兩者點積會是?0
?。如果光在平面后面,則點積會是負數(shù)。
在我們的程序里,我們假設光源在觀測點 (eye),所以?lambert
?使用了這個規(guī)則來找到平面上某點的亮度 (illumination),返回我們追蹤的光的單位向量與法向量的點積。
在?sendray
?這個值會乘上平面的顏色 (即便是有好的照明,一個暗的平面還是暗的)來決定該點之后總體亮度。
為了簡單起見,我們在模擬世界里會只有一種物體,球體。圖 9.5 包含了與球體有關的代碼。球體結構包含了?surface
?,所以一個球體會有一種顏色以及?center
?和?radius
?。調用?defsphere
?添加一個新球體至世界里。
(defstruct (sphere (:include surface))
radius center)
(defun defsphere (x y z r c)
(let ((s (make-sphere
:radius r
:center (make-point :x x :y y :z z)
:color c)))
(push s *world*)
s))
(defun intersect (s pt xr yr zr)
(funcall (typecase s (sphere #'sphere-intersect))
s pt xr yr zr))
(defun sphere-intersect (s pt xr yr zr)
(let* ((c (sphere-center s))
(n (minroot (+ (sq xr) (sq yr) (sq zr))
(* 2 (+ (* (- (x pt) (x c)) xr)
(* (- (y pt) (y c)) yr)
(* (- (z pt) (z c)) zr)))
(+ (sq (- (x pt) (x c)))
(sq (- (y pt) (y c)))
(sq (- (z pt) (z c)))
(- (sq (sphere-radius s)))))))
(if n
(make-point :x (+ (x pt) (* n xr))
:y (+ (y pt) (* n yr))
:z (+ (z pt) (* n zr))))))
(defun normal (s pt)
(funcall (typecase s (sphere #'sphere-normal))
s pt))
(defun sphere-normal (s pt)
(let ((c (sphere-center s)))
(unit-vector (- (x c) (x pt))
(- (y c) (y pt))
(- (z c) (z pt)))))
圖 9.5 球體。
函數(shù)?intersect
?判斷與何種平面有關,并調用對應的函數(shù)。在此時只有一種,?sphere-intersect
?,但?intersect
?是寫成可以容易擴展處理別種物體。
我們要怎么找到一束光與一個球體的交點 (intersection)呢?光線是表示成點?p=?x0,y0,x0??以及單位向量?v=?xr,yr,xr??。每個在光上的點可以表示為?p+nv?,對于某個?n?── 即??x0+nxr,y0+nyr,z0+nzr??。光擊中球體的點的距離至中心??xc,yc,zc??會等于球體的半徑?r?。所以在下列這個交點的方程序會成立:
r=(x0+nxr?xc)2+(y0+nyr?yc)2+(z0+nzr?zc)2??????????????????????????????????????????√
這會給出
an2+bn+c=0
其中
a=x2r+y2r+z2rb=2((x0?xc)xr+(y0?yc)yr+(z0?zc)zr)c=(x0?xc)2+(y0?yc)2+(z0?zc)2?r2
要找到交點我們只需要找到這個二次方程序的根。它可能是零、一個或兩個實數(shù)根。沒有根代表光沒有擊中球體;一個根代表光與球體交于一點 (擦過 「grazing hit」);兩個根代表光與球體交于兩點 (一點交于進入時、一點交于離開時)。在最后一個情況里,我們想要兩個根之中較小的那個;?n?與光離開觀測點的距離成正比,所以先擊中的會是較小的?n?。所以我們調用?minroot
?。如果有一個根,?sphere-intersect
?返回代表該點的??x0+nxr,y0+nyr,z0+nzr??。
圖 9.5 的另外兩個函數(shù),?normal
?與?sphere-normal
?類比于?intersect
?與?sphere-intersect
?。要找到垂直于球體很簡單 ── 不過是從該點至球體中心的向量而已。
圖 9.6 示范了我們如何產生圖片;?ray-test
?定義了 38 個球體(不全都看的見)然后產生一張圖片,叫做 “sphere.pgm” 。
(譯注:PGM 可移植灰度圖格式,更多信息參見?wiki?)
(defun ray-test (&optional (res 1))
(setf *world* nil)
(defsphere 0 -300 -1200 200 .8)
(defsphere -80 -150 -1200 200 .7)
(defsphere 70 -100 -1200 200 .9)
(do ((x -2 (1+ x)))
((> x 2))
(do ((z 2 (1+ z)))
((> z 7))
(defsphere (* x 200) 300 (* z -400) 40 .75)))
(tracer (make-pathname :name "spheres.pgm") res))
圖 9.6 使用光線追蹤器
圖 9.7 是產生出來的圖片,其中?res
?參數(shù)為 10。
更多建議: