Comments 52
Там в Direct Draw просто получается адрес видеопамяти, который передаётся в класс видео CVideo. Дальше интерфейсный класс CIEngine определяет все функции вывода графики и загрузки данных. От этого класса унаследован класс CEngine_Base, в котором определены многие функции и добавлены защищённые функции, общие для все трёх вариантов движков (текстурирование линий, например). А уже от этого класса унаследованы сами движки. Все эти классы первым делом у CVideo запрашивают параметры видеоэкрана, а рисуют уже как в MS-DOS — записью в видеопамять.
Кстати, я под QNX предыдущую версию портировал.
#include "cmain.h"
CMain cMain;
extern CVideo cVideo;
//-Конструктор класса--------------------------------------------------------
CMain::CMain()
{
VideoBufferPtr=new unsigned long[WINDOW_WIDTH*WINDOW_HEIGHT];
cVideo.SetVideoPointer(VideoBufferPtr,WINDOW_WIDTH);
}
//-Деструктор класса---------------------------------------------------------
CMain::~CMain()
{
delete[](VideoBufferPtr);
}
//-Замещённые функции предка-----------------------------------------------
//-Новые функции класса------------------------------------------------------
//----------------------------------------------------------------------------------------------------
//открытие окна
//----------------------------------------------------------------------------------------------------
bool CMain::OpenWindow(void)
{
PgSetDrawBufferSize(65535);
cControl.Init();
return(true);
}
//----------------------------------------------------------------------------------------------------
//закрытие окна
//----------------------------------------------------------------------------------------------------
bool CMain::CloseWindow(void)
{
cControl.Close();
return(true);
}
//-Функции обработки сообщений класса----------------------------------------
//----------------------------------------------------------------------------------------------------
//нажали или отпустили кнопку мыши
//----------------------------------------------------------------------------------------------------
void CMain::OnActivate_MouseButton(long x,long y,bool left,bool right,bool center)
{
OnPaint();
}
//----------------------------------------------------------------------------------------------------
//перерисовать картинку
//----------------------------------------------------------------------------------------------------
void CMain::OnPaint(void)
{
PhDim_t ImageSize;
int ImageBPL=WINDOW_WIDTH*sizeof(unsigned long);
ImageSize.w=WINDOW_WIDTH;
ImageSize.h=WINDOW_HEIGHT;
PhPoint_t pos;
pos.x=0;
pos.y=0;
PgDrawImagev(VideoBufferPtr,Pg_IMAGE_DIRECT_8888,&pos,&ImageSize,ImageBPL,0);
}
//----------------------------------------------------------------------------------------------------
//обработка таймера
//----------------------------------------------------------------------------------------------------
void CMain::OnActivate_Timer(void)
{
cVideo.SetVideoPointer(VideoBufferPtr,WINDOW_WIDTH);
cControl.Processing();
OnPaint();
}
//-Новые функции класса------------------------------------------------------
//-Прочее--------------------------------------------------------------------
/* Y o u r D e s c r i p t i o n */
/* AppBuilder Photon Code Lib */
/* Version 2.03 */
/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <photon/PkKeyDef.h>
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>
/* Local headers */
#include "ablibs.h"
#include "abimport.h"
#include "proto.h"
#include "ckbrd.h"
extern CKeyboard cKeyboard;
int OnActivate_Raw(PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )
{
if (cbinfo->event->type==Ph_EV_KEY)
{
PhKeyEvent_t *kev=(PhKeyEvent_t *)PhGetData(cbinfo->event);
long scan=kev->key_scan;
if (kev->key_flags&Pk_KF_Scan_Valid)
{
if (kev->key_flags&Pk_KF_Key_Down) cKeyboard.SetKeyState(scan,true);
else cKeyboard.SetKeyState(scan&0x7f,false);
//printf("Scan:%i\r\n",scan);
}
}
return(Pt_CONTINUE);
}
Пожалуйста, укажите лицензию на код и, если совсем не затруднит, создайте репозиторий, например на GitHub или Bitbucket. Спасибо!
А на Github я не могу выложить — я никогда не работал с такими системами разработки и понятия не имею, как там вообще осуществляется работа. Я попытался понять, нифига не понял, что там вообще нужно делать. Дело в том, что я в команде никогда не работал (хоть я и пишу ПО по работе в НИИ, но я самоучка); у нас таких систем не применяется и опыта работы с ними у меня полный ноль.
Вроде бы MIT License самая разрешающая. https://ru.m.wikipedia.org/wiki/Лицензия_MIT
Правда, не знаю, кому эти исходники пригодиться вообще могут. Мне тут на одном форуме сказали, фигурально, что я фигнёй страдаю с софтверными движками и обычным OpenGL. И что давно вместо «forward rendering» используют Deferred Rendering и надо брать готовый движок типа Ogre3d и не изобретать велосипед. :) И я в общем, почти согласен с первым, но вот со вторым не соглашусь — зачем мне Ogre3D, если я игру делать всё равно не буду. :)
Спасибо! Качается!
1) Задаём исходный портал, совпадающий с областью поля зрения игрока.
2) Задаём прямую отсечения — это не более, чем прямая, перпендикулярная вектору зрения игрока. Она нужна, чтобы выбросить из рассмотрения всё, что за спиной игрока. Дело в том, что использованная математика требует, чтобы всё происходило перед игроком.
3) Назначаем текущим сектором, сектор, где находится игрок.
4) Выводим стены текущего сектора
5) Рекурсивно бегаем через все порталы сектора, модифицируя портал на ходу и меняя текущий сектор (как зашли в сектор -выводим все его стены). При этом блокируем портал при заходе в него и разблокирует после выхода (через один портал можно смотреть по-разному, в зависимости от того, как мы в него пришли).
Вот и всё.А вот картинка, как обрезаются порталы и стены.
В OpenGL обрезать нужно только порталы — стены сами обрежутся OpenGL с помощью Z-буфера.
Вроде всё. :)
//---------------------------------------------------------------------------
//импорт карты из текстового файла
//---------------------------------------------------------------------------
void CDocument_Map::ImportMapFromTXT(FILE *file,CDialog_SettingsWall *cDialog_SettingsWall_Ptr,CDialog_SettingsSector *cDialog_SettingsSector_Ptr)
{
long n;
DeleteAll();
//считываем текстовый файл в плоский массив (0-следующая строка)
vector<unsigned char> vector_data;
bool element=false;
long x=0;
long y=0;
long max_x=0;
while(1)
{
unsigned char b;
if (fread(&b,sizeof(char),1,file)<=0) break;//файл закончился
if (b<static_cast<unsigned char>(' '))
{
if (element==true)//если в строке что-то было считано, делаем перевод строки
{
y++;
vector_data.push_back(0);
}
element=false;
if (x>max_x) max_x=x;
x=0;//строка начинается заново
continue;
}
element=true;
x++;
vector_data.push_back(b);
}
long max_y=y;
//разворачиваем считанную карту в двумерный массив [max_y][max_x] и заполняем его точками
vector< vector<unsigned char> > vector_map(max_y,vector<unsigned char>(max_x,'.'));
x=0;
y=0;
long size=vector_data.size();
for(n=0;n<size;n++)
{
if (vector_data[n]==0 || n==size-1)
{
y++;
x=0;
continue;
}
if (vector_data[n]==static_cast<unsigned char>('#')) vector_map[max_y-y-1][x]=static_cast<unsigned char>('#');//непустое поле
x++;
}
vector_data.clear();
//создаём сектора и сегменты по считанной карте
CSector cSector_Create;//создаваемый сектор
cSector_Create.vector_SSectorPoint.clear();
cSector_Create.Select=false;
cSector_Create.sSector_State=cDialog_SettingsSector_Ptr->GetState();
CWall cWall_Create;//создаваемая стена
cWall_Create.Frontier=false;
cWall_Create.Select=false;
cWall_Create.sWall_State=cDialog_SettingsWall_Ptr->GetState();
for(y=0;y<max_y;y++)
{
for(x=0;x<max_x;x++)
{
unsigned char b=vector_map[y][x];
if (b==static_cast<unsigned char>('.'))//сектор пустой
{
//создаём пустой сектор
cSector_Create.vector_SSectorPoint.clear();
//добавляем точки четырёхугольника, описывающего сектор
SSectorPoint sSectorPoint[4];
sSectorPoint[0].X=(x+1)*100;
sSectorPoint[0].Y=(y+1)*100;
sSectorPoint[1].X=(x+1)*100;
sSectorPoint[1].Y=(y+0)*100;
sSectorPoint[2].X=(x+0)*100;
sSectorPoint[2].Y=(y+0)*100;
sSectorPoint[3].X=(x+0)*100;
sSectorPoint[3].Y=(y+1)*100;
cSector_Create.vector_SSectorPoint.push_back(sSectorPoint[0]);
cSector_Create.vector_SSectorPoint.push_back(sSectorPoint[1]);
cSector_Create.vector_SSectorPoint.push_back(sSectorPoint[2]);
cSector_Create.vector_SSectorPoint.push_back(sSectorPoint[3]);
//добавляем сектор в список секторов
vector_CSector.push_back(cSector_Create);
//если вокруг сектора есть стектора-стены, то добавляем стену, иначе добавляем линию раздела
for(n=0;n<4;n++)
{
long next_n=(n+1)%4;
cWall_Create.X1=sSectorPoint[n].X;
cWall_Create.Y1=sSectorPoint[n].Y;
cWall_Create.X2=sSectorPoint[next_n].X;
cWall_Create.Y2=sSectorPoint[next_n].Y;
cWall_Create.Frontier=true;//стена - линия раздела
if (n==0)//правая стена
{
if (x<max_x-1)
{
if (vector_map[y][x+1]!=static_cast<unsigned char>('.')) cWall_Create.Frontier=false;//сектор справа - стена
}
else cWall_Create.Frontier=false;
}
if (n==1)//нижняя стена
{
if (y>0)
{
if (vector_map[y-1][x]!=static_cast<unsigned char>('.')) cWall_Create.Frontier=false;//сектор снизу - стена
else continue;//не ставим портал, так как он уже есть с этой стороны
}
else cWall_Create.Frontier=false;
}
if (n==2)//левая стена
{
if (x>0)
{
if (vector_map[y][x-1]!=static_cast<unsigned char>('.')) cWall_Create.Frontier=false;//сектор слева - стена
else continue;//не ставим портал, так как он уже есть с этой стороны
}
else cWall_Create.Frontier=false;
}
if (n==3)//верхняя стена
{
if (y<max_y-1)
{
if (vector_map[y+1][x]!=static_cast<unsigned char>('.')) cWall_Create.Frontier=false;//сектор сверху - стена
}
else cWall_Create.Frontier=false;
}
//добавляем сегмент
vector_CWall.push_back(cWall_Create);
}
}
}
}
}
Круто! Впечатляет!
Но к этой статье приложен не он, а самодельный, гораздо менее мощный.
И вот что меня удивляет. 20 лет, как вышел Duke 3D. Армия программистов. А движков самодельных (хоть с OpenGL, Direct 3D, хоть сотфверных) сделано очень мало. Как так-то? Вообще странно, столько программистов, а весь инет не завален их интересными программами (например, графическими). То ли они пишут только на работе, а дома нет, то ли ещё есть какая причина, но при таком количестве программистов их программ должно быть сильно дофига.
Итак, мы получили обход стен от игрока в бесконечность. Теперь в дело вступают линии горизонта. Как они работают, я написал в этой статье.
Метод порталов тоже позволяет упорядочить стены от игрока в бесконечность. Но заключается в рекурсивном (последовательном) обходе выпуклых секторов (! не стен !), у которых определены отрезки, через которые виден другой сектор. Если начать обход через эти отрезки из сектора в сектор от игрока (мы же можем найти в каком он секторе?), то мы получим метод порталов.
1) какая зависимость от выбора стратегии разрезания? резать можно в двух направлениях
Резать можно в любых направлениях. От разрезающей линии зависит сбалансированность дерева. Можно вообще выбирать любой отрезок за линию разрезания. Лишь бы не получалось всё время с одной стороны пусто, а с другой вся карта — это, понятно, процесс бесконечный.
2) не проще ли считать хеш от координат — это будет O(1) переходов, а не O(Log2(N))?
Я не понял, что такое хэш координат и зачем он вам. Дерево формирует упорядоченный вывод стен от наблюдателя в даль.
3) где у вас вектор взгляда и как он фигурирует в дереве?
В дереве он не фиксируется. Вектор взгляда формирует конус видимости (угол сектора взгляда вычисляется по границам экрана). Этот вектор используется для отбрасывания поддеревьев, которые в него не попадают целиком (это легко проверить, посмотрев координаты описывающего поддерева прямоугольника и сравнив их с левой и правой линиями сектора обзора). Такие поддеревья обходить при выводе не нужно.
4) и не просто вектор взгляда, а отсекающий треугольник камеры, как порталы работают если попадают в камеру частично?
Геометрический портал просто отсекается по границам области видимости. Экранный отсекается автоматически экраном.
последний абзац совсем не понимаю, откуда в вашем двоичном дереве отрезков появляются выпуклые секторы?
Порталы не используют BSP. Они требуют изначально выпуклых секторов и отсутствие стен внутри них (только по границам).
BSP-дерево не требует выпуклых секторов, но, обычно, сектора всё равно делают выпуклыми (так легко определять наличие объектов в них). Внутри секторов могут быть стены (а не только по контуру). Поэтому при разбиении можно дойти до ситуации, когда остались только стены, формирующие выпуклую фигуру. Вот её уже можно выводить целиком — в ней стены друг друга не перекрывают. Это и использует DooM.
Сразу скажу, что метод порталов быстрее, чем BSP. Именно это позволяло Duke Nukem работать быстрее DooM.
пока я не увидел объяснения этого. для меня деление на две части — суть сортировка, а так же это битовое кодирование пути. в какой момент там появляется вектор взгляда и почему деление на две части вдруг одинаково работает для любой позиции наблюдателя (без информации о камере) да еще и откуда-то берется информация о взаимном перекрытии. Вот это и не ясно.
А это и есть сортировка по отношению к выбранной линии разделения. Вектор взгляда там для возможности отбрасывать поддеревья, про которые точно известно, что они не видны из позиции наблюдателя для ускорения вывода. Можно его не использовать и выводить весь обход дерева целиком. А работает этот потому, что когда вы строите дерево, вы делаете декомпозицию сцены на всё более мелкие элементы, расположенные определённым образом относительно выбранных линий наблюдения. То есть, движетесь от большой сцены к её отдельному элементу. При обратном проходе от наблюдателя вы просто идёте от меньших элементов к большим. Вы как бы с каждой итерацией отдаляетесь от наблюдателя. Проверьте, это точно работает. :)
Ну вы собираетесь по двоичному дереву бегать, чтобы получить информацию о том какие стены попадают во взгляд, а я предлагаю заранее обсчитать все точки на уровне и присвоить им хеш, тогда в любой момент в любом месте карты у вас будет сразу доступен ограниченный перечень координат стен
Это потребует приличных вычислительных затрат на этапе построения карты. И это не так делается. Такая техника сделана для Quake для ускорения — там BSP-дерево трёхмерное. Quake бьёт до объёмных секторов, а потом цепляет к ним список видимых из данного сектора других секторов (они в поддеревьях). При раскручивании дерева, он выбрасывает всё, что не видимо. В DooM такая техника избыточна и не применяется.
Нарисованное на картинке совсем не понял, если у вас есть треугольник зоны видимости зачем вам еще прямая зоны видимости?
Просто эта картинка немного о другом изначально (я новую рисовать не стал) — она от метода порталов. Но суть в целом та же. Просто вместо портала конус видимости.
Прямая эта позволяет очень просто отбрасывать поддеревья за спиной, не возясь с конусом.
я не знаю что такое BSP и как оно работает, поэтому объяснять порталы через BSP плохая идея.
Я не объяснял порталы через BSP. Я объяснял и то и другое. :)
В общем мне кажется это бесполезной тратой времени.
Почему? Возьмите книжку «Шикин, Боресков. Компьютерная графика, полигональные модели». Я по ней в 2001 году делал свой DooM. Он под MS-Dos. А выглядит он так (в этом видео он на 3:19).
PS: еще из ваших объяснений у меня сложилось впечатление что слово «портал» тут применяется в какой-то абстрактном значении, как если бы кто-то голубя назвал квадратом просто потому что ому-то это пришло в голову.
Да нет…
Портал (англ. portal от лат. porta «ворота»): Портал — главный вход большого архитектурного сооружения. Портал (сцены) — архитектурный элемент, окаймляющий сцену и отделяющий её от зрительного зала.
Вот и вся идея в том, что из одного сектора другой можно видеть только через портал, их соединяющий.
не понимаю контекста
А вы нарисуйте карту и посмотрите, как меняется конус видимости, отсекаясь по порталам при переходе из сектора где находится игрок и в даль. Это и есть геометрический портал — вы все выводимые линии каждый раз этим конусом обрезаете ДО вывода). А экранный, это когда этот конус автоматически просто уменьшается прямо по экрану (это координаты на экране X слева и справа (зона вывода находится между ними) при выводе) при отрисовке порталов между секторами.
Потому что я с этим разобрался в 18 лет ровно по этой же книжке. Никаких чудес там нет. Всё очень просто и понятно.
Вы как себе это представляете?
На практике это просто лень затратить усилия и пытаться понять до просветления. Никогда не готовились к экзаменам? Сидишь над конспектом (бывает, совсем не очевиден кусочек перехода от одного к другому) и пытаешься понять, «ПОЧЕМУ ТАК?!».
и оно никак не совпадает с вашим вариантом даже в ближайшем приближении.
Потому что вы не попытались затратить усилия понять, как оно так работает и почему.
В книге, которую вы дали — действительно всё просто — идём по соседним комнатам и всё. Но там это описано как способ оптимизации, то есть это не алгоритм упорядоченного выбора, это минимизация выборок для проверки.
Вам не очевидно, что если начать ходить из комнаты, где игрок, всё дальше и дальше через первый портал, то вы получите упорядочение ОТ ИГРОКА через этот портал? Так же для следующего. Там обычный рекурсивный обход.
я правильно понимаю что вам в школе просто давали учебник и ждали когда наступит просветление?
Далеко не всё из учебника я понимал с первого раза. И даже с сотого не всегда получалось.
Так можно просидеть всю жизнь, не?
Интересно, интересно, как вы собираетесь постигать сложные концепции (которые и самим ими оперирующими часто не очень-то ясны — квантмех там всякий, теорию поля)?
Вы слишком много на себя берёте давая оценку другому человеку в таком виде.
Это не коррелирует с «Так можно просидеть всю жизнь, не?»? Перевожу с вашего: «мне лень тратить время на понимание. Обучите меня с минимальными затратами с моей стороны!»
1) Как хранится информация о векторе взгляда? От этого напрямую зависит направление обхода, ведь место где находится игрок может граничить с бесконечным числом порталов в соседние комнаты.
Никак не хранится. У вас в текущий момент времени есть (x,y) и угол обзора (alpha). Ищете в каком секторе игрок. Рисуете его. Дальше рекурсия: для каждого видимого (того, который попадает в сектор обзора игрока) портала сектора, где находится игрок, выводите видимую через портал часть сектора смежного с порталом. Теперь берёте этот смежный сектор за исходный и перебираете его порталы, видимые через портал с которого попали. И снова и снова всё это повторяете, обходя сектора друг за другом. Замечу, что каждый портал обрезает собой косус взгляда. По мере удаления, этот конус уменьшается (или вообще пропадает). Поймите, портал — это ограничение взгляда игрока! С каждым порталом происходит коррекция конуса видимости (он отсекается по порталу).
Например в вашем объяснении не ясно зачем нам всё дерево распилов всей карты, если игрок бегает по одной комнате в самом дальнем углу карты со всего одним порталом, который при правильной реализации п.3 будет за О(1) операций всегда выбираться из массива.
Это ДВА РАЗНЫХ метода, между собой не связанных. Двоичное разбиение пространства позволяет выполнить ту же задачу вывода от игрока вдаль, что и метод порталов, но другим способом.
3) Без использования хеширования вы будете перебирать постоянно N комнат и если N сильно большое, близкое к общему числу комнат, то вы фактически будете бегать постоянно по всем комнатам карты, хотя на экране вообще смотрите в стену.
Так не буду же. Вот я нашёл, что я в секторе 6. У меня есть список его порталов и стен. Порталы связывают его с секторами 3 и 10. Их порталы мне тоже известны. Ну и зачем мне хэширование? Я сразу на обычном массиве секторов бегаю из сектора в сектор и получаю сразу его стены и порталы.
Я всё же склонен считать, что вы в силу лёгкости и простоты темы ЛИЧНО ДЛЯ ВАС ушли в её понимании так далеко, что не в состоянии опуститься до уровня первого вхождения в тему и объяснить материал доходчиво.
У меня очень средние способности, не преувеличивайте. Я бы показал бы вам видео, как работают эти методы шаг за шагом, но я не нашёл такого видео.
Поэтому будьте попроще, я не навязываюсь и если вам неинтересно что-то рассказать и объяснить — это ваше право, но не нужно всё сводить к моей лени, вы меня не знаете.
Лень легко проверяется. Кто не ленится, тот пытается составить картинку понимания, пересказать её, и задать уточняющие вопросы, правильно ли он понял, что происходит. А вот кто ленится, тот просто сообщает, что ничего не понял и точка. Иными словами, ленящийся сопротивляется осмыслению сказанного, а пытающийся понять наоборот.
Возможно вы бы произвели революцию в образовательной сфере.
Во многих случаях просто заучить (как с английским) бывает мало.
тогда представляйте не конус а проекционную плоскость где луч из центра(прям из центра окна он может быть и тангенс центр окна в начале счета пока еще не относиться к world пространству далее надо сделать из этой точки расчет тоесть как раз пустить луч = перпендикуляр от этой плоскости в сторону направления) это вектор взгляда тоесть это w h, тоесть да, будет сначала w h по окну, потом у игрока может быть плоскость

