Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pol_Grem_-_ANSI_Common_Lisp_High_tech_-_2012.pdf
Скачиваний:
30
Добавлен:
12.03.2016
Размер:
4.85 Mб
Скачать

158

Глава 9. Числа

> (list (minusp -0.0) (zerop -0.0)) (NIL T)

и соот­вет­ст­ву­ет­ zerop, а не minusp.

Преди­ка­ты­ oddp и evenp приме­ни­мы­ лишь к цело­чис­лен­­ным аргу­мен­­ там. Первый­ исти­нен­ для нечет­ных­ чисел,­ второй ­– для четных­.

Из всех преди­ка­тов,­ упомя­ну­тых­ в этом разде­ле,­ лишь =, /= и zerop при­ мени­мы­ к комплекс­ным­ числам­.

Наиболь­ший­ и наимень­ший­ аргу­мен­ты­ могут­ быть полу­че­ны­ с помо­­ щью функций­ max и min соот­вет­ст­вен­­но, при этом должен­ быть задан­ хо­ тя бы один аргу­мент:­

> (list (max 1 2 3 4 5) (min 1 2 3 4 5)) (5 1)

Если­ среди­ аргу­мен­тов­ есть хотя­ бы одно­ число­ с плаваю­щей­ запя­той,­ то тип возвра­щае­мо­го­ значе­ния­ зави­сит­ от исполь­зуе­мой­ реали­за­ции­.

9.4. Арифметика

Сложе­ние­ и вычи­та­ние­ выполняют­ся­ с помо­щью­ + и -. Обе функции­ мо­ гут рабо­тать­ с любым­ коли­че­ст­вом­ аргу­мен­тов,­ а в случае­ их отсут­ст­вия­ возвра­ща­ют­ 0. Выра­же­ние­ вида­ (- n) возвра­ща­ет­ –n. Выра­же­ние­ вида­

(- x y z)

экви­ва­лент­но­

(- (- x y) z)

Кроме­ того,­ имеют­ся­ функции­ 1+ и 1-, кото­рые­ возвра­ща­ют­ свой аргу­­ мент, увели­чен­­ный или уменьшен­ный­ на 1 соот­вет­ст­вен­­но. Имя функ­ ции 1- может­ сбить с толку,­ пото­му­ что (1- x) возвра­ща­ет­ x - 1, а не 1 - x.

Макро­сы­ incf и decf соот­вет­ст­вен­но­ увели­чи­ва­ют­ и уменьша­ют­ свой аргу­мент­. Выра­же­ние­ вида­ (incf x n) имеет­ такой­ же эффект,­ как и (setf x (+ x n)), а (decf x n) – как (setf x (- x n)). В обоих­ случа­ях­ второй­ аргу­­ мент не обяза­те­лен­ и по умолча­нию­ равен­ 1.

Умно­же­ние­ выпол­ня­ет­ся­ с помо­щью­ функции­ *, кото­рая­ прини­ма­ет­ любое­ коли­че­ст­во­ аргу­мен­тов­ и возвра­ща­ет­ их общее­ произ­ве­де­ние­. Бу­ дучи­ вызван­ной­ без аргу­мен­тов,­ она возвра­ща­ет­ 1.

Функция­ деле­ния­ / требу­ет­ зада­ния­ хотя­ бы одно­го­ аргу­мен­та­. Вызов­ (/ n) экви­ва­лен­тен­ вызо­ву­ (/ 1 n):

> (/ 3) 1/3

Вто время­ как выра­же­ние:­

(/ x y z)

экви­ва­лент­но­

(/ (/ x y) z)

9.5. Возведение в степень

159

Обра­ти­те­ внима­ние­ на сходное­ пове­де­ние ­– и / в этом отно­ше­нии­.

Вызван­ная­ с двумя­ целы­ми­ аргу­мен­та­ми,­ / возвра­ща­ет­ рацио­наль­ную­ дробь, если­ первый­ аргу­мент­ не делит­ся­ на второй:­

> (/ 365 12) 365/12

Если­ вы буде­те­ пытать­ся­ вычис­лять,­ к приме­ру,­ усред­нен­ную­ длину­ ме­ сяца,­ то вам может­ пока­зать­ся,­ что toplevel над вами­ изде­ва­ет­ся­. В по­ добных­ случа­ях,­ когда­ вам дейст­ви­тель­но­ нужна­ деся­тич­ная­ дробь, пользуй­тесь­ функци­ей­ float:

