Нашел тут способ "эмулировать" 5-ую степень свободы без затрат на вычисления.
Так как это при проецировании используются некоторые хитрости, этот способ не даст нам настоящей проекции, но он оказался очень хорош, когда мы "ходим" по карте невысоко и не заглядываем слишком низко. Способ предельно прост: мы просто меняем угол, под которым лучи идут к карте высот. Соответственно, уравнение для координаты z точки на луче примет вид:

Увеличивая или уменьшая k и получим пятую степень свободы. Когда плоскость экрана и карта высот параллеьлны, точка на карте должна соответствовать точке на экране, но у нас всё до сих пор рисуется столбцами. Вот почему этот способ не действует всегда. Но меня удивило, что он вообще действует (и неплохо) ;)
Ещё я дорисовал к своей демке кружочек в середину экрана, который уменьшается при пересечении с более дальними объектами. Коэффициент уменьшения нахожу так:

dc - расстояние от камеры до точки пересечения, которе ищем в цикле по d
dc = d, если
1) sx = scw/2 - координата на экране x равна половине ширины экрана
2) sy <= sch/2 - текущая экранная координата y такая, что столбец будет нарисован выше, чем на полэкрана
3) sy_old>sch/2 - а предыдущая - ниже
4) maxh[sx]>sy - гарантия того, что столбец точно будет нарисован (y-буффер обновляется ниже по коду). О том, что такое maxh смотри вторую статью.
Может быть такое, что на всей длине луч не пересекся ни с одной точкой, которая отображается в середине экрана. Это проверить так: после цикла по d проверить условия:
1) sx = scw/2
2) max[sx] > sch/2 (теперь y-буффер обновлен и если самый высокий пиксел так и не достиг половины экрана, то прицел не рисуем)
Для надежности d лучше определять как среднее арифметическое между d в текущем цикле и в предыдущем (когда d соответствует sy_old)
Зная, какой sx = scw/2 соответствует луч, можно восстановить координаты на карте высот x,y пересечения луча с ландшафтом (об этом не буду, см. статью 2).
Далее я добавил возможность нарастить высоту на карте в этой точке очень корявым способом:
void addheight (int *map, int x, int y, int add)
{
if (add > 5)
{
int bin1 = mapw*(maph-1);
int bin2 = maph-1;
int idx = ((x*mapw) & bin1) + (y&bin2);
map[idx] += add/50;
addheight (map, x-1, y, add-10);
addheight (map, x+1, y, add-10);
addheight (map, x, y-1, add-10);
addheight (map, x, y+1, add-10);
}
}
При add = 100 получаются мелкие зазубринки, таким образом мы можем менять ландшафт прямо в демке - вот неоспоримое достоинство вокселей!
Код, за чистотой которого я совсем перестал следить: testgrad.bmp - ч/б картинка "прицела" 32x32
Открыть спойлер
#include <stdlib.h>
#include <math.h>
#include <SDL.h>
#include <SDL_rotozoom.h>
int scw = 1280;
int sch = 1024;
int mapw = 512;
int maph = 512;
void filldata (int *hei, int *col)
{
int p = 0;
int x,y;
for (y=0; y<maph; y++)
{
for (x=0; x<mapw; x++)
{
double x1 = (x-mapw/2)*0.3;
double y1 = (y-maph/2)*0.3;
col[p] = 3*(cos(x*0.2) + sin (y*0.3)) + 88;
hei[p] = 1000*sin(sqrt(x1*x1+y1*y1))/(1+sqrt(x1*x1+y1*y1));
p++;
}
}
}
void fillpalette (SDL_Surface *screen)
{
SDL_Color colors[256];
int i;
/* Fill colors with color information */
for(i=0;i<=256;i++)
{
colors[i].r=i;
colors[i].g=i/3;
colors[i].b=i/3;
}
SDL_SetPalette(screen, SDL_LOGPAL|SDL_PHYSPAL, colors, 0, 256);
}
void addheight (int *map, int x, int y, int add)
{
if (add > 5)
{
int bin1 = mapw*(maph-1);
int bin2 = maph-1;
int idx = ((x*mapw) & bin1) + (y&bin2);
map[idx] += add/50;
addheight (map, x-1, y, add-10);
addheight (map, x+1, y, add-10);
addheight (map, x, y-1, add-10);
addheight (map, x, y+1, add-10);
}
}
void checkevents(double *ang, int *x, int *y, int *k, int cx, int cy, int *map)
{
SDL_Event event;
if (SDL_PollEvent(&event))
{
switch (event.type) {
case SDL_QUIT:
exit(0);
}
}
Uint8 *keystate = SDL_GetKeyState (NULL);
if (keystate[SDLK_LEFT]) *ang += 0.02;
if (keystate[SDLK_RIGHT]) *ang -= 0.02;
if (keystate[SDLK_UP]) *k -= 1;
if (keystate[SDLK_DOWN]) *k += 1;
if (keystate[SDLK_a] || keystate[SDLK_d] || keystate[SDLK_w] || keystate[SDLK_s])
{
int xd, yd;
xd = 0;
yd = 0;
if (keystate[SDLK_a]) xd = -3;
if (keystate[SDLK_d]) xd = 3;
if (keystate[SDLK_w]) yd = 3;
if (keystate[SDLK_s]) yd = -3;
*x += yd*cos(*ang) + xd*sin(*ang);
*y += -yd*sin(*ang) + xd*cos(*ang);
}
if (keystate[SDLK_i])
{
addheight (map, cx, cy, 100);
}
}
void fillarray (int *a, int val, int size)
{
int i;
for (i=0; i<size; i++) a[i] = val;
}
void blacken_screen (SDL_Surface *screen)
{
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
rect.w = scw;
rect.h = sch;
SDL_FillRect (screen, &rect, 0);
}
double get_filtered_val (int *val, double xf, double yf)
{
int x = xf;
int y = yf;
double diffx = xf - x;
double diffy = yf - y;
double oppx = 1 - diffx;
double oppy = 1 - diffy;
if ((x==511) || (y==511)) return val[mapw*x+y];
return (val[x*mapw+y]*oppx + val[(x+1)*mapw+y]*diffx)*oppy + \
(val[x*mapw+y+1]*oppx + val[(x+1)*mapw+y+1]*diffx)*diffy;
}
int main ()
{
if ( SDL_Init(SDL_INIT_VIDEO) < 0 )
{
fprintf(stderr, "Cannot initialize SDL: %s\n", SDL_GetError());
exit(1);
}
SDL_Surface *screen;
screen = SDL_SetVideoMode(scw, sch, 8, SDL_SWSURFACE|SDL_HWPALETTE);
if ( screen == NULL )
{// Если установить разрешение не удалось
fprintf(stderr, "Cannot set resolution %ix%i: %s\n", scw, sch, SDL_GetError());
exit(1);
}
fillpalette (screen);
int hei[mapw*maph-1];
int col[mapw*maph-1];
filldata (hei, col);
SDL_Surface *sprite = SDL_LoadBMP ("/home/vasily/testgrad.bmp");
SDL_SetColorKey (sprite, SDL_SRCCOLORKEY|SDL_RLEACCEL, \
SDL_MapRGB (sprite->format, 255, 255, 255));
SDL_Surface *ssprite;
double factor;
int posx = 0;
int posy = 0;
double ang = 0;
int horiz = -100;
int k = 1;
double dd = 3;
double de = dd * 128; // 128 is the max "distance"
double x,y,z;
double amax = M_PI/3.4; // FOV
double a;
int sx;
int maxh[scw];
int bin1 = mapw*(maph-1);
int bin2 = maph-1;
double xc1,yc1;
int xc,yc;
while (1)
{
SDL_LockSurface(screen);
blacken_screen (screen);
int startidx = ((posx*mapw) & bin1) + (posy&bin2);
int starth = hei[startidx];
int posz = 250+starth/2;
// int posz = 400;
double sinang = sin(ang);
double cosang = cos(ang);
fillarray (maxh, sch-1, scw);
for (sx=0;sx<scw;sx++)
{
double d, dold;
int syold = 0;
dold = 0;
a = -amax + 2*amax*sx/scw;
double tana = tan (a);
for (d=0;d<=de;d+=dd)
{
double y1 = d*/*cos(amax)**/tana;
double x1 = d;//*cos(amax);
x = posx + x1*cosang+y1*sinang;
y = posy - x1*sinang+y1*cosang;
z = posz - k*d;//*sin(amax);
int xdiv = x/mapw;
int ydiv = y/maph;
x = fabs (x - xdiv*mapw);
y = fabs (y - ydiv*maph);
float h = get_filtered_val (hei, x, y);
int sy = sch/2 + 40*(z-h)/(d+1);
if (sy<0) sy = 0;
if (sy>sch-1) sy = sch-1;
if ((sx==scw/2) && (sy <= sch/2) && (syold>sch/2) && (maxh[sx]>sy))
{
double dcenter = (dold+d)/2;
factor = 10.0/sqrt((dcenter+1));
yc1 = dcenter*/*cos(amax)**/tana;
xc1 = dcenter;//*cos(amax);
xc = posx + xc1*cosang+yc1*sinang;
yc = posy - xc1*sinang+yc1*cosang;
}
if (sy<maxh[sx])
{
int startpos = maxh[sx];
maxh[sx]=sy;
int syi;
int color = get_filtered_val (col, x, y);
for (syi=startpos; syi>=sy; syi--)
{
*((Uint8*)screen->pixels+sx+scw*syi) = color;
}
}
dold = d;
syold = sy;
}
if ((sx==scw/2) && (maxh[sx]>sch/2)) factor = 0;
}
SDL_UnlockSurface(screen);
SDL_Rect rect;
rect.x = scw/2-32;
rect.y = sch/2-32;
SDL_FreeSurface (ssprite);
ssprite = zoomSurface (sprite, factor, factor, 0);
SDL_BlitSurface (ssprite, NULL, screen, &rect);
SDL_Flip (screen);
xc = abs(xc);
yc = abs(yc);
checkevents (&ang, &posx, &posy, &k, xc, yc, hei);
}
return atexit(SDL_Quit);
}
Комментариев нет:
Отправить комментарий