1 /*
2  * CRRCsim - the Charles River Radio Control Club Flight Simulator Project
3  *
4  * Copyright (C) 2000, 2001 Jan Edward Kansky (original author)
5  * Copyright (C) 2005, 2006, 2008 Jens Wilhelm Wulf
6  * Copyright (C) 2005-2009 Jan Reucker
7  * Copyright (C) 2009 Joel Lienard
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License version 2
11  * as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330,
21  * Boston, MA 02111-1307, USA.
22  *
23  */
24 
25 /** \file windfield.cpp
26  *
27  *  This file contains the windfield and thermal simulation.
28  */
29 
30 #include "windfield.h"
31 
32 #include "../global.h"
33 #include "../include_gl.h"
34 #include <math.h>
35 #include <stdio.h>
36 #include <plib/ssg.h>   // for ssgSimpleState
37 #include "../mod_misc/ls_constants.h"
38 //jwtodo #include "../config.h"
39 // remaining calls to cfg:
40 //    cfg->thermal->density     on init
41 //    cfg->wind->getVelocity()  on update and calculate_wind
42 //    cfg->wind->getDirection() on update and calculate_wind
43 //    cfg->getDynamicSoaring()  on calculate_wind
44 //  random_init:
45 //      cfg->thermal->radius_sigma
46 //      cfg->thermal->radius_mean
47 //      ....
48 
49 #include "../crrc_main.h"
50 #include "../global_video.h"
51 #include "thermal03/tschalen.h"
52 #include "../mod_landscape/crrc_scenery.h"
53 
54 #if (THERMAL_CODE == 1)
55 # include "thermalprofile.h"
56 #endif
57 
58 #if DEBUG_THERMAL_SCRSHOT == 1
59 # include <fstream>
60 #endif
61 
62 #define USE_CRRCRAND 1
63 
64 // THERMAL_TEST == 0
65 //   Simulation as usual
66 // THERMAL_TEST == 1
67 // THERMAL_TEST == 2
68 //   There is only one thermal.
69 //   It is created at a fixed position with fixed parameters.
70 //   See documentation/thermals/thermalsim.html
71 #define THERMAL_TEST 0
72 
73 #define THERMAL_NEWPOSLOG 1
74 
75 /**
76  * Which version of thermal model to use?
77  * Everthing less than 3 results in the 'old' code (which depends on THERMAL_CODE
78  * in crrc_config.h), 3 results in version 3.
79  */
80 unsigned int ThermalVersion;
81 
82 /**
83  * There is some 2D grid. Its area is
84  *    (occupancy_grid_size * occupancy_grid_res)^2
85  * It is divided into occupancy_grid_size^2 squares.
86  */
87 #define occupancy_grid_size_exp 7
88 #define occupancy_grid_size     (1 << occupancy_grid_size_exp)
89 #define occupancy_grid_res      100
90 Thermal* thermal_occupancy_grid[occupancy_grid_size][occupancy_grid_size];
91 
92 #if (THERMAL_NEWPOSLOG != 0)
93 unsigned int NewPosLogArray[occupancy_grid_size][occupancy_grid_size];
94 unsigned int PosLogArray[occupancy_grid_size][occupancy_grid_size];
95 #endif
96 
97 /**
98  * How many grid places should be free next to a thermal?
99  */
100 const int nGridDistMin = 1;
101 
102 /**
103  * The number of thermals in the above grid is
104  *    (occupancy_grid_size * occupancy_grid_res)^2 * cfg->thermal->density
105  */
106 int num_thermals;
107 
108 /**
109  * The pointer to the first thermal in the linked list.
110  */
111 Thermal* thermals = NULL;
112 
113 /**
114  * Random normal (gaussian) distribution of thermal radius, strength
115  * and lifetime.
116  */
117 RandGauss rnd_radius;
118 RandGauss rnd_strength;
119 RandGauss rnd_lifetime;
120 
121 /**
122  * One thermal influences an area of
123  *    (nInfluenceDist*occupancy_grid_res)^2
124  * or less.
125  * Radius of the largest (since init) thermal in lengths of the grid.
126  */
127 int nInfluenceDist = 5;
128 
129 /**
130  * To draw thermals, one of two methods is used:
131  * 1. loop over linked list of thermals and draw every thermal which
132  *    is near the aircraft
133  * 2. Look at grid around aircraft and draw present thermals. This
134  *    method means less effort if the thermal density is high. It also
135  *    doesn't draw thermal which are in the list but not in the grid (may
136  *    happen when thermal density is high).
137  * If the second method is used, this variable represents a distance
138  * in grid coordinates.
139  */
140 int nDrawThermalsFromGrid;
141 
142 /**
143  * Draw thermals with this maximum distance from aircraft.
144  * Currently this is not the real distance, but the one in x or y.
145  */
146 const float flThermalDistMax = 1000;
147 
148 /**
149  * Standard thermal according to thermal model version 3.
150  */
151 ThermikSchalen thermalv3;
152 
153 #if (THERMAL_CODE == 1)
154 /**
155  * Thermals get weaker before they die. Time in seconds.
156  */
157 const double dFadeOutTime = 10;
158 
159 /**
160  * In the old implementation there has been a value similar to this one. It had
161  * been set to 50 feet.
162  */
163 # if THERMAL_TEST != 0
164 const double dAltitudeFullStrength =  0.5/0.3048;
165 const double dAltitudeZeroStrength =  0/0.3048;
166 # else
167 const double dAltitudeFullStrength = 20/0.3048;
168 const double dAltitudeZeroStrength =  4/0.3048;
169 # endif
170 
171 #endif
172 
173 /**
174  *  OpenGL states for thermal drawing.
175  */
176 static ssgSimpleState   *td_state_noblend = NULL;
177 static ssgSimpleState   *td_state_blend   = NULL;
178 
179 /**
180  *  A GLU quadric object for thermal drawing
181  */
182 static GLUquadricObj *therm_quadric;
183 
184 /**
185  * calculates grid coordinate from absolute coordinate
186  */
absToGridCoor(float flAbsKoor)187 inline int absToGridCoor(float flAbsKoor)
188 {
189   return( (int)(
190                 (flAbsKoor+ (occupancy_grid_size*occupancy_grid_res/2) ) / occupancy_grid_res
191                 ));
192 }
193 
194 /**
195  * Calculates absolute coordinate from grid coordinate.
196  * Position is at center of grid square with rel=0.5.
197  */
gridToAbsCoor(int gridKoor,float rel=0.5)198 inline float gridToAbsCoor(int gridKoor, float rel=0.5)
199 {
200   //
201   // gridKoor  = (flAbsKoor+ (occupancy_grid_size*occupancy_grid_res/2) ) / occupancy_grid_res
202   // flAbsKoor = gridKoor * occupancy_grid_res - (occupancy_grid_size*occupancy_grid_res/2)
203   // should be at center of square:
204   // flAbsKoor = gridKoor * occupancy_grid_res - (occupancy_grid_size*occupancy_grid_res/2) + occupancy_grid_res/2
205   return(gridKoor * occupancy_grid_res
206          - (occupancy_grid_size*occupancy_grid_res/2)
207          + rel*occupancy_grid_res
208          );
209 }
210 
211 /**
212  * Returns true if there is a thermal nearby.
213  * <code>xcoord</code> and <code>ycoord</code> are the indices into
214  * the grid.
215  */
isThermalNearby(int xcoord,int ycoord)216 bool isThermalNearby(int xcoord, int ycoord)
217 {
218   int xmin = xcoord-nGridDistMin;
219   int xmax = xcoord+nGridDistMin;
220   int ymin = ycoord-nGridDistMin;
221   int ymax = ycoord+nGridDistMin;
222 
223   if (xmin < 0)
224     xmin = 0;
225   else if (xmax >= occupancy_grid_size)
226     xmax = occupancy_grid_size-1;
227 
228   if (ymin < 0)
229     ymin = 0;
230   else if (ymax >= occupancy_grid_size)
231     ymax = occupancy_grid_size-1;
232 
233   for (int x=xmin; x<=xmax; x++)
234   {
235     for (int y=ymin; y<=ymax; y++)
236     {
237       if (thermal_occupancy_grid[x][y]!=0)
238         return(true);
239     }
240   }
241 
242   return(false);
243 }
244 
245 /**
246  * Calculates position for a new thermal.
247  * <code>xpos</code> and <code>ypos</code> are the absolute position,
248  * <code>xcoord</code> and <code>ycoord</code> are the indices into
249  * the grid.
250  *
251  * Returns true if no new thermal position could be found.
252  */
find_new_thermal_position(float * xpos,float * ypos,int * xcoord,int * ycoord)253 bool find_new_thermal_position(float *xpos, float *ypos,
254                                int *xcoord, int *ycoord)
255 {
256   // The problem of the old method has been that it heavily relied on random
257   // values to find a new position, which needed lots of processing time.
258   // Therefore it was a two-step algorithm: if the first step failed (time limit), the second
259   // step went linearly through the grid to find the next place to put a thermal,
260   // which makes a pair or larger number of thermals next to each other, which in
261   // turn increases the probability of the first step failing at such a thermal burst,
262   // which leads to this burst growing bigger again in the second step.
263   //
264   // So what we need is a way to browse through the whole grid, but not linearily.
265   // We simply use a CRC to count non-linearily.
266   int counter = occupancy_grid_size*occupancy_grid_size;
267   // Some polynomials which do work:
268   unsigned int aPoly[] = { 1494, 2020, 2682, 5548, 5836, 5932, 5976, 6374,
269       6580, 6934, 7064, 7136, 7372, 7474, 7586, 7592 };
270 
271   // choose a poly
272   unsigned int uPoly = aPoly[CRRC_Random::rand() % (sizeof(aPoly)/sizeof(unsigned int))];
273 
274   // find an initial value
275   unsigned int uCRCVal = CRRC_Random::rand();
276   while (uCRCVal == 0)
277     CRRC_Random::rand();
278 
279   *xcoord = (uCRCVal >> occupancy_grid_size_exp) & (occupancy_grid_size-1);
280   *ycoord = uCRCVal & (occupancy_grid_size-1);
281 
282   while(isThermalNearby(*xcoord, *ycoord) &&
283         counter > 0)
284   {
285     counter--;
286 
287     uCRCVal <<= 1;
288     if ((uCRCVal & (1<<(2*occupancy_grid_size_exp))) != 0)
289     {
290       uCRCVal ^= uPoly;
291       uCRCVal |= 1;
292     }
293 
294     *xcoord = (uCRCVal >> occupancy_grid_size_exp) & (occupancy_grid_size-1);
295     *ycoord = uCRCVal & (occupancy_grid_size-1);
296   }
297   *xpos = gridToAbsCoor(*xcoord, (float)(CRRC_Random::rand())/CRRC_Random::max());
298   *ypos = gridToAbsCoor(*ycoord, (float)(CRRC_Random::rand())/CRRC_Random::max());
299 
300   // If no such square could be found, thermal density is set way too high.
301   // No visible thermal should be created.
302   if (counter == 0)
303   {
304     std::cerr << "No thermal position found\n";
305     return(true);
306   }
307   else
308     return(false);
309 }
310 
311 // Description: see header file
clear_wind_field()312 void clear_wind_field()
313 {
314   // remove linked list
315   Thermal* tptr0 = thermals;
316   Thermal* tptr1;
317 
318   while (tptr0 != NULL)
319   {
320     tptr1 = tptr0->next_thermal;
321     //~ free(tptr0);
322     delete tptr0;
323     tptr0 = tptr1;
324   }
325   thermals = NULL;
326 
327   delete td_state_noblend;
328   td_state_noblend = NULL;
329   delete td_state_blend;
330   td_state_blend = NULL;
331 
332   gluDeleteQuadric(therm_quadric);
333 }
334 
335 // Description: see header file
initialize_wind_field(SimpleXMLTransfer * el)336 void initialize_wind_field(SimpleXMLTransfer* el)
337 {
338   int loop;
339   Thermal *temp_thermal;
340   int xloop,yloop;
341 
342   // initialize wind turbulence model
343   initialize_gust();
344 
345   ThermalVersion = THERMAL_CODE;
346   // Use version 3?
347   {
348     int index;
349 
350     index = el->indexOfChild("thermal");
351     if (index >= 0)
352     {
353       el = el->getChildAt(index);
354       index = el->indexOfChild("v3");
355       if (index >= 0)
356       {
357         ThermalVersion = 3;
358         thermalv3.init(el->getChildAt(index));
359       }
360     }
361   }
362   std::cout << "Using Thermal Simulation v" << ThermalVersion << "\n";
363 
364   // calculate number of thermals in the grid
365   {
366     double dDensity    = cfg->thermal->density;
367     double dDensityMax = getMaxThermalDensity();
368 
369     if (dDensity > dDensityMax)
370       dDensity = dDensityMax;
371 
372     num_thermals = (int)(pow(occupancy_grid_size*occupancy_grid_res,2)*
373                          dDensity);
374   }
375 
376 #if THERMAL_TEST != 0
377   num_thermals = 1;
378 #endif
379 
380   // How to draw thermals?
381   {
382     // How many grid elements to check?
383     nDrawThermalsFromGrid = (int)(flThermalDistMax / occupancy_grid_res);
384     int nGridCnt = (2*nDrawThermalsFromGrid+1) * (2*nDrawThermalsFromGrid+1);
385 
386     // The fast methods doesn't need that much computing power to determine
387     // whether a thermal has to be drawn or not, so I use an additional factor.
388     if (nGridCnt/3 > num_thermals)
389       nDrawThermalsFromGrid = 0;
390 
391     if (td_state_noblend == NULL)
392     {
393       td_state_noblend = new ssgSimpleState();
394       td_state_noblend->disable(GL_CULL_FACE);
395       td_state_noblend->disable(GL_COLOR_MATERIAL);
396       td_state_noblend->disable(GL_TEXTURE_2D);
397       td_state_noblend->disable(GL_LIGHTING);
398       td_state_noblend->disable(GL_BLEND);
399     }
400     if (td_state_blend == NULL)
401     {
402       td_state_blend = new ssgSimpleState();
403       td_state_blend->disable(GL_CULL_FACE);
404       td_state_blend->disable(GL_COLOR_MATERIAL);
405       td_state_blend->disable(GL_TEXTURE_2D);
406       td_state_blend->disable(GL_LIGHTING);
407       td_state_blend->enable(GL_BLEND);
408     }
409   }
410 
411 #if (THERMAL_CODE == 1)
412   nInfluenceDist = 0;
413 #endif
414 
415   // initialize thermal grid with NULL
416   for (xloop=0;xloop<occupancy_grid_size;xloop++)
417   {
418     for(yloop=0;yloop<occupancy_grid_size;yloop++)
419     {
420       thermal_occupancy_grid[xloop][yloop] = NULL;
421 #if (THERMAL_NEWPOSLOG != 0)
422       NewPosLogArray[xloop][yloop] = 0;
423       PosLogArray[xloop][yloop] = 0;
424 #endif
425     }
426   }
427 
428   // Initialise thermal random parameters distribution
429   rnd_radius.SetSigmaAndMean( cfg->thermal->radius_sigma, cfg->thermal->radius_mean );
430   rnd_strength.SetSigmaAndMean( cfg->thermal->strength_sigma, cfg->thermal->strength_mean );
431   rnd_lifetime.SetSigmaAndMean( cfg->thermal->lifetime_sigma, cfg->thermal->lifetime_mean );
432 
433   // Create the said number of thermals as a linked list.
434   thermals = NULL;
435   for (loop=0;loop<num_thermals;loop++)
436   {
437     //~ temp_thermal=(Thermal *)malloc(sizeof(Thermal));
438     temp_thermal = new Thermal();
439 
440     temp_thermal->next_thermal = thermals;
441     thermals = temp_thermal;
442   }
443 
444   therm_quadric = gluNewQuadric();
445 }
446 
GetDefaultConf_Thermal()447 SimpleXMLTransfer* GetDefaultConf_Thermal()
448 {
449   SimpleXMLTransfer* tex = new SimpleXMLTransfer();
450 
451   tex->setName("thermal");
452   tex->addAttribute("strength_mean",        "5");
453   tex->addAttribute("strength_sigma",       "1");
454   tex->addAttribute("radius_mean",         "70");
455   tex->addAttribute("radius_sigma",        "10");
456   tex->addAttribute("density",              "2.4e-06");
457   tex->addAttribute("lifetime_mean",      "240");
458   tex->addAttribute("lifetime_sigma",      "60");
459 
460   tex->setAttribute("v3.vRefExp",    "2");
461   tex->setAttribute("v3.dz_m",      "50");
462   tex->setAttribute("v3.height_m", "600");
463 
464   tex->setAttribute("v3.inside.upper.r_m",     "30");
465   tex->setAttribute("v3.inside.upper.sl_r",    "0.8");
466   tex->setAttribute("v3.inside.upper.sl_dz_r", "0.2");
467   tex->setAttribute("v3.inside.lower.r_m",     "20");
468   tex->setAttribute("v3.inside.lower.sl_r",    "0.8");
469   tex->setAttribute("v3.inside.lower.sl_dz_r", "0.2");
470 
471   tex->setAttribute("v3.outside.upper.r_m",     "65");
472   tex->setAttribute("v3.outside.upper.sl_r",    "0");
473   tex->setAttribute("v3.outside.upper.sl_dz_r", "0.7");
474   tex->setAttribute("v3.outside.lower.r_m",     "65");
475   tex->setAttribute("v3.outside.lower.sl_r",    "0");
476   tex->setAttribute("v3.outside.lower.sl_dz_r", "0.7");
477 
478   return(tex);
479 }
480 
481 // Description: see header file
update_thermals(float flDeltaT)482 void update_thermals(float flDeltaT)
483 {
484   Thermal *thermal_ptr;
485   float x_motion;   // How much has a thermal moved in X in the last timestep
486   float y_motion;   // How much has a thermal moved in Y in the last timestep
487   float x_wind_velocity,y_wind_velocity;
488   float flWindVel = cfg->wind->getVelocity();
489 
490   // Wind velocity is the same everywhere, so every thermals relative movement is:
491   x_wind_velocity = -1 * flWindVel * cos(M_PI*cfg->wind->getDirection()/180);
492   y_wind_velocity = -1 * flWindVel * sin(M_PI*cfg->wind->getDirection()/180);
493   x_motion        = flDeltaT * x_wind_velocity;
494   y_motion        = flDeltaT * y_wind_velocity;
495 
496   // loop over linked list of thermals
497   thermal_ptr = thermals;
498   while (thermal_ptr != NULL)
499   {
500     thermal_ptr->update(flDeltaT, x_motion, y_motion);
501     thermal_ptr = thermal_ptr->next_thermal;
502   }
503 }
504 
505 // Description: see header file
calculate_wind(double X_cg,double Y_cg,double Z_cg,double & Vel_north,double & Vel_east,double & Vel_down)506 int calculate_wind(double  X_cg,      double  Y_cg,     double  Z_cg,
507                    double& Vel_north, double& Vel_east, double& Vel_down)
508 {
509   float    x_wind_velocity, y_wind_velocity, z_wind_velocity; //JL
510   Thermal* thermal_ptr;
511   int      aircraft_xcoord,aircraft_ycoord;
512   int      xloop,yloop;
513   double   thermal_wind_x = 0;
514   double   thermal_wind_y = 0;
515   double   thermal_wind_z = 0;
516 #if (THERMAL_CODE == 0)
517   double distance_from_core;
518   float total_up_airmass = 0;
519   float sink_area;
520   float sink_strength;
521   float thermal_area;
522   float lift_area  = 0;
523   int   in_thermal = FALSE;
524   float angle_in;
525   float v_in_max; // Max velocity of thermal vacuum cleaner wind.
526 #endif
527 
528   // wind from scenery, without thermal effect
529   int wind_error = Global::scenery->getWindComponents(X_cg, Y_cg, Z_cg,
530       &x_wind_velocity, &y_wind_velocity, &z_wind_velocity);
531 
532   // reduce wind speed close to ground to simulate wind
533   // boundary layer effect, using a 1/7th power law + linear sublayer
534   float layer_thickness = 30.0; // boundary layer thickness set to 30ft (10m)
535   float sublayer_thickness = 0.05; // relative thickness of linear sublayer (1.5ft, 0.5m)
536   float Z_ter = Global::scenery->getHeight(X_cg, Y_cg); //terrain height below the point
537   float Z_scale = (-Z_cg - Z_ter)/layer_thickness; //remember: Z_cg is positive down
538   float fact;
539   if (Z_scale < 0.0)
540     Z_scale = 0.0;
541   if (Z_scale > 1.0)
542     Z_scale = 1.0;
543   if (Z_scale < sublayer_thickness)
544     fact = Z_scale*pow(sublayer_thickness,-6./7.);
545   else
546     fact = pow(Z_scale,1./7.);
547 
548   Vel_north = fact*x_wind_velocity;
549   Vel_east  = fact*y_wind_velocity;
550   Vel_down  = fact*z_wind_velocity;
551 
552   // calculate indices of the aircraft in the grid
553   aircraft_xcoord = absToGridCoor(X_cg);
554   aircraft_ycoord = absToGridCoor(Y_cg);
555 
556   // Is the aircraft in a part of the grid?
557   if ((aircraft_xcoord > nInfluenceDist) &&
558       (aircraft_xcoord < occupancy_grid_size-nInfluenceDist-1) &&
559       (aircraft_ycoord > nInfluenceDist) &&
560       (aircraft_ycoord < occupancy_grid_size-nInfluenceDist-1))
561   {
562     // Check all squares of the grid surrounding the aircraft in
563     // a distance of at most (nInfluenceDist*occupancy_grid_res)
564     // of the aircraft.
565     // Sum lift_area and total_up_airmass.
566     for (xloop=(-1*nInfluenceDist);xloop<=nInfluenceDist;xloop++)
567     {
568       for (yloop=(-1*nInfluenceDist);yloop<=nInfluenceDist;yloop++)
569       {
570         // is there a thermal in this part of the grid?
571         thermal_ptr = thermal_occupancy_grid[aircraft_xcoord+xloop][aircraft_ycoord+yloop];
572 
573         if (thermal_ptr != 0)
574         {
575           switch (ThermalVersion)
576           {
577            case 3:
578             thermal_ptr->sumVelocity(X_cg, Y_cg, Z_cg,
579                                      thermalv3,
580                                      thermal_wind_x, thermal_wind_y, thermal_wind_z);
581             break;
582 
583            default:
584 #if (THERMAL_CODE == 0)
585             // area of this thermal
586             thermal_area = (M_PI*thermal_ptr->radius*thermal_ptr->radius);
587             //
588             lift_area   += thermal_area;
589             total_up_airmass+=thermal_area*thermal_ptr->strength;
590 #endif
591 #if (THERMAL_CODE == 1)
592             thermal_wind_z += thermal_ptr->getVelocity(X_cg, Y_cg, Z_cg);
593 #endif
594             break;
595           }
596         }
597       }
598     }
599 
600     switch (ThermalVersion)
601     {
602      case 3:
603       // there is no need to do something here
604       break;
605 
606      default:
607 #if (THERMAL_CODE == 0)
608       // Sink area and strength
609       sink_area=((2*nInfluenceDist+1)*(2*nInfluenceDist+1)*
610                  occupancy_grid_res*occupancy_grid_res)-lift_area;
611       sink_strength= total_up_airmass/sink_area;
612 
613       // Check all squares of the grid surrounding the aircraft in
614       // a distance of at most (nInfluenceDist*occupancy_grid_res)
615       // of the aircraft.
616       // Sum up thermal_wind_x and thermal_wind_y.
617       for (xloop=(-1*nInfluenceDist);xloop<=nInfluenceDist;xloop++)
618       {
619         for (yloop=(-1*nInfluenceDist);yloop<=nInfluenceDist;yloop++)
620         {
621           if (thermal_occupancy_grid[aircraft_xcoord+xloop][aircraft_ycoord+yloop] != 0)
622           {
623             thermal_ptr=thermal_occupancy_grid[aircraft_xcoord+xloop][aircraft_ycoord+yloop];
624             // Distance of the position in question and the thermal
625             distance_from_core=sqrt(((X_cg-thermal_ptr->center_x_position)*(X_cg-thermal_ptr->center_x_position))
626                                     +((Y_cg-thermal_ptr->center_y_position)*(Y_cg-thermal_ptr->center_y_position)));
627 
628             // If the positon is lower than 1000 feet, accumulate thermal_wind_x and thermal_wind_y.
629             if (Z_cg > -1000)
630             {
631               v_in_max=thermal_ptr->strength*thermal_ptr->radius/100;
632               if (distance_from_core > thermal_ptr->radius)
633               {
634                 v_in_max/=pow(distance_from_core/thermal_ptr->radius,2);
635               }
636               else
637               {
638                 v_in_max*=distance_from_core/thermal_ptr->radius;
639               }
640               angle_in=atan2((thermal_ptr->center_y_position-Y_cg),(thermal_ptr->center_x_position-X_cg));
641               if (Z_cg > -50)
642               {
643                 thermal_wind_x+=v_in_max*cos(angle_in);
644                 thermal_wind_y+=v_in_max*sin(angle_in);
645               }
646               else if (Z_cg > -1000)
647               {
648                 thermal_wind_x+=(v_in_max*cos(angle_in))*((950-(-Z_cg-50))/950);
649                 thermal_wind_y+=(v_in_max*sin(angle_in))*((950-(-Z_cg-0))/950);
650               }
651             }
652 
653             if (distance_from_core < thermal_ptr->radius)
654             {
655               thermal_wind_z = -1*thermal_ptr->strength;
656               in_thermal = TRUE;
657             }
658             else if (distance_from_core < thermal_ptr->radius+thermal_ptr->boundary_thickness)
659             {
660               thermal_wind_z = -1*thermal_ptr->strength
661                 + (thermal_ptr->strength+sink_strength)*((distance_from_core-thermal_ptr->radius)/thermal_ptr->boundary_thickness);
662               in_thermal = TRUE;
663             }
664           }
665         }
666       }
667       // end of loop
668       //
669       // If this is not in a thermal, sink_strength is used. If this is in a
670       // thermal, thermal_wind_z has been set in the loop above.
671       if (!in_thermal)
672       {
673         thermal_wind_z = sink_strength;
674       }
675 
676       // thermals grow stronger from the ground up
677       if (-Z_cg < 50)
678       {
679         thermal_wind_z *= (-Z_cg/50);
680       }
681 #endif
682       break;
683     }
684 
685 	  Vel_north += thermal_wind_x;
686     Vel_east  += thermal_wind_y;
687     Vel_down  += thermal_wind_z;
688   }
689 
690   return wind_error;
691 }
692 
693 // Description: see header file
calculate_wind_grad(double X_cg,double Y_cg,double Z_cg,double delta_space,CRRCMath::Matrix33 & m_V_grad)694 int calculate_wind_grad(double X_cg, double Y_cg, double Z_cg, double delta_space,
695                         CRRCMath::Matrix33& m_V_grad)
696 {
697   double V_north_xp, V_east_xp, V_down_xp;
698   double V_north_yp, V_east_yp, V_down_yp;
699   double V_north_zp, V_east_zp, V_down_zp;
700   double V_north_xm, V_east_xm, V_down_xm;
701   double V_north_ym, V_east_ym, V_down_ym;
702   double V_north_zm, V_east_zm, V_down_zm;
703 
704   int err_x = calculate_wind(X_cg+delta_space, Y_cg,             Z_cg,
705                              V_north_xp,       V_east_xp,        V_down_xp) |
706               calculate_wind(X_cg-delta_space, Y_cg,             Z_cg,
707                              V_north_xm,       V_east_xm,        V_down_xm);
708   int err_y = calculate_wind(X_cg,             Y_cg+delta_space, Z_cg,
709                              V_north_yp,       V_east_yp,        V_down_yp) |
710               calculate_wind(X_cg,             Y_cg-delta_space, Z_cg,
711                              V_north_ym,       V_east_ym,        V_down_ym);
712   int err_z = calculate_wind(X_cg,             Y_cg,             Z_cg+delta_space,
713                              V_north_zp,       V_east_zp,        V_down_zp) |
714               calculate_wind(X_cg,             Y_cg,             Z_cg-delta_space,
715                              V_north_zm,       V_east_zm,        V_down_zm);
716 
717   // Gradients are calculated from symmetric pairs to get symmetric behaviour.
718   if (!err_x)
719   {
720     m_V_grad.v[0][0] = (V_north_xp - V_north_xm)/(2*delta_space);
721     m_V_grad.v[1][0] = (V_east_xp  - V_east_xm) /(2*delta_space);
722     m_V_grad.v[2][0] = (V_down_xp  - V_down_xm) /(2*delta_space);
723   }
724   if (!err_y)
725   {
726     m_V_grad.v[0][1] = (V_north_yp - V_north_ym)/(2*delta_space);
727     m_V_grad.v[1][1] = (V_east_yp  - V_east_ym) /(2*delta_space);
728     m_V_grad.v[2][1] = (V_down_yp  - V_down_ym) /(2*delta_space);
729   }
730   if (!err_z)
731   {
732     m_V_grad.v[0][2] = (V_north_zp - V_north_zm)/(2*delta_space);
733     m_V_grad.v[1][2] = (V_east_zp  - V_east_zm) /(2*delta_space);
734     m_V_grad.v[2][2] = (V_down_zp  - V_down_zm) /(2*delta_space);
735   }
736 
737   return (err_x | err_y | err_z);
738 }
739 
740 // Description: see header file
initialize_gust()741 void initialize_gust()
742 {
743   v_V_gust_body_.r[0] = v_V_gust_body_.r[1] = v_V_gust_body_.r[2] = 0.0;
744   v_V_gust_body_old_ = v_V_gust_body_;
745 
746   v_R_omega_gust_body_.r[0] = v_R_omega_gust_body_.r[1] = v_R_omega_gust_body_.r[2] = 0.0;
747 }
748 
749 // Description: see header file
calculate_gust(double dt,double altitude,double V_rel_wind,double b,CRRCMath::Vector3 v_V_local_airmass,CRRCMath::Matrix33 LocalToBody,CRRCMath::Vector3 & v_V_gust_body,CRRCMath::Vector3 & v_R_omega_gust_body)750 void calculate_gust(double dt, double altitude, double V_rel_wind, double b,
751                     CRRCMath::Vector3 v_V_local_airmass,
752                     CRRCMath::Matrix33 LocalToBody,
753                     CRRCMath::Vector3& v_V_gust_body,
754                     CRRCMath::Vector3& v_R_omega_gust_body)
755 {
756   double intensity = cfg->wind->getTurbulence();
757   double V_wind = v_V_local_airmass.length();
758 
759   // no wind turbulence if wind velocity is zero or
760   // relative turbulence intensity has been set to zero
761   if ( intensity * V_wind == 0.)
762     return;
763 
764   v_V_local_airmass.normalize();
765   CRRCMath::Matrix33 WindToLocal(v_V_local_airmass.r[0], -v_V_local_airmass.r[1], 0.,
766                                  v_V_local_airmass.r[1],  v_V_local_airmass.r[0], 0.,
767                                  0.,                      0.,                     1.);
768 
769   // linear and rotational gust velocity estimated using digital filter
770   // form of Dryden spectra, from MIL-HDBK-1797.
771   // NB: sigma and reference length from low-altitude specification
772 
773   v_V_gust_body_old_ = v_V_gust_body_;
774 
775   double V_dt = dt*V_rel_wind;
776   double alt = altitude < 1000. ? (altitude > 10. ? altitude : 10) : 1000.;
777   double factor = 1.0/(0.177 + 0.000823*alt);
778 
779   double Lu = alt*pow(factor,1.2);
780   double Lv = 0.5*Lu;
781   double Lw = 0.5*alt;
782 
783   double pid4b = M_PI/(4.0*b);
784   double pid3b = M_PI/(3.0*b);
785 
786   double sigw = 0.1*V_wind;
787   double sigu = sigw*pow(factor,0.4);
788   double sigv = sigu;
789 
790   // align length scale "u" with wind direction, then
791   // transform length scale from local to body frame
792   CRRCMath::Matrix33 L_tensor(Lu, 0., 0.,
793                               0., Lv, 0.,
794                               0., 0., Lw);
795   L_tensor = WindToLocal*(L_tensor*WindToLocal.trans());
796   L_tensor = LocalToBody*(L_tensor*LocalToBody.trans());
797   Lu = L_tensor.v[0][0];
798   Lv = L_tensor.v[1][1];
799   Lw = L_tensor.v[2][2];
800 
801   // align sigma "u" with wind direction, then
802   // transform sigma from local to body frame
803   CRRCMath::Matrix33 s_tensor(sigu, 0., 0.,
804                               0., sigv, 0.,
805                               0., 0., sigw);
806   s_tensor = WindToLocal*(s_tensor*WindToLocal.trans());
807   s_tensor = LocalToBody*(s_tensor*LocalToBody.trans());
808   sigu = s_tensor.v[0][0];
809   sigv = s_tensor.v[1][1];
810   sigw = s_tensor.v[2][2];
811 
812   double temp = sqrt(2.0*Lw*b);
813   double Lp   = temp/2.6;
814   double sigp = 1.9*sigw/temp;
815 
816   double au_dt = V_dt/Lu;
817   double av_dt = V_dt/Lv;
818   double aw_dt = V_dt/Lw;
819   double ap_dt = V_dt/Lp;
820   double aq_dt = pid4b*V_dt;
821   double ar_dt = pid3b*V_dt;
822 
823   v_V_gust_body_.r[0]       = (1.0 - au_dt)*v_V_gust_body_.r[0]
824                               + sqrt(2.0*au_dt)*sigu*eta1.Get();
825   v_V_gust_body_.r[1]       = (1.0 - av_dt)*v_V_gust_body_.r[1]
826                               + sqrt(2.0*av_dt)*sigv*eta2.Get();
827   v_V_gust_body_.r[2]       = (1.0 - aw_dt)*v_V_gust_body_.r[2]
828                               + sqrt(2.0*aw_dt)*sigw*eta3.Get();
829 
830   // NB: signs for q and r (airmass turbulence rotation around body y and z)
831   //     are consistent with airmass rotation computed from airmass velocity
832   //     gradient (see e.g. fdm_larcsim.cpp). Sign for p is arbitrary.
833   v_R_omega_gust_body_.r[0] = (1.0 - ap_dt)*v_R_omega_gust_body_.r[0]
834                               + sqrt(2.0*ap_dt)*sigp*eta4.Get();
835   v_R_omega_gust_body_.r[1] = (1.0 - aq_dt)*v_R_omega_gust_body_.r[1]
836                               - pid4b*(v_V_gust_body_.r[2] - v_V_gust_body_old_.r[2]);
837   v_R_omega_gust_body_.r[2] = (1.0 - ar_dt)*v_R_omega_gust_body_.r[2]
838                               + pid3b*(v_V_gust_body_.r[1] - v_V_gust_body_old_.r[1]);
839 
840   v_V_gust_body = v_V_gust_body_ * intensity;
841   v_R_omega_gust_body = v_R_omega_gust_body_ * intensity;
842 }
843 
844 // Description: see header file
draw_thermals(CRRCMath::Vector3 pos)845 void draw_thermals(CRRCMath::Vector3 pos)
846 {
847   Thermal* thermal_ptr;
848 
849   double X_cg_rwy =  pos.r[0];
850   double Y_cg_rwy =  pos.r[1];
851   double H_cg_rwy = -pos.r[2];
852 
853   if (nDrawThermalsFromGrid)
854   {
855     // grid coordinates of aircraft
856     int xa = absToGridCoor(X_cg_rwy);
857     int ya = absToGridCoor(Y_cg_rwy);
858 
859     // part of the grid to look at
860     int xmin = xa - nDrawThermalsFromGrid;
861     int xmax = xa + nDrawThermalsFromGrid;
862     int ymin = ya - nDrawThermalsFromGrid;
863     int ymax = ya + nDrawThermalsFromGrid;
864 
865     if (xmin < 1)
866       xmin = 1;
867     if (xmax > occupancy_grid_size - 2)
868       xmax = occupancy_grid_size - 2;
869     if (ymin < 1)
870       ymin = 1;
871     if (ymax > occupancy_grid_size - 2)
872       ymax = occupancy_grid_size - 2;
873 
874     for (int x=xmin; x<=xmax; x++)
875       for (int y=ymin; y<=ymax; y++)
876       {
877         thermal_ptr = thermal_occupancy_grid[x][y];
878         if (thermal_ptr != NULL)
879         {
880           thermal_ptr->draw(H_cg_rwy);
881         }
882       }
883   }
884   else
885   {
886     thermal_ptr = thermals;
887     while(thermal_ptr !=NULL)
888     {
889       if (fabs(X_cg_rwy - thermal_ptr->center_x_position) < flThermalDistMax &&
890           fabs(Y_cg_rwy - thermal_ptr->center_y_position) < flThermalDistMax)
891       {
892         thermal_ptr->draw(H_cg_rwy);
893       }
894       thermal_ptr = thermal_ptr->next_thermal;
895     }
896   }
897 
898 }
899 
draw_wind(double direction_face)900 void draw_wind(double direction_face)
901 {
902   double length    = 0.8;
903   double direction = (M_PI*cfg->wind->getDirection()/180) - direction_face;
904 
905   //
906   int xsize, ysize;
907   Video::getWindowSize(xsize, ysize);
908   int h = ysize >> 3;
909   int r = ysize >> 5;
910   int dxA = (int)(floor(0.5*h*length*sin(direction-0.1)+0.5));
911   int dyA = (int)(floor(0.5*h*length*cos(direction-0.1)+0.5));
912   int dxB = (int)(floor(0.5*h*length*sin(direction+0.1)+0.5));
913   int dyB = (int)(floor(0.5*h*length*cos(direction+0.1)+0.5));
914   int dxC = (int)(floor(-0.5*h*length*sin(direction+0.1)+0.5));
915   int dyC = (int)(floor(-0.5*h*length*cos(direction+0.1)+0.5));
916 
917 #if 0
918   glDisable(GL_LIGHTING);
919   glMatrixMode (GL_PROJECTION);
920   glPushMatrix();
921   glLoadIdentity ();
922   gluOrtho2D (0, xsize-1, 0, ysize);
923 #endif
924 
925   // Hintergrund
926   glColor3f (0, 0, 0);
927   glTranslatef(xsize-r-h/2, r+h/2, 0);
928   gluDisk(therm_quadric, 0, h/2, 32, 1);
929   glTranslatef(-(xsize-r-h/2),-(r+h/2),0.1);
930 
931   // Anzeiger
932   glColor3f (0, 1, 0.);
933   glBegin(GL_LINE_STRIP);
934   glVertex2i(xsize - r - h/2 - dxC, r+h/2 - dyC);
935   glVertex2i(xsize - r - h/2 - dxA, r+h/2 - dyA);
936   glVertex2i(xsize - r - h/2 - dxB, r+h/2 - dyB);
937   glVertex2i(xsize - r - h/2 - dxC, r+h/2 - dyC);
938   glEnd();
939 
940 #if 0
941   glPopMatrix();
942   glEnable(GL_LIGHTING);
943 #endif
944 }
945 
946 #if DEBUG_THERMAL_SCRSHOT == 1
947 
948 // 0: output for scilab
949 // 1: output for gnuplot (1)
950 // 2: output for octave
951 // 3: output for gnuplot (3)
952 # define DEBUG_THERMAL_SCRSHOT_FORMAT 3
953 
windfield_thermalScreenshot(CRRCMath::Vector3 pos)954 void windfield_thermalScreenshot(CRRCMath::Vector3 pos)
955 {
956   /**
957    * using gnuplot (1):
958    * ------------------
959    * set contour surface
960    * set cntrparam levels 20
961    * splot "thermals.dat" with dots
962    *
963    * or
964    *
965    * unset contour
966    * set zrange [-0.6:6]
967    * splot "thermals.dat" with dots
968    *
969    * using gnuplot (3):
970    * ------------------
971    * splot "thermals.dat" matrix with line palette
972    *
973    * or
974    *
975    * set pm3d
976    * splot "thermals.dat" matrix with pm3d
977    *
978    * or
979    *
980    * set hidden3d
981    * splot "thermals.dat" matrix with lines
982    *
983    *
984    * using scilab:
985    * -------------
986    * thfield = file("open", "thermalsb.dat", "old")
987    * n= 2 * read(thfield, 1, 1) + 1
988    * P = read(thfield, n, n);
989    * x = linspace(0, n-1, n);
990    * y = x;
991    * plot3d(x,y,P);
992    * file("close", thfield);
993    *
994    * using octave:
995    * -------------
996    * vdown = load -text thermals.dat * ;
997    * vdl = length(vdown.vdown)
998    * nums = linspace(0, vdl-1, vdl);
999    * [xx, yy] = meshgrid(nums, nums) ;
1000    * mesh(xx, yy, vdown.vdown)
1001    *
1002    */
1003 
1004   const double dDist = 200;
1005   const double dStep = 4;
1006   const float  dirlen = 14.0;
1007 
1008   std::ofstream tf_up;
1009   std::ofstream tf_northeast;
1010   double        dVNorth;
1011   double        dVEast;
1012   double        dVDown;
1013 
1014   const int nSteps = (int)(dDist/dStep);
1015 
1016   tf_up.open("thermals.dat");
1017   tf_northeast.open("thermals_northeast.dat");
1018 
1019 # if DEBUG_THERMAL_SCRSHOT_FORMAT == 0
1020   tf_up << nSteps << "\n";
1021 # endif
1022 # if DEBUG_THERMAL_SCRSHOT_FORMAT == 2
1023   tf_up << "# name: vdown\n";
1024   tf_up << "# type: matrix\n";
1025   tf_up << "# rows: " << 2*nSteps << "\n";
1026   tf_up << "# columns: " << 2*nSteps << "\n";
1027 # endif
1028 
1029   for (int nX=-nSteps; nX<nSteps; nX++)
1030   {
1031     double x = pos.r[0] + nX*dStep;
1032 
1033     for (int nY=-nSteps; nY<nSteps; nY++)
1034     {
1035       double y = pos.r[1] + nY*dStep;
1036 
1037       calculate_wind(x, y, pos.r[2], dVNorth, dVEast, dVDown);
1038 
1039 # if (DEBUG_THERMAL_SCRSHOT_FORMAT == 0) || (DEBUG_THERMAL_SCRSHOT_FORMAT == 2) || (DEBUG_THERMAL_SCRSHOT_FORMAT == 3)
1040       tf_up << -1*dVDown << " ";
1041 # endif
1042 # if DEBUG_THERMAL_SCRSHOT_FORMAT == 1
1043       tf_up  << x << " " << y << " " << -1*dVDown << "\n";
1044 # endif
1045 
1046       // use:  plot 'thermals_northeast.dat' with vectors
1047       tf_northeast << x              << " " << y             << " ";
1048       tf_northeast << dirlen*dVNorth << " " << dirlen*dVEast << "\n";
1049 
1050     }
1051     tf_up << "\n";
1052 
1053   }
1054   tf_up.close();
1055   tf_northeast.close();
1056 
1057 # if (THERMAL_NEWPOSLOG != 0)
1058   /*
1059    Using gnuplot:
1060    *
1061    set pm3d map
1062    splot  'thermalnewpos.dat' matrix with pm3d
1063    */
1064   {
1065     std::ofstream tlog;
1066     tlog.open("thermalnewpos.dat");
1067     for (unsigned int xc=0; xc<occupancy_grid_size; xc++)
1068     {
1069       for (unsigned int yc=0; yc<occupancy_grid_size; yc++)
1070       {
1071         tlog << (NewPosLogArray[xc][yc]) << " ";
1072       }
1073       tlog << "\n";
1074     }
1075     tlog.close();
1076 
1077     tlog.open("thermalpos.dat");
1078     for (unsigned int xc=0; xc<occupancy_grid_size; xc++)
1079     {
1080       for (unsigned int yc=0; yc<occupancy_grid_size; yc++)
1081       {
1082         tlog << PosLogArray[xc][yc] << " ";
1083       }
1084       tlog << "\n";
1085     }
1086     tlog.close();
1087   }
1088 # endif
1089 }
1090 #endif
1091 
1092 // ----- implementation of class Thermal --------------------
1093 /**
1094  *  The constructor. Creates the object and initializes it
1095  *  with some random values.
1096  */
Thermal()1097 Thermal::Thermal()
1098 {
1099   random_init();
1100   // to have a higher level of initial randomness:
1101   lifetime *= rand()/(RAND_MAX+1.0);
1102 }
1103 
1104 /**
1105  *  Formerly known as make_new_thermal(). This method
1106  *  initializes the object with some sensible random values.
1107  */
random_init()1108 void Thermal::random_init()
1109 {
1110   float xpos,ypos;
1111   int   xco,yco;
1112 
1113   // determine position of new thermal
1114   fInvisible = find_new_thermal_position(&xpos,&ypos,&xco,&yco);
1115 
1116 #if (THERMAL_NEWPOSLOG != 0)
1117   if (!fInvisible)
1118   {
1119     NewPosLogArray[xco][yco]++;
1120   }
1121 #endif
1122 
1123 #if THERMAL_TEST != 0
1124 # if THERMAL_TEST == 1
1125   xpos = -160;
1126   ypos = -57;
1127 # endif
1128 # if THERMAL_TEST == 2
1129   xpos = -170;
1130   ypos = -0;
1131 # endif
1132   xcoord = absToGridCoor(xpos);
1133   ycoord = absToGridCoor(ypos);
1134 #endif
1135 
1136   // Put it into the grid. If no valid position was found, this thermal stays invisible during
1137   // its current lifecycle.
1138   if (!fInvisible)
1139     thermal_occupancy_grid[xco][yco] = this;
1140 
1141   // Describe thermal
1142   center_x_position = xpos;
1143   center_y_position = ypos;
1144   xcoord = xco;
1145   ycoord = yco;
1146   radius   = rnd_radius.Get();
1147   strength = rnd_strength.Get();
1148   lifetime = rnd_lifetime.Get();
1149 #if THERMAL_TEST != 0
1150   radius   = 50;
1151   strength = 15;
1152   lifetime = 9999;
1153 #endif
1154 #if (THERMAL_CODE == 0)
1155   // todo: is this boundary thickness correct? Until 2005-01-15 the initial thermals
1156   // have not been created using this code. It was radius/5 there.
1157   // 2005-01-20: gradient is very high -- using /5 now.
1158   boundary_thickness = radius/5;
1159 #endif
1160 
1161   switch (ThermalVersion)
1162   {
1163    case 3:
1164     {
1165       int nDist = (int)ceil((radius * thermalv3.get_r_max()/thermalv3.get_r_ref())/occupancy_grid_res);
1166       if (nDist > nInfluenceDist)
1167         nInfluenceDist = nDist;
1168     }
1169     break;
1170 
1171    default:
1172 #if (THERMAL_CODE == 1)
1173     {
1174       int nDist = (int)ceil((radius/ThermalRadius)/occupancy_grid_res);
1175       if (nDist > nInfluenceDist)
1176         nInfluenceDist = nDist;
1177     }
1178 #endif
1179     break;
1180   }
1181 }
1182 
1183 /**
1184  *  Remove a thermal from the grid
1185  */
remove_from_grid()1186 void Thermal::remove_from_grid()
1187 {
1188   thermal_occupancy_grid[xcoord][ycoord] = NULL;
1189 }
1190 
1191 /**
1192  *  Update the thermal. The thermal will move with the windfield
1193  *  and slowly die. The movement due to the windfield is calculated
1194  *  outside of this function because it is the same for all
1195  *  thermals.
1196  *
1197  *  \param flDeltaT elapsed time since last update
1198  *  \param x_motion x movement due to wind
1199  *  \param y_motion y movement due to wind
1200  */
update(float flDeltaT,float x_motion,float y_motion)1201 void Thermal::update(float flDeltaT, float x_motion, float y_motion)
1202 {
1203   // Move thermal
1204   center_x_position += x_motion;
1205   center_y_position += y_motion;
1206   // let it grow older
1207   lifetime -= flDeltaT;
1208 
1209   // This thermal has to replaced by a new one if its lifetime is over or if
1210   // it has moved out of the grid.
1211   if ((lifetime < 0) ||
1212       (center_x_position <  -1*(occupancy_grid_size/2) * occupancy_grid_res) ||
1213       (center_x_position >     (occupancy_grid_size/2) * occupancy_grid_res) ||
1214       (center_y_position <  -1*(occupancy_grid_size/2) * occupancy_grid_res) ||
1215       (center_y_position >     (occupancy_grid_size/2) * occupancy_grid_res))
1216   {
1217     // remove thermal from the grid
1218     remove_from_grid();
1219 
1220     // create a new thermal
1221     random_init();
1222   }
1223   else if (!fInvisible)
1224   {
1225     int   new_xcoord,new_ycoord;
1226     // new indices into grid
1227     new_xcoord = absToGridCoor(center_x_position);
1228     new_ycoord = absToGridCoor(center_y_position);
1229 
1230     // has it moved to a new square of the grid?
1231     if ((new_xcoord != xcoord) ||
1232         (new_ycoord != ycoord))
1233     {
1234       // Is this place in the grid occupied by another thermal?
1235       if (thermal_occupancy_grid[new_xcoord][new_ycoord]!=0)
1236       {
1237         // This should never happen with nGridDistMin>0. If it does,
1238         // there is work to be done.
1239         // It does! Why? I do NOT understand it...
1240         fprintf(stderr, "Error: multiple thermals in one location!\n");
1241       }
1242       else
1243       {
1244         // leave the old place in the grid
1245         thermal_occupancy_grid[xcoord][ycoord] = NULL;
1246         // enter the new place in the grid
1247         thermal_occupancy_grid[new_xcoord][new_ycoord] = this;
1248         xcoord = new_xcoord;
1249         ycoord = new_ycoord;
1250       }
1251     }
1252 #if (THERMAL_NEWPOSLOG != 0)
1253     PosLogArray[xcoord][ycoord]++;
1254 #endif
1255   }
1256 }
1257 
1258 #if (THERMAL_CODE == 1)
1259 /**
1260  *  Calculate the influence of a thermal on the given
1261  *  location.
1262  *
1263  *  \param dX X location in world coordinates
1264  *  \param dY Y location in world coordinates
1265  *  \param dZ Z location in world coordinates
1266  *  \return vertical thermal velocity
1267  */
getVelocity(double dX,double dY,double dZ)1268 double Thermal::getVelocity(double dX, double dY, double dZ)
1269 {
1270   // distance from aircraft to center of thermal
1271   double dDist   = sqrt((center_x_position - dX)*(center_x_position - dX)
1272                         +
1273                         (center_y_position - dY)*(center_y_position - dY));
1274   // radius of thermal, including downwind
1275   double dRadius = radius/ThermalRadius;
1276 
1277   if (dDist >= dRadius || dZ > -dAltitudeZeroStrength)
1278     return(0);
1279   else
1280   {
1281     int nIndex = (int)((1<<(ThermalProfile_bits+8)) * dDist/dRadius);
1282 
1283     // it gets weaker before it dies
1284     double current_strength;
1285 
1286     if (lifetime < dFadeOutTime)
1287       current_strength = strength * lifetime / dFadeOutTime;
1288     else
1289       current_strength = strength;
1290 
1291     if (dZ > -dAltitudeFullStrength)
1292       current_strength *= (-dZ - dAltitudeZeroStrength) / (dAltitudeFullStrength - dAltitudeZeroStrength);
1293 
1294     // interpolation of table values
1295     double dVal0 = ThermalProfile[ nIndex>>8   ];
1296     double dVal1 = ThermalProfile[(nIndex>>8)+1];
1297 
1298     return(-1*(dVal0 + (nIndex&0xFF)*(dVal1-dVal0)/256) * current_strength);
1299   }
1300 }
1301 #endif
1302 
1303 /**
1304  *  Draws a thermal
1305  *
1306  *  \param H_cg_rwy height at which the thermal shall be drawn
1307  */
draw(double H_cg_rwy)1308 void Thermal::draw(double H_cg_rwy)
1309 {
1310 #if THERMAL_TEST != 0
1311   if (H_cg_rwy < 3*dAltitudeFullStrength)
1312     H_cg_rwy = 3*dAltitudeFullStrength;
1313 #endif
1314 
1315   glPushMatrix();
1316   td_state_noblend->apply();
1317 
1318 #if (THERMAL_CODE == 0)
1319   glColor4f(1,0,0,1);
1320   glTranslatef(center_y_position, H_cg_rwy, -center_x_position);
1321   gluSphere(therm_quadric,1,3,3);
1322   td_state_blend->apply();
1323   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1324   glRotatef(90,1,0,0);
1325   glColor4f(0.4,0,0,0.2);
1326   gluDisk(therm_quadric,0, radius + boundary_thickness,16,1);
1327 #endif
1328 
1329 #if (THERMAL_CODE == 1)
1330   if (H_cg_rwy > dAltitudeZeroStrength)
1331   {
1332     double strength_height;
1333 
1334     if (H_cg_rwy < dAltitudeFullStrength)
1335       strength_height = 0.2 * (H_cg_rwy - dAltitudeZeroStrength) / (dAltitudeFullStrength - dAltitudeZeroStrength);
1336     else
1337       strength_height = 0.2;
1338 
1339     glColor4f(1,0,0,1);
1340     glTranslatef(center_y_position, H_cg_rwy, -center_x_position);
1341     gluSphere(therm_quadric,1,3,3);
1342     td_state_blend->apply();
1343     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1344     glRotatef(90,1,0,0);
1345     glColor4f(0.4,0,0, strength_height);
1346     gluDisk(therm_quadric,0, radius, 16, 1);
1347 
1348     // The whole radius of the thermal is limited to not get annoying.
1349     double RadiusInnerPartRel = ThermalRadius;
1350     if (RadiusInnerPartRel < 0.4)
1351       RadiusInnerPartRel = 0.4;
1352 
1353     double dRadius = radius / RadiusInnerPartRel;
1354     glColor4f(0,0.4,0, strength_height);
1355     gluDisk(therm_quadric, radius, dRadius, 16,1);
1356   }
1357 #endif
1358   glPopMatrix();
1359 }
1360 
sumVelocity(double X_cg,double Y_cg,double Z_cg,ThermikSchalen & thermalv3,double & Vel_north,double & Vel_east,double & Vel_down)1361 void Thermal::sumVelocity(double X_cg, double Y_cg, double Z_cg,
1362                           ThermikSchalen& thermalv3,
1363                           double& Vel_north, double& Vel_east, double& Vel_down)
1364 {
1365   // it gets weaker before it dies
1366   double current_strength;
1367 
1368   if (lifetime < dFadeOutTime)
1369     current_strength = strength * lifetime / dFadeOutTime;
1370   else
1371     current_strength = strength;
1372 
1373   // distance from aircraft to center of thermal
1374   double dDist   = sqrt((center_x_position - X_cg)*(center_x_position - X_cg)
1375                         +
1376                         (center_y_position - Y_cg)*(center_y_position - Y_cg));
1377 
1378   //
1379   double localLenToThLen = thermalv3.get_r_ref()/radius;
1380   flttype dx, dy;
1381 
1382   thermalv3.vectorAt(dDist*localLenToThLen, -1*Z_cg*localLenToThLen,
1383                      dx, dy,
1384                      current_strength);
1385 
1386   Vel_down -= dy;
1387 
1388   // split up dx into vnorth and veast:
1389   float alpha  = atan2(Y_cg - center_y_position, X_cg - center_x_position);
1390   float vnorth = cos(alpha) * dx;
1391   float veast  = sin(alpha) * dx;
1392   Vel_north += vnorth;
1393   Vel_east  += veast;
1394 }
1395 
getMaxThermalDensity()1396 double getMaxThermalDensity()
1397 {
1398   return(1.0 / (
1399                 (2*nGridDistMin+1) * occupancy_grid_res * (2*nGridDistMin+1) * occupancy_grid_res
1400                 ));
1401 }
1402