Создание демки специально для HABR — Часть 1

в 9:00, , рубрики: ruvds_статьи, ассемблер, Блог компании RUVDS.com, Демосцена, Микроша, ненормальное программирование, ПЭВМ, старое железо

Создание демки специально для HABR — Часть 1 - 1


Многие из нас любят интеллектуальные игры, всякие головоломки, квесты, стратегии и многое другое. Но что, если игрой является само железо, а сценарий создаёте вы сами? В результате этого рождается невероятно интересная головоломка, которая невероятно меня увлекла на несколько месяцев.

Здесь я хочу поделиться «прохождением» этой «игры», под названием Демка для ПЭВМ «Микроша». В процессе чтения статьи может показаться, что всё просто и очевидно. Это всё так, когда есть документация и описание всех подводных камней. Когда каждый подводный камень ищешь сам, то это всё превращается в невероятно сложный квест.

Часто люди покупают старые ПЭВМ, играют в игры, им это быстро надоедает, и они их кладут на полку. Это не мой путь, мне интересно понять железо, плюс мне всегда хотелось поупражняться в создании демки, выжав по максимуму из слабого железа. Этакий интеллектуальный вызов самому себе. И вот вызов брошен, и задача полностью овладела мной и всем моим свободным временем. Работа над демкой велась в любую свободную минуту, код писался в метро, в поездках, в обед. Если не шла работа над кодом, то шло чтение документации или попытки поиска каких-либо примеров. Иной раз даже в будни, я засиживался до пяти утра. Часто бывало, когда ложился спать, не мог уснуть и шёл писать куски программ. Если я не работал, не занимался семейными делами, то я занимался этим проектом, в общем, выпал из жизни по полной программе. Это было невероятно круто и интересно, настолько, что на сто процентов заняло всё моё свободное время и мысли.

Правила игры

Для того Чтобы вписать всё в некоторые рамки, для себя определил правила игры. Прежде чем я о них расскажу, для начала надо дать представление об особенностях ПЭВМ «Микроша».

В «Микроше» установлен микропроцессор КР580ВМ80А, работающий на тактовой частоте 1,77 МГц, быстродействие которого составляет всего 300 тыс. оп/с (грубо 300 кГц!). Это достаточно мало даже для тех лет. Например, первый микроконтроллер AVR AT90S1200 просто мейнфрейм в сравнении с этим процессором (хотя рассматривать их в одной линейке не совсем корректно). У него хотя бы каждая операция выполнялась за один такт, была команда умножения и таймер генерировал прерывания. Однако, это возлагает на меня дополнительную ответственность за то, чтобы код должен быть оптимальным и быстрым, чтобы успевать работать с этой скоростью.

Оперативной памяти всего 32 килобайта, в которую нужно втиснуть и код и данные. Как оказалось впоследствии, 32 кБ — это очень и очень мало.

Формат изображения: при отображении алфавитно-цифровой информации — 25 строк по 64 символа в каждой, при выводе псевдографической информации — 128 точек по горизонтали и 50 точек по вертикали. Плюс-минус строка снизу и сверху, необходимо всё уместить в этом объёме. При этом градаций цвета не предусмотрено. Изображение выводится на ЭЛТ монитор, и не всегда возможно вывести всё что ты хочешь, например, сплошная заливка белым — не работает.

Звук представляет собой программируемый один канал таймера КР580ВИ53. Таким образом можно играть мелодию по одной ноте, как новичок, играя одним пальцем. Никаких аккордов или одновременных нот.

Резюмируя, демка должна быть загружена с аудиокассеты и воспроизводиться именно на отечественном ЭЛТ-мониторе. Никаких аппаратных доработок в ПЭВМ вводить нельзя (ни расширять память, ни добавлять аудиоканалов, ни добавлять цветов). Хотя всё это, без сомнения, хотелось попробовать сделать. Но таковы были внесённые ограничения, которые определяли правила разработки.

Из бонусов, которые я себе позволил: разработка может вестись на обычном ПК, используя любые языки программирования, любые эмуляторы и технические решения, но так чтобы конечная программа работала уже на «Микроше».

Основной источник знаний

Что удивительно, основным источником сакральных знаний по программированию «Микроши» стала его документация. Этим может похвастаться не каждый отечественный ПЭВМ. Документация очень хорошо написана, приведено множество примеров, каждый из которых можно применить на практике.

Создание демки специально для HABR — Часть 1 - 2

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

С чего же начать?

