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