示例#1
0
文件: Turning.cpp 项目: LK8000/LK8000
void Turning(NMEA_INFO *Basic, DERIVED_INFO *Calculated)
{
  static double LastTrack = 0;
  static double StartTime  = 0;
  static double StartLong = 0;
  static double StartLat = 0;
  static double StartAlt = 0;
  static double StartEnergyHeight = 0;
  static double LastTime = 0;
  static int MODE = CRUISE;
  static bool LEFT = FALSE;
  double Rate;
  static double LastRate=0;
  double dRate;
  double dT;

  if (!Calculated->Flying) {
	if (MODE!=CRUISE) {
		#if TESTBENCH
		StartupStore(_T(".... Not flying, still circling -> Cruise forced!\n"));
		#endif
		goto _forcereset;
	}
	return;
  }

  // Back in time in IGC replay mode?
  if(Basic->Time <= LastTime) {
    #if TESTBENCH
    StartupStore(_T("...... Turning check is back in time. Now=%f Last=%f: reset %s\n"),Basic->Time, LastTime,WhatTimeIsIt());
    #endif
_forcereset:
    LastTime = Basic->Time; // 101216 PV not sure of this.. 
    LastTrack = 0;
    StartTime  = 0;
    StartLong = 0;
    StartLat = 0;
    StartAlt = 0;
    StartEnergyHeight = 0;
    LastTime = 0;
    LEFT = FALSE;
    if (MODE!=CRUISE) {
	MODE = CRUISE;
        // Finally do the transition to cruise
        Calculated->Circling = false;
        SwitchZoomClimb(Basic, Calculated, false, LEFT);
        InputEvents::processGlideComputer(GCE_FLIGHTMODE_CRUISE);
    }
    return;
  }
  dT = Basic->Time - LastTime;
  LastTime = Basic->Time;

  #if BUGSTOP
  LKASSERT(dT!=0);
  #else
  if (dT==0) dT=1;
  #endif

  Rate = AngleLimit180(Basic->TrackBearing-LastTrack)/dT;

  #if DEBUGTURN
  StartupStore(_T("... Rate=%f  in time=%f\n"),Rate,dT);
  #endif

  if (dT<2.0 && dT!=0) {
    // time step ok

    // calculate acceleration
    dRate = (Rate-LastRate)/dT;

    double dtlead=0.3;
    // integrate assuming constant acceleration, for one second
    Calculated->NextTrackBearing = Basic->TrackBearing
      + dtlead*(Rate+0.5*dtlead*dRate);
    // s = u.t+ 0.5*a*t*t

    Calculated->NextTrackBearing = 
      AngleLimit360(Calculated->NextTrackBearing);
    
  } else {
    // time step too big, so just take it at last measurement
    Calculated->NextTrackBearing = Basic->TrackBearing;
  }

  Calculated->TurnRate = Rate;

  // JMW limit rate to 50 deg per second otherwise a big spike
  // will cause spurious lock on circling for a long time
  if (Rate>50) {
    Rate = 50;
  } 
  if (Rate<-50) {
    Rate = -50;
  }

  // average rate, to detect essing
  static double rate_history[60];
  double rate_ave=0;
  for (int i=59; i>0; i--) {
    rate_history[i] = rate_history[i-1];
    rate_ave += rate_history[i];
  }
  rate_history[0] = Rate;
  rate_ave /= 60;
 
  // THIS IS UNUSED in 4.0 
  Calculated->Essing = fabs(rate_ave)*100/MinTurnRate;

  if (MODE==CLIMB||MODE==WAITCRUISE)
	Rate = LowPassFilter(LastRate,Rate,0.9);
  else
	Rate = LowPassFilter(LastRate,Rate,0.3);

  LastRate = Rate;

  if(Rate <0)
    {
      if (LEFT) {
        // OK, already going left
      } else {
        LEFT = true;
      }
      Rate *= -1;
    } else {
    if (!LEFT) {
      // OK, already going right
    } else {
      LEFT = false;
    }
  }

  PercentCircling(Basic, Calculated, Rate);

  LastTrack = Basic->TrackBearing;

  bool forcecruise = false;
  bool forcecircling = false;

  #if 1 // UNUSED, EnableExternalTriggerCruise not configurable, set to false since ever
  if (EnableExternalTriggerCruise ) {
    if (ExternalTriggerCruise && ExternalTriggerCircling) {
      // this should never happen
      ExternalTriggerCircling = false;
    }
    forcecruise = ExternalTriggerCruise;
    forcecircling = ExternalTriggerCircling;
  }
  #endif

  switch(MODE) {
  case CRUISE:

    double cruise_turnthreshold;
    if (ISPARAGLIDER)
	cruise_turnthreshold=5;
    else
	cruise_turnthreshold=4;

    if((Rate >= cruise_turnthreshold)||(forcecircling)) {
      // This is initialising the potential thermalling start
      // We still dont know if we are really circling for thermal
      StartTime = Basic->Time;
      StartLong = Basic->Longitude;
      StartLat  = Basic->Latitude;
      StartAlt  = Calculated->NavAltitude;
      StartEnergyHeight  = Calculated->EnergyHeight;
      #if DEBUGTURN
      StartupStore(_T("... CRUISE -> WAITCLIMB\n"));
      #endif
      MODE = WAITCLIMB;
    }
    if (forcecircling) {
      MODE = WAITCLIMB;
    } else {
      break;
    }
  case WAITCLIMB:
    if (forcecruise) {
      MODE = CRUISE;
      break;
    }

    double waitclimb_turnthreshold;
    double cruiseclimbswitch;
    if (ISPARAGLIDER) {
	waitclimb_turnthreshold=5;
        cruiseclimbswitch=15; // this should be finetuned for PGs
    } else {
	waitclimb_turnthreshold=4;
        cruiseclimbswitch=15; // this is ok for gliders
    }

    if((Rate >= waitclimb_turnthreshold)||(forcecircling)) {
      // WE CANNOT do this, because we also may need Circling mode to detect FF!!
      // if( (Calculated->FreeFlying && ((Basic->Time  - StartTime) > cruiseclimbswitch))|| forcecircling) {
       if( (!ISCAR && !ISGAAIRCRAFT && ((Basic->Time  - StartTime) > cruiseclimbswitch))|| forcecircling) { 

         #ifdef TOW_CRUISE
         // If free flight (FF) hasn�t yet been detected, then we may
         // still be on tow.  The following prevents climb mode from
         // engaging due to normal on-aerotow turns.

         if (!Calculated->FreeFlying && (fabs(Calculated->TurnRate) < 12))
           break;
         #endif

         #if DEBUGTURN
         StartupStore(_T("... WAITCLIMB -> CLIMB\n"));
         #endif
       Calculated->Circling = true;
        // JMW Transition to climb
        MODE = CLIMB;
	// Probably a replay flight, with fast forward with no cruise init
	if (StartTime==0) {
	      StartTime = Basic->Time;
	      StartLong = Basic->Longitude;
	      StartLat  = Basic->Latitude;
	      StartAlt  = Calculated->NavAltitude;
	      StartEnergyHeight  = Calculated->EnergyHeight;
	}
        Calculated->ClimbStartLat = StartLat;
        Calculated->ClimbStartLong = StartLong;
        Calculated->ClimbStartAlt = StartAlt+StartEnergyHeight;
        Calculated->ClimbStartTime = StartTime;
        
        if (flightstats.Altitude_Ceiling.sum_n>0) {
          // only update base if have already climbed, otherwise
          // we will catch the takeoff height as the base.

          flightstats.Altitude_Base.
            least_squares_update(max(0.0, Calculated->ClimbStartTime
                                     - Calculated->TakeOffTime)/3600.0,
                                 StartAlt);
        }
        
        SwitchZoomClimb(Basic, Calculated, true, LEFT);
        InputEvents::processGlideComputer(GCE_FLIGHTMODE_CLIMB);
      }
    } else {
      // nope, not turning, so go back to cruise
      #if DEBUGTURN
      StartupStore(_T("... WAITCLIMB -> CRUISE\n"));
      #endif
      MODE = CRUISE;
    }
    break;
  case CLIMB:
    if ( (AutoWindMode == D_AUTOWIND_CIRCLING) || (AutoWindMode==D_AUTOWIND_BOTHCIRCZAG) ) {
      LockFlightData();
      windanalyser->slot_newSample(Basic, Calculated);
      UnlockFlightData();
    }

    double climb_turnthreshold;
    if (ISPARAGLIDER)
	climb_turnthreshold=10;
    else
	climb_turnthreshold=4;
    
    if((Rate < climb_turnthreshold)||(forcecruise)) {
      StartTime = Basic->Time;
      StartLong = Basic->Longitude;
      StartLat  = Basic->Latitude;
      StartAlt  = Calculated->NavAltitude;
      StartEnergyHeight  = Calculated->EnergyHeight;
      // JMW Transition to cruise, due to not properly turning
      MODE = WAITCRUISE;
      #if DEBUGTURN
      StartupStore(_T("... CLIMB -> WAITCRUISE\n"));
      #endif
    }
    if (forcecruise) {
      MODE = WAITCRUISE;
    } else {
      break;
    }
  case WAITCRUISE:

    if (forcecircling) {
      MODE = CLIMB;
      break;
    }

    double waitcruise_turnthreshold;
    double climbcruiseswitch;
    if (ISPARAGLIDER) {
	waitcruise_turnthreshold=10;
        climbcruiseswitch=15;
    } else {
	waitcruise_turnthreshold=4;
        climbcruiseswitch=9; // ok for gliders
    }
    //
    // Exiting climb mode?
    //
    if((Rate < waitcruise_turnthreshold) || forcecruise) {

      if( ((Basic->Time  - StartTime) > climbcruiseswitch) || forcecruise) {

	// We are no more in climb mode

        if (StartTime==0) {
          StartTime = Basic->Time;
          StartLong = Basic->Longitude;
          StartLat  = Basic->Latitude;
          StartAlt  = Calculated->NavAltitude;
          StartEnergyHeight  = Calculated->EnergyHeight;
        }
        Calculated->CruiseStartLat  = StartLat;
        Calculated->CruiseStartLong = StartLong;
        Calculated->CruiseStartAlt  = StartAlt;
        Calculated->CruiseStartTime = StartTime;

	// Here we assign automatically this last thermal to the L> multitarget
	if (Calculated->ThermalGain >100) {

		// Force immediate calculation of average thermal, it would be made
		// during next cycle, but we need it here immediately
		AverageThermal(Basic,Calculated);

		if (EnableThermalLocator) {
			InsertThermalHistory(Calculated->ClimbStartTime, 
				Calculated->ThermalEstimate_Latitude, Calculated->ThermalEstimate_Longitude,
				Calculated->ClimbStartAlt, Calculated->NavAltitude, Calculated->AverageThermal);
		} else {
			InsertThermalHistory(Calculated->ClimbStartTime, 
				Calculated->ClimbStartLat, Calculated->ClimbStartLong, 
				Calculated->ClimbStartAlt, Calculated->NavAltitude, Calculated->AverageThermal);
		}

	}

	InitLDRotary(&rotaryLD);
	InitWindRotary(&rotaryWind);
        
        flightstats.Altitude_Ceiling.
          least_squares_update(max(0.0, Calculated->CruiseStartTime
                                   - Calculated->TakeOffTime)/3600.0,
                               Calculated->CruiseStartAlt);
        
        // Finally do the transition to cruise
        Calculated->Circling = false;
        MODE = CRUISE;
        #if DEBUGTURN
        StartupStore(_T("... WAITCRUISE -> CRUISE\n"));
        #endif
        SwitchZoomClimb(Basic, Calculated, false, LEFT);
        InputEvents::processGlideComputer(GCE_FLIGHTMODE_CRUISE);

      } // climbcruiseswitch time in range

    } else { // Rate>Minturnrate, back to climb, turning again
      #if DEBUGTURN
      StartupStore(_T("... WAITCRUISE -> CLIMB\n"));
      #endif
      MODE = CLIMB;
    }
    break;
  default:
    // error, go to cruise
    MODE = CRUISE;
  }
  // generate new wind vector if altitude changes or a new
  // estimate is available
  if (AutoWindMode>D_AUTOWIND_MANUAL && AutoWindMode <D_AUTOWIND_EXTERNAL) {
    LockFlightData();
    windanalyser->slot_Altitude(Basic, Calculated);
    UnlockFlightData();
  }

  if (EnableThermalLocator) {
    if (Calculated->Circling) {
      thermallocator.AddPoint(Basic->Time, Basic->Longitude, Basic->Latitude,
			      Calculated->NettoVario);
      thermallocator.Update(Basic->Time, Basic->Longitude, Basic->Latitude,
			    Calculated->WindSpeed, Calculated->WindBearing,
			    Basic->TrackBearing,
			    &Calculated->ThermalEstimate_Longitude,
			    &Calculated->ThermalEstimate_Latitude,
			    &Calculated->ThermalEstimate_W,
			    &Calculated->ThermalEstimate_R);
    } else {
      Calculated->ThermalEstimate_W = 0;
      Calculated->ThermalEstimate_R = -1;
      thermallocator.Reset();
    }
  }

  // update atmospheric model
  CuSonde::updateMeasurements(Basic, Calculated);

}
示例#2
0
//
// LK8000 SS1 = Soaring Simulator V1 by Paolo Ventafridda
// Still basic, but usable
//
void LKSimulator(void) {

  LockFlightData();

  // 
  GPS_INFO.NAVWarning = false;
  GPS_INFO.SatellitesUsed = 6;
  // Even on ground, we can turn the glider in the hangar
  BEARING += SimTurn; 
  if (BEARING<0) BEARING+=360;
  else if (BEARING>359) BEARING-=360;

  #if SIMLANDING
  static bool crashed=false, landedwarn=true;
  #endif
  static bool doinit=true, landing=false, stallwarn=true, circling=false, flarmwasinit=false;
  static short counter=0;

  double tdistance, tbearing;
  double thermalstrength=0, sinkstrength=0;

  extern void SimFlarmTraffic(long id, double offset);

  if (doinit) {
	if (counter++<4) {
		UnlockFlightData();
		return;
	}
	#if TESTBENCH
	StartupStore(_T(". SIMULATOR: real init%s"),NEWLINE);
	#endif

	// Add a couple of thermals for the boys
	InsertThermalHistory(GPS_INFO.Time-1887, GPS_INFO.Latitude-0.52, GPS_INFO.Longitude-0.52, 873, 1478,1.5);
	InsertThermalHistory(GPS_INFO.Time-987, GPS_INFO.Latitude-0.41, GPS_INFO.Longitude-0.41, 762, 1367,1.8);
	InsertThermalHistory(GPS_INFO.Time-100, GPS_INFO.Latitude-0.02, GPS_INFO.Longitude-0.02, 650, 1542,2.2);
	WayPointList[RESWP_LASTTHERMAL].Latitude  = GPS_INFO.Latitude-0.022;
	WayPointList[RESWP_LASTTHERMAL].Longitude = GPS_INFO.Longitude-0.022;
	WayPointList[RESWP_LASTTHERMAL].Altitude  = 650;
	ThLatitude=GPS_INFO.Latitude-0.022;
	ThLongitude=GPS_INFO.Longitude-0.022;

	if (EnableFLARMMap) {
		srand( GetTickCount());
		SimFlarmTraffic(0xdd8951,22.0+(double)(rand()/1000.0));
		SimFlarmTraffic(0xdd8944,31.0+(double)(rand()/1000.0));
		SimFlarmTraffic(0xdd8a43,16.0+(double)(rand()/1000.0));
		SimFlarmTraffic(0xdd8a42,41.0+(double)(rand()/1000.0));
	}
	doinit=false;
  }

  // First Aircraft min altitude is at ground level
  if (ALTITUDE==0)
	if (CALCULATED_INFO.TerrainValid) ALTITUDE= CALCULATED_INFO.TerrainAlt;


  if (ISGAAIRCRAFT) {
	// todo: fuel consumption, engine efficiency etc.
  }

  // We cannot use doinit for flarm, because it could be enabled from configuration AFTER startup,
  // and it must work all the way the same in order not to confuse users.
  if (EnableFLARMMap) {
	if (!flarmwasinit) {
		srand( GetTickCount());
		// Add a poker of traffic for the boys
		SimFlarmTraffic(0xdd8951,22.0+(double)(rand()/1000.0));
		SimFlarmTraffic(0xdd8944,31.0+(double)(rand()/1000.0));
		SimFlarmTraffic(0xdd8a43,16.0+(double)(rand()/1000.0));
		SimFlarmTraffic(0xdd8a42,41.0+(double)(rand()/1000.0));
		DoStatusMessage(gettext(TEXT("_@M279_"))); // FLARM DETECTED (in sim)
		flarmwasinit=true;
	} else {
		// Let one of the objects be a ghost and a zombie, and keep the rest real
		SimFlarmTraffic(0xdd8951,0);
		SimFlarmTraffic(0xdd8944,0);
		SimFlarmTraffic(0xdd8a43,0);
	}
  }

  if (ISPARAGLIDER || ISGLIDER) {
 
    // SetBallast is calculating sinkratecache for values starting from 4 to MAXSPEED, in m/s .
    // ONLY during flight, we will sink in the air
    if (FLYING && (IASMS>3) && (IASMS<MAXSPEED) ) {

	double sinkias=-1*(GlidePolar::sinkratecache[(int)IASMS]);
	if (sinkias>10) sinkias=10; // set a limiter for sink rate
	// StartupStore(_T(".... ias=%.0f sinkias=%.3f oldAlt=%.3f newAlt=%.3f\n"), 
	// CALCULATED_INFO.IndicatedAirspeedEstimated*TOKPH, sinkias, GPS_INFO.Altitude, GPS_INFO.Altitude+sinkias);
	double simlift=0;
	if (THERMALLING == TRUE) {
		// entering the thermal mode right now
		if (!circling) {
			circling=true;
			
			DistanceBearing(GPS_INFO.Latitude,GPS_INFO.Longitude,ThLatitude,ThLongitude,&tdistance,&tbearing);
			if (tdistance>1000) {
				// a new thermal
				ThLatitude=GPS_INFO.Latitude; // we mark the new thermal
				ThLongitude=GPS_INFO.Longitude;
				ALTITUDE+=simlift; // sink rate adjusted later
			} else {
				// start circling near the old thermal
			}
		} else {
			// already thermalling
		}
		// ALTITUDE+=simlift+GlidePolar::minsink;
	} else {
		if (circling) {
			// we were circling, now leaving the thermal
			circling=false;
		} else {
			// not circling, already cruising
		}
	}

	// Are we near the thermal?
	DistanceBearing(GPS_INFO.Latitude,GPS_INFO.Longitude,ThLatitude,ThLongitude,&tdistance,&tbearing);
	thermalstrength=4; // m/s
	ThermalRadius=200; // we assume a perfect thermal, a circle of this diameter. Stronger in the center.
	// thermalbase
	sinkstrength=2; //  how intense is the fallout of the thermal
	SinkRadius=150; //  circular ring of the fallout

	if (tdistance>=ThermalRadius && tdistance<(ThermalRadius+SinkRadius) ) {
		// we are in the sinking zone of the thermal..
		simlift= sinkstrength- ((tdistance-ThermalRadius)/SinkRadius)*sinkstrength;
		simlift+=0.1; // adjust rounding errors
		simlift*=-1;
		//StartupStore(_T(".. sinking zone:  dist=%.1f  sink=%.1f\n"), tdistance,simlift);
	}
	if (tdistance<ThermalRadius) {
		// we are in the lift zone
		simlift= thermalstrength- (tdistance/ThermalRadius)*thermalstrength;
		simlift+=0.1; // adjust rounding errors
		//StartupStore(_T(".. climbing zone:  dist=%.1f  climb=%.1f\n"), tdistance,simlift);
	}
	// Update altitude with the lift or sink, 
	ALTITUDE+=simlift;
	// Update the new altitude with the natural sink, but not going lower than 0
	ALTITUDE-=(sinkias+0.1); // rounding errors require a correction
	if (ALTITUDE<=0) ALTITUDE=0;

	#if SIMLANDING
	if (CALCULATED_INFO.TerrainValid && (CALCULATED_INFO.AltitudeAGL <=20) ) {
		if (IAS <= (MINSPEED+3)) landing=true;
		else {
			// we dont simulate crashing. LK8000 pilots never crash. 
			crashed=true;
		}
	} 
	if (CALCULATED_INFO.TerrainValid && (CALCULATED_INFO.AltitudeAGL >100) ) {
		landing=false;
	}

	if (!landing && CALCULATED_INFO.TerrainValid && (CALCULATED_INFO.AltitudeAGL <=0) ) {
		GPS_INFO.Speed=0;
		landing=true;
		if (landedwarn) {
			DoStatusMessage(_T("YOU HAVE LANDED"));
			landedwarn=false;
		}
	} else landedwarn=true;
	#endif
		
    } 
  } // Glider/Paragliders

  if (FLYING) {
	// simple stall at 1 G
	if (!landing && (IAS<=STALLSPEED && IASMS>3)) {
		if (stallwarn) {
			// DoStatusMessage(_T("STALLING")); // OK, people do not like stalling.
			stallwarn=false;
		}
		#if 0 // DO NOT SIMULATE STALLING NOW
		// GPS_INFO.Speed= (GlidePolar::Vminsink*0.85)+1;
		ALTITUDE-=20;
		if (ALTITUDE<=0) ALTITUDE=0;
		#endif
	} else stallwarn=true;
	#if SIMLANDING
	if (landing || IASMS<4) {
		GPS_INFO.Speed-=GPS_INFO.Speed*0.2;
	}
	if (crashed) {
		GPS_INFO.Speed=0;
	}
	#endif

	if (GS<0) {
		GPS_INFO.Speed=0;
	}
  }


  FindLatitudeLongitude(GPS_INFO.Latitude, GPS_INFO.Longitude, 
                          GPS_INFO.TrackBearing, GPS_INFO.Speed*1.0,
                          &GPS_INFO.Latitude,
                          &GPS_INFO.Longitude);
  GPS_INFO.Time+= 1.0;
  long tsec = (long)GPS_INFO.Time;
  GPS_INFO.Hour = tsec/3600;
  GPS_INFO.Minute = (tsec-GPS_INFO.Hour*3600)/60;
  GPS_INFO.Second = (tsec-GPS_INFO.Hour*3600-GPS_INFO.Minute*60);

  UnlockFlightData();
  }
