SGDK. Растягиваем/Сжимаем картинку.

Предисловие.

Sega Megadrive в отличии от SNES, не умеет увеличивать изображение на уровне железа. Однако, используя прерывания, можно растянуть картину, или наоборот сжать. Что-бы понять как это работает, необходимо понять, как раньше рисовалось изображение на телевизорах.

Как рисуется изображения на старых телевизорах.

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

Далее, данный луч проходит по всем пикселям, рисуя пиксели, линия за линией. И, как только, луч отрисовал экран, он возвращается на начальную позицию, и повторяет процесс. Таким образом и рисуется изображение.

Есть временные промежутки HBLANK и VBLANK:

  • HBLANK — время перемещения луча, от конца текущей линии, в начало следующей.
  • VBLANK — время за которое луч, вернется из конечной точки экрана в начальную.

Время VLANK больше чем HBLANK, из-за разницы в расстоянии.

Но, причем здесь SGDK?

Да, притом, что можно во время HBLANK, двигать плейн (слой), да, прямо во время рендера. Если скроллить плейн, во время рендера, можно растянуть его или наоборот, сжать.

Пишем код.

За основу SGDK проекта, я взял проект из SGDK. Создание изображения.

Откройте main.c и замените функцию main на следующее:

int main()
{
	VDP_drawImage(BG_A, &img, 0, 0);

    VDP_setScrollingMode(HSCROLL_PLANE, VSCROLL_PLANE);
    VDP_setPaletteColors(0, palette_black, 64);


    SYS_disableInts();
    {
        VDP_setHIntCounter(0);
        VDP_setHInterrupt(1);
        SYS_setHIntCallback(HIntHandler);
        SYS_setVIntCallback(VIntHandler);
    }
    SYS_enableInts();
    PAL_fadeIn(0, 15, img.palette->data, 32, FALSE);


    while (TRUE)
    {
        SYS_doVBlankProcess();
    }
}

Разберем новые строки:

SYS_disableInts();
{
  VDP_setHIntCounter(0);
  VDP_setHInterrupt(1);
  SYS_setHIntCallback(HIntHandler);
  SYS_setVIntCallback(VIntHandler);
}
SYS_enableInts();
  • SYS_disableInts отключаем прерывания, если этого не сделать, подпортятся вызовы VDP.
  • VDP_setHIntCounter — указываем, спустя сколько линий, вызывается прерывание, 0 значит, каждую линию, 4 значит, каждую 5-тую линию.
  • VDP_setHInterrupt — включаем горизонтальные (HBLANK) прерывания.
  • SYS_setHIntCallback — указываем функцию, которая будет вызываться, во время HBLANK прерывания.
  • SYS_setVIntCallback — определяем функцию, что вызывается, во время VBLANK прерывания.
  • SYS_enableInts — Прерывания настроены, можно включать.

Все остальное, я разбирал в SGDK. Переходы Fade-in и Fade-out, а также в SGDK. Сколл слоя, тайлов, линий.

Теперь, добавьте следующие строки где-нибудь в начале скрипта:

u16 cur_line = 0; //текущая линия
f16 v_offset = 0; //смещение плейна
f16 v_scroll_step = FIX16(3); //шаг увеличения смещения v_offset

void HIntHandler()
{
    VDP_setVerticalScroll(BG_A, cur_line+fix16ToInt(v_offset)); //двигаем плейн по вертикали относительно cur_line
    v_offset -= v_scroll_step; //меняем смещение
   
}

void VIntHandler()
{
    //кадр отрисовался, значит
    v_scroll_step = FIX16(3.0); //Нужно сбросить шаг. Понадобится позже
    v_offset = 0; //и убрать смещение плейна относительно текущей линии.
 }

Весь код я объяснил в комментариях. Теперь, запустите.

Как видим картинка сжалась, и перевернулась.

Картинка перевернулась, потому что плейн мы двигаем вверх.

VDP_setVerticalScroll(BG_A, cur_line+fix16ToInt(v_offset));

v_offset — уходит вы минус.

Таким образом, текущая линия идет вниз, а плейн идет вверх, вот и получается перевернутое изображение.

Усложним пример, добавив движение. Доработайте функции HIntHandler и VIntHandler.

void HIntHandler()
{
    VDP_setVerticalScroll(BG_A, cur_line+fix16ToInt(v_offset));
    v_scroll_step += FIX16(0.02); //Меняем шаг, тем самым, скорость движения плейна - увеличивается
    v_offset -= v_scroll_step;
}

void VIntHandler()
{
    cur_line += 1; //двигаем текущую линию, каждый кадр
    v_scroll_step = FIX16(3.0); //Сбрасывам шаг, с которым двигается плейн.
    v_offset = 0; 
 }

Опять же, овсе объяснение в комментариях.

Теперь, скомпилируйте и запустите.

Видно, что чем ниже изображение, тем оно меньше. То что написали, то и получили.

Заключение.

Горизонтальные прерывания позволяют, достичь красивых эффектов на Sega Megadrive, что не может, не радовать. Также, в папке C:\SGDK\sample\fx\h-int, есть 2 примера (по ним и учился) scaling и wobble. Изучив которые, сможете лучше понять данную тему.

Итоговый результат.