Программирование под ZX-Spectrum: 3D графика

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
image
ZX-Spectrum был моим первым компьютером еще в те времена, когда я себя не очень хорошо помню. Однако в памяти остались бесконечно долгие экраны загрузки игр с магнитофона и невероятной радости, когда (и если) эта загрузка состоялась. Чуть позже помню первые написанные списанные программы на бейсике из книги «ZX-Spectrum 48 программ для изучающих Basic». Я почти ничего не понимал из того, что я набирал, и для детского мозга работа программиста представлялась как умение быстро находить в книжке нужную программу и перепечатывать ее без ошибок (привет StackOverflow!).

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

Задача и инструментарий


Первое, с чем следует определиться, — это что писать и на чем. На своей предыдущей работе я занимался созданием графического движка визуализации научных данных, и эта работа приносила мне огромное удовольствие. Однако, по независящим от меня причинам, я вынужден был поменять работу и ушел в мир кровавого энтерпрайза (о чем, кстати, тоже нисколько не жалею). Помня то, насколько мне нравилось работать с графикой, я решил в качестве своей первой программы попробовать написать некоторую сильно упрощенную версию программного 3D графического движка для ZX-Spectrum. Я понимаю, что спектрум далеко не самая быстрая платформа и что он мало приспособлен для 3D графики (хотя есть пример прекрасной игры Elite, которая выглядит как полноценная 3D игра), однако хотелось бы начать эксперименты с интересной для меня тематики.
image
Касаемо второго вопроса, при просмотре разных роликов на YouTube про спектрум (особенно понравилась рубрика «Дневники разработки» на канале 8-Bit Tea Party) у меня сложилось впечатление, что правильно будет писать на ассемблере, но это для меня практически неизведанная территория, и поскольку это хобби-проект, тратить слишком много времени на изучение еще и нового языка я не хотел.

К счастью, оказалось, что есть прекрасный sdk для спектрума и ему подобных машин — z88dk. Он содержит в себе компилятор языка C и реализацию стандартной библиотеки языка С для zx-spectrum. Его и решено было использовать для разработки.

Начало разработки


Первое, что нужно понять перед разработкой, — это как компилировать программу и как ее запустить на эмуляторе.

Небольшое лирическое отступление: к сожалению, на данный момент у меня нет реальной машины. Как оказалось, спектрум моего детства был безжалостно определен на свалку уже много лет назад. Все что я смог — это по воспоминаниям о внешнем виде понять, что это был российский клон «Символ»

image

На данный момент в продаже имеется множество современных клонов, (к примеру, ZX-Spectrum Next) использование которых не предполагает дружбу с паяльником, однако цены на эти компьютеры откровенно кусаются (хоть и выглядит тот же Next просто прекрасно). Возможно, в будущем я и куплю себе Next, но на данный момент было решено тестировать код в эмуляторе.

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

Во-первых, для компиляции используется команда zcc формата

zcc +[target] {options} {files}

В качестве таргета выбираем +zx. Как я понял, этот кросс-компилятор умеет собирать не только под ОС спектрума, но и, например, для ОС CP/M. Главное, чтобы процессор был семейства z80.

Далее в качестве опций необходимо перечислить список библиотек:
-lmzx – используем библиотеку математики для тригонометрических функций;
-lndos – библиотека для возможности отладочного вывода на экран.

Говорим, что хотим создать бинарное приложение, пригодное для запуска на эмуляторе:

-create-app -o zx3dEngine

И перечисляем список файлов. Итоговая команда получается следующая:

zcc +zx -lndos -lmzx -v -create-app -o zx3dEngine main.c engine.c point.c vector.c model3d.c linear_alg.c

В результате компиляции получаем файл zx3dEngine.tap, который прекрасно можно использовать в эмуляторе.

Разработка


Проблем с написанием и запуском Hello World не возникло никаких. Интересные моменты начались с попыток создать объект в динамической памяти.

Оказывается, что перед тем, как работать с кучей, ее нужно проинициализировать. Для этого нужно завести переменную long heap в статической памяти, вызвать mallinit() и, при помощи функции sbrk(), указать, с какого адреса и какой объем памяти выделяется под кучу данной программы.

#include <malloc.h>

long heap;

