Консультация № 178453
17.05.2010, 23:55
43.38 руб.
0 1 1
Уважаемые эксперты! Нужно написать программу - вращение пирамиды в 3-х мерном пространстве.
Пирамида состоит из 5-ти вершин(т.е. квадрат в основании).
ОС - DOS, использовать TASM.

Обсуждение

давно
Посетитель
7438
7205
18.05.2010, 09:58
общий
это ответ
Здравствуйте, rmka.
Легко Вот Вам вращающаяся пирамида.
Все необходимые формулы и комментарии найдете в тексте программы
Код:
;Используемые формулы для вращения
; Вокруг оси x ;
; YT = Y * COS(xang) - Z * SIN(xang) ;
; ZT = Y * SIN(xang) + Z * COS(xang) ;
; Y = YT ;
; Z = ZT ;
; ;
; Вокруг оси y ;
; XT = X * COS(yang) - Z * SIN(yang) ;
; ZT = X * SIN(yang) + Z * COS(yang) ;
; X = XT ;
; Z = ZT ;
; ;
; Вокруг оси x ;
; XT = X * COS(zang) - Y * SIN(zang) ;
; YT = X * SIN(zang) + Y * COS(zang) ;
; X = XT ;
; Y = YT ;

;Структура для хранения одной вершины пирамиды
POINT3D struc
xx dw ?
yy dw ?
zz dw ?
POINT3D ends

;структура для хранения координат вершины пирамиды на проскости
POINT2D struc
xx dw ?
yy dw ?
POINT2D ends

;макро вызова п/п рисования линии по номерам вершин пирамиды
PRLINE macro num1, num2, colour
mov bx, num2
shl bx, 2
push colour
push [POINT2D ptr bx+si].yy
push [POINT2D ptr bx+si].xx
mov bx, num1
shl bx, 2
push [POINT2D ptr bx+si].yy
push [POINT2D ptr bx+si].xx
call Line
add sp, 10
endm

.model tiny, C ; модель памяти и порядок вызова параметров
.386 ; нужно для команды fsincos
.code ; сегмент кода
.startup ; точка входа

mov ax, 0013h ; vga 320x200x256
int 10h

mov ax, @DATA
mov ds, ax ; инициируем сегмент данных
mov ax, 0a000h
mov es, ax ; es - сегмент видео

;установим глобальные переменные
mov [DeltaX], 1 ; скорость вращения (приращение угла)
mov [DeltaY], 1 ;
mov [DeltaZ], 1 ;

;переменные, используемые для расчета координат на эране
mov [Xoff], 256 ; пропорции пирамиды
mov [Yoff], 256
mov [Zoff], 300 ; расстояние от взгляда

MainLoop:
call MainProgram ; рисуем пирамиду один раз

mov ah, 1 ; проверяем на код клавиши
int 16h
jz MainLoop
mov ah, 0
int 16h
cmp ah, 1 ; по Esc выходим
jne MainLoop

;выход в DOS
mov ax, 0003h ; назад в текстовый режим
int 10h

mov ax, 4c00h ; bye-bye
int 21h

;подпрограммы

;расчет новых углов для вращения
;делаем простой инкремент углов на Delta_
;при переходе через 360 обнуляем
UpdateAngles PROC
mov ax, [XAngle]
add ax, [DeltaX]
cmp ax, 360 ; проверяем 0..359
jb set_XAngle
sub ax, 360
set_XAngle:
mov [XAngle], ax

mov ax, [YAngle]
add ax, [DeltaY]
cmp ax, 360 ; проверяем 0..359
jb set_YAngle
sub ax, 360
set_YAngle:
mov [YAngle], ax

mov ax, [ZAngle]
add ax, [DeltaZ]
cmp ax, 360 ; проверяем 0..359
jb set_ZAngle
sub ax, 360
set_ZAngle:
mov [ZAngle], ax
ret
UpdateAngles ENDP

