[Назад] [Далее]

5.5.4. Трансцендентные функции

Многие операции при работе с графикой используют умножение числа на синус (или косинус) некоторого угла, например при повороте: s = sin(n) * v. При использовании арифметики с фиксированной точкой 16:16 это уравнение преобразуется в s = int(sin(n) * 65 536) * v/65 536 (где int — целая часть). Для требовательных ко времени работы участков программ, например для работы с графикой, принято вообще не вычислять значения синусов, а считывать из таблицы, содержащей значения выражения int(sin(n) * 65 535), где n меняется от 0 до 90 градусов с требуемым шагом (редко требуется шаг меньше 0,1 градуса). Затем синус любого угла от 0 до 90 градусов можно вычислить с помощью всего одного умножения и сдвига на 16 бит. Синусы других углов и косинусы вычисляются в соответствии с обычными формулами приведения:

    sin(x) = sin(180-x)  для  90 < х < 180
    sin(x) = -sin(x-180) для 180 < х < 270
    sin(x) = -sin(360-x) для 270 < х < 360
    cos(x) = sin(90-x)

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

Таблицы синусов (или косинусов), используемые в программе, можно создать заранее с помощью простой программы на языке высокого уровня в виде текстового файла с псевдокомандами DW и включить в текст программы директивой include. Другой способ, занимающий меньше места в тексте, но чуть больше времени при запуске программы, — однократное вычисление всей таблицы. Таблицу можно вычислять как с помощью команды FPU fsin и потом преобразовывать к желаемому формату, так и сразу в формате с фиксированной запятой. Существует довольно популярный алгоритм, позволяющий вычислить таблицу косинусов (или синусов, с небольшой модификацией), используя рекуррентное выражение

    cos(xk) = 2cos(step)cos(xk-1) - cos(xk-2)

где step — шаг, с которым вычисляются косинусы, например 0,1 градуса.

; liss.asm
; строит фигуры Лиссажу, используя арифметику с фиксированной запятой
; и генерацию таблицы косинусов.
; Фигуры Лиссажу - семейство кривых, задаваемых параметрическими выражениями
; x(t) = cos(SCALE_V * t)
; y(t) = sin(SCALE_H * t)
;
; чтобы выбрать новую фигуру, измените параметры SCALE_H и SCALE_V,
; для построения незамкнутых фигур удалите строку add di,5l2 
; в процедуре move_point

        .model     tiny
        .code
        .386                        ; будут использоваться 32-битные регистры
        org        100h             ;  СОМ-программа

SCALE_H equ        3                ; число периодов в фигуре по горизонтали
SCALE_V equ        5                ; число периодов по вертикали

start   proc       near
        cld                         ; для команд строковой обработки
        mov        di,offset cos_table ; адрес начала таблицы косинусов
        mov        ebx,16777137     ; 224 * cos(360/2048) - заранее вычисленное
        mov        cx,2048          ; число элементов для таблицы
        call       build_table      ; построить таблицу косинусов

        mov        ax,0013h         ; графический режим
        int        10h              ; 320x200x256

        mov        ax,1012h         ; установить набор регистров палитры VGA,
        mov        bx,70h           ; начиная с регистра 70h
        mov        cx,4             ; четыре регистра
        mov        dx,offset palette ; адрес таблицы цветов
        int        10h

        push       0A000h           ; сегментный адрес видеопамяти
        pop        es               ; в ES

main_loop:
        call       display_picture  ; изобразить точку со следом
        mov        dx,5000
        xor        cx,cx
        mov        ah,86h
        int        15h              ; пауза на CX:DX микросекунд
        mov        ah,11h           ; проверить, была ли нажата клавиша,
        int        16h
        jz         main_loop        ; если нет - продолжить основной цикл

        mov        ах,0003h         ; текстовый режим
        int        10h              ; 80x24

        ret                         ; конец программы
start   endp

; процедура build_table
; строит таблицу косинусов в формате с фиксированной запятой 8:24
; по рекуррентной формуле cos(xk) = 2 * cos(span/steps) * cos(xk-1) - cos(xk-2),
; где span - размер области, на которой вычисляются косинусы (например, 360),
; a steps - число шагов, на которые разбивается область
; Ввод: DS:DI = адрес таблицы
;       DS:[DI] = 224
;       EBX = 224 * cos(span/steps)
;       СХ = число элементов таблицы, которые надо вычислить
; Вывод: таблица размером СХ * 4 байта заполнена
; Модифицируются: DI,CX,EAX,EDX
 
build_table        proc     near
        mov        dword ptr [di+4],ebx   ; заполнить второй элемент таблицы
        sub        ex,2                   ; два элемента уже заполнены
        add        di,8
        mov        eax,ebx
build_table_loop:
        imul       ebx              ; умножить cos(span/steps) на cos(xk-1)
        shrd       eax,edx,23       ; поправка из-за действий с фиксированной
                                    ; запятой 8:24 и умножение на 2
        sub        eax,dword ptr [di-8]  ; вычитание cos(xk-2)
        stosd                       ; запись результата в таблицу
        loop       build_table_loop
        ret