> (float 365/12) 30.416666

9.5. Возведение в степень

Чтобы­ найти­ xn, вызо­вем­ (expt x n):

>(expt 2 5)

32

Ачтобы­ вычис­лить­ lognx, вызо­вем­ (log x n):

>(log 32 2)

5.0

Обычно­ при вычис­ле­нии­ лога­риф­ма­ возвра­ща­ет­ся­ число­ с плаваю­щей­ запя­той­.

Для нахо­ж­де­ния­ степе­ни­ числа­ e суще­ст­ву­ет­ специ­аль­ная­ функция­ exp:

> (exp 2) 7.389056

Вычис­ле­ние­ нату­раль­но­го­ лога­риф­ма­ выпол­ня­ет­ся­ функци­ей­ log, кото­­ рой доста­точ­но­ сооб­щить­ лишь само­ число:­

> (log 7.389056) 2.0

Вычис­ле­ние­ корней­ может­ выпол­нять­ся­ с помо­щью­ expt, для чего­ ее второй­ аргу­мент­ должен­ быть рацио­наль­ной­ дробью:­

> (expt 27 1/3) 3.0

Но для вычис­ле­ния­ квадрат­ных­ корней­ быст­рее­ рабо­та­ет­ функция­ sqrt:

> (sqrt 4) 2.0

9.6. Тригонометрические функции

Представ­­лени­ем­ числа­ π с плаваю­щей­ запя­той­ яв­ляет­ся­ констан­та­ pi. Ее точность­ зави­сит­ от исполь­зуе­мой­ реали­за­ции­. Функции­ sin, cos и tan

160

 

Глава 9. Числа

вычис­ля­ют­ соот­вет­ст­вен­но­ синус,­ коси­нус­ и тангенс­ задан­но­го­ в радиа­­

нах угла:­

 

 

> (let ((x (/ pi 4)))

 

 

(list (sin x) (cos x) (tan x)))

 

(0.7071067811865475d0

0.707167811865476d0

1.0d0)

Все вы­шепе­ре­чис­лен­­ные функции­ умеют­ рабо­тать­ с комплекс­ны­ми­ ар­ гумен­та­ми­.

Обрат­ные­ преоб­ра­зо­ва­ния­ выпол­ня­ют­ся­ функция­ми­ asin, acos, atan. Для аргу­мен­тов,­ нахо­дя­щих­ся­ на отрез­ке­ от -1 до 1, asin и acos возвра­­ щают­ дейст­ви­тель­ные­ значе­ния­.

Гипер­бо­ли­че­ские­ синус,­ коси­нус­ и тангенс­ вычис­ля­ют­ся­ через­ sinh, cosh и tanh соот­вет­ст­вен­­но. Для них также­ опре­де­ле­ны­ обрат­ные­ преоб­­ разо­ва­ния:­ asinh, acosh, atanh.

9.7. Представление

Common Lisp не накла­ды­ва­ет­ каких­-либо­ огра­ни­че­ний­ на размер­ целых­ чисел­. Целые­ числа­ неболь­шо­го­ разме­ра,­ кото­рые­ поме­ща­ют­ся­ в ма­ шинном­ слове,­ отно­сят­ся­ к типу­ fixnum. Если­ для хране­ния­ числа­ тре­ бует­ся­ памя­ти­ больше,­ чем слово,­ Лисп пере­клю­ча­ет­ся­ на представ­ле­ние­ bignum, для кото­ро­го­ выде­ля­ет­ся­ несколь­ко­ слов для хране­ния­ цело­го­ числа­. Это озна­ча­ет,­ что сам язык не накла­ды­ва­ет­ каких­-либо­ огра­ни­че­­ ний на размер­ чисел,­ и он зави­сит­ лишь от доступ­но­го­ объема­ памя­ти­.

Констан­ты­ most-positive-fixnum и most-negative-fixnum зада­ют­ макси­маль­­ ные вели­чи­ны,­ кото­рые­ могут­ обра­ба­ты­вать­ся­ реали­за­ци­ей­ без исполь­­ зова­ния­ типа­ bignum. Во многих­ реали­за­ци­ях­ они имеют­ следую­щие­ значе­ния:­

