1 /*
2  * CRRCsim - the Charles River Radio Control Club Flight Simulator Project
3  *
4  *  Copyright (C) 2010 Joel Lienard (original author)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2
8  * as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  */
21 
22 
23 /*************
24  * crrc_wind_from_terrain.cpp
25  *
26  * very simple wind calculation from terrain profil
27  *
28  *********/
29 
30  /*
31  We make a modelling locally the ground by a simple shape.
32  That modelling is made from some points (2 - 10) in the vertical plane aligned with the wind.
33  The distance from these measurement points of the terrain is all the bigger as the height is big.
34  Mode 1: modelling by a plane
35  Mode 2: modelling by in-plane potential flow with 2D panel method
36  */
37 
38 #include <crrc_config.h>
39 
40 #include <plib/ssg.h>
41 
42 #include "../global.h"
43 #include "../crrc_main.h"
44 #include "model_based_scenery.h"
45 #include "wind_from_terrain.h"
46 
47 
48 #define H_MIN     1.            // minimum height above terrain for wind estimation (1 foot)
49 
50 /*
51 data used by simple 2D panel method
52 */
53 #define N_UP_PTS  6             // number of up/down-stream points used
54 #define NPTS      (2*N_UP_PTS)  // total number of points defining terrain profile (panels)
55 #define NPAN      (NPTS-1)      // total number of panels
56 #define REF_L     300.          // reference length to smooth terrain slope beyond land's end
57 #define REF_Z     100.          // reference height from terrain
58 #define POW_Z     0.5           // power law to scale ratio of actual to reference height
59 #define BASELEN   0.5           // first panel length realtive to height from terrain
60 #define RATE      1.5           // panel length growth rate
61 #define EPS_END   .1            // accuracy in defining streamwise position of land's end
62 #define IT_MAX    20            // max iter in defining streamwise position of land's end
63 #define DELTAX    10.           // to compute terain slope at land's end
64 
65 static float x[NPTS], z[NPTS];    // panel vertex coords
66 static float cx[NPAN], cz[NPAN];  // panel control point coords
67 static float cc[NPAN], ss[NPAN];  // panel cosine & sine
68 static float A[NPAN][NPAN];       // system matrix
69 static float src[NPAN];           // unknown sources
70 
71 /**
72  * Compute normal velocity induced on control point of panel i
73  * by a uniform source strength distribution on panel j
74  *
75  */
vn_src0(int i,int j)76 static float vn_src0(int i, int j)
77 {
78 	float xr1, zr1, xr2, zr2, rq1, rq2, b0, c0, d0, e0;
79 
80   if (i == j)
81   {
82     return M_PI;
83   }
84   else
85   {
86     xr1 = cx[i] - x[j];
87     zr1 = cz[i] - z[j];
88     xr2 = cx[i] - x[j+1];
89     zr2 = cz[i] - z[j+1];
90     rq1 = xr1*xr1 + zr1*zr1;
91     rq2 = xr2*xr2 + zr2*zr2;
92     b0 = atan2(zr2*xr1 - xr2*zr1, xr2*xr1 + zr2*zr1);
93     c0 = ss[i]*cc[j] - cc[i]*ss[j];
94     d0 = cc[i]*cc[j] + ss[i]*ss[j];
95     e0 = .5*log(rq2/rq1);
96     return c0*e0 + d0*b0;
97   }
98 }
99 
100 /**
101  * Compute velocity components vx,vz induced on point px,pz
102  * by a uniform source strength distribution on panel j.
103  *
104  */
v_src0(float px,float pz,int j,float * vx,float * vz)105 static void v_src0(float px, float pz, int j, float *vx, float *vz)
106 {
107 	float xr1, zr1, xr2, zr2, rq1, rq2, b0, c0, d0, e0;
108 
109   xr1 = px - x[j];
110   zr1 = pz - z[j];
111   xr2 = px - x[j+1];
112   zr2 = pz - z[j+1];
113   rq1 = xr1*xr1 + zr1*zr1;
114   rq2 = xr2*xr2 + zr2*zr2;
115   b0 = atan2(zr2*xr1 - xr2*zr1, xr2*xr1 + zr2*zr1);
116   c0 = -ss[j];
117   d0 = cc[j];
118   e0 = .5*log(rq2/rq1);
119   *vz = c0 * e0 + d0 * b0;
120   *vx = c0 * b0 - d0 * e0;
121 }
122 
123 /**
124  * Solve linear system by Gauss elimination
125  *
126  */
solve_gs(float A[][NPAN],float x[],int n)127 static void solve_gs(float A[][NPAN], float x[], int n)
128 {
129   for(int k = 0; k < n-1; k++)
130     for(int i = n-1; i > k; i--)
131     {
132       A[i][k] /= A[k][k];
133       for(int j = n-1; j > k; j--)
134         A[i][j] -= A[k][j]*A[i][k];
135     }
136 
137   for(int i = 1; i < n; i++)
138     for(int j = 0; j < i; j++)
139       x[i] -= A[i][j]*x[j];
140   x[n-1] /= A[n-1][n-1];
141   for(int i = n-2; i >= 0; i--)
142   {
143     for(int j = n-2; j > i; j--)
144       x[i] -= A[i][j]*x[j];
145     x[i] /= A[i][i];
146   }
147 }
148 
wind_from_terrain(double X,double Y,double Z,float * x_wind_velocity,float * y_wind_velocity,float * z_wind_velocity)149 int wind_from_terrain(double X, double Y, double Z,
150                       float *x_wind_velocity, float *y_wind_velocity, float *z_wind_velocity)
151 {
152   // freestream wind velocity
153   float flWindVel = cfg->wind->getVelocity();
154   float flWindDir = cfg->wind->getDirection()*M_PI/180.;
155   float flWindDirX = cos(flWindDir); //upstream versor
156   float flWindDirY = sin(flWindDir); //upstream versor
157 
158   float z_c = Global::scenery->getHeight(X, Y); //terrain height below the point
159   float H = -Z; //positive down -> positive up
160   if ((H - z_c) < H_MIN)
161     H = z_c + H_MIN;
162   float dz = H - z_c;
163 
164   // if is above a defined terrain, estimate wind speed from terrain
165   if (z_c > DEEPEST_HELL)
166   {
167     sgVec3 wind;
168     sgSetVec3(wind, 0, 0, 0);
169 
170     if (Global::wind_mode == 1)
171     {
172       //
173       //We tilt the vector of wind along the slope, with the same speed in module
174       //
175       const float COEF = 0.5; //define angle of measure
176       float dx = COEF*dz*flWindDirX; //upstream vector
177       float dy = COEF*dz*flWindDirY; //upstream vector
178 
179       float z_f = Global::scenery->getHeight(X+dx, Y+dy);//terrain height upstream
180       if (z_f==DEEPEST_HELL) { z_f = z_c;}
181       float z_b = Global::scenery->getHeight(X-dx, Y-dy);//terrain height downstream
182       if (z_b==DEEPEST_HELL) { z_b = z_c;}
183       sgVec3 p_c, p_f, p_b;
184       sgSetVec3(p_c, X, Y, -z_c);
185       sgSetVec3(p_f, X+dx, Y+dy, -z_f);
186       sgSetVec3(p_b, X-dx, Y-dy, -z_b);
187       //float z_l = Global::scenery->getHeight(X+dx, Y-dsin);//left
188       //float z_r = Global::scenery->getHeight(X-dx, Y+dy);//right
189       //sgVec3 p_0, p_l, p_r;
190       //sgSetVec3(p_0,X,Y,H);
191 
192       sgVec3 dir;
193       sgSubVec3(dir, p_f, p_b);
194       sgNormaliseVec3(dir); //-> Unit vector in the direction of the wind
195       sgScaleVec3(wind, dir, -flWindVel);
196     }
197     else if (Global::wind_mode == 2)
198     {
199       //
200       //2D potential flow in a wind-aligned vertical plane
201       //
202 
203       // define panels vertex points in a reference system with x axis aligned
204       // with wind direction (negative upstream) and origin on the point X,Y
205       if (dz < 0.1*REF_Z) dz = 0.1*REF_Z; // do not reduce ref length too much
206       float dd = 0.5*BASELEN*pow(dz/REF_Z,POW_Z)*REF_Z;
207       float ds = dd;
208       float d1 = 0.0;
209       float d2 = 0.0;
210       float dzdd1 = 0.0;
211       float dzdd2 = 0.0;
212       float z1 = DEEPEST_HELL;
213       float z2 = DEEPEST_HELL;
214       for(int i=1; i <= N_UP_PTS; i++)
215       {
216         float dx = ds*flWindDirX;
217         float dy = ds*flWindDirY;
218 
219         x[N_UP_PTS-i]   = -ds;
220         z[N_UP_PTS-i]   = Global::scenery->getHeight(X+dx, Y+dy);
221         x[N_UP_PTS-1+i] = +ds;
222         z[N_UP_PTS-1+i] = Global::scenery->getHeight(X-dx, Y-dy);
223 
224         // check if upwind point is outside defined terrain profile
225         // in case find terrain elevation and slope at upstream land's end
226         if (z[N_UP_PTS-i] == DEEPEST_HELL)
227         {
228           if (z1 == DEEPEST_HELL)
229           {
230             float xa = x[N_UP_PTS-i+1];
231             float za = z[N_UP_PTS-i+1];
232             float xb = x[N_UP_PTS-i];
233             float xc, zc;
234             int it = 0;
235             while ((fabs(xa - xb) > EPS_END) && it < IT_MAX)
236             {
237               it++;
238               xc = 0.5*(xa + xb);
239               zc = Global::scenery->getHeight(X-xc*flWindDirX, Y-xc*flWindDirY);
240               if (zc == DEEPEST_HELL)
241                 xb = xc;
242               else
243               {
244                 xa = xc;
245                 za = zc;
246               }
247             }
248             d1 = xa;
249             z1 = za;
250             // estimate terrain slope at land's end
251             xc = xa + DELTAX;
252             zc = Global::scenery->getHeight(X-xc*flWindDirX, Y-xc*flWindDirY);
253             dzdd1 = (z1 - zc)/DELTAX;
254           }
255           z[N_UP_PTS-i] = z1 + dzdd1*REF_L*(1. - exp(-fabs(x[N_UP_PTS-i] - d1)/REF_L));
256         }
257         // check if downwind point is outside defined terrain profile
258         // in case find terrain elevation and slope at downstream land's end
259         if (z[N_UP_PTS-1+i] == DEEPEST_HELL)
260         {
261           if (z2 == DEEPEST_HELL)
262           {
263             float xa = x[N_UP_PTS-1+i-1];
264             float za = z[N_UP_PTS-1+i-1];
265             float xb = x[N_UP_PTS-1+i];
266             float xc, zc;
267             int it = 0;
268             while ((fabs(xa - xb) > EPS_END) && it < IT_MAX)
269             {
270               it++;
271               xc = 0.5*(xa + xb);
272               zc = Global::scenery->getHeight(X-xc*flWindDirX, Y-xc*flWindDirY);
273               if (zc == DEEPEST_HELL)
274                 xb = xc;
275               else
276               {
277                 xa = xc;
278                 za = zc;
279               }
280             }
281             d2 = xa;
282             z2 = za;
283             // estimate terrain slope at land's end
284             xc = xa - DELTAX;
285             zc = Global::scenery->getHeight(X-xc*flWindDirX, Y-xc*flWindDirY);
286             dzdd2 = (z2 - zc)/DELTAX;
287           }
288           z[N_UP_PTS-1+i] = z2 + dzdd2*REF_L*(1. - exp(-fabs(x[N_UP_PTS-1+i] - d2)/REF_L));
289         }
290 
291         dd *= (i == 1 ? 2. : 1.)*RATE;
292         ds += dd;
293       }
294 
295       // compute panels geometry
296       for(int i = 0; i < NPAN; i++)
297       {
298         cx[i] = .5*(x[i+1] + x[i]);
299         cz[i] = .5*(z[i+1] + z[i]);
300         float lx = x[i+1] - x[i];
301         float lz = z[i+1] - z[i];
302         float ll = hypot(lx, lz);
303         ss[i] = lz/ll;
304         cc[i] = lx/ll;
305       }
306 
307       // construct system matrix and source term
308       for(int i = 0; i < NPAN; i++)
309       {
310         src[i] = ss[i]; // freestream flow = horizontal wind
311         for( int j = 0; j < NPAN; j++ )
312           A[i][j] = vn_src0(i, j);
313       }
314 
315       // solve system matrix for the unknown source/vortex strength
316       solve_gs(A, src, NPAN);
317 
318       // compute velocity induced on target point
319       float vx = 1.; // freestream flow = horizontal wind
320       float vz = 0.; // freestream flow = horizontal wind
321       for(int i = 0; i < NPAN; i++)
322       {
323         float dvx, dvz;
324         v_src0(0., H, i, &dvx, &dvz); // induced (in plane) velocity on target point
325         vx += src[i]*dvx;
326         vz += src[i]*dvz;
327       }
328 
329       // define resulting wind vector
330       sgSetVec3(wind, flWindDirX*vx, flWindDirY*vx, vz);
331       sgScaleVec3(wind, -flWindVel);
332     }
333 
334     // return results
335     *x_wind_velocity = wind[0];
336     *y_wind_velocity = wind[1];
337     *z_wind_velocity = wind[2];
338 
339     return 0;
340   }
341   else
342   {
343     // point is invalid
344     return 1;
345   }
346 }
347