Цветопередача в Fallout

В документе приводится описание алгоритмов цветопередачи (Палитра => RGB, RGB => Индекс в палитру, Анимированные цвета) используемых в Fallout.

Информация о них была получена в ходе разбора соответствующей функции в файле Mapper2.exe и Fallout2.exe.

Автор документа: Anchorite (anchorite2001@yandex.ru).

1. Преобразование 'Палитра => RGB'

Преобразование 'Палитра => RGB' представлено на следующей схеме.


                                   +--------------------------------+
                                   |  Яркость (currentGamma) (****) |
                                   +--------------------------------+
                                                   |               
+--------------+                                   V               
|  Palette     |                     +----------------------------+         +-------------+                 +-------------+
|   from       |                     |   Таблица перобразования   |         |   System    |                 | DirectDraw  |
| PAL-file (*) |                     |          цвета (**)        |         |   palette   |                 |   palette   |
+-----+--------+                     +----+-----------------------+         +-----+-------+                 +-----+-------+
|     | Red    |-------------------->|  0 |  pow(0, currentGamma) |         |     | Red   |                 |     | Red   |
|     +--------+                     +----+-----------------------+         |     +-------+                 |     +-------+
|  0  | Green  |                     |  1 |  pow(1, currentGamma) |-------->|  0  | Green |                 |  0  | Green |
|     +--------+                     +----+-----------------------+         |     +-------+                 |     +-------+
|     | Blue   |                     |            ...             |         |     | Blue  |                 |     | Blue  |
+-----+--------+                     +----+-----------------------+         +-----+-------+   Red   << 2    +-----+-------+
|     | Red    |               +---->| 63 |  pow(63, currentGamma)|         |     | Red   |-- Green << 2 -->|     | Red   |
|     +--------+               |     +----+-----------------------+         |     +-------+   Blue  << 2    |     +-------+
|  1  | Green  |               |                                            |  1  | Green |                 |  1  | Green |
|     +--------+               |                                            |     +-------+                 |     +-------+
|     | Blue   |               |                                            |     | Blue  |                 |     | Blue  |
+-----+--------+               |                                            +-----+-------+                 +-----+-------+
|     ...      |               |                                            |     ...     |                 |     ...     |
+-----+--------+                                                            +-----+-------+                 +-----+-------+
|     | Red    |           Red   >> 2                                       |     | Red   |                 |     | Red   |
|     +--------+           Green >> 2                                       |     +-------+                 |     +-------+
| 255 | Green  |           Blue  >> 2                                       | 255 | Green |                 | 255 | Green |
|     +--------+                                                            |     +-------+                 |     +-------+
|     | Blue   |               |                                            |     | Blue  |                 |     | Blue  |
+-----+--------+               |                                            +-----+-------+                 +-----+-------+
                               |
                               |
                               |
+---------------------------+  |
| Анимированные цвета (***) |--+
+---------------------------+



*    - Если при загрузке из PAL-файла значение одного из компонента цвета не находится в диапазоне 0..63, то значения всех
       компонентов этого цвета сбрасывается в 0.
**   - Если вычисленное значение функции получается меньшим 0, значение соответствующего элемента таблицы сбрасывается в 0.
       Если вычисленное значение функции получается большим 63, значение соответствующего элемента таблицы устанавливается
       в 63. (pow - функция возведения в степень)
***  - См. 3. Анимированные цвета
**** - В Fallout переменная currentGamma имеет тип double и изменяется в пределах от 1.000000 до 1.179993.

Следует заметить, что используемый алгоритм изменения яркости имеет дефект - нулевой и первый элемент таблицы преобразования цвета никогда не изменяются.

2. Преобразование 'RGB => Индекс в палитру'

Преобразование 'RGB => Индекс в палитре' представлено на следующей схеме.


       Red                Green                 Blue         
+---------|-----+    +---------|-----+    +---------|-----+
|7|6|5|4|3|2|1|0|    |7|6|5|4|3|2|1|0|    |7|6|5|4|3|2|1|0|
+---------|-----+    +---------|-----+    +---------|-----+
     |                    |                    |
     |                    |                    |
     +-----+              |              +-----+
           |              |              |
           V              V              V
 +-|--------------|--------------|--------------+
 |0|R7|R6|R5|R4|R3|G7|G6|G5|G4|G3|B7|B6|B5|B4|B3|
 +-|--------------|--------------|--------------+

Полученное в результате число используется как индекс в считанную из PAL-файла таблицу преобразования 'RGB => Индекс в палитре'. Значение таблицы по соответствующему индексу и является искомым индексом в палитру.

3. Анимированные цвета

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

Пользователь может контролировать 'Анимированные цвета' с помощью двух параметров в файлах fallout2.cfg и mapper2.cfg


[system]
color_cycling=1
cycle_speed_factor=1

С помощью параметра color_cycling разрешается (1) / запрещается (0) анимация цветов.
Параметр cycle_speed_factor регулирует скорость анимации - чем больше значение параметра тем реже изменяются цвета.