示例#3
0
//
// LK8000 SS1 = Soaring Simulator V1 by Paolo Ventafridda
// Still basic, but usable
//
void LKSimulator(void) {

  LockFlightData();

  //
  GPS_INFO.NAVWarning = false;
  GPS_INFO.SatellitesUsed = 6;
  // Even on ground, we can turn the glider in the hangar
  BEARING += SimTurn;
  if (BEARING<0) BEARING+=360;
  else if (BEARING>359) BEARING-=360;

  #if SIMLANDING
  static bool crashed=false, landedwarn=true;
  #endif
  static bool doinit=true, landing=false, stallwarn=true, circling=false, flarmwasinit=false;
  static short counter=0;

  double tdistance, tbearing;
  double thermalstrength=0, sinkstrength=0;

  extern void SimFlarmTraffic(long id, double offset);

  if (doinit) {
	if (counter++<4) {
		UnlockFlightData();
		return;
	}
	#if TESTBENCH
	StartupStore(_T(". SIMULATOR: real init%s"),NEWLINE);
	#endif

	// Add a couple of thermals for the boys
	InsertThermalHistory(GPS_INFO.Time-1887, GPS_INFO.Latitude-0.21, GPS_INFO.Longitude+0.13, 873, 1478,1.5);
	InsertThermalHistory(GPS_INFO.Time-1250, GPS_INFO.Latitude+0.15, GPS_INFO.Longitude-0.19, 991, 1622,0.9);
	InsertThermalHistory(GPS_INFO.Time-987, GPS_INFO.Latitude-0.11, GPS_INFO.Longitude+0.13, 762, 1367,1.8);
	InsertThermalHistory(GPS_INFO.Time-100, GPS_INFO.Latitude-0.02, GPS_INFO.Longitude-0.03, 650, 1542,2.2);
	WayPointList[RESWP_LASTTHERMAL].Latitude  = GPS_INFO.Latitude-0.022;
	WayPointList[RESWP_LASTTHERMAL].Longitude = GPS_INFO.Longitude-0.033;
	WayPointList[RESWP_LASTTHERMAL].Altitude  = 650;
	ThLatitude=GPS_INFO.Latitude-0.022;
	ThLongitude=GPS_INFO.Longitude-0.033;

	if (EnableFLARMMap && !LiveTrackerRadar_config ) {
		srand(MonotonicClockMS());
		SimFlarmTraffic(0xdd8951,22.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8944,31.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a43,16.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a42,41.0+(double)(rand() % 32));
/*
		SimFlarmTraffic(0xdd8a11,11.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a12,21.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a13,31.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a14,41.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a15,51.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a16,61.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a17,71.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a18,81.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a19,91.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a10,01.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a21,21.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a22,22.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a23,23.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a24,24.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a25,25.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a26,26.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a27,27.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a28,28.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a29,29.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a20,31.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a31,32.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a32,33.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a33,34.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a34,35.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a35,36.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a36,37.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a37,41.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a38,42.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a39,43.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a30,44.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a41,45.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a42,46.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a43,47.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a44,48.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a45,49.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a46,01.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a47,02.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a48,03.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a49,04.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a50,81.1+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a51,82.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a52,83.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a53,84.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a54,85.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a55,86.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a56,87.0+(double)(rand() % 32));
*/
	}
	doinit=false;
  }

  // First Aircraft min altitude is at ground level
  if (ALTITUDE==0)
	if (CALCULATED_INFO.TerrainValid) ALTITUDE= CALCULATED_INFO.TerrainAlt;


  if (ISGAAIRCRAFT) {
	// todo: fuel consumption, engine efficiency etc.
  }

  // We cannot use doinit for flarm, because it could be enabled from configuration AFTER startup,
  // and it must work all the way the same in order not to confuse users.
  if (EnableFLARMMap && !LiveTrackerRadar_config ) {
	if (!flarmwasinit) {
		srand(MonotonicClockMS());
		// Add a poker of traffic for the boys
		SimFlarmTraffic(0xdd8951,22.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8944,31.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a43,16.0+(double)(rand() % 32));
		SimFlarmTraffic(0xdd8a42,41.0+(double)(rand() % 32));
		DoStatusMessage(MsgToken(279)); // FLARM DETECTED (in sim)
		flarmwasinit=true;
	} else {
		// Let one of the objects be a ghost and a zombie, and keep the rest real
		SimFlarmTraffic(0xdd8951,0);
		SimFlarmTraffic(0xdd8944,0);
		SimFlarmTraffic(0xdd8a43,0);

		// update relative altitude for ghost/zombie traffic
		extern int FLARM_FindSlot(NMEA_INFO *GPS_INFO, long Id);
		int flarmslot=FLARM_FindSlot(&GPS_INFO, 0xdd8a42);
		if (flarmslot>=0)
			GPS_INFO.FLARM_Traffic[flarmslot].RelativeAltitude = GPS_INFO.FLARM_Traffic[flarmslot].Altitude - GPS_INFO.Altitude;

	}
  }

  if (ISPARAGLIDER || ISGLIDER) {

    // SetBallast is calculating sinkratecache for values starting from 4 to MAXSPEED, in m/s .
    // ONLY during flight, we will sink in the air
    if (FLYING && (IASMS>3) && (IASMS<MAXSPEED) ) {

	double sinkias=-1*AirDensitySinkRate(IASMS, GPS_INFO.Altitude);
	if (sinkias>10) sinkias=10; // set a limiter for sink rate
	// StartupStore(_T(".... ias=%.1f sinkias=%.3f oldAlt=%.3f newAlt=%.3f\n"),
	// CALCULATED_INFO.IndicatedAirspeedEstimated*TOKPH, sinkias, GPS_INFO.Altitude, GPS_INFO.Altitude-sinkias);
	double simlift=0;
	if (THERMALLING == TRUE) {
		// entering the thermal mode right now
		if (!circling) {
			circling=true;

			DistanceBearing(GPS_INFO.Latitude,GPS_INFO.Longitude,ThLatitude,ThLongitude,&tdistance,&tbearing);
			if (tdistance>1000) {
				// a new thermal
				ThLatitude=GPS_INFO.Latitude; // we mark the new thermal
				ThLongitude=GPS_INFO.Longitude;
				ALTITUDE+=simlift; // sink rate adjusted later
			} else {
				// start circling near the old thermal
			}
		} else {
			// already thermalling
		}
		// ALTITUDE+=simlift+GlidePolar::minsink;
	} else {
        sinkias -= SimNettoVario;
		if (circling) {
			// we were circling, now leaving the thermal
			circling=false;
		} else {
			// not circling, already cruising
		}
	}

	// Are we near the thermal?
	DistanceBearing(GPS_INFO.Latitude,GPS_INFO.Longitude,ThLatitude,ThLongitude,&tdistance,&tbearing);
	thermalstrength=4; // m/s
	ThermalRadius=200; // we assume a perfect thermal, a circle of this diameter. Stronger in the center.
	// thermalbase
	sinkstrength=2; //  how intense is the fallout of the thermal
	SinkRadius=150; //  circular ring of the fallout

	if (tdistance>=ThermalRadius && tdistance<(ThermalRadius+SinkRadius) ) {
		// we are in the sinking zone of the thermal..
		simlift= sinkstrength- ((tdistance-ThermalRadius)/SinkRadius)*sinkstrength;
		simlift+=0.1; // adjust rounding errors
		simlift*=-1;
		//StartupStore(_T(".. sinking zone:  dist=%.1f  sink=%.1f\n"), tdistance,simlift);
	}
	if (tdistance<ThermalRadius) {
		// we are in the lift zone
		simlift= thermalstrength- (tdistance/ThermalRadius)*thermalstrength;
		simlift+=0.1; // adjust rounding errors
		//StartupStore(_T(".. climbing zone:  dist=%.1f  climb=%.1f\n"), tdistance,simlift);
	}
	// Update altitude with the lift or sink,
	ALTITUDE+=simlift;
	// Update the new altitude with the natural sink, but not going lower than 0
	ALTITUDE-=sinkias;
	if (ALTITUDE<=0) ALTITUDE=0;

	#if SIMLANDING
	if (CALCULATED_INFO.TerrainValid && (CALCULATED_INFO.AltitudeAGL <=20) ) {
		if (IAS <= (MINSPEED+3)) landing=true;
		else {
			// we dont simulate crashing. LK8000 pilots never crash.
			crashed=true;
		}
	}
	if (CALCULATED_INFO.TerrainValid && (CALCULATED_INFO.AltitudeAGL >100) ) {
		landing=false;
	}

	if (!landing && CALCULATED_INFO.TerrainValid && (CALCULATED_INFO.AltitudeAGL <=0) ) {
		GPS_INFO.Speed=0;
		landing=true;
		if (landedwarn) {
			DoStatusMessage(_T("YOU HAVE LANDED"));
			landedwarn=false;
		}
	} else landedwarn=true;
	#endif

    }
  } // Glider/Paragliders

  if (FLYING) {
	// simple stall at 1 G
	if (!landing && (IAS<=STALLSPEED && IASMS>3)) {
		if (stallwarn) {
			// DoStatusMessage(_T("STALLING")); // OK, people do not like stalling.
			stallwarn=false;
		}
		#if 0 // DO NOT SIMULATE STALLING NOW
		// GPS_INFO.Speed= (GlidePolar::Vminsink*0.85)+1;
		ALTITUDE-=20;
		if (ALTITUDE<=0) ALTITUDE=0;
		#endif
	} else stallwarn=true;
	#if SIMLANDING
	if (landing || IASMS<4) {
		GPS_INFO.Speed-=GPS_INFO.Speed*0.2;
	}
	if (crashed) {
		GPS_INFO.Speed=0;
	}
	#endif

	if (GS<0) {
		GPS_INFO.Speed=0;
	}
  }


  if (GS>0) {
      FindLatitudeLongitude(GPS_INFO.Latitude, GPS_INFO.Longitude,
                          GPS_INFO.TrackBearing, GPS_INFO.Speed,
                          &GPS_INFO.Latitude,
                          &GPS_INFO.Longitude);
  }
  GPS_INFO.Time+= 1.0;
  long tsec = (long)GPS_INFO.Time;
  GPS_INFO.Hour = tsec/3600;
  GPS_INFO.Minute = (tsec-GPS_INFO.Hour*3600)/60;
  GPS_INFO.Second = (tsec-GPS_INFO.Hour*3600-GPS_INFO.Minute*60);

  UnlockFlightData();
}