int main() {
	mallinit();
	sbrk(30000, 6000);

	int * a = (int *) malloc(20 * sizeof(int));

	free(a);
}

С рисованием сначала не возникло никаких проблем. Достаточно подключить <graphics.h>, и появляется возможность рисовать графические примитивы.

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

К счастью, копаясь в примерах к z88dk, я нашел нужные функции, но работают они только в режиме графики с низким разрешением 32х48 (изначально графический режим 256х192). Решил поддержать оба режима и добавил опцию препроцессора LOW_RESOLUTION_MODE.

#define bufferedgfx 1

#include <zxlowgfx.h>

#ifdef ALTLOWGFX  
    #define ddraw(x,y,x1,y1,c) cdraw(2*(x),y,2*(x1),y1,c);
#else
    #define ddraw(x,y,x1,y1,c) cdraw(x,2*(y),x1,2*(y1),c);
#endif

int main() {
    cclg(0);

    while (1) {			
        cclgbuffer(0);

        ddraw(10, 10, 20, 20, 6);

        ccopybuffer();	
    }
}

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

К примеру, классическая матрица поворота вокруг оси Y имеет вид:

image

Как видно, в матрице присутствуют функции косинуса и синуса. Результат выполнения этих функций — вещественные числа. Соответственно, при применении матрицы поворота к точке, придется еще и умножать на вещественное число, а потом округлять полученный результат.

Я не придумал ничего проще, чем умножить полученное значение тригонометрической функции на 1000, округлить и пометить, что при применении матрицы к точке для этого значения нужно не забыть целочисленно разделить полученный результат на 1000. Таким образом удалось уменьшить количество операций с вещественными числами. Обратной стороной этого является потеря точности и искажения, возникающие при применении нескольких матриц к объекту. Уверен, что есть способ оптимизировать гораздо лучше, но я пока не придумал, как это сделать.

Для реализации перспективной проекции я использовал формулу, подсмотренную в книге М. Мозгового «Занимательное программирование», которую очень любил, когда начинал программировать на Delphi.

X1 = HALF_SCREEN_WIDTH + SCREEN_DEPTH * x / z
Y1 = HALF_SCREEN_HEIGHT + SCREEN_DEPTH * y / z

В данной формуле SCREEN_DEPTH — некоторая константа, определяющая, насколько сильно объекты будут уменьшаться при увеличении координаты z.

Полные исходные коды программы можно посмотреть здесь

Итоги


В результате у меня получилось отобразить проволочные графические примитивы в перспективной проекции и повращать их вокруг осей X и Y.

image

Скорость работы при классической частоте в 3.5Mhz составляет около одного кадра в секунду. Эмулятор позволяет увеличивать частоту процессора, и при 14Mhz все работает довольно шустро.


В режиме низкого разрешения:

Подводя итоги, хотелось бы сказать, что я получил огромное удовольствие, программируя под ZX-Spectrum, и мне хотелось бы продолжить разработку под эту платформу. У меня есть несколько интересных идей, о которых возможно я напишу на хабр.

В заключение хочется поблагодарить создателей ZX-Spectrum-а, z88dk и эмуляторов спектрума, а также мою супругу за редактуру данной статьи. Всем спасибо за внимание!
Источник: https://habr.com/ru/post/565728/


Интересные статьи

Интересные статьи

Хочу поделиться опытом автоматизации экспорта заказов из Aliexpress в несколько CRM. Приведенные примеры написаны на PHP, но библиотеки для работы с Aliexpress есть и для...
Есть несколько способов добавить водяной знак в Битрикс. Рассмотрим два способа.
На этой неделе мы посмотрим, как можно работать чуточку быстрее, чем вчера. Разбираемся и внедряем в свои проекты пайплайны реактивного программирования, автоматически потрошим тексты и...
Привет, друзья! Меня зовут Петр, я представитель малого белорусского бизнеса со штатом чуть более 20 сотрудников. В данной статье хочу поделиться негативным опытом покупки 1С-Битрикс. ...
Однажды, в понедельник, мне пришла в голову мысль — "а покопаюсь ка я в новом ядре" (новым относительно, но об этом позже). Мысль не появилась на ровном месте, а предпосылками для нее стали: ...