Опыта разработки демок у меня не было от слова совсем как и более-менее вменяемого опыта разработки для ЭВМ на базе процессора i8080 (игры с "Волшебным чемоданом" не в счёт). Поэтому начал искать информацию, кто же делал аналогичные проекты, хотя бы для «Радио-86РК», и в результате наткнулся на статью "Псевдо 3D-демо для Радио-86РК". Публикация оказалась весьма полезной и определила дальнейший вектор моей разработки.

Изначально планировал написать письмо её создателю с вопросами о том, как же всё работает, но по ходу быстро разобрался сам и даже набросал на питоне свой конвертер картинок из pgm-файлов для получения растрового изображения. В результате у меня получилась вот такая демка:

На видео видно, что используется либо пустой, либо закрашенный символ, при этом псевдографика не используется вовсе. Сперва, я даже начал дорабатывать эту демку, чтобы добавить полную поддержку псевдографики, так чтобы изображение было не 64х25, а 128х50 точек. Но, в какой-то момент понял, что эту демку уже кто-то писал, уже кто-то делал. При этом я был уже далеко не первым, кто мучает данное решение, и мне стало скучно. Решил, что пускай не мой код не будет шедевром демостроя, однако реализую всё так, как умею, с теми эффектами, которые понял и осознал сам, плюс заодно чему-то смогу научиться. И, оглядываясь назад, это было единственно правильным решением, хотя путь оказался очень тернист.

Принял решение сделать по такому сценарию:

  1. Начальная сцена, приветствия хабра. Начинает играть музыка.
  2. Смена кадра, некоторая анимация, которая переводит к следующей сцене.
  3. Какая-то простая псевдо-3D сценка, которая отображает логотип компании.

По графике: решил использовать псевдографику 128х50 точек (на деле оказалось больше, подробности ниже), которая рисуется из символов. Для отображения 3D сцены решил использовать методику разницы фреймов, идею которой позаимствовал в первой демке у begoon. Хотя, конечно, круто было бы всё обсчитывать в рельном времени. Однако, посмотрев как «быстро» работает расчёт синусов в реализации в исходной демке (ввести g и нажать Enter), отказался от этой затеи — это всё очень медленно. Поэтому пускай не очень элегантно, но решение оказалось вполне рабочим.

Предварительная подготовка

Как вы уже помните по предыдущим моим публикациям (например), для сборки я использую транслятор zasm, на выходе которого получается rom-файл. Но для того чтобы его загрузить либо в эмулятор, либо на сам ПЭВМ «Микроша», необходимо его преобразовать в формат RKM. Описание формата нашёл на этой веб-странице:

Смещение Назначение
00-01 Два байта. Адрес начала файла в памяти. Вначале старший байт числа, потом младший.
02-03 Два байта. Конечный адрес файла в памяти. Вначале старший байт числа, потом младший.
... Данные файла. Длина данных — это адрес конца минус адрес начала.
Два последних байта Контрольная сумма для данных

В моём случае начальный адрес был равен нулю, конечный адрес файла в памяти — это размер выходного rom-файла. Данные файла — это собственно говоря содержимое самого rom-файла. Самое сложное было разобраться, как производить расчёт контрольной суммы. Очень-очень долго искал код, как посчитать контрольную сумму для ПЭВМ «Микроша», ведь формула расчёта контрольной суммы для моего компьютера отличается от формулы расчёта для Радио-86РК. На помощь пришёл Вячеслав Славинский, который скинул код генерации звукового файла для различных платформ и там как раз был пример, посвящённый «Микроше». Осознав, что же там делается, реализовал свой вариант на си.

Если отбросить шелуху, то код, формирующий из rom-файла RKM выглядит следующим образом:

write_byte_to_file(file_out, 0);
write_byte_to_file(file_out, 0);
write_byte_to_file(file_out, (uint8_t)(filesize >> 8 & 0x00FF));
write_byte_to_file(file_out, (uint8_t)(0x00FF & filesize));
while  ( ( x = fgetc( file ) ) != EOF ) {
	write_byte_to_file(file_out, (uint8_t)(0x00FF & x));
	if (i++ % 2 == 0) {
		csm_lo ^= (uint32_t)0x00FF & x;
	} else {
		csm_hi ^= (uint32_t)0x00FF & x;
	}
}
write_byte_to_file(file_out, csm_hi);
write_byte_to_file(file_out, csm_lo);

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

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

Скорее всего я изобрёл велосипед, который уже давно есть, но мне это тоже было полезно и интересно понять и разобраться. Обращаю внимание, как я уже сказал, алгоритм расчёта контрольной суммы для «Микроши» и «Радио-86РК» различается!

