Здравствуйте,
Задался целью написать простенькую реализацию 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) Если мы применяем телесный угол, то объекты становятся бочкообразными (см. скрины) и, более того, начинают не совсем адекватно реагировать на опускание / поднимание камеры и т.п. (сжатие, изменение формы).
Мое предположение заключается в том, что я как-то неправильно определяю "видимый угловой размер" линии (ну или угловое отклонение точки) через этот пресловутый телесный угол. Вернее, формула-то там правильная для картинки, но, может быть, стоит применить другую модель вычисления этого самого "углового размера".
Скрины:
- сферический монитор, видна "бочкообразность"
- плоский (просто для сравнения двух экранов проекции)
- плоский экран, камера опущена вниз.
На последнем скрине видно, что при опускании камеры вниз линии "разъезжаются". Достаточно запустить любую игру и найти в ней две вертикальные балки, чтобы убедиться, что при опускании взгляда линии должны сходиться (да и это следует из здравого смысла).
В общем, ошибка где-то в замене обычного горизонтального угла телесным. С радостью выслушаю замечания / советы.