;расчет новых 3D координат для выбранного угла
;вращаем вокруг какой-либо оси, меняются 2 оставшиеся координаты
;(смотри формулы в начале и дальше)
;параметрами являются адреса координат и угол
;новые координаты записываются на то же место
CalcRotation PROC uses si di, coord1:word, coord2:word, angle:word
;преобразуем градусы в радианы
fild angle ;угол
fldpi ;пи
fmulp ;угол*пи
fdiv c180 ;угол*пи/180
;найдем sin и cos
fsincos ;st=sin, st(1)=cos

;адреса координат
mov di, coord1
mov si, coord2

fild word ptr [di] ;первая координата (пусть, для определенности, x)
fmul st, st(1) ;x * cos(a)
fild word ptr [si] ;вторая координата (y)
fmul st, st(3) ;y * sin(a)
fsubp ;x * cos(a) - y * sin(a)

fild word ptr [di] ;x
fmul st, st(3) ;x * sin(a)
fild word ptr [si] ;y
fmul st, st(3) ;y * cos(a)
faddp ;x * sin(a) + y * cos(a)

fistp word ptr [si] ;y = x * sin(a) + y * cos(a)
fstp st(1) ;уберем из стека sin
fstp st(1) ; и cos
fistp word ptr [di] ;x = x * cos(a) - y * sin(a)
ret
CalcRotation endp

;вращаем точку вокруг выбранных осей
;si указывает на структуру POINT3D
RotatePoint PROC
;читаем 3D координаты во временные переменные
mov ax, [POINT3D ptr si].xx
mov [X], ax
mov ax, [POINT3D ptr si].yy
mov [Y], ax
mov ax, [POINT3D ptr si].zz
mov [Z], ax

;вращаем вокруг оси x
; YT = Y * COS(xang) - Z * SIN(xang)
; ZT = Y * SIN(xang) + Z * COS(xang)
; Y = YT
; Z = ZT
; call CalcRotation, offset [Y], offset [Z], [XAngle]

;вращаем вокруг оси y
; XT = X * COS(yang) - Z * SIN(yang)
; ZT = X * SIN(yang) + Z * COS(yang)
; X = XT
; Z = ZT
call CalcRotation, offset [X], offset [Z], [YAngle]

;вращаем вокруг оси z
; XT = X * COS(zang) - Y * SIN(zang)
; YT = X * SIN(zang) + Y * COS(zang)
; X = XT
; Y = YT
call CalcRotation, offset [X], offset [Y], [ZAngle]

ret
RotatePoint endp

;преобразуем 3D координаты вершин пирамиды в 2D координаты на экране
;используем переменные [X], [Y], [Z]
;результат пишем по адресу [di], в элемент POINT2D
Conv3Dto2D PROC
mov ax, [Xoff] ; Xoff*X / Z+Zoff = экранное X
mov bx, [X]
imul bx
mov bx, [Z]
add bx, [Zoff] ; расстояние от глаз
idiv bx
add ax, [Mx] ; центр экрана
mov [POINT2D ptr di].xx, ax ; сохраним экранное X

mov ax, [Yoff] ; Yoff*Y / Z+Zoff = экранное Y
mov bx, [Y]
imul bx
mov bx, [Z]
add bx, [Zoff] ; расстояние от глаз
idiv bx
add ax, [My] ; центр экрана
mov [POINT2D ptr di].yy, ax ; сохраним экранное Y
ret
Conv3Dto2D ENDP

;основная подпрограмма расчета и рисования
MainProgram PROC
call UpdateAngles ; расчитываем новые углы

lea di, Cube2D ; адрес 2D координат на экране
lea si, Cube ; адрес 3D координат вершин пирамиды
mov cx, MaxPoints ; число вершин
ConvLoop:
call RotatePoint ; вращаем вершины
call Conv3Dto2D ; 3D в 2D
add si, size POINT3D ; на следующую 3D вершину
add di, size POINT2D ; на следующую 2D вершину
loop ConvLoop

;рисуем
lea si, Cube2D ;адрес координат вершин на экране
;по всем ребрам
;задаем номера вершин и цвет
;(при желании можно раскрасить все ребра в разые цвета)
PRLINE 0, 1, 11
PRLINE 1, 2, 11
PRLINE 2, 3, 11
PRLINE 3, 0, 11
PRLINE 0, 4, 11
PRLINE 1, 4, 11
PRLINE 2, 4, 11
PRLINE 3, 4, 11