В результате у меня получилась готовая программа, код которой после трансляции превращался в формат готовый для загрузки в эмулятор, либо непосредственно на ПЭВМ.

Заставка демо

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

В «Руководстве пользователя» на «Микрошу» есть такой замечательный пример:

Создание демки специально для HABR — Часть 1 - 3

Он слишком прост, примитивен и не интересен. Хотелось сделать что-то этакое.

И тут я вспомнил про такой жанр, как ASCII-арт. Пошёл на сайт patorjk.com и начал подбирать наиболее подходящий вариант.

Создание демки специально для HABR — Часть 1 - 4

Мне понравилось, что тут использованы unicode-символы, которые аналогичны имеющимся в микроше: полный блок, верхний полублок, нижний полублок. И уже можно как-то проверить возможности псевдографики. Сохраняю всё это в текстовый файл и начинаю думать, как его конвертировать в формат «Микроши». Для начала создал файл ruvds.txt содержит просто скопированный текст с этими символами.

Создание демки специально для HABR — Часть 1 - 5
Содержимое текстового файла.

Для конвертации в формат «Микроши» набросал небольшую программу, на си. Планирую выводить как текст: вызовом функции программы «Монитор».

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

#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <errno.h>
#include <locale.h>
 
int main(void) {
	setlocale(LC_ALL, "en_US.utf8");
	FILE *fp = fp = fopen("ruvds.txt", "r");
	if(!fp) {
		perror("Can't open file for reading");
		return EXIT_FAILURE;
	}
	wint_t wc;
	errno = 0;
	while ((wc = fgetwc(fp)) != WEOF) {
		//putwchar(wc);
		switch (wc) {
			case L'▄': //0x14
				//putchar('_');
				printf("14h, ");
				break;
			case L'▀': //0x03
				//putchar('-');
				printf("03h, ");
				break;
			case L'█': //0x17
				//putchar('*');
				printf("17h, ");
				break;
			case L' ':
				//putchar(' ');
				printf("' ', ");
				break;
			case L'n':
				printf("0dh, 0ahntdb ");
		}
	}
	printf("0dh, 0ah, 7, 0");
	fclose(fp);
	return 0;
}

После компиляции, в результате выполнения получим вот такую петрушку:

Создание демки специально для HABR — Часть 1 - 6

0x0D — это просто символ возврата каретки ака 'r', а 0x0A — символ перевода строки ака 'n'.

Далее можно набросать простейшую программу, для вывода на экран. Она проста как валенок:

puts    equ 0F818h
	org 0
	lxi h, msg
	call puts
loop:
	jmp loop

Загружаем в регистр h расположение сообщения и вызываем подпрограмму системного монитора для вывода на экран. Само сообщение начинается с символа 0x1F — он очищает экран и помещает курсор в нулевую позицию. Далее идёт текст сообщения, и как в си, строка должна заканчиваться нулём.

Пример загружаемого сообщения msg

msg:
	db 1fh, 0dh, 0ah
	db ' ', ' ', ' ', 14h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 14h, ' ', ' ', ' ', 14h, 17h, ' ', ' ', ' ', ' ', 17h, 14h, ' ', ' ', 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 14h, ' ', ' ', ' ', ' ', ' ', 14h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 0dh, 0ah
	db ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', 03h, 17h, 17h, 17h, ' ', ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, 0dh, 0ah
	db ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 03h, ' ', 0dh, 0ah
	db ' ', 14h, 17h, 17h, 17h, 14h, 14h, 14h, 14h, 17h, 17h, 03h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0dh, 0ah
	db 03h, 03h, 17h, 17h, 17h, 03h, 03h, 03h, 03h, 03h, ' ', ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 03h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 0dh, 0ah
	db 03h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 17h, 17h, 17h, 0dh, 0ah
	db ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, ' ', ' ', ' ', 14h, 17h, 17h, 17h, ' ', ' ', ' ', ' ', 14h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, 0dh, 0ah
	db ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 03h, ' ', ' ', ' ', 03h, 17h, 17h, 17h, 17h, 17h, 17h, 03h, ' ', ' ', 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 03h, ' ', ' ', ' ', 14h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 17h, 03h, ' ', 0dh, 0ah
	db ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', 17h, 17h, 17h, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 0dh, 0ah
	db 0dh, 0ah, 0dh, 0ah
	db "demka specialxno dlq HABR", 0dh, 0ah
	db "ideq i realizaciq DLINYJ", 0dh, 0ah, 0dh, 0ah
	db 0dh, 0ah, 0dh, 0ah, 0dh, 0ah, 0dh, 0ah
	db 0dh, 0ah, 0dh, 0ah, 0dh, 0ah,0dh, 0ah, 0dh
	db "bolx{aq blagodarnostx MAN OF LETTERS", 0dh, 0ah
	db 0