в таких мирах всегда будет рейкаст из центра окна даже в 3д
opengl/vulkan/direct
суть в том, что в таких древних реализациях мы имеем карту в виде 2д и её надо перекинуть либо в уме в 3д и побить через BVH, или изза того что у нас древний подход который работает сдесь и сейчас, мы делим сегменты на подсегменты или карту, соотв там еще есть подходы кидать луч по сырому массиву 2д говорят он быстрее, но потом из-за свойств отрисовки и построения самого мира мы возращаемся к центровому рейкасту
теперь насчет карты(она называется разбиение карты) в статье, карта представлена в метрике имеет квадраты, и на такой чертежной карте нарисованы линии, что бы вы не путались представляйте просто там где линия черная - 1, там где пусто 0, синие линии пока можно избегать на первое время
синие линии это портал или лестница
теперь зная что есть направление и ширина(напоминаю ширина по окну по горизонтали - а возможно по горизонту, ниже пол, если видим единичку кидаем линию цвета), мы просто заполняем линиями в первом подходе цветовыми далее если надо полнять текстуру текстурными линиями, но это всё равно будет покачто рейкаст, а так конечно можно и точками и кубиками и идти прям строго по проекции ортогональной, самое изящное будет как раз строить чанки вокселей, там будет всё тоже самое в первых подходах
соотв бсп работает в контексте рёбер поидее
есть плоскости, плоскости делятся на квадраты квадраты на линии, процессинг бсп как бы отсекая квадрат по иерархии не зайдёт в линии, или через линии не зайдёт в квадрат, соотв у нас дерево линий(ну интервалов) и бидирекция типо спереди/сзади(или какаято двоичная логика вобщем)
Создание софтверного движка 2.5D