Trivial Solutions Corp. Happy Programmers Make Us Happy The LoseThos 64-bit PC Operating System File:/LT/Demo/GameStarters/FlightSim.CPZ This, and all LoseThos files, are public domain. Do whatever you like. //This program can be static compiled to a .BIZ or JIT. //If static compiling, override the finename extention warning. #ifstatic #include "::/LT/OSMain/StaticAdam.HPZ" #endif //This might get turned into a game where you are an Eagle //and you dive for fish. //Keep these power of two so shift is used instead of multiply //to index arrays. #define MAP_WIDTH 1024 #define MAP_HEIGHT 1024 #define MAP_SCALE 150 #define DISPLAY_SCALE 100 #define CONTROLS_SCALE 0.05 //I think I did these so the heads-up showed intelligable numbers. //Scaling is a mess. #define COORDINATE_SCALE 256 #define COORDINATE_BITS 8 #define WATER_ELEVATION 15 #define ROCK_ELEVATION 45 #define SNOW_ELEVATION 55 //Too big makes off-screen draws take place. #define MAX_PANEL_SIZE 16 class Panel { Panel *next; I4 n, //num polygon sides num; //panel number P3I4 *points; I8 color,cnt; }; I8 critical_section_flag; U0 CriticalSectionStart() { while (LBts(&critical_section_flag,0)) Yield; } U0 CriticalSectionEnd() { LBtr(&critical_section_flag,0); } //These are not deallocated after the program ends, //but I did them this way for your benefit //so 2D array indexing is not done by hand. //You can type "FlightSim;" at the command line //to rerun without new allocations. //Normally, just spawn a popup task, //run it and kill it using an icon. #ifstatic #exe {OptOn(OPTf_GLBLS_ON_DATA_HEAP);}; #endif I2 elevations[MAP_HEIGHT][MAP_WIDTH]; I4 normals[MAP_HEIGHT][MAP_WIDTH*2]; Panel *panels[MAP_HEIGHT][MAP_WIDTH*2]; #ifstatic #exe {OptOff(OPTf_GLBLS_ON_DATA_HEAP);}; #endif I8 num_panels; Panel *panel_root; U1 *panels_processed_bitmap; F8 v_grid_size,span_factor,phi,theta1,theta2,speed; I8 x,y,z; BoolI1 crashed=FALSE; class MPCtrl { I8 mp_cnt; U8 do_update; F8 update_done_t[MP_MAX_PROCESSORS]; U8 done_flags; BoolI8 app_done; } mp; U0 FSTransform(GrBitMap *base,I8 *x,I8 *y,I8 *z) { I8 zz; //I accidentally made the Z coordinate sign backward //from the fill-poly depth buf scheme, so it is negated at the end. //Negative Z coordinates are the ones we want plotted. GrRot(base->r,x,y,z); //We don't want divide by zero or make too monstrous foreground objects. //The foreground object boundary is kinda crappy looking when //you point toward the ground. *z=-*z; zz=*z+50; if (zz>0) { //The divide causes "foreshortening" which makes the distance go to a vanishing point. *x = *x * DISPLAY_SCALE/ zz; *y = *y * DISPLAY_SCALE/ zz; } *x+=base->x; *y =base->y-*y; } U0 CalcNormals() { /*The idea is that there are two triangles forming every grid square and we find the normal vector with a curl. i j k 0 1 dz2 1 0 dz1 Normal: dz1*i + dz2*j - k i j k -1 0 -dz3 0 -1 -dz4 Normal: -dz3*i - dz4*j + k Basically, we want to combine grid locations with the same normals. These are not unit vectors for one thing. What I came-up with is dz1/dz2 and dz3/dz4. It works pretty well. It combines more than it should, but that's good because it would result in too many panels. */ I8 i,j,k,dz1,dz2; for (j=0;j<MAP_HEIGHT-1;j++) { k=0; for (i=0;i<MAP_WIDTH-1;i++) { dz1=elevations[j][i+1]-elevations[j][i]; dz2=elevations[j+1][i]-elevations[j][i]; if (dz2) normals[j][k++]=dz1<<16/dz2; else { if (!dz1) normals[j][k++]=0; else if (dz1>0) normals[j][k++]=MAX_I4; else normals[j][k++]=MIN_I4; } dz1=elevations[j+1][i+1]-elevations[j][i+1]; dz2=elevations[j+1][i+1]-elevations[j+1][i]; if (dz2) normals[j][k++]=dz1<<16/dz2; else { if (!dz1) normals[j][k++]=0; else if (dz1>0) normals[j][k++]=MAX_I4; else normals[j][k++]=MIN_I4; } } normals[j][k++]=0; normals[j][k++]=0; } k=0; for (i=0;i<MAP_WIDTH-1;i++) { normals[j][k++]=0; normals[j][k++]=0; } } BoolI8 TestSameSlope(I8 x,I8 y,I8 w,I8 h) { I8 k1,k2,xx,yy,s; if (!(0<=x && x+w<MAP_WIDTH && 0<=y && y+h<MAP_HEIGHT)) return FALSE; //Works well averaging the two slopes of two triangles. //If it is too picky, we get too many panels. s=normals[y][x*2]+normals[y][x*2+1]; for (k2=0;k2<h;k2++) for (k1=0;k1<w;k1++) { xx=(x+k1)*2; yy=y+k2; if (normals[yy][xx]+normals[yy][xx+1]!=s) return FALSE; } return TRUE; } U0 MPDoPanels(TaskStruct *task) { I8 i,j,k,l,k1,k2,w,h,threshold,lo,hi; BoolI1 cont; Panel *tempp,*start_ptr=NULL,*end_ptr=NULL; P3I4 *poly; BoolI1 old_preempt=Preempt(ON); lo=Gs->num*(MAP_HEIGHT-1)/mp.mp_cnt; hi=(Gs->num+1)*(MAP_HEIGHT-1)/mp.mp_cnt; for (threshold=8;threshold>=1;threshold--) for (j=lo;j<hi;j++) { k=0; for (i=0;i<MAP_WIDTH-1;i++) { if (!panels[j][k]) { w=1; h=1; do { cont=FALSE; if (w<MAX_PANEL_SIZE && TestSameSlope(i,j,w+1,h)) { w++; cont=TRUE; } if (h<MAX_PANEL_SIZE && TestSameSlope(i,j,w,h+1)) { h++; cont=TRUE; } } while (cont); if (w>=threshold || h>=threshold) { tempp=CAlloc(sizeof(Panel),task); lock {tempp->num=num_panels++;} l=elevations[j][i]; if (l<=WATER_ELEVATION*MAP_SCALE) tempp->color=BLUE; else { if (RandI2&1) { if (l<ROCK_ELEVATION*MAP_SCALE) tempp->color=LTGREEN; else if (l<SNOW_ELEVATION*MAP_SCALE) { if (!(RandU2&3)) tempp->color=LTGRAY; else tempp->color=LTGREEN; } else { if (!(RandU2&3)) tempp->color=WHITE; else tempp->color=LTGRAY; } } else { if (l<ROCK_ELEVATION*MAP_SCALE) tempp->color=GREEN+LTGREEN<<16+ROPF_DITHER; else if (l<SNOW_ELEVATION*MAP_SCALE) { if (!(RandU2&3)) tempp->color=DKGRAY+LTGRAY<<16+ROPF_DITHER; else tempp->color=GREEN+LTGREEN<<16+ROPF_DITHER; } else { if (!(RandU2&3)) tempp->color=LTGRAY+WHITE<<16+ROPF_DITHER; else tempp->color=DKGRAY+LTGRAY<<16+ROPF_DITHER; } } } tempp->n=4; poly=tempp->points=MAlloc(sizeof(P3I4)*tempp->n,task); poly[0].x=MAP_SCALE*i; poly[0].y=MAP_SCALE*j; poly[0].z=elevations[j][i]; poly[1].x=MAP_SCALE*(i+w); poly[1].y=MAP_SCALE*j; poly[1].z=elevations[j][i+w]; poly[2].x=MAP_SCALE*(i+w); poly[2].y=MAP_SCALE*(j+h); poly[2].z=elevations[j+h][i+w]; poly[3].x=MAP_SCALE*i; poly[3].y=MAP_SCALE*(j+h); poly[3].z=elevations[j+h][i]; tempp->next=start_ptr; start_ptr=tempp; if (!end_ptr) end_ptr=tempp; for (k2=0;k2<h;k2++) for (k1=0;k1<w;k1++) { panels[j+k2][(i+k1)*2]=tempp; panels[j+k2][(i+k1)*2+1]=tempp; } } } k+=2; } } if (end_ptr) { CriticalSectionStart; end_ptr->next=panel_root; panel_root=start_ptr; CriticalSectionEnd; } Preempt(old_preempt); LBts(&mp.done_flags,Gs->num); } U0 InitMap() { //We make a topographic data structure "evevations[][]" //and convert it to panels. The conversion to panels leaves //gaps the way I did it, but it looks good, except for sky leaking through //There is no wrong way to pick colors if it looks good, unless //you wanted to do lighting. //"panels[][]" holds the flat and sloped panels for each spot, while //"hstep_panels[][]" holds side panels in cases of step-like formations. //They require a separate array because a single "elevation[][]" spot can //have a panel on top and a panel on the side like stairs. I8 i,j,l,k1,k2,x,y,xx,yy; MemSet(elevations,0,sizeof(elevations)); MemSet(panels,0,sizeof(panels)); for (i=0;i<MAP_WIDTH*MAP_HEIGHT/128;i++) { x=RandU4%MAP_WIDTH; y=RandU4%MAP_HEIGHT; j=1<<(RandU4%6); l=0; while (j--) { if (!l && RandU2<MAX_U2/4) l=RandU2%(j+1); if (l) { for (k1=-j;k1<=j;k1++) for (k2=-j;k2<=j;k2++) { xx=x+k2; yy=y+k1; if (0<=xx<MAP_WIDTH && 0<=yy<MAP_HEIGHT) elevations[yy][xx]+=MAP_SCALE/2; } l--; } } } xx=x/(MAP_SCALE*COORDINATE_SCALE); yy=y/(MAP_SCALE*COORDINATE_SCALE); z+=elevations[yy][xx]*COORDINATE_SCALE; for (j=0;j<MAP_HEIGHT;j++) for (i=0;i<MAP_WIDTH;i++) if (elevations[j][i]<WATER_ELEVATION*MAP_SCALE) elevations[j][i]=WATER_ELEVATION*MAP_SCALE; CalcNormals; panel_root=NULL; num_panels=0; mp.done_flags=0; for (i=1;i<mp.mp_cnt;i++) MPSpawn(&MPDoPanels,Fs,"Do Panels",1<<i); MPDoPanels(Fs); while (mp.done_flags!=1<<mp.mp_cnt-1) Yield; panels_processed_bitmap=MAlloc((num_panels+7)>>3); } U0 PanelsDel() { Panel *tempp=panel_root,*tempp1; while (tempp) { tempp1=tempp->next; Free(tempp->points); Free(tempp); tempp=tempp1; } Free(panels_processed_bitmap); } GrBitMap *main_base; U0 UpdateWin(TaskStruct *task) { GrBitMap *base=GrAlias(gr_refreshed_base,task); GrBlot(base,0,0,main_base); GrDel(base); } /*/* Graphics Not Rendered in HTML */ Cores render strips. The strips are wider toward the horizon by span_factor and overlap +/- 15%. The cores check the panel map array and render the panel for each square and mark-it done. The depth buf is not locked in the graphic routines so we get some glitches. */ U0 MPDrawIt(TaskStruct *task,GrBitMap *base) { //This is identical to the previous routine but is run by core#1 I8 i,j,ww,hh,*s2w,x1,y1,z1, xx,yy, xh,yh,zh, yh2,xh2, reg x1w, reg y1w, x1h,y1h, x3,y3,z3, cx=task->win_pixel_width>>1, cy=task->win_pixel_height>>1; F8 h_grid_size; Panel reg *tempp; xx=x/(MAP_SCALE*COORDINATE_SCALE); yy=y/(MAP_SCALE*COORDINATE_SCALE); //World to screen coordinates GrIdentEqu(base->r); GrRotZEqu(base->r,theta2); GrRotXEqu(base->r,phi); GrRotZEqu(base->r,theta1); GrSetRotMat(base,base->r); //Screen to world coordinates //This gives us the vectors for stepping through the grid in //the direction the plane is facing. we step horizontally and vertically //and use the reciprocal slope principle y=mx+b and y=(-1/m)x+b are perpendicular. s2w=GrIdent; GrRotZEqu(s2w,-theta1); GrRotXEqu(s2w,-phi); GrRotZEqu(s2w,-theta2); xh=0; yh=0; zh=-256; GrRot(s2w,&xh,&yh,&zh); //The layer for core#1 is not cleared automatically //it is persistent. I have carefully syncronized to the update //cycle initiated by core#0 to prevent flicker. base->flags|=BMF_TRANSFORMATION; base->transform=&FSTransform; base->x=cx; base->y=cy; //base->x and the translation part of base->r are identical in effect, //but we use both to show-off. We could add offsets together and use one or the other. x1=-x>>COORDINATE_BITS; y1=-y>>COORDINATE_BITS; z1=-z>>COORDINATE_BITS; GrRot(base->r,&x1,&y1,&z1); GrSetTranslation(base->r,x1,y1,z1); //This is a refinement. if (Abs(phi*180/pi)>90) { x3=0; y3=-cy; z3=0; GrRot(s2w,&x3,&y3,&z3); xx+=x3; yy+=y3; } h_grid_size=4.0*v_grid_size*(Gs->num+span_factor)/span_factor; if (Gs->num&1) { //alternate left-right,right-left yh2=-yh; xh2=-xh; } else { yh2=yh; xh2=xh; } ww=h_grid_size; hh=1.30*v_grid_size; //Cores overlap 15% on each side of strip. //Calc starting point. x1h=xx<<8+0.5*yh2*h_grid_size+xh*v_grid_size*(Gs->num+1.15); y1h=yy<<8-0.5*xh2*h_grid_size+yh*v_grid_size*(Gs->num+1.15); xh=-xh; //Back to front to help with depth. yh=-yh; //Take half steps to cover whole grid. xh>>=1; yh>>=1; xh2>>=1; yh2>>=1; ww<<=1; hh<<=1; for (j=0;j<hh;j++) { x1w=x1h; y1w=y1h; for (i=0;i<ww;i++) { x1=x1w>>8; y1=y1w>>8; if (0<=x1<MAP_WIDTH && 0<=y1<MAP_HEIGHT) { if ((tempp=panels[y1][x1*2]) && !LBts(panels_processed_bitmap,tempp->num)) { base->color=tempp->color; if (tempp->cnt>8*(1.1-Gs->idle_factor)) tempp->cnt=GrFillPolygon3(base,tempp->n,tempp->points); else tempp->cnt++; } } x1w-=yh2; y1w+=xh2; } x1h+=xh; y1h+=yh; } Free(s2w); mp.update_done_t[Gs->num]=tNP; } U0 APFlightSimTask(TaskStruct *master_task) { GrBitMap *base=GrAlias(main_base,master_task); Preempt(ON); while (!mp.app_done) { LBts(&Fs->task_flags,TASKf_IDLE); while (!LBtr(&mp.do_update,Gs->num) && !mp.app_done) Yield; LBtr(&Fs->task_flags,TASKf_IDLE); if (!mp.app_done) MPDrawIt(master_task,base); } //We made an alias of this we don't want freed. base->depth_buf=NULL; GrDel(base); LBts(&mp.done_flags,Gs->num); } U0 BSPFlightSim(GrBitMap *base) { F8 t0,last_t; I8 i,xx,yy,height, cx=Fs->win_pixel_width>>1, cy=Fs->win_pixel_height>>1; t0=tNP; MemSet(panels_processed_bitmap,0,(num_panels+7)>>3); MemSet(mp.update_done_t,0,sizeof(F8)*MP_MAX_PROCESSORS); base->color=LTCYAN; GrRect(base,0,0,GR_WIDTH,GR_HEIGHT); GrDepthBufReset(base); //Signal all cores to start update mp.do_update=1<<mp.mp_cnt-1-1; xx=x/(MAP_SCALE*COORDINATE_SCALE); yy=y/(MAP_SCALE*COORDINATE_SCALE); height=z/COORDINATE_SCALE-elevations[yy][xx]; if (height<0) { if (!crashed) { music_mute=1; Snd(1000); //Signal plane crashed crashed=TRUE; } } else { if (crashed) { Snd(0); music_mute=0; crashed=FALSE; } } //Wait for all cores to start while (mp.do_update) Yield; MPDrawIt(Fs,base); //Wait for all cores to complete. LBts(&Fs->task_flags,TASKf_IDLE); for (i=1;i<mp.mp_cnt;i++) while (!mp.update_done_t[i]) Yield; LBtr(&Fs->task_flags,TASKf_IDLE); base->pen_width=2; base->color=BLACK; base->flags&=~BMF_TRANSFORMATION; GrLine3(base,cx+5,cy,0,cx-5,cy,0); GrLine3(base,cx,cy+5,0,cx,cy-5,0); GrPrintF(base,0,0,"Theta1:%5.1f Phi:%5.1f Theta2:%5.1f Box:%5.1f", theta1*180/pi,phi*180/pi,theta2*180/pi,v_grid_size); GrPrintF(base,0,FONT_HEIGHT,"x:%5.1f y:%5.1f z:%5.1f height:%3d", ToF8(x)/COORDINATE_SCALE,ToF8(y)/COORDINATE_SCALE,ToF8(z)/COORDINATE_SCALE, height); last_t=mp.update_done_t[0]; for (i=1;i<mp.mp_cnt;i++) last_t=Max(last_t,mp.update_done_t[i]); //Adjust size of updated grid. v_grid_size *=0.90+0.10*(0.65/win_max_refresh)/(last_t-t0); //Regulate how much grid expands in the distance. if (mp.mp_cnt>1) span_factor*=0.90+0.10*(mp.update_done_t[mp.mp_cnt-1]-t0)/(mp.update_done_t[0]-t0); WinSync; } U0 AnimateTask() { //This just steadily moves the airplane forward. I8 *s2w,x1,y1,z1; while (TRUE) { //Screen to world coordinates s2w=GrIdent; GrRotZEqu(s2w,-theta1); GrRotXEqu(s2w,-phi); GrRotZEqu(s2w,-theta2); x1=0;y1=0;z1=-speed*COORDINATE_SCALE; GrRot(s2w,&x1,&y1,&z1); x+=x1; y+=y1; z+=z1; Free(s2w); Sleep(1); } } U0 MPEnd() { mp.done_flags=0; mp.app_done=TRUE; //Wait for all cores to exit while (mp.done_flags!=1<<mp.mp_cnt-1-1) Yield; } U0 TaskEndCB() { MPEnd; Exit; } U0 SongTask() { //Song by Terry A. Davis #ifstatic Fs->task_end_cb=ExtFind("SndTaskEndCB");//(Cannot take address of an extern function.) #else Fs->task_end_cb=&SndTaskEndCB; #endif MusicSettingsReset; music_stacatto_factor=0.2; while (TRUE) { Play("W13eBDEDBDEDFEEDFEED"); Play("BDEDBDEDFEEDFEED"); } } U0 FlightSim(I8 _mp_cnt=MP_MAX_PROCESSORS) { I8 i,ch,sc; F8 pp,tt1,tt2; main_base=GrNew(BMT_COLOR4,GR_WIDTH,GR_HEIGHT); SettingsPush; //See SettingsPush //We really can't afford to run at 60Hz. This is how often VGA //memory is updated, not related to actual hardware refresh rates. WordStat(OFF); MenuPush( "File {" " Abort(,CH_CTRLQ);" " Exit(,CH_ESC);" "}" "Play {" " Down(,,SC_CURSOR_UP);" " Up(,,SC_CURSOR_DOWN);" " Left(,,SC_CURSOR_LEFT);" " Right(,,SC_CURSOR_RIGHT);" "}" ); WinMax; coutln "Initializing..."; critical_section_flag=0; Fs->song_task=Spawn(&SongTask,NULL,"Song",Fs); MemSet(&mp,0,sizeof(MPCtrl)); if (_mp_cnt<=mp_cnt) mp.mp_cnt=_mp_cnt; else mp.mp_cnt=mp_cnt; InitMap; Fs->animate_task=Spawn(&AnimateTask,NULL,"Animate",Fs); GrAllocDepthBuf(main_base); win_max_refresh=30.0; v_grid_size=64.0; span_factor=1.0; phi =-90.0*pi/180.0; theta1= 0.0*pi/180.0; theta2= 0.0*pi/180.0; speed = 2.5; x=MAP_WIDTH>>1 *COORDINATE_SCALE*MAP_SCALE; y=MAP_HEIGHT>>1*COORDINATE_SCALE*MAP_SCALE; z=64 *COORDINATE_SCALE*MAP_SCALE; for (i=1;i<mp.mp_cnt;i++) MPSpawn(&APFlightSimTask,Fs,"AP FlightSim",1<<i); Fs->task_end_cb=&TaskEndCB; Fs->update_win=&UpdateWin; try //in case CTRL-ALT-C is pressed. do { if (ScanKey(&ch,&sc)) { pp=phi; tt1=theta1; tt2=theta2; switch (ch) { case 0: switch (sc.u1[0]) { case SC_CURSOR_DOWN: phi +=-CONTROLS_SCALE*Cos(tt1); theta2+=-CONTROLS_SCALE*Sin(tt1)*Sin(phi); break; case SC_CURSOR_UP: phi -=-CONTROLS_SCALE*Cos(tt1); theta2-=-CONTROLS_SCALE*Sin(tt1)*Sin(phi); break; case SC_CURSOR_RIGHT: theta1+=CONTROLS_SCALE; break; case SC_CURSOR_LEFT: theta1-=CONTROLS_SCALE; break; } break; } theta1=Unwrap(theta1); phi =Unwrap(phi); theta2=Unwrap(theta2); } else BSPFlightSim(main_base); } while (ch!=CH_CTRLQ && ch!=CH_ESC); catch Fs->catch_except=TRUE; SettingsPop; WinSync; MPEnd; MenuPop; PanelsDel; GrDel(main_base); } FlightSim;