EBLAN_ENGINE/docs/ENGINE_ARCHITECTURE.md

7 KiB
Raw Blame History

Архитектура и математический пайплайн EBLAN ENGINE 3D

1. Системы координат и единицы измерения

Мировое пространство (World Space)

  • Мир разделен на клетки сетки размером 64x64 единицы (CELL_SIZE equ 64).
  • Координаты игрока playerX и playerY хранятся в формате 16.16 signed fixed point:
    • Целая часть = координата / 64 (индекс клетки в сетке gameMap).
    • Дробная часть = смещение внутри клетки.
  • Карта gameMap имеет размер 32x32 клетки (всего 1024 байта). Индекс клетки вычисляется через сдвиг: index = (mapY << 5) + mapX.

Углы и тригонометрия (Angles & Trig)

  • Полная окружность (360°) разделена на 512 дискретных угловых шагов (ANGLE_512):
    • 0 = Восток (вправо, +X)
    • 128 = Юг (вниз, +Y)
    • 256 = Запад (влево, -X)
    • 384 = Север (вверх, -Y)
  • Угол всегда маскируется: and eax, 511 (исключает переполнение и ветвления).
  • Таблицы tableSin и tableCos предвычисляются на старте через математический сопроцессор FPU:
    rad = (angle * PI) / 256
    tableCos[angle] = round(cos(rad) * 65536)
    tableSin[angle] = round(sin(rad) * 65536)
    

2. Конвейер лучевого рендеринга (2.5D Raycasting)

Каждый кадр для экрана шириной 320 пикселей выпускается ровно 320 лучей (по 1 лучу на каждый вертикальный столбец экрана):

       Левый край экрана                                       Правый край экрана
          (screenX = 0)              Игрок                  (screenX = 319)
               \                       |                       /
                \                      |                      /
                 \           Угол обзора (FOV = 96)          /
                  \                    |                    /
                   \                   v                   /

Алгоритм на столбец:

  1. Вычисление угла луча:
    \text{rayAngle} = \left(\text{playerAngle} - \frac{\text{FOV}}{2} + \frac{\text{screenX} \cdot \text{FOV}}{320}\right) \ \& \ 511
  2. Трассировка по сетке (DDA - Digital Differential Analysis):
    • Вычисляются шаг сетки (stepX = \pm 1, stepY = \pm 1) и расстояния до следующих линий сетки (sideDistX, sideDistY).
    • В цикле луч шагает от клетки к клетке, пока не встретит твердую ячейку (IsSolidCell).
    • Флаг sideHit фиксирует, была ли пересечена вертикальная грань (X) или горизонтальная (Y).
  3. Устранение дисторсии «рыбий глаз»:
    • Расстояние по лучу умножается на косинус разницы между углом луча и углом камеры:
      \text{dist}_{\text{perp}} = \text{dist} \cdot \cos(\text{rayAngle} - \text{playerAngle})
  4. Проекция высоты стены:
    \text{lineHeight} = \frac{\text{CELL\_SIZE} \cdot \text{PROJ\_DIST}}{\text{dist}_{\text{perp}}}
    • Центр проекции — середина экрана (Y = 120).
    • Верхняя граница: \text{drawStart} = 120 - \frac{\text{lineHeight}}{2}.
    • Нижняя граница: \text{drawEnd} = 120 + \frac{\text{lineHeight}}{2}.
  5. Текстурирование и затенение:
    • Вычисляется горизонтальная координата текстуры U \in [0, 127].
    • Текстура интерполируется по вертикали с шагом \text{texStep} = \frac{128 \ll 16}{\text{lineHeight}}.
    • $Y$-грани затемняются на 50% (>> 1) для создания ощущения трехмерного объема.
    • Далекие стены плавно уходят в туман (половинная яркость от 350 единиц, почти черный от 700).

3. Билборд-спрайты (Sprites)

  1. Спрайты хранят позицию в сырых мировых единицах (1 клетка = 64 единицы, как playerX/playerY). Перед отрисовкой все спрайты сортируются по убыванию квадрата расстояния (алгоритм художника, insertion sort по spriteOrder).
  2. Позиция каждого спрайта переводится в систему координат камеры:
    X_{\text{cam}} = \Delta X \cdot \cos(-\text{ang}) - \Delta Y \cdot \sin(-\text{ang})
    Y_{\text{cam}} = \Delta X \cdot \sin(-\text{ang}) + \Delta Y \cdot \cos(-\text{ang})
  3. Если Y_{\text{cam}} > 8 (спрайт находится перед камерой):
    • Проекция на экран: \text{screenX} = \frac{X_{\text{cam}} \cdot \text{PROJ\_DIST}}{Y_{\text{cam}}} + 160.
    • Размер: \text{size} = \frac{64 \cdot \text{PROJ\_DIST}}{Y_{\text{cam}}} \cdot \text{scale} (scale — 16.16 fixed).
  4. Спрайт «стоит на полу»: нижняя граница = горизонт + size/2 (как низ стены на той же глубине), верхняя = низ − size.
  5. Отрисовка вертикальных текстурированных полос с проверкой буфера глубины depthBuffer[stripeX]: если спрайт ближе стены, пиксели выводятся в screenBuffer. Тексели 00FF00FFh (маджента) прозрачны. Применяется тот же дистанционный туман, что и у стен.

4. 2D Буфер кадра и вывод на экран

  • screenBuffer — плоский массив в оперативной памяти размером 320 \times 240 \times 4 = 307\,200 байт.
  • Формат пикселя — 32-битный BGRA (00RRGGBBh).
  • Передача кадра в окно Windows производится аппаратным вызовом GDI:
    invoke StretchDIBits, hEngineDC,
           0, 0, 1280, 960,             ; Область окна назначения
           0, 0, 320, 240,               ; Область исходного буфера
           OFFSET screenBuffer, OFFSET bmiEngine,
           DIB_RGB_COLORS, SRCCOPY
    
  • Обеспечивает честное ретро-разрешение и пиксельный вид с нулевой нагрузкой на видеокарту.