> (values most-positive-fixnum most-negative-fixnum) 536870911 -536870912

Принад­леж­­ность к опре­де­лен­­ному­ типу­ прове­ря­ет­ся­ с помо­щью­ преди­­ ката­ typep:

>(typep 1 ’fixnum)

T

>(typep (1+ most-positive-fixnum) ’bignum)

T

Вкаждой­ реали­за­ции­ имеют­ся­ свои огра­ни­че­ния­ на размер­ чисел­ с пла­ вающей­ запя­той­. Common Lisp предос­тав­ля­ет­ четы­ре­ типа­ чисел­ с пла­ вающей­ запя­той:­ short-float, single-float, double-float и long-float. Реа­ лиза­ции­ стандар­та­ не обяза­ны­ исполь­зо­вать­ различ­ные­ представ­ле­ния­ для разных­ типов­ (и лишь неко­то­рые­ это дела­ют)­ .

Суть типа­ short-float в том, что он зани­ма­ет­ одно­ машин­ное­ слово­. Ти­ пы single-float и double-float зани­ма­ют­ столько­ места,­ сколько­ нужно,­

9.8. Пример: трассировка лучей

161

чтобы­ удовле­тво­рять­ уста­нов­лен­­ным требо­ва­ни­ям­ к числам­ одинар­ной­ и двойной­ точно­сти­ соот­вет­ст­вен­­но. Числа­ типа­ long-float могут­ быть больши­ми­ настоль­ко,­ насколь­ко­ это требу­ет­ся­. Но в конкрет­ной­ реали­­ зации­ все 4 ти­па могут­ иметь одина­ко­вое­ внутрен­нее­ представ­ле­ние­.

Тип числа­ можно­ задать­ прину­ди­тель­но,­ исполь­зуя­ соот­вет­ст­вую­щие­ бук­вы:­ s, f, d или l, а также­ e для экспо­нен­ци­аль­ной­ формы­. (Заглав­ные­ бук­вы­ также­ допус­ка­ют­ся,­ и этим стоит­ восполь­зо­вать­ся­ для представ­­ ления­ типа­ long-float, по­тому­ что малую­ l легко­ спутать­ с цифрой­ 1.) Наиболь­шее­ представ­ле­ние­ числа­ 1.0 можно­ задать­ как 1L0.

Глобаль­ные­ огра­ни­че­ния­ на размер­ чисел­ разных­ типов­ зада­ют­ся­ шест­­ надца­тью­ констан­та­ми­. Их имена­ выгля­дят­ как m-s-f, где m может­ быть most или least, s соот­вет­ст­ву­ет­ positive или negative, а f – один из четы­­ рех типов­ чисел­ с плаваю­щей­ запя­той­.°

Превы­ше­ние­ задан­ных­ огра­ни­че­ний­ приво­дит­ к возник­но­ве­нию­ ошиб­ ки в Common Lisp:

> (* most-positive-long-float 10) Error: floating-point-overflow.

9.8. Пример: трассировка лучей

В каче­ст­ве­ при­мера­ програм­мы,­ постро­ен­ной­ на числен­­ных расче­тах,­ в этом разде­ле­ при­водит­ся­ реше­ние­ зада­чи­ трасси­ров­ки­ лучей­. Трасси­­ ровка­ лучей ­– это превос­ход­ный­ алго­ритм­ ренде­рин­га­ изобра­же­ний,­ с помо­щью­ кото­ро­го­ мож­но полу­чать­ реали­стич­ные­ карти­ны­. Одна­ко­ данный­ метод­ явля­ет­ся­ доволь­но­ доро­го­стоя­щим­.

Для моде­ли­ро­ва­ния­ трехмер­но­го­ изобра­же­ния­ нам необ­хо­ди­мо­ опре­де­­ лить как мини­мум­ четы­ре­ предме­та:­ наблю­да­те­ля,­ один или более­ ис­ точни­ков­ света,­ набор­ объек­тов­ моде­ли­руе­мо­го­ мира­ и плоскость­ ри­ сунка­ (image plane), кото­рая­ служит­ окном­ в этот мир. Наша­ зада­ча ­– сгене­ри­ро­вать­ изобра­же­ние,­ соот­вет­ст­вую­щее­ проек­ции­ мира­ на об­ ласть плоско­сти­ рисун­ка­.

