2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




 
 3D -> 2D (устранение багов, приведение к общепринятому виду)
Сообщение03.03.2011, 21:52 
Здравствуйте,

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

В результате это всё работает, но осталось несколько багов, которые хотелось бы устранить. Если еще точнее - привести всё это к общепринятому виду. :)

Сначала - листинг основных моментов кода (без рутины вроде описания вершин тестового куба и т.п. вещей). Пишу на C/C++ (хотя от C++ тут фактически ничего нет):

Функция GetAngleXflat вычисляет азимутальный угол между направлением из точки src в dst и направлением вдоль оси X ("горизонтальный" угол):

Код:
double GetAngleXflat (ThePoint src, ThePoint dst)
{
   double tempangle;
   if(src.x == dst.x)  // if TAN = +/- infinity
   {
      if(dst.y > src.y) // uppermost part
         tempangle = pi/2;
      else // lowermost part
         tempangle = 3*pi/2;
   }
   else  // usual case
   {
      if(dst.x > src.x)  // right semicircle
         tempangle = atan((dst.y-src.y)/(dst.x-src.x));
      else // left one
         tempangle = atan((dst.y-src.y)/(dst.x-src.x))+pi/2;
   }
   if(tempangle < 0)
      tempangle = tempangle + 2*pi; // right bottom quarter
   return tempangle;
}


Функция GetAngleY вычисляет зенитный угол ("вертикальный" угол), правда, не от направления по Z, как обычно принято, а от направления "вдоль горизонта".

Код:
double GetAngleY (ThePoint src, ThePoint dst)
{
   double tempangle;
   double xydist;
   double zdiff;
   xydist = sqrt(pow((src.x-dst.x),2)+pow((src.y-dst.y),2));
   zdiff = dst.z-src.z;
   if(xydist==0) // just in case camera and some point is the same point
      tempangle = 0;
   else
      tempangle = atan(zdiff/xydist);
   return tempangle;
}


Функция GetAngleSky вычисляет телесный "горизонтальный" угол (он отличается от "обычного" горизонтального, см. картинку после кода):

Код:
double GetAngleSky (double AngleX, double AngleY)
{
   double tempangle;
   tempangle = asin(sin(AngleX)*cos(AngleY));
   re
turn tempangle;
}

Изображение

Именно этот телесный угол должен влиять на координату точки по X - иначе все вертикальные прямые всегда будут вертикальными на мониторе.

Функция UpdCoord для каждой точки по ее координатам x,y,z определяет ее экранные координаты с учетом того, насколько далеко она отстоит от точки, куда смотрит камера.
Два "типа монитора" - сферический и плоский. В сферическом воображаемый луч от точки проходит через сферическую поверхность перед камерой, оставляя на ней отметку в опреденном месте, в плоском - через "плоский прямоугольник". В первом случае зависимость координаты от угла прямая, во втором пришлось немного поупражняться в тригонометрии.

Код:
void UpdCoord(ThePoint& Pnt)
{
   double tempangleX;
   double tempangleY;
   double coeff;
   double delta;

   tempangleX = GetAngleXflat(cam,Pnt);
   tempangleX = tempangleX-CamAngleX; // насколько оно ЛЕВЕЕ центра взгляда
   tempangleY = GetAngleY(cam,Pnt);
   tempangleY = tempangleY-CamAngleY; // насколько оно ВЫШЕ центра взгляда
   tempangleX = GetAngleSky(tempangleX,tempangleY);
   if(MonitorShape==1)
   {
      coeff = tempangleX / FOVx;
   }
   else
   {
      coeff = cos(pi/2-tempangleX)/2/cos(pi/2-FOVx/2);
   }
   Pnt.scrx = 320-coeff*640;
   if(MonitorShape==1) // сферический монитор
   {
      coeff = tempangleY / FOVy;
   }
   else // плоский монитор
   {
      coeff = cos(pi/2-tempangleY)/2/cos(pi/2-FOVy/2);
   }
   Pnt.scry = 240-coeff*480;
}


Собственно, вот что происходит при обновлении экрана:

Код:
void TheDraw (void)
{
   int i;
   Form1->Image1->Picture = 0;
   for (i = 0; i < 4; i++)
      UpdCoord(grid[i]);
   for (i = 0; i < 12; i++)
      UpdCoord(TheCube[i]);
   for (i = 0; i < 20; i++)
      DrawLine(TheCube[CubeLines[2*i]], TheCube[CubeLines[2*i+1]]);
}


Всё бы было хорошо (как в сферическом, так и в плоском мониторе), если бы не одно но. Вернее, два, для двух случаев:

1) Если мы игнорируем телесный угол и строим координату X по углу отклонения точки от вертикальной плоскости, выходящей из камеры, а координату Y - по углу отклонения от такой же горизонтальной плоскости, то все вертикали всегда будут прямыми. Даже если мы смотрим вверх / вниз (наклоняя камеру), что не есть хорошо.
2) Если мы применяем телесный угол, то объекты становятся бочкообразными (см. скрины) и, более того, начинают не совсем адекватно реагировать на опускание / поднимание камеры и т.п. (сжатие, изменение формы).

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

Скрины:

Изображение - сферический монитор, видна "бочкообразность"
Изображение - плоский (просто для сравнения двух экранов проекции)
Изображение - плоский экран, камера опущена вниз.

На последнем скрине видно, что при опускании камеры вниз линии "разъезжаются". Достаточно запустить любую игру и найти в ней две вертикальные балки, чтобы убедиться, что при опускании взгляда линии должны сходиться (да и это следует из здравого смысла).

В общем, ошибка где-то в замене обычного горизонтального угла телесным. С радостью выслушаю замечания / советы.

 
 
 [ 1 сообщение ] 


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group