第九章:數(shù)字

2018-02-24 15:50 更新

處理數(shù)字是 Common Lisp 的強項之一。Common Lisp 有著豐富的數(shù)值類型,而 Common Lisp 操作數(shù)字的特性與其他語言比起來更受人喜愛。

9.1 類型 (Types)

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。

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號