Что же дела­ет­ необыч­ным­ метод­ трасси­ров­ки­ лучей?­ Попик­сель­ная­ от­ рисов­ка­ всей карти­ны­ и симу­ля­ция­ прохо­ж­де­ния­ луча­ через­ вирту­аль­­ ный мир. Такой­ подход­ позво­ля­ет­ достичь­ реали­стич­ных­ опти­че­ских­ эффек­тов:­ прозрач­но­сти,­ отра­же­ний,­ зате­не­ний;­ он позво­ля­ет­ зада­вать­ отри­со­вы­вае­мый­ мир как набор­ геомет­ри­че­ских­ тел, вместо­ того­ чтобы­ строить­ их из поли­го­нов­. Отсю­да­ выте­ка­ет­ отно­си­тель­ная­ прямо­ли­ней­­ ность в реали­за­ции­ мето­да­.

На рис. 9.2 пока­за­ны­ основ­ные­ мате­ма­ти­че­ские­ утили­ты,­ кото­ры­­ми мы будем­ пользо­вать­ся­. Первая,­ sq, вычис­ля­ет­ квадрат­ аргу­мен­та­. Вторая,­ mag, возвра­ща­ет­ длину­ векто­ра­ по трем его ком­понен­там­ x, y и z. Эта функция­ исполь­зу­ет­ся­ в следую­щих­ двух. unit-vector возвра­ща­ет­ три значе­ния,­ соот­вет­ст­вую­щие­ коор­ди­на­там­ единич­но­го­ векто­ра,­ имею­ щего­ то же направ­ле­ние,­ что и задан­ный:­

162

Глава 9. Числа

