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;