Далее собираем программу zasm, конвертируем ранее написанным конвертером в формат RKM.

zasm --asm8080 ruvds.asm && convert_rkm ruvds.rom

Ну а теперь можно попробовать, как же это выглядит, пускай хотя бы на эмуляторе.

Создание демки специально для HABR — Часть 1 - 7

Сразу видно, как всё дико тормозит. Нет, эта гифка не замедленно, это и вправду так медленно отображается символ, когда печатается с помощью функции программы «Монитор». И если в данном решении мне показалось, что это выглядит интересно, последовательная загрузка, то если хочется какой-то крутой динамики, надо искать другой подход.

В реальном железе, тоже всё работает отлично. К сожалению, монитор «Электроника» оказался подсевшим и с дрожащим изображением, но на нём всё вполне нормально работает.

Создание демки специально для HABR — Часть 1 - 8
Тест на реальном железе.

Символы псевдографики «Микроши»

Как можно вспомнить, ПЭВМ использует псевдографику, для вывода изображений на экран. В «Руководстве пользователя» в самом начале есть табличка соответствия символов псевдографики.

Создание демки специально для HABR — Часть 1 - 9

Как видно, можно символом добавить ещё 4 точки, и таким образом увеличить количество отображаемых точек в 4 раза. Решил посмотреть, как же будут выглядеть полученные символы на экране, видоизменил программу выше, и в результате ряда экспериментов получил следующую картину.

Создание демки специально для HABR — Часть 1 - 10

Внимательный читатель может обратить внимание, что одного символа не хватает (кого я обманываю, кто внимательно читает статьи). А именно символа '▜'. При этом как я не пыжился вывести через программу «Монитор» мне этого сделать не удалось. И в «Руководстве пользователя» его нет. Так, что-то тут не чисто, обещают все символы, но его вывести нельзя что ли?

В интернете гуляет другая картинка, с демонстрацией символов ПЭВМ.

Создание демки специально для HABR — Часть 1 - 11

И там, на седьмой позиции присутствует этот символ. Но согласно документации 0x07 — это вывод звукового сигнала.

Как оказалось, есть другой способ отображения информации, более быстрый, минуя программу «Монитор» — это запись непосредственно в видеопамять и тогда всё будет работать. И когда начал писать в видеопамять, то данный символ с успехом отображается! На поиск ответа куда подевался символ, я потратил несколько дней.

Пару слов об устройстве видеопамяти

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

Видеопамять располагается по адресам 0x76D0-0x7FFF в оперативной памяти ПЭВМ, и занимает 2352 байта. Расположение этой области памяти определяется настройками контроллера прямого доступа к памяти (ПДП) КР580ВТ57, который считывает эту область памяти и передаёт данные в контроллер дисплея КР580ВГ75, который, в свою очередь, берёт соответствующее изображение символа из своего ПЗУ и отображает его на экране. В этой демке контроллер ПДП мне конфигурировать не пришлось (хотя я представляю как), и адреса и размер видеопамяти остаётся прежним.

Таким образом, просто записываем данные в область памяти, и получаем отображение их на дисплее. Но есть один нюанс, если внимательно почитать документацию, спеки на ПЭВМ, то везде пишут, что количество выводимых символов у «Микроши» 64х25 или общее 1600 штук, а память имеет размер 2352 байта, как это? Обратимся к документации.

Создание демки специально для HABR — Часть 1 - 12

Тут открывается очень тонкий момент, что фактически реальный размер экрана составляет 78х30 символов. Только первые три строки у нас должны быть чёрными, и с каждой стороны по 7 символов должны быть чёрными, и снизу 2 строки должны быть чёрными.

Создание демки специально для HABR — Часть 1 - 13
Схема разбивки памяти для размещения изображения.

Такая странная структура имеет свой смысл, как мне поведали в чате "Ретрокомпьютеров", то это связано с особенностями микросхемы видеовыхода КР580ВГ75. Данная микросхема не генерирует синхроимпульсов, и формально этим должна заниматься внешняя схема. В ПЭВМ «Микроша» такой схемы нет, и микросхема ВГ75 настраивается таким образом, чтобы формировать эту «рамку» за экраном. В отсутствии синхроимпульсов телевизор воспринимает её, как синхроимпульс (хотя последний должен быть «чернее чёрного», видимо, цепляется за перепад «белое-черное»).