Всего существует шесть групп анимированных цветов с различными начальными параметрами (см. Таблица 3.1).


Таблица 3.1 - Начальные параметры групп 'Анимированных цветов'
Название Число элементов Индексы в палитре Значения компонент цвета Период изменения
Слизь 4 229..232 0 Red 0 200 мс
Green 108
Blue 0
1 Red 11
Green 115
Blue 7
2 Red 27
Green 123
Blue 15
3 Red 43
Green 131
Blue 27
Побережье 6 248..253 0 Red 83 200 мс
Green 63
Blue 43
1 Red 75
Green 59
Blue 43
2 Red 67
Green 55
Blue 39
3 Red 63
Green 51
Blue 39
4 Red 55
Green 47
Blue 35
5 Red 51
Green 43
Blue 35
Медленногорящее плямя 5 238..242 0 Red 255 200 мс
Green 0
Blue 0
1 Red 215
Green 0
Blue 0
2 Red 147
Green 43
Blue 11
3 Red 255
Green 119
Blue 0
4 Red 255
Green 59
Blue 0
Быстрогорящее плямя 5 243..247 0 Red 71 142 мс
Green 0
Blue 0
1 Red 123
Green 0
Blue 0
2 Red 179
Green 0
Blue 0
3 Red 123
Green 0
Blue 0
4 Red 71
Green 0
Blue 0
Мониторы 5 233..237 0 Red 107 100 мс
Green 107
Blue 111
1 Red 99
Green 103
Blue 127
2 Red 87
Green 107
Blue 143
3 Red 0
Green 147
Blue 163
4 Red 107
Green 187
Blue 255
Сигнализация 1 254 0 Red 252 33 мс
Green 0
Blue 0
Примечание:
Периоды изменения приведены для cycle_speed_factor = 1

Законы изменения цвета приведены ниже в виде функции.


// Палитра
BYTE g_Palette[768];

// Начальное значение цвета
BYTE g_nSlime[] = { 0, 108, 0, 11, 115, 7, 27, 123, 15, 43, 131, 27 };                             // Слизь
BYTE g_nMonitors[] = { 107, 107, 111, 99, 103, 127, 87, 107, 143, 0, 147, 163, 107, 187, 255 };    // Мониторы
BYTE g_nFireSlow[] = { 255, 0, 0, 215, 0, 0 , 147, 43, 11, 255, 119, 0, 255, 59, 0 };              // Медленногорящий огонь
BYTE g_nFireFast[] = { 71, 0, 0, 123, 0, 0, 179, 0, 0, 123, 0, 0, 71, 0, 0 };                      // Быстрогорящий огонь
BYTE g_nShoreline[] = { 83, 63, 43, 75, 59, 43, 67, 55, 39, 63, 51, 39, 55, 47, 35, 51, 43, 35 };  // Побережье
BYTE  g_nBlinkingRed = 252;                                                                        // Сигнализация

// Текущее значение параметра цикла
DWORD g_dwSlimeCurrent = 0;
DWORD g_dwMonitorsCurrent = 0;
DWORD g_dwFireSlowCurrent = 0;
DWORD g_dwFireFastCurrent = 0;
DWORD g_dwShorelineCurrent = 0;
BYTE  g_nBlinkingRedCurrent = 0;    

// Время предыдущего изменения цвета
DWORD g_dwLastCycleSlow = 0;
DWORD g_dwLastCycleMedium = 0;
DWORD g_dwLastCycleFast = 0;
DWORD g_dwLastCycleVeryFast = 0;

// Текущее значение коэффициета скорости анимации
DWORD g_dwCycleSpeedFactor = 1;     

