Недавняя статья открыла для меня устройство столь упоро необычное и несущее столь любимый нами трудноуловимый флёр «балдёжности», существование которого раньше проходило мимо меня — NeoGeo. Как говорится, «это не первое моё родео» — на использовании 2D‑ускорителей в качестве 3D‑ускорителей я съел изрядного бобика.
Но если там надо было выжать из никакущего девайса хотя бы графику уровня «Брус» (кстати, можете смело портировать! Половину доступных прямоугольников стенки уже сожрали, но второй половинки вполне должно хватить на рисование агрессивных табуреток и квадратных пулек, летающих туда‑сюда), то здесь у нас есть аппаратные даунскейлеры текстур. А это уже очень, оооочень серьёзная заявка на победу… Месиво из стен и летающие в воздухе импы, которые рисуются поверх тех стенок, которые должны быть перед ними, привели меня в смешанные чувства. И чувство «Дункан, подержи мой ананас» среди них явно доминировало — и я схватился за свои родовые кинжальчики. Увы, этот Ледберри мне известен весьма мало… и, главное, информации о нём тоже не очень много, несмотря на наличие фанатской Development Wiki. Я долго пинал всевозможных роботов, и роботы хором клялись, что я могу просто задать спрайты в определённом порядке и таким образом гарантировать правильное перекрытие одного спрайта другим. Приняв это на веру, я сделал ставку на ключевой момент — рисуем стены спрайтовой «черепицей» слева направо, лишнее просто прикрываем соседним спрайтом.
Спрайты я откровенно «зажал». Имея всего 96, нельзя все их отдать под одну стену — наша цель Doom, а там обязательно где‑то будут и текстуры друг к другу «в гости ходить» (хорошо, что хотя бы в основном будут жить «каждая на своём этаже»), и решётки потребуется рисовать (хотя бы символически, из нескольких независимых полосок), и нечисть с нежитью будет корчить хари, требуя свою долю текстур. Короче, я ограничился 64 пятипиксельными полосами, а остальные 32 спрайта отдал на всё остальное — в рамках теста это до 8 мобов шириной 4×16=64 пиксела каждый. В принципе, 8 мобов на сцену — более‑менее достаточно, если правильно задавать приоритеты (или просто самых дальних тупо вместо стенок рисовать, всё равно там уже невозможно заметить подделку).
И начались тесты.
Сначала, конечно, надо написать «тестбенч». Сей испытательный полигон является прямым наследником оптимизированного «Модбреза», в котором каждый из 8 случаев расписан отдельно, как в упомянутой в самом начале заготовке для «the Bio» — но тут я, конечно, писал по‑человечески, а не в терминах «почти эмуляции 6502». Впрочем, менее корявым рендер от этого не стал — он лишь немногим лучше того поделия, которое стриггерило меня это всё писать. Но это и не являлось целью. Wolf3D работал на машине уровнем чуть‑чуть выше центрального процессора NeoGeo — но он сам попиксельно рисовал кадр! Думаю, что нет смысла повторяться — надо просто пересобрать под проц «неогейки» (не подумайте дурного!) нормальный Wolf3D со всем в него входящим, а его «выхлоп» вместо скалеров Кармака направить в мою «дробилку», которая разбивает сцену на спрайты NeoGeo и рендерит их со всей мощью аппаратного ускорения. Ну, а потом уже замахнуться проделать то же самое с BSP‑движком Doom…
Следующий этап — вообразить, что память у нас бесконечная и аппаратный скалер может делать что угодно. На этом этапе (нажатие правой клавиши мыши переключает на него, как и на последующие этапы; на левую клавишу назначен выход) я делаю рейкаст только 64 столбцов вместо 320. Для каждого столбца я рисую его кусочек текстуры прямоугольной формы, сжав его по вертикали сообразно расстоянию до него, а по горизонтали — разумеется, сообразно и расстоянию, и углу, под которым мы его наблюдаем! Картинка довольно‑таки адекватная. Имеет смысл продолжать! Хотя, конечно, сверху и снизу появились «лесенки». Но их, в принципе, можно будет закрыть тайлами, отвечающими за пол и потолок — их мы, конечно, не сможем заставить нормально поворачиваться, как в Doom (не хватит ресурсов), но их можно сделать из различных форм, заполненных шумом разного цвета, и переключать так, чтобы было плюс‑минус понятно, ящик под нами или кислота. Рандомизировать, кстати, можно в зависимости от угла, умноженного на расстояние до данного элемента пола — дальние при повороте камеры «переливаются» быстрее, а ближние меняются совсем не каждый кадр. Это ещё немного добавит ощущения перспективы. Но об этом пока рано говорить.
Теперь нам надо вспомнить о грустном: каждая экранная полоска в общем случае может начинаться с любой колонки текстуры. Это означает, что мы не можем просто взять да и разбить текстуры на полоски по 16 пикселов: нам потребуется 16 экземпляров всего этого хозяйства, начинающихся с 0, 1, 2, 3 … 15 столбца. Да, у нашей целевой платформы преизрядная память выделена для спрайтов, но и в неё не влезет весь Doom 16 раз подряд.
И тут начинается магия. Как уже довольно‑таки прожжённый колдун, я сразу заметил одно обстоятельство: полоски по 16 пикселов в ширину, которые можно сжать до 5, означают сжатие почти в 4 раза (а если допустить дублирование краевых пикселов, то и в 4). Это значит, что на максимальном расстоянии рендера (когда текстуры вот‑вот разойдутся и между ними возникнут «дыры») точно попадать в пикселы не нужно, достаточно задать шаг смещения в 4 пиксела. Это мы себе уже можем позволить, потому что стены, хранящиеся со сдвигом 0, 4, 8 и 12 пикселов (мобов это вообще не касается) уже более‑менее имеют шанс влезть в память. Но что же делать, когда мы разглядываем текстуру вблизи? Да ничего особенного: на минимальном расстоянии рендера (когда текстуры рисуются 1:1, нарисовали бы и больше, но не умеет НеоГео растягивать, вот в чём загвоздка‑то) полоски занимают все положенные 16 экранных пикселов, а перекрыть‑то надо только 5! Так что мы можем просто подвигать их по экрану и наша «лесенка» станет слегка неоднородной, но стенка в целом приобретёт пристойный вид без двоящихся колонок. Промежуточные же варанты… эээ… промежуточны: там мы не можем сильно двигать спрайтовую полоску, но и промахнуться имеем право на пиксел‑другой.
Ну а теперь закодим всё это! За основу я беру центр полоски и, для начала, просто ляпаю ближайшую из 4 экземпляров текстуру, смело округляя до ближайшего. В качестве текстуры беру одну из думовских стенок 256×128, растянув её по вертикали до 256×256: это заметно увеличивает диапазон работы рендерера, потому что обычно в замесе стенки наблюдают под углом (ну не бежит же прямо на неё наш спейсмарин, как таранный баран Клауса Вольфенбаха!), и по горизонтали они сжаты сильнее. Поэтому по вертикали нужен больший запас на растяжение — эх, если бы только мы могли растягивать спрайты! Этот центр принят за ноль, а остальные экранные колонки — за ±2. Если спрайта не хватает даже с допущением о повторяемости колонок (то есть он даже 3 из 5 не закрывает) — то вместо него рисуем разблюренную текстуру, типа такой LOD (хотя почему же «типа», он самый и получился, только закрывает несколько иные задачи, чем обычно). Если спрайт уже вопиюще требует растягивания (более чем до 18, потому что, опять же, потерять две пиксельных колонки не проблема) — делаем то же самое. Увы, если близорукость движку к лицу (все более‑менее привыкли), то дальнозоркость вызывает резкое отторжение. Расплывающаяся текстура из‑за того, что мы встали слишком близко — такое себе решение…
Столбцы «переливаются», конечно, не настолько жутко, как у моего коллеги‑предшественника, заварившего всю эту кашу, но тоже, увы, не сахар. Что ж, берём и доделываем до конца! У каждой полоски есть размер, до которого её нужно растянуть, участок текстуры, который должен соответствовать центральному столбцу из пяти, и допуски для размещения на экране. Допуски эти в любом случае не больше ±2 (иначе «лесенка» рискует стать совсем уж жуткой, когда пара колонок вовсе самоустраняется из картинки, а третья рисуется огромным прямоугольником шириной аж в 15), но левый край ещё и ограничен дополнительно тем, где закончилась предыдущая полоска, чтобы у удалённых текстур не появлялись разрывы. И, как я выше уже говорил, при достижении пятипиксельного размера все допуски размещения превращаются в тыкву — есть только один способ разместить текстуры, потому что наложение стало равно нулю, но это и не страшно, потому что в таком масштабе это всё равно не видно.
Чтобы это реализовать, я просто взял метод перебора и поиска минимума. Да, я понимаю, что эту задачу надо решить один раз и положить в LUT! Но сейчас я проверяю принципиальную реализуемость (хотя заголовок статьи уже всё про неё сказал), и не пытаюсь снова ставить рекорды малотребовательности к железу (хотя, честно, то обстоятельство, что первоавтор жалуется на тормоза, меня преизрядно напугало; ну никак не должен такой процессор тупить, если рендеринг аппаратный! Как бы для Doom не пришлось в картридж впаивать отдельный сопроцессор, аутентичный эпохе (что‑то с производительностью i8088) и скармливать ему чисто BSP, оставив центральному процу только наивную и маложручую думовскую физику). Короче говоря, я просто беру все 5 (или даже меньше, если наше черепичное наложение не дало запаса) вариантов и выбираю тот, который ближе всего округляется до ближайшей четвёрки. После чего беру нужную полоску с нужным смещением.
Дальше, правда, задача стала терять для меня весь свой развлекательный потенциал. Добившись практически гладкой картинки, я попробовал что‑то сделать со спрайтами, но наловил только кучу глюков. Кроме того, пропорции у графики в целом люто сбиты, поэтому все попытки работать с масштабами превращаются не просто в тыкву, а в гнилую тыкву. Мобы дёргаются, рисуются «где‑то там», короче, нажав правую кнопку ещё раз, вы увидите в комнате с центральной колонной нашего любимого «кокоса», нарезающего круги вокруг колонны и правильно ей затеняемого — но не более. Всё это переписывать и ещё раз настраивать заново главную «рисовалку» у меня нет никакого желания, особенно с учётом того, что всё это предназначено «в топку» — тестбенч своё дело выполнил, идея сшивать сцену при помощи масштабирования и смещения оправдала себя на все 100%. Так что здесь традиционно идёт полный листинг кода.
Скрытый текст
#include <conio.h>#include <stdlib.h>#include <math.h>#include <fstream.h>#include <stdio.h>//Debug only#include "LevelMap.h"#define PI 3.1415926535897932384626433832795#define max(a,b) (((a) > (b)) ? (a) : (b))#define min(a,b) (((a) < (b)) ? (a) : (b))static unsigned char __far TestWall[256][256];//128x256 Doom wall, vert. scaled x2static unsigned char __far TestCaco[64][64];//64x64 Doom mob, 1:1static unsigned char __far Frame[200][320];static unsigned char __far ShiftedColumns00[16][256][16];//16 vertical stripes of the original texture, 16x256 each. Comes in five different flavors: horizontal offser = 0, 4, 8 and 12, and blurry beyond recognition.static unsigned char __far ShiftedColumns04[16][256][16];static unsigned char __far ShiftedColumns08[16][256][16];static unsigned char __far ShiftedColumns12[16][256][16];static unsigned char __far ShiftedColumnsBl[16][256][16];static unsigned char __far MobColumns[4][64][16];//4 vertical stripes of the original texture, 16x64 each.char const * SetPalette256 (void __far * Pal){unsigned long pval = (unsigned long)Pal;unsigned short Segm = pval>>16;unsigned short Offs = pval;Segm+=Offs>>4;Offs&=0x000F;//if (Offs > 15) return "Error! Paragraph address should never be inside next paragraphs";╒╛╔АБ╝ Б╝ё╝, ГБ╝║К ╕═╚╝╒═БЛАО, ╛К А═╛╗ МБ╝ ё═Ю═╜Б╗Ю╝╒═╚╗ ╞═Ю╝╘ АБЮ╝╙ ╒КХ╔.__asm{mov dx,0x3c8mov al,0out dx,al//Starting palette address = 0inc dxmov cx,768mov es, Segmmov si, OffsRegL:mov al,es:[si]//and we DO load all 256 colors.inc si//That's why we requre addresses loaded in the segment partshr al,1//Don't forget VGA was 6-bit RGB!shr al,1//Don't forget VGA was 6-bit RGB!out dx,alloop RegL}return NULL;}char const * Blt256 (void __far * Body){unsigned long pval = (unsigned long)Body;unsigned short Segm = pval>>16;unsigned short Offs = pval;Segm+=Offs>>4;Offs&=0x000F;//if (Offs > 15) return "Error! Paragraph address should never be inside next paragraphs";╒╛╔АБ╝ Б╝ё╝, ГБ╝║К ╕═╚╝╒═БЛАО, ╛К А═╛╗ МБ╝ ё═Ю═╜Б╗Ю╝╒═╚╗ ╞═Ю╝╘ АБЮ╝╙ ╒КХ╔.__asm{push dsmov ds, Segmmov si, Offsmov ax, 0xA000mov es, axmov di, 0mov cx, 32000SR:mov ax,ds:[si]inc si//That's why we requre addresses loaded in the segment partinc simov es:[di], axinc diinc diloop SRpop ds}return NULL;}#pragma pack(push)#pragma pack(0)struct BMP256HEADER{short Id;long FSize, Ver, DataOffset, Unused, Width, Height;short WTF, BPP;long Compr; //0 for uncompressed, 1,2 for RLElong DataSize, XRes, YRes, PalSize, Unused2;char Palette[256][4];};#pragma pack(pop)char const * LoadBMP256(char *Name, unsigned char Pal[256][3], unsigned char __far * ImgBody, int BodyArrayWidth, int MaxH){fstream f;unsigned long Line, W,H,W4, X;BMP256HEADER Head;f.open (Name, ios::in|ios::binary);f.read ((char*)&Head, sizeof (Head));if (f.fail()) return "Can't open file!";if (Head.Id!=0x4D42) return "Wrong BMP id! Not a BMP file?";if (Head.Compr) return "Compressed files are not supported for now!";if (Head.BPP!=8) return "Wrong bits per pixel! Expected to read 256-color image.";//cout<<Head.PalSize<<endl;for (H=0; H<256; H++) {Pal[H][0]=Head.Palette[H][2];Pal[H][1]=Head.Palette[H][1];Pal[H][2]=Head.Palette[H][0];}H=Head.Height;W=Head.Width;if (!W||!H||W>BodyArrayWidth||H>MaxH) return ("Wrong image size!");W4=(W+3)&(~3);for (Line=0;Line<H;Line++){f.seekg (Head.DataOffset+Line*W4, ios::beg);f.read (ImgBody+(H-Line-1)*BodyArrayWidth, W);}f.close();return NULL;}float TheFamousFastRSqRt (float Val){long i;float f=Val;i = *(long *)&f;i = 0x5f3759df - (i>>1);f = *(float *)&i;return f * (1.5 - Val*.5*f*f);}short LessFamousBhaskaraI_Sine_0_to_180deg(short Arg)//Arg 512 = 180 deg.{signed long Temp,Temp2;Temp=Temp2=Arg;Temp2=511-Temp2;Temp*=4096*Temp2;Temp/=327680-((signed long)Arg)*Temp2;return Temp;//Sine X 1024}unsigned short X, Y;//6 MSB = map tile, 6 bits = pixel inside a tile, 4 LSB = pixel fractions.signed short Angle=0;//+-32768 = +-180 deg.signed short DX=1024, DY=0;//cos and sin of Angle X 1024.#define GRUDAK (12<<4)int DoClipping (signed short StepX, signed short StepY)//Wolf3d Clone{unsigned short MinX, MaxX, MinY, MaxY;MinX = X-GRUDAK+StepX;MaxX = X+GRUDAK+StepX;MinY = Y-GRUDAK+StepY;MaxY = Y+GRUDAK+StepY;if (Map[MinY>>10][MinX>>10]| Map[MinY>>10][MaxX>>10]| Map[MaxY>>10][MinX>>10]| Map[MaxY>>10][MaxX>>10]){if (!StepX || !StepY) return 0;return DoClipping(StepX, 0) || DoClipping(0, StepY);}X+=StepX;Y+=StepY;return 1;}struct{unsigned short X, Y;//6 MSB = map tile, 6 bits = pixel inside a tile, 4 LSB = pixel fractions.signed short Rotation;//+-32768 = +-180 deg.} TestMob = {32<<10, 32<<10, 0};void AnimateTestMob(){short A_0_180, SpinX, SpinY;Map[(TestMob.Y-(32<<4))>>10][(TestMob.X-(32<<4))>>10]=0;Map[(TestMob.Y-(32<<4))>>10][(TestMob.X+(32<<4))>>10]=0;Map[(TestMob.Y+(32<<4))>>10][(TestMob.X-(32<<4))>>10]=0;Map[(TestMob.Y+(32<<4))>>10][(TestMob.X+(32<<4))>>10]=0;A_0_180 = TestMob.Rotation>>6;if (A_0_180>0) SpinY=LessFamousBhaskaraI_Sine_0_to_180deg(A_0_180);else SpinY=-LessFamousBhaskaraI_Sine_0_to_180deg(-A_0_180);A_0_180 = TestMob.Rotation+16384; //Native overflow to a correct angle.A_0_180 >>= 6;if (A_0_180>0) SpinX=LessFamousBhaskaraI_Sine_0_to_180deg(A_0_180);else SpinX=-LessFamousBhaskaraI_Sine_0_to_180deg(-A_0_180);TestMob.Rotation+=300;TestMob.X = (32<<10) + SpinX*4;TestMob.Y = (32<<10) + SpinY*4;Map[(TestMob.Y-(32<<4))>>10][(TestMob.X-(32<<4))>>10]=128;Map[(TestMob.Y-(32<<4))>>10][(TestMob.X+(32<<4))>>10]=128;Map[(TestMob.Y+(32<<4))>>10][(TestMob.X-(32<<4))>>10]=128;Map[(TestMob.Y+(32<<4))>>10][(TestMob.X+(32<<4))>>10]=128;}int ModeVGA =5;//5 for literal VGA, 1 for grouped columnsint Mode2Dfx=0;//columns: 0 for accurate VGA draw, 1 for NeoGeo restrictions, 2 for NeoGeo+position optimization, 3=2+sprite listint Clicked=0;int DoGameplayTick(){int MouseX, MouseY, MouseB;short A_0_180;//printf ("Tick!\r\n");__asm{mov ax,0x03int 0x33mov MouseB,bx//mouse buttonsmov ax,0x0bint 0x33mov MouseX,cx//mouse xmov MouseY,dx//mouse y}//ClearKBBuffer();//ToDo!//printf ("%i \r\n", MouseB);Angle-=MouseX*16;//printf ("%u \r\n", Angle);A_0_180 = Angle>>6;if (A_0_180>0) DY=LessFamousBhaskaraI_Sine_0_to_180deg(A_0_180);else DY=-LessFamousBhaskaraI_Sine_0_to_180deg(-A_0_180);A_0_180 = Angle+16384; //Native overflow to a correct angle.A_0_180 >>= 6;if (A_0_180>0) DX=LessFamousBhaskaraI_Sine_0_to_180deg(A_0_180);else DX=-LessFamousBhaskaraI_Sine_0_to_180deg(-A_0_180);/*if (MouseB&2)//RStrafe{DoClipping( - DX*((signed long)MouseY)/256 + DY/16, - DY*((signed long)MouseY)/256 - DX/16);}else if (MouseB&4)//LStrafe{DoClipping( - DX*((signed long)MouseY)/256 - DY/16, - DY*((signed long)MouseY)/256 + DX/16);} else*/ {DoClipping( - DX*((signed long)MouseY)/256, - DY*((signed long)MouseY)/256);}if (MouseB&2){if (!Clicked){if (ModeVGA == 5) ModeVGA = 1;else if (Mode2Dfx<3) Mode2Dfx++;else ModeVGA=5+(Mode2Dfx=0);}Clicked=1;} else Clicked=0;if (MouseB&1)//Fire!{return 0;//debug: shoot anything to exit.}AnimateTestMob();return 1;//Still alive}static const char OmitMap[16]={/* Size FlagsNeoGeo size code100000000100000000x020000100010000000 0x130000100010001000 0x240010100010001000 0x350010100010001010 0x460010101010001010 0x570010101010101010 0x681010101010101010 0x791010101011101010 0x8101011101011101010 0x9111011101011101011 0xA121011101111101011 0xB131011101111101111 0xC141111101111101111 0xD151111101111111111 0xE161111111111111111 0xF"Draw if Size > ..." */0x07, 0x0D, 0x03, 0x09, 0x01, 0x0F, 0x05, 0x0B, 0x00, 0x08, 0x06, 0x0E, 0x02, 0x0C, 0x04, 0x0A};void BlitWallLikeTheNeoGeo (int XPos, int YPos, int XSize, int YSize, unsigned char __far *Column){//No upscale. Downscale only. Max. 16 size X and 512 size Y. Y downscale is not accurate (written from scratch). X downscale is based on the NeoGeoDevWiki.if (YSize>256) YSize=256;for (int Y=0; Y<YSize; Y++){int YSrc = (long)Y*256/(long)YSize;for (int X=0, XDst=0; X<16; X++){if (OmitMap[X]<XSize)//...or <= NeoGeo size code{if (XDst+XPos<320 && XDst+XPos>=0)//AFAIK NeoGeo can auto-crop and auto-shingle sprites if they are set in the certain orderFrame[Y+YPos][XDst+XPos] = Column[X + 16*YSrc];XDst++;}}}}void BlitMobLikeTheNeoGeo (int XPos, int YPos, int XSize, int YSize, unsigned char __far *Column){//No upscale. Downscale only. Max. 16 size X and 512 size Y. Y downscale is not accurate (written from scratch). X downscale is based on the NeoGeoDevWiki.if (YSize>64) YSize=64;for (int Y=0; Y<YSize; Y++){int YSrc = (long)Y*64/(long)YSize;for (int X=0, XDst=0; X<16; X++){if (OmitMap[X]<XSize)//...or <= NeoGeo size code{if (XDst+XPos<320 && XDst+XPos>=0)//AFAIK NeoGeo can auto-crop and auto-shingle sprites if they are set in the certain order if (Column[X + 16*YSrc] != 0xF7)//Doom transparentFrame[Y+YPos][XDst+XPos] = Column[X + 16*YSrc];XDst++;}}}}struct Tile{long SqDist;int X, Y;int XSize, YSize;unsigned char __far *ImgPtr;} WallTileList[64], MobTileList[8][4];//Actually, only 1st of 8 is used.int MobCount=0;void DebugDraw()//Like that youtube demo, sprites are drawn over walls even if they are behind them{for (int i=0; i<64;i++){BlitWallLikeTheNeoGeo (WallTileList[i].X, WallTileList[i].Y, WallTileList[i].XSize, WallTileList[i].YSize, WallTileList[i].ImgPtr);}for (int j=0; j<MobCount;j++) for (int i=0; i<4;i++) {BlitMobLikeTheNeoGeo (MobTileList[j][i].X, MobTileList[j][i].Y, MobTileList[j][i].XSize, MobTileList[j][i].YSize, MobTileList[j][i].ImgPtr); }}void LayeredDraw ()//A single mob is enough for the test{//return DebugDraw();for (int i=0; i<64;i++){if (WallTileList[i].SqDist>MobTileList[0][0].SqDist) BlitWallLikeTheNeoGeo (WallTileList[i].X, WallTileList[i].Y, WallTileList[i].XSize, WallTileList[i].YSize, WallTileList[i].ImgPtr);}for (int i=0; i<4;i++){BlitMobLikeTheNeoGeo (MobTileList[0][i].X, MobTileList[0][i].Y, MobTileList[0][i].XSize, MobTileList[0][i].YSize, MobTileList[0][i].ImgPtr);}for (int i=0; i<64;i++){if (WallTileList[i].SqDist<=MobTileList[0][0].SqDist) BlitWallLikeTheNeoGeo (WallTileList[i].X, WallTileList[i].Y, WallTileList[i].XSize, WallTileList[i].YSize, WallTileList[i].ImgPtr);}}/*void LayeredDrawII ()//A single mob is enough for the test{for (int WallN=0, MobN=0; WallN<64; WallN++){if (MobCount && MobN<4)//The mob exists and has not ended yet{if (WallTileList[WallN].X+WallTileList[WallN].XSize > MobTileList[0][MobN].X)//Mob begins before the wall stripe ends{if (WallTileList[WallN].SqDist > MobTileList[0][MobN].SqDist)//The mob covers the wall{while (WallN<64 && WallTileList[WallN].SqDist > MobTileList[0][MobN].SqDist) //This case is easy to check. All mob tiles have the same SqDist.{//Drawing as many wall tiles as possibleBlitWallLikeTheNeoGeo (WallTileList[WallN].X, WallTileList[WallN].Y, WallTileList[WallN].XSize, WallTileList[WallN].YSize, WallTileList[WallN].ImgPtr);WallN++;}while (MobN<4 && WallTileList[WallN].X+WallTileList[WallN].XSize > MobTileList[0][MobN].X) //Now drawing mob tiles until the mob ends or that tile from the prev. loop covers it (including the first [partially] covered tile).{}WallN--;//Retreat because we failed to draw that one (it's either non-existing 64th or just covers the mob)} else {//The wall covers the mob}} else BlitWallLikeTheNeoGeo (WallTileList[WallN].X, WallTileList[WallN].Y, WallTileList[WallN].XSize, WallTileList[WallN].YSize, WallTileList[WallN].ImgPtr);} else BlitWallLikeTheNeoGeo (WallTileList[WallN].X, WallTileList[WallN].Y, WallTileList[WallN].XSize, WallTileList[WallN].YSize, WallTileList[WallN].ImgPtr);}}*//*int SqDistCompare( const void *op1, const void *op2 ){ const Tile *W1 = (const Tile *) op1; const Tile *W2 = (const Tile *) op2; return( W2->SqDist - W1->SqDist );}void LayeredDrawIII ()//A single mob is enough for the test{qsort( WallTileList, 64, sizeof(Tile), SqDistCompare );for (int i=0; i<64;i++){if (WallTileList[i].SqDist<MobTileList[0][0].SqDist) for (int i=0; i<4;i++) {BlitMobLikeTheNeoGeo (MobTileList[0][i].X, MobTileList[0][i].Y, MobTileList[0][i].XSize, MobTileList[0][i].YSize, MobTileList[0][i].ImgPtr); }BlitWallLikeTheNeoGeo (WallTileList[i].X, WallTileList[i].Y, WallTileList[i].XSize, WallTileList[i].YSize, WallTileList[i].ImgPtr);}}*/static unsigned short ScaleF[320];int ShingledReserve=0;int AddToZBufferAndBlacklist (int Type, unsigned short CameraX, unsigned short CameraY, unsigned short RayX, unsigned short RayY, int Column, char WhichWall, float CosA){unsigned short DistToX, DistToY;unsigned long SqDist;int TextureHeight, TexturePos, Texture;if (Type>127)//We hit the mob, not the wall (not always the mob itself, but one of it's "link cells"). So we add it to the drawing list, in order to place properly later.{float x1,x2,y1,y2;//linefloat x0,y0;//mob centerfloat d;//distancex1=RayX;x2=CameraX;y1=RayY;y2=CameraY;x0=TestMob.X;y0=TestMob.Y;d=(x2-x1)*(y1-y0) - (x1-x0)*(y2-y1);d*=TheFamousFastRSqRt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );//d+=(float)DemoSargWidth/2.0;//ZBuff[0].Texture = d / 16.0;//if (ZBuff[0].Texture>=0 && ZBuff[0].Texture<DemoSargWidth)//ray passes inside the mob texture bounds//{if (TestMob.X>CameraX) DistToX = TestMob.X - CameraX; else DistToX = CameraX - TestMob.X;if (TestMob.Y>CameraY) DistToY = TestMob.Y - CameraY; else DistToY = CameraY - TestMob.Y;SqDist = DistToX;SqDist *= SqDist;SqDist += ((long)DistToY) * ((long)DistToY);//ZBuff[0].TextureHeight = 65536.0 * TheFamousFastRSqRt (SqDist/16);//ZBuff[0].TextureHeight = (long)(ScaleF[Column])*(long)(ZBuff[0].TextureHeight)/2048/4;//ZBuff[0].TexturePos = 100-ZBuff[0].TextureHeight/8;//}TextureHeight = 32768.0 * TheFamousFastRSqRt (SqDist/16);TextureHeight = (long)(ScaleF[Column*5+2])*(long)TextureHeight/2048/24;d/=4.0;//in texture pixelsd+=32.0;//zero if ray hits exactly the leftmost column of the mobd*=TextureHeight;d/=64.0;//in screen pixelsif (MobCount<8){for (int i=0; i<4;i++){MobTileList[MobCount][i].SqDist = SqDist;MobTileList[MobCount][i].X=Column*5+2 -d + TextureHeight*i/4;MobTileList[MobCount][i].Y=100-TextureHeight/5;MobTileList[MobCount][i].XSize=TextureHeight*(i+1)/4 - TextureHeight*i/4;MobTileList[MobCount][i].YSize=TextureHeight;MobTileList[MobCount][i].ImgPtr=MobColumns[i][0];}MobCount++;} // else sacrifice the most distant onereturn Type;//Blacklist this mob}switch (WhichWall)//In the most generic case, the wall block can have all four textures set independently. In Wolf3D, L+R and U+D were different (some are darker, also day/night and lift switch/wall). Here, all texture are the same but must be calculated in the proper direction.{case 'L':Texture = 255-RayY%1024/4;break;case 'R':Texture = RayY%1024/4;break;case 'U':Texture = 255-RayX%1024/4;break;case 'D':Texture = RayX%1024/4;break;default: Texture = 0;}if (RayX>CameraX) DistToX = RayX - CameraX; else DistToX = CameraX - RayX;if (RayY>CameraY) DistToY = RayY - CameraY; else DistToY = CameraY - RayY;SqDist = DistToX;SqDist *= SqDist;SqDist += ((long)DistToY) * ((long)DistToY);TextureHeight = 65536.0 * TheFamousFastRSqRt (SqDist/16);if (ModeVGA == 5)//Perfect, literal, 320 ray VGA{TextureHeight = (long)(ScaleF[Column])*(long)TextureHeight/2048/24;for (int y=0; y<200; y++) //In DOSBox, this loop takes... forever.{int TexY;if (TextureHeight) TexY = (signed long)(y-100)*256L / (signed long)(TextureHeight) + 128;else TexY=256-128*(y==100);if (TexY<0 || TexY>255) Frame[y][Column]=0;else Frame[y][Column]=TestWall[TexY][Texture];}} else {if (Mode2Dfx>1)//Optimal NeoGeo{TextureHeight = (long)(ScaleF[Column*5+2])*(long)TextureHeight/2048/24;float Min=256;int MinX=0, Best;long TextureWidth = 16 * TextureHeight * CosA / 128;if (TextureHeight>200) TextureHeight=200;//Debug! Should be 1..256 (up to 1:1) but it'll require croppingunsigned char __far *Col;if (TextureWidth>18 || TextureWidth<3)//Two repeating pixels are still OK{Col = ShiftedColumnsBl[Texture/16][0];//Nothing to optimize here -- it'll be blurry either wayMinX = ShingledReserve;}else {for (int x=-2; x<=ShingledReserve; x++){float Offset = (float)x * 128.0/(float)TextureHeight / CosA;float Candidate = (float)Texture+Offset;int Nearest4 = Candidate+2.5;//+2 and roundingNearest4&=~3;float Err = fabs((float)Nearest4-Candidate);if (Min>Err) {Min=Err;MinX=x;Best=Nearest4;}}switch (Best%16/4){case 0: Col = ShiftedColumns00[Best/16][0]; break;case 1: Col = ShiftedColumns04[Best/16][0]; break;case 2: Col = ShiftedColumns08[Best/16][0]; break;case 3: Col = ShiftedColumns12[Best/16][0]; break;}}if (TextureWidth<5) TextureWidth=5;ShingledReserve = MinX+TextureWidth-5;if (ShingledReserve>2) ShingledReserve=2;if (Mode2Dfx>2){WallTileList[Column].SqDist = SqDist;WallTileList[Column].X = Column*5+MinX;WallTileList[Column].Y = 100-TextureHeight/2;WallTileList[Column].XSize = TextureWidth;WallTileList[Column].YSize = TextureHeight;WallTileList[Column].ImgPtr = Col;}else BlitWallLikeTheNeoGeo (Column*5+MinX, 100-TextureHeight/2, TextureWidth, TextureHeight, Col);}else if (Mode2Dfx)//Trivial (Naive) NeoGeo{TextureHeight = (long)(ScaleF[Column*5+2])*(long)TextureHeight/2048/24;long TextureWidth = 16 * TextureHeight * CosA / 128;if (TextureHeight>200) TextureHeight=200;//Debug! Should be 1..256 (up to 1:1) but it'll require croppingunsigned char __far *Col;switch (Texture%16/4){case 0: Col = ShiftedColumns00[Texture/16][0]; break;case 1: Col = ShiftedColumns04[Texture/16][0]; break;case 2: Col = ShiftedColumns08[Texture/16][0]; break;case 3: Col = ShiftedColumns12[Texture/16][0]; break;}if (TextureWidth>18 || TextureWidth<3)//Two repeating pixels are still OKCol = ShiftedColumnsBl[Texture/16][0];if (TextureWidth<5)BlitWallLikeTheNeoGeo (Column*5, 100-TextureHeight/2, 5 , TextureHeight, Col);elseBlitWallLikeTheNeoGeo (Column*5, 100-TextureHeight/2, TextureWidth, TextureHeight, Col);} else {//Unrestricted spritesTextureHeight = (long)(ScaleF[Column*5+2])*(long)TextureHeight/2048/24;for (int x=-2; x<=2; x++){float Offset = (float)x * 128.0/(float)TextureHeight / CosA;int TexCol = Texture+Offset;TexCol %= 256;for (int y=0; y<200; y++){int TexY;if (TextureHeight) TexY = (signed long)(y-100)*256L / (signed long)(TextureHeight) + 128;else TexY=256-128*(y==100);if (TexY<0 || TexY>255) Frame[y][Column*5+2+x]=0;else Frame[y][Column*5+2+x]=TestWall[TexY][TexCol];}}}}return 0;// 0 is for "blacklist everything" (ray hits an opaque texture and terminates).}static unsigned char __far Pal256[256][3];void main (void){char const * Err;int MobX, MobH;int i, j, k, pt;unsigned short RayX, RayY, BrezDistToStartSteps, BrezDistToEndShifts, BrezPrev, BrezShiftPerCell, DistToX, DistToY;//6 MSB = map tile, 6 bits = pixel inside a tile, 4 LSB = pixel fractions.signed short RayDX, RayDY;unsigned long SqDist;unsigned char ThisTick, PrevTick;signed long Temp;int MapCell;static char BlackList[256]; //When the ray meets the MObj, it can hit up to 3 (of 4 total) "link cells", pointing to the same MObj. To avoid surplus calls, we should blacklist it's pointer value after first encounter.__asm{mov ax, 0x0013 //VGA 320x200int 0x10}Err=LoadBMP256("Shhhhhh!.bmp",Pal256,TestCaco[0],64,64);if (Err){cout<<Err<<endl;return;}for (int Stripe=0; Stripe<4; Stripe++) for (int Y=0; Y<64; Y++) for (int X=0; X<16; X++) {int XSrc = Stripe*16+X;MobColumns[Stripe][Y][X] = TestCaco[Y][XSrc]; }Err=LoadBMP256("Blurry.bmp",Pal256,TestWall[0],256,256);if (Err){cout<<Err<<endl;return;}for (int Stripe=0; Stripe<16; Stripe++) for (int Y=0; Y<256; Y++) for (int X=0; X<16; X++) {int XSrc = (Stripe*16+X) % 256;ShiftedColumnsBl[Stripe][Y][X] = TestWall[Y][XSrc]; }Err=LoadBMP256("TestWall.bmp",Pal256,TestWall[0],256,256);if (Err){cout<<Err<<endl;return;}SetPalette256 (Pal256[0]);for (i=0; i<320; i++)for (j=0; j<256; j++)Frame[(long)j*200L/256L][i] = TestWall[j][(long)i*256L/320L];Blt256 (Frame[0]);for (int Stripe=0; Stripe<16; Stripe++) for (int Y=0; Y<256; Y++) for (int X=0; X<16; X++) {int XSrc = (Stripe*16+X) % 256;ShiftedColumns00[Stripe][Y][X] = TestWall[Y][XSrc];XSrc = (Stripe*16+X+4) % 256;ShiftedColumns04[Stripe][Y][X] = TestWall[Y][XSrc];XSrc = (Stripe*16+X+8) % 256;ShiftedColumns08[Stripe][Y][X] = TestWall[Y][XSrc];XSrc = (Stripe*16+X+12) % 256;ShiftedColumns12[Stripe][Y][X] = TestWall[Y][XSrc]; }/*for (int RndX=0; RndX<2; RndX++) for (int RndY=0; RndY<2; RndY++) for (int Y=0; Y<256; Y++) for (int X=0; X<16; X++) {int SpotX=rand()%256;int SpotY=rand()%256;long AveR = Pal256[ TestWall[SpotY][SpotX] ][ 0 ]*2;long AveG = Pal256[ TestWall[SpotY][SpotX] ][ 1 ]*2;long AveB = Pal256[ TestWall[SpotY][SpotX] ][ 2 ]*2;AveR += Pal256[ TestWall[SpotY][(SpotX+1 )%256] ][ 0 ];AveG += Pal256[ TestWall[SpotY][(SpotX+1 )%256] ][ 1 ];AveB += Pal256[ TestWall[SpotY][(SpotX+1 )%256] ][ 2 ]; AveR += Pal256[ TestWall[SpotY][(SpotX+255)%256] ][ 0 ];AveG += Pal256[ TestWall[SpotY][(SpotX+255)%256] ][ 1 ];AveB += Pal256[ TestWall[SpotY][(SpotX+255)%256] ][ 2 ]; AveR += Pal256[ TestWall[(SpotY+1 )%256][SpotX] ][ 0 ];AveG += Pal256[ TestWall[(SpotY+1 )%256][SpotX] ][ 1 ];AveB += Pal256[ TestWall[(SpotY+1 )%256][SpotX] ][ 2 ]; AveR += Pal256[ TestWall[(SpotY+255)%256][SpotX] ][ 0 ];AveG += Pal256[ TestWall[(SpotY+255)%256][SpotX] ][ 1 ];AveB += Pal256[ TestWall[(SpotY+255)%256][SpotX] ][ 2 ];AveR /= 6;AveG /= 6;AveB /= 6;int C;long CM=2000000000L;for (int i=0; i<256; i++){long Err = ( (long)(Pal256[i][0])-AveR )*( (long)(Pal256[i][0])-AveR );Err += ( (long)(Pal256[i][1])-AveG )*( (long)(Pal256[i][1])-AveG );Err += ( (long)(Pal256[i][2])-AveB )*( (long)(Pal256[i][2])-AveB );if (CM>Err) {C=i; CM=Err;}}Blurry[RndX][RndY][Y][X] = C; }*/for (i=-160;i<160;i++) ScaleF[i+160]=sqrt(i*i*10000L + 16000L*16000L);X = Y = 1.5 * 1024;//Player start position and angleAngle = 0;__asm{mov ax,0x00int 0x33mov i,ax}if (i==-1) printf ("Mouse init completed\r\n");else{printf ("This demo requires a mouse.\r\n");return;}for(;;){pt=0;if (ModeVGA == 1 && Mode2Dfx){ShingledReserve=0;memset (Frame[0], 0xF7, 320*100);memset (Frame[100], 0xF8, 320*100);}/*memset (Blacklist, 0, 128);*/ BlackList[128] = 0;//No blacklist (for NeoGeo Test Bench; the mob is drawn at once, unlike the VGA column-by-column drawing, so it's blacklisted per scene, not per column, too).MobCount = 0;for (i=0; i<64*ModeVGA; i++)//320 pixels, 64 columns 5 pixels each.{MobH = 0;RayDX = DX + (long)DY*(i-32*ModeVGA)/(32*ModeVGA);RayDY = DY - (long)DX*(i-32*ModeVGA)/(32*ModeVGA);RayX=X;RayY=Y;/*memset (Blacklist, 0, 128);*/ BlackList[0] = 0;//No blacklist (for literal VGA, which is disabled completely).float R = sqrt ((float)RayDX*(float)RayDX + (float)RayDY*(float)RayDY);//ToDo: fast 1/sqrt, of course!//BlackList[128]=1; //quick hack to fully remove our only test mob from the raycast engineif (RayDX>0){if (RayDY>0){if (RayDX>RayDY)//X+ Y+ |X|>|Y|{BrezDistToStartSteps = 1024 - (RayX%1024);BrezShiftPerCell = 1024L*(long)RayDY/(long)RayDX;BrezPrev = RayY;RayY += (long)BrezDistToStartSteps * (long)BrezShiftPerCell / 1024L;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = 1024 - (BrezPrev%1024);BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX+(long)BrezDistToEndShifts*(long)RayDX/(long)RayDY, BrezPrev+BrezDistToEndShifts, i, 'D', RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}RayX += BrezDistToStartSteps;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'L', RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}while(1)//ToDo: after some reasonable distance, we need no more steps because only single-pixel horizon is visible!{BrezPrev = RayY;RayY += BrezShiftPerCell;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = 1024 - (BrezPrev%1024);BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX+(long)BrezDistToEndShifts*(long)RayDX/(long)RayDY, BrezPrev+BrezDistToEndShifts, i, 'D', RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}RayX += 1024;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'L', RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}}//HitOpaque(some horizon blabla);//goto RayFinished;} else {//X+ Y+ |X|<|Y|BrezDistToStartSteps = 1024 - (RayY%1024);BrezShiftPerCell = 1024L*(long)RayDX/(long)RayDY;BrezPrev = RayX;RayX += (long)BrezDistToStartSteps * (long)BrezShiftPerCell / 1024L;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = 1024 - (BrezPrev%1024);BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, BrezPrev+BrezDistToEndShifts, RayY+(long)BrezDistToEndShifts*(long)RayDY/(long)RayDX, i, 'L', RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}RayY += BrezDistToStartSteps;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'D', RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}while(1)//ToDo: after some reasonable distance, we need no more steps because only single-pixel horizon is visible!{BrezPrev = RayX;RayX += BrezShiftPerCell;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = 1024 - (BrezPrev%1024);BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, BrezPrev+BrezDistToEndShifts, RayY+(long)BrezDistToEndShifts*(long)RayDY/(long)RayDX, i, 'L', RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}RayY += 1024;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'D', RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}}//AddToZBufferAndBlacklist(some horizon blabla);//goto RayFinished;}} else {if (RayDX>-RayDY)//X+ Y- |X|>|Y|{BrezDistToStartSteps = 1024 - (RayX%1024);BrezShiftPerCell = 1024L*(long)(-RayDY)/(long)RayDX;BrezPrev = RayY;RayY -= (long)BrezDistToStartSteps * (long)BrezShiftPerCell / 1024L;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = BrezPrev%1024 + 1;BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX+(long)BrezDistToEndShifts*(long)RayDX/(long)(-RayDY), BrezPrev-BrezDistToEndShifts, i, 'U', -RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}RayX += BrezDistToStartSteps;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'L', RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}while(1)//ToDo: after some reasonable distance, we need no more steps because only single-pixel horizon is visible!{BrezPrev = RayY;RayY -= BrezShiftPerCell;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = BrezPrev%1024 + 1;BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX+(long)BrezDistToEndShifts*(long)RayDX/(long)(-RayDY), BrezPrev-BrezDistToEndShifts, i, 'U', -RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}RayX += 1024;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'L', RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}}//HitOpaque(some horizon blabla);//goto RayFinished;} else {//X+ Y- |X|<|Y|BrezDistToStartSteps = RayY%1024 + 1;BrezShiftPerCell = 1024L*(long)RayDX/(long)(-RayDY);BrezPrev = RayX;RayX += (long)BrezDistToStartSteps * (long)BrezShiftPerCell / 1024L;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = 1024 - (BrezPrev%1024);BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, BrezPrev+BrezDistToEndShifts, RayY-(long)BrezDistToEndShifts*(long)(-RayDY)/(long)RayDX, i, 'L', RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}RayY -= BrezDistToStartSteps;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'U', -RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}while(1)//ToDo: after some reasonable distance, we need no more steps because only single-pixel horizon is visible!{BrezPrev = RayX;RayX += BrezShiftPerCell;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = 1024 - (BrezPrev%1024);BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, BrezPrev+BrezDistToEndShifts, RayY-(long)BrezDistToEndShifts*(long)(-RayDY)/(long)RayDX, i, 'L', RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}RayY -= 1024;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'U', -RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}}//AddToZBufferAndBlacklist(some horizon blabla);//goto RayFinished;}}} else {//Big Second Half Of X<0if (RayDY>0){if (-RayDX>RayDY)//X- Y+ |X|>|Y|{BrezDistToStartSteps = RayX%1024 + 1;BrezShiftPerCell = 1024L*(long)RayDY/(long)(-RayDX);BrezPrev = RayY;RayY += (long)BrezDistToStartSteps * (long)BrezShiftPerCell / 1024L;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = 1024 - (BrezPrev%1024);BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX-(long)BrezDistToEndShifts*(long)(-RayDX)/(long)RayDY, BrezPrev+BrezDistToEndShifts, i, 'D', RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}RayX -= BrezDistToStartSteps;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'R', -RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}while(1)//ToDo: after some reasonable distance, we need no more steps because only single-pixel horizon is visible!{BrezPrev = RayY;RayY += BrezShiftPerCell;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = 1024 - (BrezPrev%1024);BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX-(long)BrezDistToEndShifts*(long)(-RayDX)/(long)RayDY, BrezPrev+BrezDistToEndShifts, i, 'D', RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}RayX -= 1024;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'R', -RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}}//HitOpaque(some horizon blabla);//goto RayFinished;} else {//X- Y+ |X|<|Y|BrezDistToStartSteps = 1024 - (RayY%1024);BrezShiftPerCell = 1024L*(long)(-RayDX)/(long)RayDY;BrezPrev = RayX;RayX -= (long)BrezDistToStartSteps * (long)BrezShiftPerCell / 1024L;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = BrezPrev%1024 + 1;BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, BrezPrev-BrezDistToEndShifts, RayY+(long)BrezDistToEndShifts*(long)RayDY/(long)(-RayDX), i, 'R', -RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}RayY += BrezDistToStartSteps;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'D', RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}while(1)//ToDo: after some reasonable distance, we need no more steps because only single-pixel horizon is visible!{BrezPrev = RayX;RayX -= BrezShiftPerCell;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = BrezPrev%1024 + 1;BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, BrezPrev-BrezDistToEndShifts, RayY+(long)BrezDistToEndShifts*(long)RayDY/(long)(-RayDX), i, 'R', -RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}RayY += 1024;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'D', RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}}//AddToZBufferAndBlacklist(some horizon blabla);//goto RayFinished;}} else {if (RayDX<RayDY)//X- Y- |X|>|Y|{BrezDistToStartSteps = RayX%1024 + 1;BrezShiftPerCell = 1024L*(long)RayDY/(long)RayDX;BrezPrev = RayY;RayY -= (long)BrezDistToStartSteps * (long)BrezShiftPerCell / 1024L;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = BrezPrev%1024 + 1;BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX-(long)BrezDistToEndShifts*(long)RayDX/(long)RayDY, BrezPrev-BrezDistToEndShifts, i, 'U', -RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}RayX -= BrezDistToStartSteps;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'R', -RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}while(1)//ToDo: after some reasonable distance, we need no more steps because only single-pixel horizon is visible!{BrezPrev = RayY;RayY -= BrezShiftPerCell;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = BrezPrev%1024 + 1;BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX-(long)BrezDistToEndShifts*(long)RayDX/(long)RayDY, BrezPrev-BrezDistToEndShifts, i, 'U', -RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}RayX -= 1024;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'R', -RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}}//HitOpaque(some horizon blabla);//goto RayFinished;} else {//X- Y- |X|<|Y|BrezDistToStartSteps = RayY%1024 + 1;BrezShiftPerCell = 1024L*(long)RayDX/(long)RayDY;BrezPrev = RayX;RayX -= (long)BrezDistToStartSteps * (long)BrezShiftPerCell / 1024L;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = BrezPrev%1024 + 1;BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, BrezPrev-BrezDistToEndShifts, RayY-(long)BrezDistToEndShifts*(long)RayDY/(long)RayDX, i, 'R', -RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}RayY -= BrezDistToStartSteps;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'U', -RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" is terminated}while(1)//ToDo: after some reasonable distance, we need no more steps because only single-pixel horizon is visible!{BrezPrev = RayX;RayX -= BrezShiftPerCell;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BrezDistToEndShifts = BrezPrev%1024 + 1;BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, BrezPrev-BrezDistToEndShifts, RayY-(long)BrezDistToEndShifts*(long)RayDY/(long)RayDX, i, 'R', -RayDX/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}RayY -= 1024;if ( (MapCell=Map[RayY>>10][RayX>>10]) != 0 && !BlackList[MapCell])//Order does matter!{BlackList[AddToZBufferAndBlacklist(MapCell, X, Y, RayX, RayY, i, 'U', -RayDY/R)]=1;if (BlackList[0]) goto RayFinished;//Ray "i" stops marching}}//AddToZBufferAndBlacklist(some horizon blabla);//goto RayFinished;}}}RayFinished:__asm{mov ax, 0mov es,axmov al,es:[0x046c]//system timermov ThisTick,al}if (ThisTick!=PrevTick){PrevTick=ThisTick;if (!DoGameplayTick() ) return;}}if (Mode2Dfx>2) LayeredDraw();Blt256 (Frame[0]);}}
LevelMap.H:
static unsigned char Map[64][64]=//such a TRADITIONAL size!{{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}};
Чтобы собрать это поделие, вполне подойдёт OpenWatcom 1.9 с проектом DOS-16 8086 Large memory model (офигенная, реально офигенная среда разработки для такого вот ретро). Надеюсь, выбор модели более‑менее облегчит портирование на целевую консоль (хотя тут и портировать‑то нечего — тут даже кода готового нет, просто тестбенч для демонстрации работы алгоритма). Палитра Doom, Testwall.BMP и Blurry.BMP — как уже говорил выше, текстуры 256×128, растянутые вертикально до 256×256, одна — разблюрена. Shhhhhh!.bmp — естественно, наша красная шипучка с содовой, никуда не растянутая, палитра та же (особенно прозрачный цвет). Я попробую прицепить к статье.PNG, который можно пересохранить «ирфаном» как равку и она откроется как 7z‑архив — но ничего гарантировать, конечно, не могу. Вообще было бы очень неплохо в условиях нынешней социальной катастрофы в бывшем Интернете, раздираемом на куски как политиками, так и потерявшими берега корпорациями, сделать опцию прикрепления к статьям небольших архивов с проектами, увы… Спойлер: ирония в том, что я потерял доступ к своему аккаунту на «итче» из‑за того, что несколько копрораций одновременно сделали «для моей безопасности» новую стофакторную авторизацию, от которой нельзя отказаться и которая в этот раз срослась в цепочку, уходящую в бесконечность. В кои‑то веки это (почти) не вина сами знаете кого, прямо даже смешно получилось. В связи с этим хотелось бы попросить кого‑нибудь из постоянных переводчиков Хабра «сделать бэкпорт» этой статьи на английский и скинуть ссылку людям, замутившим всё это безобразие — ведь трудно пройти мимо такого безобразия, не приняв деятельного участия:) Возможно, они всё‑таки сделают для NeoGeo настоящий, чистопородный Wolfenstein 3-D. А потом, может быть, и форк Doom8088 под «неогешный» проц.
Update: пока я над этим работал, вышла статья про аналогичные успехи коллег. И да, Doom действительно заставили работать на «устройстве процессорсодержащем», а выхлоп BSP действительно скормили тем или иным аппаратным скалерам. Думаю, и мой скалер кому‑нибудь из них пригодится, ведь он в самом деле практически не «полосит» и потенциально позволяет отрисовывать несколькими наборами полосок все текстуры стен (lower, upper, middle), буде они таки сумеют это прочесть.
ссылка на оригинал статьи https://habr.com/ru/articles/1060368/