> (multiple-value-call #’mag (unit-vector 23 12 47)) 1.0

Кроме­ того,­ mag исполь­зу­ет­ся­ в функции­ distance, вычис­ляю­щей­ рас­ стояние­ между­ двумя­ точка­ми­ в трехмер­ном­ простран­ст­ве­. (Опре­де­ле­­ ние структу­ры­ point содер­жит­ пара­метр­ :conc-name, равный­ nil. Это зна­ чит, что функции­ досту­па­ к соот­вет­ст­вую­щим­ полям­ структу­ры­ будут­ иметь такие­ же имена,­ как и сами­ поля,­ напри­мер­ x вместо­ point-x.)

(defun sq (x) (* x x))

(defun mag (x y z)

(sqrt (+ (sq x) (sq y) (sq z))))

(defun unit-vector (x y z) (let ((d (mag x y z)))

(values (/ x d) (/ y d) (/ z d))))

(defstruct (point (:conc-name nil)) x y z)

(defun distance (p1 p2) (mag (- (x p1) (x p2))

(- (y p1) (y p2)) (- (z p1) (z p2))))

(defun minroot (a b c) (if (zerop a)

(/ (- c) b)

(let ((disc (- (sq b) (* 4 a c)))) (unless (minusp disc)

(let ((discrt (sqrt disc)))

(min (/ (+ (- b) discrt) (* 2 a))

(/ (- (- b) discrt) (* 2 a))))))))

Рис. 9.2. Мате­ма­ти­че­ские­ утили­ты­

Нако­нец,­ функция­ minroot прини­ма­ет­ три дейст­ви­тель­ных­ числа­ a, b и c и возвра­ща­ет­ наимень­шее­ x, для кото­ро­го­ ax2 + bx + c=0. В случае­ когда­ a не равно­ нулю,­ корни­ этого­ уравне­ния­ могут­ быть полу­че­ны­ по хоро­­ шо извест­ной­ форму­ле:­

Код, реали­зую­щий­ функцио­наль­но­ огра­ни­чен­­ный трасси­ров­щик­ лу­ чей, представ­лен­ на рис. 9.3. Он гене­ри­ру­ет­ черно­-белые­ изобра­же­ния,­ осве­щае­мые­ одним­ источ­ни­ком­ света,­ распо­ло­жен­ным­ там же, где и глаз наблю­да­те­ля­. (Таким­ обра­зом­ дости­га­ет­ся­ эффект­ фото­гра­фии­ со вспышкой­.)

9.8. Пример: трассировка лучей

163

 

 

 

 

(defstruct surface color)

 

 

(defparameter *world* nil)

 

 

(defconstant eye (make-point :x 0 :y 0 :z 200))

 

 

(defun tracer (pathname &optional (res 1))

 

 

(with-open-file (p pathname :direction :output)

 

 

(format p "P2 ~A ~A 255" (* res 100) (* res 100))

 

 

(let ((inc (/ res)))

 

 

(do ((y -50 (+ y inc)))

 

 

((< (- 50 y) inc))

 

 

(do ((x -50 (+ x inc)))

 

 

((< (- 50 x) inc))

 

 

(print (color-at x y) p))))))

 

 

(defun color-at (x y)

 

 

(multiple-value-bind (xr yr zr)

 

 

(unit-vector (- x (x eye))

 

 

(- y (y eye))

 

 

(- 0 (z eye)))

 

 

(round (* (sendray eye xr yr zr) 255))))

 

 

(defun sendray (pt xr yr zr)

 

 

(multiple-value-bind (s int) (first-hit pt xr yr zr)

 

 

(if s

 

 

(* (lambert s int xr yr zr) (surface-color s))

 

 

0)))

 

 

(defun first-hit (pt xr yr zr)

 

 

(let (surface hit dist)

 

 

(dolist (s *world*)

 

 

(let ((h (intersect s pt xr yr zr)))

 

 

(when h

 

 

(let ((d (distance h pt)))

 

 

(when (or (null dist) (< d dist))

 

 

(setf surface s hit h dist d))))))

 

 

(values surface hit)))

 

 

(defun lambert (s int xr yr zr)

 

 

(multiple-value-bind (xn yn zn) (normal s int)

 

 

(max 0 (+ (* xr xn) (* yr yn) (* zr zn)))))

 

 

 

 

Рис. 9.3. Трасси­ров­ка­ лучей­

Для представ­ле­ния­ объек­тов­ в вирту­аль­ном­ мире­ исполь­зу­ет­ся­ струк­ тура­ surface. Гово­ря­ точнее,­ она будет­ включе­на­ в структу­ры,­ представ­­ ляющие­ конкрет­ные­ разно­вид­но­сти­ объек­тов,­ напри­мер­ сферы­. Сама­ структу­ра­ surface содер­жит­ лишь одно­ поле­ color (цвет) в интер­ва­ле­ от 0 (черный)­ до 1 (белый)­ .

Плоскость­ рисун­ка­ распо­ла­га­ет­ся­ вдоль осей x и y. Глаз наблю­да­те­ля­ смотрит­ вдоль оси z и нахо­дит­ся­ на расстоя­нии­ 200 единиц­ от рисун­ка­.

164

Глава 9. Числа

Чтобы­ доба­вить­ объект,­ необ­хо­ди­мо­ помес­тить­ его в список­ *world* (из­ началь­но­ nil). Чтобы­ он был види­мым,­ его z-коор­ди­на­та­ долж­на быть отри­ца­тель­ной­. Рисунок 9.4 демон­ст­ри­ру­ет­ прохо­ж­де­ние­ лучей­ через­ поверх­ность­ рисун­ка­ и их паде­ние­ на сферу­.

y

z

глаз наблюдателя плоскость рисунка

x

Рис. 9.4. Трасси­ров­ка­ лучей­

Функция­ tracer запи­сы­ва­ет­ изобра­же­ние­ в файл по задан­но­му­ пути­. Запись­ произ­во­дит­ся­ в обычном­ ASCII-форма­те,­ назы­­ваемом­ PGM. По умолча­нию­ размер­ изобра­же­ния­ – 100×100. Заго­ло­вок­ PGM-файла­ на­ чина­ет­ся­ с тега­ P2, за кото­рым­ следу­ют­ числа,­ соот­вет­ст­вую­щие­ шири­­ не (100) и высо­те­ (100) в пиксе­лах,­ причем­ макси­маль­но­ возмож­ное­ зна­ чение­ равно­ 255. Остав­шая­ся­ часть файла­ содер­жит­ 10 000 целых­ чисел­ от 0 (черный)­ до 255 (белый),­ кото­рые­ вместе­ дают­ 100 гори­зон­таль­ных­ полос­ по 100 пиксе­лов­ в каждой­.

Разре­ше­ние­ изобра­же­ния­ может­ быть изме­не­но­ с помо­щью­ res. Если­ res равно,­ напри­мер,­ 2, то изобра­же­ние­ будет­ содер­жать­ 200×200 пиксе­лов­.

По умолча­нию­ изобра­же­ние ­– это квадрат­ 100×100 на плоско­сти­ рисун­­ ка. Каждый­ пиксел­ представ­ля­ет­ собой­ коли­че­ст­во­ света,­ прохо­дя­ще­го­ через­ данную­ точку­ к глазу­ наблю­да­те­ля­. Для нахо­ж­де­ния­ этой вели­­ чины­ tracer вызы­­вает­ color-at. Эта функция­ ищет вектор­ от глаза­ на­ блюда­те­ля­ к задан­ной­ точке,­ затем­ вызы­­вает­ sendray, направ­ляя­ луч вдоль этого­ векто­ра­ через­ моде­ли­руе­мый­ мир. В резуль­та­те­ sendray по­ луча­ет­ неко­то­рое­ число­ от 0 до 1, кото­рое­ затем­ масшта­би­ру­ет­ся­ на от­ резок­ от 0 до 255.

Для опре­де­ле­ния­ интен­сив­но­сти­ света­ sendray ищет объект,­ от кото­ро­го­ он был отра­жен­. Для этого­ вызы­ва­ет­ся­ функция­ first-hit, кото­рая­ нахо­­ дит среди­ всех объек­тов­ в *world* тот, на кото­рый­ луч пада­ет­ первым,­ или же убеж­да­ет­ся­ в том, что луч не пада­ет­ ни на один объект­. В послед­­ нем случае­ возвра­ща­ет­ся­ цвет фона,­ кото­рым­ мы усло­ви­лись­ считать­ 0 (черный)­ . Если­ луч пада­ет­ на один из объек­тов,­ то нам необ­хо­ди­мо­ найти­ долю­ света,­ отра­жен­но­го­ от него­. Соглас­но­ зако­ну­ Ламбер­та,­ интен­сив­­ ность света,­ отра­жен­но­го­ точкой­ поверх­но­сти,­ пропор­цио­наль­на­ ска­ лярно­му­ произ­ве­де­нию­ единич­но­го­ векто­ра­ N, направ­лен­но­го­ из этой

9.8. Пример: трассировка лучей

165

точки­ вдоль норма­ли­ к поверх­но­сти­ (вектор,­ дли­на кото­ро­го­ равна­ 1, направ­лен­­ный перпен­ди­ку­ляр­но­ поверх­но­сти­ в задан­ной­ точке),­ и еди­ нично­го­ векто­ра­ L, направ­лен­­ного­ вдоль луча­ к источ­ни­ку­ света:­

i = N   L

Если­ источ­ник­ света­ нахо­дит­ся­ в этой точке,­ N и L будут­ иметь одина­­ ковое­ направ­ле­ние,­ и их скаляр­ное­ произ­ве­де­ние­ будет­ иметь макси­­ мальное­ значе­ние,­ равное­ 1. Если­ поверх­ность­ нахо­дит­ся­ под углом­ 90° к источ­ни­ку­ света,­ то N и L будут­ перпен­ди­ку­ляр­ны­ и их произ­ве­де­ние­ будет­ равно­ 0. Если­ источ­ник­ света­ нахо­дит­ся­ за поверх­но­стью,­ то про­ изве­де­ние­ будет­ отри­ца­тель­ным­.

Внашей­ програм­ме­ мы предпо­ла­га­ем,­ что источ­ник­ света­ нахо­дит­ся­ там же, где и глаз наблю­да­те­ля,­ поэто­му­ функция­ lambert, исполь­зую­­ щая выше­опи­сан­ное­ прави­ло­ для нахо­ж­де­ния­ осве­щен­но­сти­ неко­то­­ рой точки­ поверх­но­сти,­ возвра­ща­ет­ скаляр­ное­ произ­ве­де­ние­ норма­ли­ и трасси­руе­мо­го­ луча­.

Вфункции­ sendray это число­ умно­жа­ет­ся­ на цвет поверх­но­сти­ (темная­ поверх­ность­ отра­жа­ет­ меньше­ света),­ чтобы­ опре­де­лить­ интен­сив­ность­ в задан­ной­ точке­. Упро­стим­ зада­чу,­ огра­ни­чив­шись­ лишь объек­та­ми­ типа­ сферы­. Код, реали­зую­щий­ поддерж­­ку сфер, при­веден­ на рис. 9.5. Структу­ра­ sphere включа­ет­ surface, поэто­му­ sphere будет­ иметь пара­­ метр color, так же как и пара­мет­ры­ center и radius. Вызов­ defsphere до­ бавля­ет­ в наш мир новую­ сферу­.

Функция­ intersect (пере­се­че­ние)­ распо­зна­ет­ тип объек­та­ и вызы­­вает­ со­ ответ­ст­вую­щую­ ему функцию­. На данный­ момент­ мы умеем­ рабо­тать­ лишь со сфера­ми­. Для сферы­ из intersect будет­ вызы­­ваться­ sphere-in­ tersect­, но наш код может­ быть с легко­стью­ расши­рен­ для поддерж­­ки других­ объек­тов­.

Как найти­ пере­се­че­ние­ луча­ со сферой?­ Луч представ­ля­ет­ся­ точкой­ p = (x0, y0, z0) и единич­ным­ векто­ром­ v = (xr, yr, zr). Любая­ точка­ луча­ мо­ жет быть выра­же­на­ как p + nv для неко­то­ро­го­ n или, что то же самое,­ (x0+nxr, y0+nyr, z0+nzr). Когда­ луч пада­ет­ на сферу,­ расстоя­ние­ до центра­ (xc, yc, zc) равно­ радиу­су­ сферы­ r. Таким­ обра­зом,­ усло­вие­ пере­се­че­ния­ луча­ и сферы­ можно­ запи­сать­ следую­щим­ обра­зом:­

Из этого­ следу­ет,­ что

где

166

Глава 9. Числа

(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. Сферы­

Для нахо­ж­де­ния­ точки­ пере­се­че­ния­ нам необ­хо­ди­мо­ решить­ это квад­ ратное­ уравне­ние­. Оно может­ иметь ноль, один или два веще­ст­вен­­ных корня­. Отсут­ст­вие­ реше­ний­ озна­ча­ет,­ что луч прохо­дит­ ми­мо сферы;­ одно­ реше­ние­ озна­ча­ет,­ что луч пере­се­ка­ет­ сферу­ лишь в одной­ точке­ (то есть каса­ет­ся­ ее); два реше­ния­ сигна­ли­зи­ру­ют­ о том, что луч прохо­­ дит через­ сферу,­ пере­се­кая­ ее поверх­ность­ два раза. В послед­нем­ случае­ нам инте­ре­сен­ лишь наи­меньший­ из корней­. При удале­нии­ луча­ от гла­ за наблю­да­те­ля­ n увели­чи­ва­ет­ся,­ поэто­му­ наимень­шее­ реше­ние­ будет­

9.8. Пример: трассировка лучей

167

соот­вет­ст­во­вать­ меньше­му­ n. Для нахо­ж­де­ния­ корня­ исполь­зу­ет­ся­ min­ root­. Если­ корень­ суще­ст­ву­ет,­ sphere-intersect возвра­тит­ соот­вет­ст­вую­­ щую ему точку­ (x0 + nxr, y0 + nyr, z0 + nzr).

Две другие­ функции­ на рис. 9.5, normal и sphere-normal, связа­ны­ между­ собой­ анало­гич­но­ intersect и sphere-intersect. Поиск­ норма­ли­ для сфе­ ры крайне­ прост – это всего­ лишь вектор,­ направ­лен­­ный из точки­ на поверх­но­сти­ к ее центру­.

На рис. 9.6 пока­за­но,­ как будет­ выпол­нять­ся­ гене­ра­ция­ изобра­же­ния;­ ray-trace созда­ет­ 38 сфер (не все они будут­ види­мы­ми),­ а затем­ гене­ри­ру­­ ет изобра­же­ние,­ кото­рое­ запи­сы­ва­ет­ся­ в файл "spheres.pgm". Итог рабо­­ ты програм­мы­ с пара­мет­ром­ res = 10 представ­лен­ на рис. 9.7.

(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. Изобра­же­ние,­ полу­чен­ное­ мето­дом­ трасси­ров­ки­ лучей­

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]