void AnimatePalette()
{
    BOOL bPaletteChanged = FALSE;
    DWORD dwCurrentTime = GetTickCount();

    if (dwCurrentTime - g_dwLastCycleSlow >= 200 * g_dwCycleSpeedFactor) {
        // Slime
        DWORD dwSlimeCurrentWork = g_dwSlimeCurrent;

        for(int i = 3; i >= 0; i--) {
            g_Palette[687 + i * 3] = g_nSlime[dwSlimeCurrentWork * 3] >> 2;                       // Red  
            g_Palette[687 + i * 3 + 1] = g_nSlime[dwSlimeCurrentWork * 3 + 1] >> 2;               // Green
            g_Palette[687 + i * 3 + 2] = g_nSlime[dwSlimeCurrentWork * 3 + 2] >> 2;               // Blue

            if (dwSlimeCurrentWork == 3)
                dwSlimeCurrentWork = 0;
            else
            dwSlimeCurrentWork++;
        }

        if (g_dwSlimeCurrent == 3)
            g_dwSlimeCurrent = 0;
        else
            g_dwSlimeCurrent++;

        // Shoreline
        DWORD dwShorelineCurrentWork = g_dwShorelineCurrent;

        for(int i = 5; i >= 0; i--) {
            g_Palette[744 + i * 3] = g_nShoreline[dwShorelineCurrentWork * 3] >> 2;               // Red  
            g_Palette[744 + i * 3 + 1] = g_nShoreline[dwShorelineCurrentWork * 3 + 1] >> 2;       // Green
            g_Palette[744 + i * 3 + 2] = g_nShoreline[dwShorelineCurrentWork * 3 + 2] >> 2;       // Blue 

            if (dwShorelineCurrentWork == 5)
                dwShorelineCurrentWork = 0;
            else
                dwShorelineCurrentWork++;
        }

        if (g_dwShorelineCurrent == 5)
            g_dwShorelineCurrent = 0;
        else
            g_dwShorelineCurrent++;

        // Fire_slow
        DWORD dwFireSlowCurrentWork = g_dwFireSlowCurrent;

        for(int i = 4; i >= 0; i--) {
            g_Palette[714 + i * 3] = g_nFireSlow[dwFireSlowCurrentWork * 3] >> 2;                 // Red  
            g_Palette[714 + i * 3 + 1] = g_nFireSlow[dwFireSlowCurrentWork * 3 + 1] >> 2;         // Green
            g_Palette[714 + i * 3 + 2] = g_nFireSlow[dwFireSlowCurrentWork * 3 + 2] >> 2;         // Blue 

            if (dwFireSlowCurrentWork == 4)
                dwFireSlowCurrentWork = 0;
            else
                dwFireSlowCurrentWork++;
        }

        if (g_dwFireSlowCurrent == 4)
            g_dwFireSlowCurrent = 0;
        else
            g_dwFireSlowCurrent++;

        g_dwLastCycleSlow = dwCurrentTime;
        bPaletteChanged = TRUE;
    }

    dwCurrentTime = GetTickCount();

    if (dwCurrentTime - g_dwLastCycleMedium >= 142 * g_dwCycleSpeedFactor) {
        // Fire_fast
        DWORD dwFireFastCurrentWork = g_dwFireFastCurrent;

        for(int i = 4; i >= 0; i--) {
            g_Palette[729 + i * 3] = g_nFireFast[dwFireFastCurrentWork * 3] >> 2;                 // Red  
            g_Palette[729 + i * 3 + 1] = g_nFireFast[dwFireFastCurrentWork * 3 + 1] >> 2;         // Green
            g_Palette[729 + i * 3 + 2] = g_nFireFast[dwFireFastCurrentWork * 3 + 2] >> 2;         // Blue 

            if (dwFireFastCurrentWork == 4)
                dwFireFastCurrentWork = 0;
            else
                dwFireFastCurrentWork++;
        }

        if (g_dwFireFastCurrent == 4)
            g_dwFireFastCurrent = 0;
        else
            g_dwFireFastCurrent++;

        g_dwLastCycleMedium = dwCurrentTime;
        bPaletteChanged = TRUE;
    }

    dwCurrentTime = GetTickCount();

    if (dwCurrentTime - g_dwLastCycleFast >= 100 * g_dwCycleSpeedFactor) {
        // Monitors
        DWORD dwMonitorsCurrentWork = g_dwMonitorsCurrent;

        for(int i = 4; i >= 0; i--) {
            g_Palette[699 + i * 3] = g_nMonitors[dwMonitorsCurrentWork * 3] >> 2;                 // Red  
            g_Palette[699 + i * 3 + 1] = g_nMonitors[dwMonitorsCurrentWork * 3 + 1] >> 2;         // Green
            g_Palette[699 + i * 3 + 2] = g_nMonitors[dwMonitorsCurrentWork * 3 + 2] >> 2;         // Blue 

            if (dwMonitorsCurrentWork == 4)
                dwMonitorsCurrentWork = 0;
            else
                dwMonitorsCurrentWork++;
        }

        if (g_dwMonitorsCurrent == 4)
            g_dwMonitorsCurrent = 0;
        else
            g_dwMonitorsCurrent++;

        g_dwLastCycleFast = dwCurrentTime;
        bPaletteChanged = TRUE;
    }

    dwCurrentTime = GetTickCount();

    if (dwCurrentTime - g_dwLastCycleVeryFast >= 33 * g_dwCycleSpeedFactor) {
        // Blinking red
        if ((g_nBlinkingRedCurrent == 0) ||(g_nBlinkingRedCurrent == 60))
            g_nBlinkingRed = BYTE(-g_nBlinkingRed);


        g_Palette[762] = g_nBlinkingRed + g_nBlinkingRedCurrent;                                  // Red  
        g_Palette[763] = 0;                                                                       // Green
        g_Palette[764] = 0;                                                                       // Blue 

        g_nBlinkingRedCurrent = g_nBlinkingRed + g_nBlinkingRedCurrent;

        g_dwLastCycleVeryFast = dwCurrentTime;
        bPaletteChanged = TRUE;
    }

    if (bPaletteChanged)
        UpdatePalette();
}