Тут есть главный вопрос, у нас есть поле размером 70х30, вместо поля 64х25, возможно ли использовать его? Оказалось возможно, правда не все мониторы его отображают целиком, но вполне. С некоторыми особенностями. Например, нельзя заливать всё целиком белым цветом, или хотя бы половину. Надо следить, чтобы хотя бы на половине строк была чёрная рамка. Но сей грязный хак с успехом у себя провернул. Хотя впоследствии и пособирал граблей.

Итого, получается, если с псевдографикой, то поле для рисования выходит 140х60 пикселей, уже можно даже жить! Разумеется, по краям, в особенности по углам, из-за особенностей ЭЛТ-мониторов не всё будет видно, но всё равно это веселее.

Основная анимация

▍ Теория

Как я уже выше говорил, хотелось создать, хоть и очень простую, но исключительно свою псевдо-3D анимацию. При этом мне было по-настоящему интересно не читать всякие умные книги по трёхмерной графике, а додуматься до всего самому. Пускай это будет дольше, но зато разомну мозг.

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

Когда я поставил себе эту задачу, то моя тетрадка начала обрастать кучей треугольников с формулами, как же это реализовать и пересчитать. В результате получилось следующая вариация, после того как смог себе это всё представить.
У нас есть три оси: X, Y, Z:

  • Ось Z — виртуальная, она направлена к зрителю от монитора, с ней и будет основная магия.
  • Ось X — это горизонтальная ось, столбцы, направлена как обычно слева направо.
  • Ось Y — это строки, растёт она из левого верхнего угла монитора вниз.

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

Создание демки специально для HABR — Часть 1 - 14
Пример эффекта вращения.

Если мы поворачиваем относительно оси Y, то координаты оси Y у нас не меняются, необходимо только каждый раз рассчитывать новую координату X’. При этом нужно помнить, что если в одну и ту же точку попадает и чёрная и белая точка, то мы её окрашиваем в белый цвет. Таким образом, когда угол ɑ = 90 , то мы должны получить на выходе сплошную белую линию, размером в 1 пиксель.
Формула пересчёта координаты X’ достаточно простая.

$X’=Xcdotcosalpha$

Это вроде бы простая формула, но её поиск у меня занял несколько бессонных ночей, хотя было дико интересно. Не буду лукавить, мне хотелось сделать более сложное вращение, как у подброшенной монеты, но по ряду причин остановился на таком простом варианте.

▍ Конвертер

Идею конвертера позаимствовал у begoon. Поскольку необходимо конвертировать в символы псевдографики, то я сделал полный холст, равный виртуальному разрешению. Один константный холст содержит картинку, которую мы будем вращать. Другой полный холст содержит картинку, на которую будет производиться пересчёт в зависимости от угла. Размеры холстов соответственно:

#define SCREEN_WIDTH 156
#define SCREEN_HEIGHT 60

В качестве светящегося белого пикселя в массиве типа char у меня применён символ 'X', а в качестве чёрного — пробел.

Далее два других холста, на которые будет уже идти конвертация в символы псевдографики, и уже с ними будет идти основная обработка: будет вычисляться изменения между холстами (diff), и они будут сохраняться в ассемблеровский файл (по какому адресу памяти, какой символ изменился). Размеры этих холстов соответственно:

#define M_SCREEN_WIDTH 78
#define M_SCREEN_HEIGHT 30

Чтобы каждый раз не запускать симулятор «Микроши», или вообще сам ПЭВМ, набросал небольшой эмулятор дисплея, на который выводил результат в псевдографике. Вообще, каждый уважающий себя демосценер пишет свой эмулятор — это правило (сарказм). Для этого заморочился и подобрал символы unicode, которые соответствуют символам на ПЭВМ «Микроша» Получилось 15 символов (16 — пробел):

'▘', '▝', '▀', '▗', '▚', '▐', '▜', '▖', '▌', '▞', '▛', '▄', '▙', '▟', '█'

Принцип отрисовки достаточно прост, с помощью ESC-последовательностей очищаю экран и далее в зависимости от расположения в двумерном массиве 'X' и ' ', выбираю подходящий символ.