call DELAY ; пауза

;вытираем все ребра цветом 0(черным)!
PRLINE 0, 1, 0
PRLINE 1, 2, 0
PRLINE 2, 3, 0
PRLINE 3, 0, 0
PRLINE 0, 4, 0
PRLINE 1, 4, 0
PRLINE 2, 4, 0
PRLINE 3, 4, 0

ret
MainProgram ENDP

;рисуем линию (x1,y1)-(x2,y2) цветом color
Line proc uses di bx, x1:word, y1:word, x2:word, y2:word, color:byte
local i:word, \ ;для работы со сопроцессором
delta_x:word, \ ;длина проекции на ось абсцисс
delta_y:word, \ ;длина проекции на ось ординат
incx:word, \ ;приращение по X
incy:word ;приращение по Y

;определим длину проекции на ось абсцисс и шаг по оси X
mov ax, x2
sub ax, x1 ;ax=x2-x1;

;определим шаг по X (+1 если вперед, -1 если назад, 0 если не меняется)
mov incx, 0 ;пусть incx=0
test ax, ax ;ax=delta_x
jz set_delta_x ;не меняется
jg set_x_1 ;вперед?
dec incx ;назад, значит incx=-1
neg ax ;найдем ax=abs(delta_x)
jmp set_delta_x ;на сохранение
set_x_1:
inc incx ;вперед, значит incx=1;
set_delta_x:
mov delta_x, ax ;delta_x = abs(x2-x1)

;определим длину проекции на ось ординат и шаг по оси Y
mov ax, y2
sub ax, y1 ;ax=y2-y1;

;определим шаг по Y (+1 если вперед, -1 если назад, 0 если не меняется)
mov incy, 0 ;пусть incy=0
test ax, ax ;ax=delta_y
jz set_delta_y ;не меняется
jg set_y_1 ;вперед?
dec incy ;назад, значит incy=-1
neg ax ;найдем ax==abs(delta_y)
jmp set_delta_y ;на сохранение
set_y_1:
inc incy ;вперед, значит incy=1;
set_delta_y:
mov delta_y, ax ;delta_y=abs(y2-y1)

;определим большее из проекций как основное напрвление
cmp ax, delta_x ;ax=delta_y
jge from_y ;y будет основным
cmp delta_x, 0 ;проверим, чтобы не было delta_x=0 (для точки),
jz Line_ret ; иначе будет деление на 0
;delta_x>delta_y && delta_x!=0
;основное направление - по оси X
fild delta_y
fidiv delta_x ;st=k=(float)(delta_y/delta_x)

;for (int i=0;i<delta_x;i++)
xor cx, cx ;cx=i
jmp cmp_i_x ;на проверку i<delta_x
x_loop: ;тело цикла
mov i, cx ;запишем переменную цикла в память (для сопроцессора)
fld st ;st=st(1)=k
fimul i ;st=k*i
fimul incy ;st=incy*k*i
call floor ;округлим до целого в большую сторону
fistp i ;сохраним в переменной
mov ax, i ;относительный номер строки на экране
add ax, y1 ;добавим до ординаты начальной точки
mov dx, 320 ;получим индекс начала строки экрана в сегменте экрана
imul dx ; для этого умножим на длину в байтах одной стоки
mov bx, ax ;сохраним bx=y=(y1+floor(incy*k*i))*320
;посчитаем X
mov ax, incx ;X меняется ровно на шаг приращения,
imul cx ; умноженному на индекс точки
add ax, x1 ;добавим абциссу начальной точки ax=x=x1+incx*i

add ax, bx ;сложим с индексом начала строки
mov di, ax ;будем адресовать через di

mov al, color ;цвет точки
mov es:[di], al ;рисуем!

inc cx ;на следующую точку
cmp_i_x:
cmp cx, delta_x ;дошли до конца?
jl x_loop
jmp Line_ret ;на выход