build_table        endp

; процедура display_picture
; изображает точку со следом

display_picture    proc     near
        call       move_point       ; переместить точку
        mov        bp,73h           ; темно-серый цвет в нашей палитре
        mov        bx,3             ; точка, выведенная три шага назад,
        call       draw_point       ; изобразить ее
        dec        bp               ; 72h - серый цвет в нашей палитре
        dec        bx               ; точка, выведенная два шага назад,
        call       draw_point       ; изобразить ее
        dec        bp               ; 71h - светло-серый цвет в нашей палитре
        dec        bx               ; точка, выведенная один шаг назад,
        call       draw_point       ; изобразить ее
        dec        bp               ; 70h - белый цвет в нашей палитре
        dec        bx               ; текущая точка
        call       draw_point       ; изобразить ее
        ret
display_picture    endp

; процедура draw_point
; Ввод: BP - цвет
;       BX - сколько шагов назад выводилась точка
;
draw_point         proc    near
        movzx      сx,byte ptr point_x[bx] ; Х-координата
        movzx      dx,byte ptr point_y[bx] ; Y-координата
        call       putpixel_13h            ; вывод точки на экран
        ret
draw_point         endp

; процедура move_point
; вычисляет координаты для следующей точки,
; изменяет координаты точек, выведенных раньше

move_point         proc     near
        inc        word ptr time
        and        word ptr time,2047     ; эти две команды организуют
                                          ; счетчик в переменной time, который
                                          ; изменяется от 0 до 2047 (7FFh)
        mov        еах,dword ptr point_x  ; считать координаты точек
        mov        ebx,dword ptr point_y  ; (по байту на точку)
        mov        dword ptr point_x[1],eax ; и записать их со сдвигом
        mov        dword ptr point_y[1],ebx ; 1 байт
        mov        di,word ptr time       ; угол (или время) в DI
        imul       di,di,SCALE_H          ; умножить его на SCALE_H
        and        di,2047                ; остаток от деления на 2048,
        shl        di,2                   ; так как в таблице 4 байта на косинус
        mov        ax,50                  ; масштаб по горизонтали
        mul        word ptr cos_table[di+2] ; Умножение на косинус.
                                    ; Берется старшее слово (смещение + 2) от
                                    ; косинуса, записанного в формате 8:24,
                                    ; фактически происходит умножение на косинус
                                    ; в формате 8:8
        mov        dx,0A000h        ; 320/2 (X центра экрана) в формате 8:8
        sub        dx,ax            ; расположить центр фигуры в центре экрана
        mov        byte ptr point_x,dh    ; и записать новую текущую точку
        mov        di,word ptr time       ; угол (или время) в DI
        imul       di,di,SCALE_V    ; умножить его на SCALE_V
        add        di,512           ; добавить 90 градусов, чтобы заменить
                                    ; косинус на синус. Так как у нас 2048
                                    ; шагов на 360 градусов,
                                    ; 90 градусов - это 512 шагов
        and        di,2047          ; остаток от деления на 2048,
        shl        di,2             ; так как в таблице 4 байта на косинус
        mov        ax,50            ; масштаб по вертикали
        mul        word ptr cos_table[di+2]   ; умножение на косинус
        mov        dx,06400h        ; 200/2 (Y центра экрана) в формате 8:8
        sub        dx,ax            ; расположить центр фигуры в центре экрана
        mov        byte ptr point_y,dh    ; и записать новую текущую точку
        ret
move_point         endp

; putpixel_13h
; процедура вывода точки на экран в режиме 13h
; DX = строка, СХ = столбец, ВР = цвет, ES = A000h
putpixel_13h       proc    near
        push       di
        mov        ax,dx        ; номер строки
        shl        ax,8         ; умножить на 256
        mov        di,dx
        shl        di,6         ; умножить на 64
        add        di,ax        ; и сложить - то же, что и умножение на 320
        add        di,cx        ; добавить номер столбца
        mov        ax,bp
        stosb                   ; записать в видеопамять
        pop        di
        ret
putpixel_13h       endp

point_x     db    0FFh,0FFh,0FFh,0FFh      ; Х-координаты точки и хвоста
point_y     db    0FFh,0FFh,0FFh,0FFh      ; Y-координаты точки и хвоста
            db    ?                        ; пустой байт - нужен для команд
                                           ; сдвига координат на один байт
time        dw    0          ; параметр в уравнениях Лиссажу - время или угол
palette     db    3Fh,3Fh,3Fh              ; белый
            db    30h,30h,30h              ; светло-серый
            db    20h,20h,20h              ; серый
            db    10h,10h,10h              ; темно-серый
cos_table   dd    1000000h                 ; здесь начинается таблица косинусов

        end        start

При генерации таблицы использовались 32-битные регистры, что приводит к увеличению на 1 байт и замедлению на 1 такт каждой команды, использующей их в 16-битном сегменте, но на практике большинство программ, интенсивно работающих с графикой, — 32-битные.


п»ї
"target=_blank><\/a>") //-->