#define ESC "33"
#define home() 		printf(ESC "[H")
#define clrscr()	printf(ESC "[2J") 
...
void print_image(char ** image) {
	int i, j;
	home();
	clrscr();
	for (i = 0; i < SCREEN_HEIGHT; i+=2) {
		for (j =0; j < SCREEN_WIDTH; j+=2) {
			if ((image[i][j]   == 'X') && (image[i][j+1]   == ' ') && 
				(image[i+1][j] == ' ') && (image[i+1][j+1] == ' ')){
				printf("%lc", TOP_LEFT_POINT);
			}
			
			if ((image[i][j]   == ' ') && (image[i][j+1]   == 'X') && 
				(image[i+1][j] == ' ') && (image[i+1][j+1] == ' ')){
				printf("%lc", TOP_RIGHT_POINT);
			}
...
		}
		putchar('n');
	}
}

Аналогичный код конвертации в холст с псевдосимволами, только #define соответствует символу на «Микроше».

Всё, теперь можно по приколу покрутить линию. Вспоминаем формулу функции прямой, вспоминаем что коэффициент k — это тангенс угла наклона и поехали. Только помним, что до 45 градусов нужно считать по X, а после 45 по Y, иначе линию будет разрывать. В результате функция рисования линии получилась следующая.

#define Y1 (SCREEN_HEIGHT/2)
#define X1 (SCREEN_WIDTH/2)
void line (char ** canvas, double degree_axix_of_rotation) {
	double k = tan((180.0 - degree_axix_of_rotation) * M_PI / 180.0);
	if ((degree_axix_of_rotation > 45.0)  && (degree_axix_of_rotation < 135.0)) {
		for (int y = 0; y < SCREEN_HEIGHT; y++) {
			int x = (int)((y - Y1)/k + X1 + 0.5);
			if ((x >= 0) && (x <= SCREEN_WIDTH)) {
				canvas[y][x] = 'X';
			}
		}
	}
	if ((degree_axix_of_rotation <= 45.0) || (degree_axix_of_rotation >= 135.0)){
		for (int x = 0; x < SCREEN_WIDTH; x++) {
			int y = (int)(k * (x - X1) + Y1 + 0.5);
			if ((y >= 0) && (y <= SCREEN_HEIGHT)) {
				canvas[y][x] = 'X';
			}
		}s
	} 
}

Если рисовать линию с шагом 3 градуса, предыдущую линию не стирать, то получится вот такая вот красота.

Создание демки специально для HABR — Часть 1 - 16

Функция вращения картинки ещё проще, чем функция рисования вращающейся линии.

void rotate(char ** image, char ** canvas, double r_degree) {
	for (int y = 0; y < SCREEN_HEIGHT; y++) {
		for (int x = 0; x < SCREEN_WIDTH; x++) {
			int xnew = (int)((X1 - x) * cos ((180 -r_degree) * M_PI / 180.0) + X1 + 0.5);
			if (canvas[y][xnew] != 'X') {
				canvas[y][xnew] = image[y][x];
			}
		}
	}
}

Формула применена та же, просто добавлено округление. image — это указатель на исходную картинку, canvas — на выходной холст, и градус поворота. В результате, на ПК у меня получилось очень реалистичное вращение, если вращать с шагом 1 градус. Пардон за артефакты съёмки.

Вроде бы всё, вот он результат, конвертируй в ассемблеровский выхлоп и дело в шляпе.
Первое что я попытался сделать, после того как получил этот выхлоп — это попытаться транслировать его и скорее посмотреть окончательный результат в эмуляторе. Но столкнулся с очень странным поведением:

zasm --asm8080 microsha_demo.asm 

in file frames.asm:
88: 
    ^ segment  size out of range: 0
assembled file: microsha_demo.asm
    90996 lines, 1 pass, 0.4046 sec.
    1 error

Проще говоря: программа не лезет в 64 кБ, то есть, бесконечных мультиков смотреть не получиться. И придётся даже такую простенькую анимацию оптимизировать, чтобы уместить со всеми хотелками в оперативной памяти. При этом в моём случае они не влезли аж в 64к адресуемой памяти, а в ПЭВМ «Микроша» всего 32 килобайта!

Заключение первой части

Статья и так получилась очень большой, хотя я не рассказал даже и половины того, что собирался. Но на затравку покажу промежуточный результат, который мне удалось достичь.

Создание демки специально для HABR — Часть 1 - 17

Но поскольку задачу ещё не удалось решить окончательно, то значит предстоит ещё большая кропотливая работа. Но об этом в следующей части.

P/s — кликабельная картинка из шапки

Автор: Сергей

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js