from_y: ;вдоль оси Y
fild delta_x
fidiv delta_y ;st=k=(float)(delta_x/delta_y)

;for (int i=0;i<delta_y;i++)
xor cx, cx ;cx=i
jmp cmp_i_y ;на проверку i<delta_y
y_loop: ;тело цикла
mov ax, incy ;Y меняется ровно на шаг приращения,
imul cx ; умноженному на индекс точки
add ax, y1 ;добавим абциссу начальной точки ax=y=y1+incy*i
mov dx, 320 ;получим индекс начала строки экрана в сегменте экрана
imul dx ; для этого умножим на длину в байтах одной стоки
mov bx, ax ;сохраним bx=y=(y1+incy*i)*320
;посчитаем X
mov i, cx ;запишем переменную цикла в память (для сопроцессора)
fld st ;st=st(1)=k
fimul i ;st=k*i
fimul incx ;st=incx*k*i
call floor ;округлим до целого в большую сторону
fistp i ;сохраним в переменной
mov ax, i ;относительный номер строки на экране
add ax, x1 ;ax=x=x1+floor(incx*k*i)

add ax, bx ;сложим с индексом начала строки
mov di, ax ;будем адресовать через di

mov al, color ;цвет точки
mov es:[di], al ;рисуем!

inc cx ;на следующую точку
cmp_i_y:
cmp cx, delta_y ;дошли до конца?
jl y_loop
Line_ret:
fistp i ;удалим из сопроцессора k
ret
Line endp

;округление до целого в большую сторону
;округление по умолчанию, до ближайщего, не устраивает
floor proc
local CtrlWordOld:word, CtrlWordNew:word
fstcw CtrlWordOld ;сохраним управляющее слово
fclex ;сбросим исключения
mov CtrlWordNew,0763h ;установим необходимое значение управляющего слова
fldcw CtrlWordNew ;загружаем управляющее слово
frndint ;округляем st до целого
fclex ;сбросим исключения
fldcw CtrlWordOld ;восстановим старое управляющее слово
ret
floor endp

;пауза ~20мс. Под Windows весьма условно, скорее всего будет больше
time equ 2 ;число интервалов по 10мс
DELAY: pusha ;сохраним все регистры
mov ah,2dh ;сбросим "локальное" системное время (под nt+ все равно не поменяет)
xor cx,cx
xor dx,dx
int 21h

dl2: mov ah,2ch
int 21h ;читаем время
;считаем сотни мс
mov al,100
mul dh ;секунды умножаем на 100 -> ax = количество интервалов по 10мс
xor dh,dh ;dx - число сотых
xchg ax,dx ;поменяем местами
mov cl,10
div cl ;ax = количество интервалов по 10мс из сотых
add ax,dx ;складываем с количеством из секунд
cmp ax,time ;сравним с ожидаемым интервалом
jl dl2 ;ждем, если меньше
popa ;восстановим все регистры
ret

.data
c180 dd 180. ;180 градусов - для преобразоваия в радианы

;вершины нашей пирамиды, центр - точка (0, 0, 0)
Cube POINT3D <-60, -60, -60>
POINT3D <-60, 60, -60>
POINT3D < 60, 60, -60>
POINT3D < 60, -60, -60>
POINT3D < 0, 0, 60>

;здесь будут экранные координаты вершин пирамиды
Cube2D POINT2D 8 dup (<>)

X DW ? ;временные переменные для вычисления координат при вращении
Y DW ?
Z DW ?

XAngle DW 0 ;углы поворота относительно осей
YAngle DW 0
ZAngle DW 0

DeltaX DW ? ;инкремент по осям
DeltaY DW ?
DeltaZ DW ?

Xoff DW ? ;пропорции по X
Yoff DW ? ; и Y
Zoff DW ? ;расстояние от глаз

Mx DW 160 ;середина экрана
My DW 100

MaxPoints EQU 8 ;число вершин

END
Об авторе:
"Если вы заметили, что вы на стороне большинства, —
это верный признак того, что пора меняться." Марк Твен
Форма ответа