home *** CD-ROM | disk | FTP | other *** search
- /***********************************************************************
- *
- * PROJECT: Raycast
- * FILE: render.goc
- *
- * AUTHOR: Marcus Gröber
- *
- ***********************************************************************/
-
- //
- // Projekt Raycast-Engine
- // (C)1995 by Stefan Becker
- //
- // Render.goc
- // Haupt-Render/Zeichen-Routinen
- //
-
- //
- // Geos-Includes:
- //
-
- @include <stdapp.goh>
-
- //
- // ANSI-Includes:
- //
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <math.h>
-
- //
- // Eigene Includes:
- //
-
- @include "raycast.goh"
- #include "Globals.h" // Allg. Definitionen
-
- void _pascal clear_line_low(char *p,char mask,word hoehe);
- void _pascal grey_line_low(char *p,char mask,word hoehe,word evenodd);
- void _pascal scale_line_low(char *p,char mask,char *tp,char tmask,
- int ypos,word hoehe);
-
- //
- // Global Variablen
- //
-
- struct {
- Bitmap bmp;
- char data[YAUF][(XAUF+7)/8];
- } Offscreen;
-
- char texture[TEX_SIZE][(TEX_SIZE+7)/8]={
- #include "texture\texture.inc"
- };
-
- SKIP_TAB hsc_dx_tab, // dx/dy-Tabellen für effizienteres Raycasting
- hsc_dy_tab;
- SKIP_TAB vsc_dx_tab,
- vsc_dy_tab;
- WINKEL_TAB x_winkel_tab, // Tabelle fƒr Richtungsvektoren aller Winkel
- y_winkel_tab; // (Nötig, um schnell einen Einheitsvektor
- // dieser Richtung zu ermitteln)
-
- MAZE maze; // Wird später aus maze_user generiert.
-
- // Definition des begehbaren Areals: '*':Wand, ' ':freie Fläche
- // Achtung, wird noch vertikal gespiegelt, damit (0,0) links unten ist
- MAZE maze_user =
- {
- "****************", // 15
- "* * *", // 14
- "* * *", // 13
- "* *******", // 12
- "* ****** *", // 11
- "* * *", // 10
- "**** * *", // 9
- "* * * *", // 8
- "* * *", // 7
- "* **** ****", // 6
- "* * * *", // 5
- "******* ******", // 4
- "* ** * *", // 3
- "* * *", // 2
- "* * **", // 1 Feld (1,1) muß frei sein (Spieler wird dahin
- "****************" // 0 mit Blickrichtung nach rechts positioniert!)
- }; // (0,0) links unten, erster Index ist X
-
- //
- // Koordinatentransformation für Karten-Modus:
- //
-
- int tx(int x) // Punkt aus virt. Welt in Kartenkoordinaten umsetzen
- {
- int erg;
-
- erg = (int)( (dword)x * XAUF / MAZE_MAX_X );
- erg = MAX(0,erg); // Legale Position (sicherheitshalber)
- erg = MIN(XAUF-1,erg);
- return erg;
- }
-
- int ty(int y) // Punkt aus virt. Welt in Kartenkoordinaten umsetzen
- {
- int erg;
-
- erg = (YAUF-1) - (int)( (dword)y * YAUF / MAZE_MAX_Y );
- erg = MAX(0,erg); // Legale Position (sicherheitshalber)
- erg = MIN(YAUF-1,erg);
- return erg;
- }
-
- //
- // Kartenmodus darstellen:
- //
-
- void do_map_raycasting(
- GStateHandle window, int xw,int yw, int plx,int ply, int plw)
- {
- int x,y; // x/y-Laufvariablen für Felder
- int p1x,p1y,p2x,p2y; // Variablen für Spieler-x/y-Punkte
- char str[80]; // String für "sprintf"
-
- // Voreinstellungen für die Grafik (Geos):
- GrSetAreaMaskSys(window,SDM_100);
- GrSetLineMaskSys(window,SDM_100);
- GrSetLineStyle(window,LS_SOLID,0,NULL,0);
- GrSetLineColor(window,CF_INDEX,C_BLACK,0,0);
-
- // Das Labyrinth hinmalen:
- for(x=0; x<MAZE_X; x++) // Alle Spalten...
- for(y=0; y<MAZE_Y; y++) // Alle Zeilen...
- {
- if(maze[x][y] != ' ') // Ist da eine Wand?
- GrSetAreaColor(window,CF_INDEX,C_BLACK,0,0);
- else
- GrSetAreaColor(window,CF_INDEX,C_WHITE,0,0);
-
- // Kästchen zeichnen (Geos):
- GrFillRect(window,
- xw+tx(x*TEX_SIZE),yw+ty((y+1)*TEX_SIZE),
- xw+tx((x+1)*TEX_SIZE),yw+ty(y*TEX_SIZE));
- }
-
- // Den Spieler mit einzeichnen:
- p1x = tx(plx); // Aktuelle Spielerposition
- p1y = ty(ply);
- GrFillRect(window,xw+p1x-1,yw+p1y-1,xw+p1x+1,yw+p1y+1);
- // Spieler einzeichnen
- // Linie mit Länge 100 in Blickrichtung einzeichnen.
- p2x = tx(plx + IntegerOf(GrMulWWFixed(x_winkel_tab[plw],MakeWWFixed(100))));
- p2y = ty(ply + IntegerOf(GrMulWWFixed(y_winkel_tab[plw],MakeWWFixed(100))));
- GrDrawLine(window,xw+p1x,yw+p1y,xw+p2x,yw+p2y);
- // Linie zeichnen (Geos)
- }
-
- //
- // Eine skalierte senkrechte Texturlinie malen:
- //
-
- void draw_scaled_line(int xpos, int txt_xpos, int hoehe)
- {
- int ypos; // Oberkante der zu zeichnenden Textur
- char mask;
-
- // Höhe sicherheitshalber auf "vernünftige" Grenzen limitieren:
- hoehe = MIN(hoehe,32000);
- hoehe = MAX(1,hoehe);
-
- // Texturposition sicherheitshalber auch auf "vernünftige" Werte limitieren:
- txt_xpos = MAX(0,txt_xpos);
- txt_xpos = MIN(TEX_SIZE-1,txt_xpos);
-
- // Wo muß die Oberkante der Linie im Grafikfenster hin? (0,0) ist links oben.
- ypos = (YAUF/2) - (hoehe/2); // virtueller Horizont bei YAUF/2
-
- mask=1<<(7-(xpos%8));
-
- if(ypos>0) // Draw "sky"
- clear_line_low(&Offscreen.data[0][xpos/8], mask, ypos);
-
- scale_line_low(
- &Offscreen.data[MAX(0,ypos)][xpos/8], mask,
- &texture[0][txt_xpos/8], 1<<(7-(txt_xpos%8)),
- ypos, hoehe); // Draw wall
-
- if(ypos+hoehe<YAUF) // Draw "floor"
- grey_line_low(
- &Offscreen.data[ypos+hoehe][xpos/8], mask,
- YAUF-(ypos+hoehe), ((ypos+hoehe)^xpos) & 1);
- }
-
- //
- // Berechnen der Entfernung mittels Streckfaktor der orthogonalen Projektion:
- //
-
- WWFixedAsDWord calc_dist(int x, int y, int plw)
- {
- // Geht, da Richtungsvektor (x_winkel_tab,y_winkel_tab) normiert ist.
- return GrMulWWFixed(MakeWWFixed(x),x_winkel_tab[plw]) +
- GrMulWWFixed(MakeWWFixed(y),y_winkel_tab[plw]);
- }
-
- //
- // Ist dieser Punkt eine legale Position im Spielfeld?
- //
-
- #define legal_pos(_x,_y)\
- ( (_x)>=0 && (_x)<MAZE_MAX_X && (_y)>=0 && (_y)<MAZE_MAX_Y )
-
- //
- // Horizontalen Scan durchführen:
- //
-
- int hscan(int *lx, int *ly, int winkel)
- {
- WWFixedAsDWord fx,fy;
- WWFixedAsDWord delta_x,delta_y;
- int iy,rx,ry;
- int zy;
-
- // Ist dieser Winkel für horizontales Scannen verwendbar?
- if((hsc_dx_tab[winkel]==0) && (hsc_dy_tab[winkel]==0))
- return FALSE; // Nein! Illegaler Winkel => Keine Wand!
-
- // Startposition übernehmen:
- fx = MakeWWFixed(*lx); fy = MakeWWFixed(iy=*ly);
-
- // Sehen, daß man zu Beginn auf einer horizontalen Liniengrenze landet:
- if((iy % TEX_SIZE) != 0)
- {
- if((sdword)y_winkel_tab[winkel] > 0) // Die Linie darüber oder darunter?
- {
- // Wir müssen auf die Linie darüber:
- zy = ((iy / TEX_SIZE) + 1) * TEX_SIZE; // Y-Koordinate der gewünschten Zeile
- }
- else
- {
- // Wir müssen auf die Linie darunter:
- zy = (iy / TEX_SIZE) * TEX_SIZE; // Y-Koordinate der gewünschten Zeile
- }
-
- // Schritt auf die nächste Linie durchführen:
- delta_y = MakeWWFixed(zy - iy); // So weit ist es dahin
- delta_x = GrMulWWFixed(
- delta_y,
- GrSDivWWFixed(
- x_winkel_tab[winkel],
- y_winkel_tab[winkel])); // So weit zur Seite gehen.
- fx += delta_x; // Da liegt der neue X-Wert
- fy = MakeWWFixed(zy); // Da liegt der neue Y-Wert
- } // if (Aufsetzen)
-
- delta_x = hsc_dx_tab[winkel]; delta_y = hsc_dy_tab[winkel];
-
- // Schnell scannen:
- // So lange man sich noch in virt. Welt befindet...
- while( legal_pos(WWFixedToInt(fx),WWFixedToInt(fy)) )
- {
- // In Rasterposition umrechnen.
- rx = WWFixedToInt(fx) >> TEX_SHIFT; ry = WWFixedToInt(fy) >> TEX_SHIFT;
-
- // Ist da etwas oberhalb und unterhalb der aktuellen hor. Linie?
- if( (maze[rx][ry]!=' ') || (maze[rx][ry-1]!=' ') )
- {
- // Da ist eine Wand!
- *lx = WWFixedToInt(fx); *ly = WWFixedToInt(fy); // Wandposition.
- return TRUE; // Hier ist eine Wand!
- } // if
-
- // Nächsten Punkt holen:
- fx += delta_x; fy += delta_y;
- } // while
-
- // Keine Wand gefunden. Strahl geht ins Leere!
- return FALSE;
- }
-
- //
- // Vertikalen Scan durchführen:
- //
-
- int vscan(int *lx, int *ly, int winkel)
- {
- WWFixedAsDWord fx,fy;
- WWFixedAsDWord x,y,delta_x,delta_y;
- int ix,rx,ry;
- int zx;
-
- // Ist dieser Winkel für horizontales Scannen verwendbar?
- if((vsc_dx_tab[winkel]==0) && (vsc_dy_tab[winkel]==0))
- return FALSE; // Nein! Illegaler Winkel => Keine Wand!
-
- // Startposition übernehmen:
- fx = MakeWWFixed(ix=*lx); fy = MakeWWFixed(*ly);
-
- // Sehen, daß man zu Beginn auf einer vertikalen Linie landet:
- if((ix % TEX_SIZE) != 0)
- {
- if((sdword)x_winkel_tab[winkel] > 0) // Die Linie rechts oder links?
- {
- // Wir müssen auf die Linie rechts:
- zx = ((ix / TEX_SIZE) + 1) * TEX_SIZE; // X-Koordinate der gewünschten Spalte
- }
- else
- {
- // Wir müssen auf die Linie links:
- zx = (ix / TEX_SIZE) * TEX_SIZE; // X-Koordinate der gewünschten Spalte
- }
-
- // Schritt auf die nächste Linie durchführen:
- delta_x = MakeWWFixed(zx - ix); // So weit ist es dahin
- delta_y = GrMulWWFixed(
- delta_x,
- GrSDivWWFixed(
- y_winkel_tab[winkel],
- x_winkel_tab[winkel])); // So weit nach oben gehen.
- fy += delta_y; // Da liegt der neue Y-Wert
- fx = MakeWWFixed(zx); // Da liegt der neue X-Wert
- } // if (Aufsetzen)
-
- delta_x = vsc_dx_tab[winkel]; delta_y = vsc_dy_tab[winkel];
-
- // Schnell scannen:
- // So lange man sich noch in der vrt. Welt befindet...
- while( legal_pos(WWFixedToInt(fx),WWFixedToInt(fy)) )
- {
- // In Rasterposition umrechnen:
- rx = WWFixedToInt(fx) >> TEX_SHIFT; ry = WWFixedToInt(fy) >> TEX_SHIFT;
-
- // Ist da etwas linkt und rechts von der senkrechten Linie?
- if( (maze[rx][ry]!=' ') || (maze[rx-1][ry]!=' ') )
- {
- // Ja: Eine Wand ist hier.
- *lx = WWFixedToInt(fx); *ly = WWFixedToInt(fy); // Wandposition.
- return TRUE; // Hier ist eine Wand!
- } // if
-
- // Nächsten Punkt holen:
- fx += delta_x; fy += delta_y;
- } // while
-
- // Nichts gefunden. Strahl geht ins Leere!
- return FALSE;
- }
-
- //
- // Einen Strahl "Raycasten" und eventuellen Auftreffpunkt auf einer Mauer
- // samit zugehöriger Texturposition liefern:
- // (Arbeitet ohne Bresenham mit der Delta-X/Delta-Y-Methode für max. Geschwindigkeit)
- //
-
- int cast_one_ray(int plx, int ply, int plw, int winkel, WWFixedAsDWord *dist, int *pat_pos)
- {
- int hwall_hit,vwall_hit; // Horizontales bzw. vertikales Auftreffen auf Wand?
- int vx,vy; // Auftreffpunkt auf eine vertikale Linie
- int hx,hy; // Auftreffpunkt auf eine horizontale Linie
- WWFixedAsDWord h_dist,v_dist; // Entfernung des horizontalen und vert. Auftreffpunkts
-
- //
- // Horizontalen Schnellscan durchführen:
- //
- hx = plx; hy = ply; // Start bei Spielerposition
- hwall_hit = hscan(&hx,&hy,winkel); // Treffer auf eine hor. Wand? Wenn ja: wo?
- if(hwall_hit)
- h_dist = calc_dist(hx-plx,hy-ply,plw);
- else
- h_dist = MakeWWFixed(32767);
-
- //
- // Vertikalen Schnellscan durchführen:
- //
- vx = plx; vy = ply; // Start bei Spielerposition
- vwall_hit = vscan(&vx,&vy,winkel); // Treffer auf eine vert. Wand? Wenn ja: wo?
- if(vwall_hit)
- v_dist = calc_dist(vx-plx,vy-ply,plw);
- else
- v_dist = MakeWWFixed(32767);
-
- //
- // Gar kein Treffer?
- //
- if(!vwall_hit && !hwall_hit)
- return FALSE; // Da ist absolut garnichts!
-
- //
- // Zum Spieler näheren Treffer nehmen:
- //
- if(h_dist<v_dist) // Horizontaler oder vertikaler Treffer näher?
- {
- *dist = h_dist; // horizontal war näher
- *pat_pos = hx % TEX_SIZE; // Diese Texturspalte wird genommen.
- }
- else
- {
- *dist = v_dist; // vertikal war näher
- *pat_pos = vy % TEX_SIZE;
- // Diese Texturspalte wird genommen.
- }
- return TRUE; // Treffer. Näherer der beiden Schnittpunkte.
- }
-
- //
- // Höhe eines Objekts aus seiner Entfernung berechnen.
- // (Wird mit Konstante/Entfernung berechnet.)
- //
-
- int calc_hoehe(int dist)
- {
- // Höhe berechnen:
- dist = ABS(dist); // sicherheitshalber!!!
- dist = MIN(dist,MAX_SICHT); // sicherheitshalber!!!
- return PERSPEKTIVE / (dist+1); // Zurodnung Entfernung->Wandhöhe
- }
-
- //
- // Haupt-Raycast-Routine (3D-Modus):
- //
-
- void do_3d_raycasting(int plx, int ply, int plw)
- {
- int wall_x,wall_y; // Schnittpunkt mit der Wand
- int x,winkel,startw,endw; // Laufvariable, Start-, Endwinkel
- WWFixedAsDWord dist; // Distanz der Wand.
- int hoehe; // Daraus errechnete Höhe der darzustellenden Wand in Pixeln
- int txt_pos; // Zu skalierende Spalte der Textur.
- char str[80]; // String für Koordinaten-Ausgabe.
-
- // Start- und Endwinkel für Spieler-Blickwinkel errechnen:
- startw = plw + (XAUF / 2); // Start-Blickwinkel errechnen
- if(startw >= ANZ_WINKEL) // Legal?
- startw -= ANZ_WINKEL; // Nein, muß "modulo" korrigiert werden.
- if(startw < 0) // Legal?
- startw += ANZ_WINKEL; // Nein, muß "modulo" korrigiert werden.
-
- // Raycasting für alle Winkel durchführen:
- x = 0; // Dieser Strahl gehört zur X-Koordinate 0 im generierten Bild
- winkel = startw; // Mit dem Startwinkel wird links angefangen
- while(x < XAUF) // So lange bis das gesamte Bild berechnet ist
- {
- // Einen Strahl absetzen und ggf. Mauerschnittpunkte ermitteln:
- if(cast_one_ray(plx,ply,plw,winkel,&dist,&txt_pos))
- {
- // Ja, da ist eine Wand. Aus der Entfernung
- // berechnen, wie groß diese dargestellt werden soll:
- hoehe = calc_hoehe(WWFixedToInt(dist));
- // Wie hoch muß das Objekt nun sein?
-
- // Skalierte Texturspalte senkrecht zeichnen:
- draw_scaled_line(x,txt_pos,hoehe);
- }
-
- // Schleifenende: (Hier muß x inkrementiert und der Winkel "modulo" erhöht werden)
- x++; // Nächste X-Koordinate
- winkel--; // Nächster Winkel
- if(winkel < 0) // Illegal bzw. Unterlauf?
- winkel += ANZ_WINKEL; // Korrigieren!
- } // while
- }
-
- //
- // Routine verzweigt je nach Flagge "my_3d_flag" auf Karten- oder
- // 3D-Modus und leitet Zeichenoperationen in eine Offscreen-Bitmap um.
- // (Mac-spezifisch)
- //
-
- void do_raycasting(
- GStateHandle window, int x,int y, int plx,int ply, int plw, Boolean DrawMap)
- {
- // 3D- oder Kartensicht?
- if(DrawMap)
- do_map_raycasting(window,x,y,plx,ply,plw); // Karten-Sicht
- else
- {
- do_3d_raycasting(plx,ply,plw); // 3D-Sicht
- GrDrawImage(window,x,y,IBS_1,&Offscreen.bmp); // Offscreen-Bitmap
- }
- }
-
- //
- // Tabellen initialisieren etc. (für alle Rechnersysteme):
- //
-
- // Spielfeld vertikal spiegeln. Nötig, damit vorinitialisiertes
- // Feld oben "richtig herum" im Quellcode angezeigt wird.
-
- void flip_maze(void)
- {
- int zeile,spalte; // Indizes
-
- for(spalte=0; spalte<MAZE_X; spalte++)
- for(zeile=0; zeile<MAZE_Y; zeile++)
- maze[spalte][zeile] = maze_user[MAZE_Y-1-zeile][spalte];
- // Daten übernehmen
- }
-
- void Init_Tabellen(void) // dx/dy-Tabelle und Winkeltabellen errechnen:
- {
-
- WWFixedAsDWord z,d,alpha,d_alpha;
- int i; // Laufvariable
-
- //
- // Tabelle für Richtungsvektoren initialisieren:
- //
-
- // Größe eines Winkelschrittes berechnen (360/Winkelanzahl):
- d_alpha = GrSDivWWFixed(MakeWWFixed(360),MakeWWFixed(ANZ_WINKEL));
-
- // Fƒr alle Winkel den zugehörigen Richtungsvektor errechnen.
- // Dabei ist der 0. Winkel bei 0 Grad (entspricht rechts).
- for(alpha=0, i=0; i<ANZ_WINKEL; i++, alpha+=d_alpha) // Alle Winkel...
- {
- // Punkte auf dem Einheitskreis als Richtungsvektoren:
- x_winkel_tab[i] = GrQuickCosine(alpha);
- // Cos gibt die X-Koordinate an
- y_winkel_tab[i] = GrQuickSine(alpha);
- // Sin gibt die Y-Koordinate an
- } // for
-
- //
- // Tabelle fƒr schnelleres dx/dy-Raycasting initialisieren:
- //
- for(i=0; i<ANZ_WINKEL; i++) // Alle Winkel...
- {
- //
- // Tabelle fƒr horizontalen Scan generieren:
- //
- if(y_winkel_tab[i]==0) // Degenerierte Richtung fƒr hor. Scan?
- hsc_dx_tab[i] = hsc_dy_tab[i] = 0; // "Degenerierte" Richtung fƒr hor. Scan!
- else
- {
- hsc_dy_tab[i] =
- MakeWWFixed( ((sdword)y_winkel_tab[i]>0)? TEX_SIZE : -TEX_SIZE );
- d = GrMulWWFixed(
- hsc_dy_tab[i],
- GrSDivWWFixed(
- x_winkel_tab[i],
- y_winkel_tab[i]));
- if(ABS(WWFixedToInt(d))>MAX_SICHT)
- hsc_dx_tab[i] = hsc_dy_tab[i] = 0; // Dieser Winkel ist auch "degeneriert"!
- else
- hsc_dx_tab[i] = d; // Dieser Wert kann verwendet werden.
- }
- //
- // Tabelle fƒr vertikalen Scan erstellen:
- //
- if(x_winkel_tab[i]==0) // Degenerierte Richtung fƒr vert. Scan?
- vsc_dx_tab[i] = vsc_dy_tab[i] = 0; // "Degenerierte" Richtung fƒr vert. Scan!
- else
- {
- vsc_dx_tab[i] =
- MakeWWFixed( ((sdword)x_winkel_tab[i]>0)? TEX_SIZE : -TEX_SIZE );
- d = GrMulWWFixed(
- vsc_dx_tab[i],
- GrSDivWWFixed(
- y_winkel_tab[i],
- x_winkel_tab[i]));
- if(ABS(WWFixedToInt(d))>MAX_SICHT)
- vsc_dx_tab[i] = vsc_dy_tab[i] = 0; // Dieser Winkel ist auch "degeneriert"!
- else
- vsc_dy_tab[i] = d; // Dieser Wert kann verwendet werden.
- }
- } // for
-
- //
- // Spielfeld vertikal spiegeln:
- //
- flip_maze();
- }
-
- /***********************************************************************
- * Methods for RaycastClass
- ***********************************************************************/
- @classdecl RaycastClass;
-
- @method RaycastClass, MSG_VIS_RECALC_SIZE
- {
- return MAKE_SIZE_DWORD(XAUF,YAUF);
- }
-
- @method RaycastClass, MSG_VIS_DRAW
- {
- VisInstance *vself;
-
- vself = ObjDerefVis(oself);
- do_raycasting(
- gstate, vself->VI_bounds.R_left, vself->VI_bounds.R_top,
- pself->RCI_plx, pself->RCI_ply, pself->RCI_plw, pself->RCI_drawMap);
- }
-
- @method RaycastClass, MSG_RAYCAST_INIT_TABLES
- {
- Init_Tabellen();
- Offscreen.bmp.B_width=XAUF;
- Offscreen.bmp.B_height=YAUF;
- Offscreen.bmp.B_compact=BMC_UNCOMPACTED;
- Offscreen.bmp.B_type=BMF_MONO;
- }
-
- @method RaycastClass, MSG_RAYCAST_SET_MAP_MODE
- {
- pself->RCI_drawMap = drawMap; // Set new mapping mode and redraw
- @send self::MSG_VIS_REDRAW_ENTIRE_OBJECT();
- }
-
- @method RaycastClass, MSG_RAYCAST_GET_PLAYER
- {
- *plx = pself->RCI_plx; // Return player coordinates
- *ply = pself->RCI_ply;
- *plw = pself->RCI_plw;
- }
-
- @method RaycastClass, MSG_RAYCAST_SET_PLAYER
- {
- pself->RCI_plx = plx; // Set new player coordinates & redraw
- pself->RCI_ply = ply;
- pself->RCI_plw = plw;
- @send self::MSG_VIS_REDRAW_ENTIRE_OBJECT();
- }
-