Spectrum Expert #01
31 октября 1997

Программирование - 3D на спектруме: вращение проволочного обьекта (без отсечения вышедших за экран линий).

<b>Программирование</b> - 3D на спектруме: вращение проволочного обьекта (без отсечения вышедших за экран линий).
СПЕКТРУМ + 3D #1              
                              
(c)1997 Dark/X-trade and -STS-/VolgaSoft
─────────────────────────────────────────
                                                            
 Данным циклом статей  авторы  поставили
перед собой задачу показать способы  ре-
ализации на спеке "быстрой" 3D графики. 
 В принципе, эти статьи ориентированы на
начинающего кодера,  так  как  кодер  со
стажем и так всё это,  вероятно,  знает.
Хотя, возможно и он сможет найти для се-
бя что-нибудь интересное (по крайней ме-
ре в алгоритмах). Наша цель - не столько
дать готовые рецепты и шаблоны,  сколько
подтолкнуть читателя к дальнейшему само-
стоятельному изучению предмета.         
 Ничего особо сложного мы  здесь  объяс-
нять не будем, но тем не менее,  понаде-
емся, что Ваше Серое Вещество не  выйдет
из берегов,  прежде  чем  Вы  достигнете
окончания текста.                       
 A начнем мы с решения несложной задачи:
вращение проволочного обьекта (без отсе-
чения вышедших за экран линий). Для пер-
вой статьи вполне достаточно.           
                                        
 Все что нам нужно, чтобы объект  закру-
тился, это его задать и  накодить  прос-
тенький вычислитель, да быструю процеду-
ру линии.                               
                                        
─────────────────────────────────────────
                                                            
 Те строчки кода, которые Вы  увидите  в
тексте, писаны для  того,  чтобы  просто
пояснить суть вещей, и НЕ БЫЛИ  подверг-
нуты какой-либо капитальной оптимизации.
Рабочий код Вы можете найти на  диске  в
файле 3DROTATE. Там есть еще  2  файла -
ALGORITM и DIVTABS, подробности о  кото-
рых смотрите в конце статьи,  в  разделе
"Алгоритмы".  Все  исходники  написаны с
использованием специфического синтаксиса
"STORM TURBO ASSEMBLER'а"  (который,  по
возможности, Вы можете найти тут же  или
в ZX-Format'е #7). Кое-что о  синтаксисе
Storm'а Вы также сможете  найти  разделе
"Алгоритмы". Файлы даны в текстовом  ви-
де, чтобы посмотреть их  можно  было  бы
вне ассемблера.  Примеры  в  тексте,  по
возможности, даны в классическом синтак-
сисе. Для загрузки их  в  STORM  следует
воспользоваться функцией импорта тексто-
вого файла - BREAK+Т.                   
                                        
─────────────────────────────────────────
                                                            
 Итак, зададим обьект.  Пусть  это будет
простая пирамида.                       
 Мы будем описывать его как набор  ребер
(edge) каждое из которых протянуто  меж-
ду двух вершин (vertex).  Вершины заданы
в т.н. обьектном пространстве,  т.е. от-
носительно центра вращения обьекта.     
                                        
Массив выглядит примерно так:           
                                        
PIRAMID DW edge                         
vertex  ;DB хп, yn, zn                  
                                        
        DB 0,40,-20     ;основание      
        DB 60,-20,-20   ;//             
        DB -60,-20,-20  ;/              
        DB 0,10,40      ;пик            
        DB #80 ;конец                   
                                        
edge    ;DB из_точки_М, в_точку_N       
                                        
        DB  0,1 ;соединение основания   
        DB  1,2 ;//                     
        DB  2,0 ;/                      
        DB  0,3 ;соед. пика с основанием
        DB  1,3 ;//                     
        DB  2,3 ;/                      
        DB  -1 ;конец                   
                                        
                                        
Объект вроде задали. Теперь займемся вы-
числителем.                             
                                        
 Для вращения обьекта достаточно провер-
нуть его  вершины.  Вращение  происходит
относительно нуля в том же самом объект-
ном пространстве по следующим формулам: 
                                        
 воркуг оси Z                           
  X' =х*cos AZ + y*sin AZ               
  Y' =y*cos AZ - х*sin AZ               
                                        
 воркуг оси Y                           
  Z' =z*cos AY + х'*sin AY              
  X''=х'*cos AY - z*sin AY              
                                        
 воркуг оси X                           
  Y''=y'*cos AX + z'*sin AX             
  Z''=z'*cos AX - y'*sin AX             
                                        
Естественно, мы будем  вычислять  данные
формулы в реалтайме и без помощи  всяких
там гигантских  таблиц  типа  A=В*SIN C,
для всех возможных  значений  В и C, так
как, естественно, это годится  лишь  для
интры или небольшой демы.               
                                        
Пишем сначала процедуру вида:           
                                        
Rotate (а,b,angle)                      
 {                                      
   ат=а*cos+b*sin                       
   b=b*cos-а*sin                        
   а=ат                                 
 }                                      
                                        
Допустим, входные и выходные параметры у
неё будут 8-битные:                     
                                        
IN :D,Е=Y,X (SGN) C=ANGLE               
OUT:D,Е=Y,X (SGN)                       
;эта процедура представлена  просто  для
 иллюстрации.                           
;так  как  в  3DROTATE  применена  более
 быстрая.                               
                                        
ROTATE  LD Н,COSTB[                     
        LD L,C                          
        LD C,(HL)                       
        LD A,L:SUB #40:LD L,A           
        LD В,(HL)                       
;В=sin C=cos                            
        LD L,C:LD Н,Е                   
        CALL MULS ;Н=Н*L (со знаками)   
        LD A,Н:ЕХА                      
;A'=X*COS                               
        LD L,В:LD Н,D                   
        CALL MULS                       
        ЕХА:ADD A,Н:LD LX,A             
;LX=X*COS+Y*SIN                         
        LD L,C:LD Н,D                   
        CALL MULS                       
        LD A,Н:ЕХА                      
;A'=Y*COS                               
        LD L,В:LD Н,Е                   
        CALL MULS                       
        ЕХА:ADD A,Н:LD D,A              
;D=Y*COS-X*SIN                          
        LD Е,LX                         
;Е=X*COS+Y*SIN                          
        RET                             
                                        
                                        
Пишем финальную процедуру:              
                                        
Rot3D (х,y,z,rotX,rotY,rotZ)            
 {                                      
  Rotate (х,y,rotZ)                     
  Rotate (z,х,rotY)                     
  Rotate (y,z,rotX)                     
 }                                      
                                        
IN :D,Е,C=Y,X,Z                         
OUT:D,Е,C=Y,X,Z                         
                                        
ROT3D   PUSH ВС                         
        LD ВС,(ROTZ)                    
;вращаем х,y на угол rotZ               
        CALL ROTATE;XY                  
        РОР ВС                          
;заменяем t=y; y=х; х=z                 
        LD В,D:LD D,Е:LD Е,C            
;вращаем z,х на угол rotY               
        PUSH ВС                         
        LD ВС,(ROTY)                    
        CALL ROTATE                     
        РОР ВС                          
;заменяем z=y; y=z; х=t                 
        LD C,D:LD D,Е:LD Е,В            
;вращаем y,z на угол rotX               
        PUSH ВС                         
        LD ВС,(ROTX)                    
        CALL ROTATE                     
        РОР ВС                          
;заменяем t=y; Y=х; X=z; Z=t            
        LD A,D:LD D,Е:LD Е,C:LD C,A     
        RET                             
                                        
ROTX    DB 0                            
ROTY    DB 0                            
ROTZ    DB 0                            
                                        
 Итак, у нас есть повернутая вершина.   
 Для достижения  нашей  цели  достаточно
перевести  её  в  экранное  пространство
(спроецировать на экран). Будем считать,
что ось Z у нас направлена от наблюдате-
ля вглубь экрана.                       
                                        
Наиболее  часто   используемых  проекций
две:                                    
                                        
- Параллельная                          
                                        
   Величина Z просто не учитывается.    
  Xr=X''+Xoffset                        
  Yr=Y''+Yoffset                        
                                        
- Перспективная                         
                                        
  Обычно используется формула           
                                        
  Xr=((X''+Xcnt)*Scale/(Z+Zcnt))+Xoffset
  Yr=((Y''+Ycnt)*Scale/(Z+Zcnt))+Yoffset
                                        
 ! Z+Zcenter у видимой  точки  не  может
 быть <=0                               
                                        
Scale - коэффициент масштабирования.    
 Он влияет на угол обзора и  подбирается
по вкусу.                               
                                        
X/Yoffset - центр экрана (#80,#60)      
X/Y/Z cnt (center) - центр обьекта в 3D.
X/Yr (real)- координаты точки на экране.
                                        
 Полученные значения Xr и Yr складывают-
ся в буфер спроецированных вершин.      
                                        
DOTS2D  ds количество вершин*2          
                                        
Воспользуемся перспективной проекцией.  
Коэффициент масштабирования  (scale) яв-
ляется константой, поэтому разумно изба-
вится от умножения на него, заменив  ум-
ножение сдвигом  или  перекодировкой  по
таблице.  Мы примем  коэффициент  равным
256.                                    
                                        
 Все эти действия (начиная с вращения  и
до сих пор) выполняются для всех вершин,
коих у нас 4.                           
                                        
Всё это делает процедура:               
                                        
PROJECT ;PROJECT ALL VERTEXES           
        LD HL,(OBJECT)                  
        INC HL:INC HL                   
;HL=вершины                             
        LD IX,DOTS2D                    
PROJ    LD A,(HL):INC HL                
        СР #80                          
;признак конца                          
        RET Z                           
        LD Е,A                          
        LD D,(HL):INC HL                
        LD C,(HL):INC HL                
        PUSH HL                         
        CALL ROT3D                      
        LD A,CENTERZ:ADD A,C:LD L,A     
        LD A,CENTERY:ADD A,D:LD Н,A     
        PUSH HL                         
        LD A,CENTERX:ADD A,Е:LD Н,A     
        CALL FDIVB; Н.L(SGN)=Н(SGN)/L   
        LD A,OFFSETX:ADD A,L            
;L=Н.L*256 (Н=0)                        
        LD (IX),A:INC LX                
;отписали Xr                            
        РОР HL                          
        CALL FDIVB                      
;Y координата экрана идет сверху вниз   
;а в преобразованиях снизу вверх        
        LD A,CENTERY:SUB L              
;а не выходит ли Yr за экран?           
;отсечем заранее, чтоб потом не         
;заботиться                             
        СР #C0:JR C,$+4:LD A,#BF        
        LD (IX),A:INC LX                
;отписали Yr                            
        РОР HL                          
        JR PROJ                         
                                        
Массив DOTS2D должен быть расположен    
по красивому адресу (младший байт=0),   
                                        
Осталась самая малость - лишь  соединить
точки линиями, однако эта малость  отни-
мает львиную долю времени. Для этого для
каждого ребра берем из массива edge (см.
выше) номера двух вершин - N1 и N2      
                                        
X1=DOTS2D[N1*2]                         
Y1=DOTS2D[N1*2+1]                       
                                        
X2=DOTS2D[N2*2]                         
Y2=DOTS2D[N2*2+1]                       
                                        
и рисуем линию между точками  (х1,y1)  и
(х2,y2).                                
Для этого существует  процедура  RENDER,
которая выводит объект на экран (предва-
рительно очищенный). В принципе эта про-
цедура должна заботится об отсечении ли-
ний, уходящих за экран, однако мы  этого
делать не будем. Поэтому заранее ограни-
чили диапазоны чисел, тех, что в буфере.
                                        
;Вывод обьекта на экран                 
RENDER  ;RENDER OBJECT (EDGES)          
        LD HL,(OBJECT)                  
        LD Е,(HL):INC HL                
        LD D,(HL)                       
;DE=ребра                               
                                        
RENDR   LD Н,DOTS2D[                    
        LD A,(DE):INC DE                
;из вершины ...                         
        ADD A,A:LD L,A                  
;признак конца                          
        RET C                           
        LD C,(HL):INC L                 
        LD В,(HL)                       
;получили х1,y1                         
        LD A,(DE):INC DE                
;в вершину ...                          
        ADD A,A:LD L,A                  
        PUSH DE                         
        LD Е,(HL):INC L                 
        LD D,(HL)                       
;получили х2,y2                         
        CALL LINE                       
        РОР DE                          
        JR RENDR                        
                                        
Прорисовка линии занимает  львиную  долю
времени.                                
 В Алгоритмах Вы найдете  самую  быструю
из всех  существующих  на_данный  момент
на Спектруме реализацию  линии  (причем,
с возможностью  дальнейшего   увеличения
быстродействия).                        
                                        
----------------------------------------
                                        
Итак, главный цикл должен быть  примерно
такой:                                  
                                        
        LD HL,PIRAMID                   
        LD (OBJECT),HL                  
MAIN    CALL EXGVIEW;Обмен экранов      
        CALL PROJECT;Проекция           
        CALL RENDER ;Вывод в теневой    
                    ;экран              
        LD HL,ROTX  ;Изменение углов    
.1      INC (HL)                        
        INC HL                          
.3      DEC (HL)                        
        INC HL                          
.2      INC (HL)                        
        LD A,#7F:IN A,(#FE)             
        RRA:JR C,MAIN                   
        RET                             
                                        
ROTX    DB 0                            
ROTY    DB 0                            
ROTZ    DB 0                            
                                        
OBJECT  DW 0                            
                                        
Как видите, все это очень просто,  проще
и быть не может.                        
                                        
 Вообще-то, для объектов с большим коли-
чеством вершин и, особенно, со  сложными
преобразованиями (поворотами вокруг про-
извольной оси, масштабированиями)  очень
выгодно строить матрицу  преобразований,
тогда для все что  нам  надо  сделать  с
вершиной - это выполнить 9  умножений  и
записать  X и Y в буфер...  Как  нибудь,
возможно, мы поработаем и с матрицами.  
                                        
 Забегая вперед, скажем,  что в  следую-
щей статье будет описан  метод,  который
позволяет с легкостью обрабатывать  объ-
екты, состоящие из сотни вершин,  но для
такого простого обьекта он не эффективен.



Другие статьи номера:

Оболочка - описание оболочки журнала.

Вступление - нам удалось сделать журнал абсолютно "с нуля" всего за полтора месяца, начиная с раздумий и заканчивая продажей.

От редакции - мы взваливаем на себя нелегкую ношу производства качественного и интересного спектрумовского журнала.

Enlight 1997 - А верной ли дорогой идем товарищи? Крах Enlight'97.

Интервью - Kano VS RST7: Быль о том, как не смогли понять друг друга элитные представители спектрумовской сцены.

Игры - описание игр: Bogaboo, Towdie, Delta, Score 3020, Konami Tennis, Hyper Sports, Para Academy, Run for gold, Centurions, Atrog, Little computer people.

Программирование - 3D на спектруме: вращение проволочного обьекта (без отсечения вышедших за экран линий).

Программирование - реализация на ассемблере Z80: умножение, композиционное деление, вычисление COS/SIN, рисование линии Брезенхема/Хорна.

Софт - описание нового ассемблера - Storm.

Софт - описание новшеств и доработок музыкального редактора Pro Tracker 3.31

Софт - описание музыкального редактора под "GS" RIFF ТRACKЕR'у v 2.9

Программирование - адаптация игровых программ под музыкальную карту General Sound.

Железо - GMX: видео карточка для ZS Scropion.

Железо - ZX BUS - Шина на спектруме: Кажущаяся простота синклеровского железа чрезвычайно обманчива.

Юмор - доктор Фомин спешит на помощь!

Юмор - юмористически обзор компьютерных игр.

Реклама - Наша фирма предлагает программное обеспечение для ZX-SРЕCТRUМ.


Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Ликбез - полный диассемблер ПЗУ (часть 25).
Разное - интервью с автором нашумевшей демки под VIC-20 - Viznut/PWP.
BBS - список станций BBS ZXNet.
ZXNet - Правила конференции ZX.SPECTRUM.
Юмор - Как спиваются иностранцы.

В этот день...   20 апреля