FlightGear next
Integrator.cpp
Go to the documentation of this file.
1#include "Math.hpp"
2#include "Integrator.hpp"
3namespace yasim {
4
5// Updates a position vector for a body c.g. motion with velocity v
6// over time dt, from orientation o0 to o1. Because the position
7// references the local coordinate origin, but the velocity is that of
8// the c.g., this gets a bit complicated.
9void Integrator::extrapolatePosition(double* pos, float* v, float dt,
10 float* o1, float* o2)
11{
12 // Remember that it's the c.g. that's moving, so account for
13 // changes in orientation. The motion of the coordinate
14 // frame will be l2gOLD(cg) + deltaCG - l2gNEW(cg)
15 float cg[3], tmp[3];
16
17 _body->getCG(cg);
18 l2gVector(o1, cg, cg); // cg = l2gOLD(cg) ("cg0")
19 Math::set3(v, tmp); // tmp = vel
20 Math::mul3(dt, tmp, tmp); // = vel*dt ("deltaCG")
21 Math::add3(cg, tmp, tmp); // = cg0 + deltaCG
22 _body->getCG(cg);
23 l2gVector(o2, cg, cg); // cg = l2gNEW(cg) ("cg1")
24 Math::sub3(tmp, cg, tmp); // tmp = cg0 + deltaCG - cg1
25
26 pos[0] += tmp[0]; // p1 = p0 + (cg0+deltaCG-cg1)
27 pos[1] += tmp[1]; // (positions are doubles, so we
28 pos[2] += tmp[2]; // can't use Math::add3)
29}
30
31#if 0
32// A straight euler integration, for reference. Don't use.
33void Integrator::calcNewInterval()
34{
35 float tmp[3];
36 State s = _s;
37
38 float dt = _dt / 4;
39
40 orthonormalize(_s.orient);
41 int i;
42 for(i=0; i<4; i++) {
43 _body->reset();
44 _env->calcForces(&s);
45
46 _body->getAccel(s.acc);
47 l2gVector(_s.orient, s.acc, s.acc);
48
49 _body->getAngularAccel(s.racc);
50 l2gVector(_s.orient, s.racc, s.racc);
51
52 float rotmat[9];
53 rotMatrix(s.rot, dt, rotmat);
54 Math::mmul33(_s.orient, rotmat, s.orient);
55
56 extrapolatePosition(s.pos, s.v, dt, _s.orient, s.orient);
57
58 Math::mul3(dt, s.acc, tmp);
59 Math::add3(tmp, s.v, s.v);
60
61 Math::mul3(dt, s.racc, tmp);
62 Math::add3(tmp, s.rot, s.rot);
63
64 _s = s;
65 }
66
67 _env->newState(&_s);
68}
69#endif
70
71void Integrator::calcNewInterval()
72{
73 // In principle, these could be changed for something other than
74 // a 4th order integration. I doubt if anyone cares.
75 const static int NITER=4;
76 static float TIMESTEP[] = { 1.0, 0.5, 0.5, 1.0 };
77 static float WEIGHTS[] = { 6.0, 3.0, 3.0, 6.0 };
78
79 // Scratch pads:
80 double pos[NITER][3]; float vel[NITER][3]; float acc[NITER][3];
81 float ori[NITER][9]; float rot[NITER][3]; float rac[NITER][3];
82
83 float *currVel = _s.v;
84 float *currAcc = _s.acc;
85 float *currRot = _s.rot;
86 float *currRac = _s.racc;
87
88 // First off, sanify the initial orientation
89 orthonormalize(_s.orient);
90
91 int i;
92 for(i=0; i<NITER; i++) {
93 //
94 // extrapolate forward based on current values of the
95 // derivatives and the ORIGINAL values of the
96 // position/orientation.
97 //
98 float dt = _dt * TIMESTEP[i];
99 float tmp[3];
100
101 // "add" rotation to orientation (generate a rotation matrix)
102 float rotmat[9];
103 rotMatrix(currRot, dt, rotmat);
104 Math::mmul33(_s.orient, rotmat, ori[i]);
105
106 // add velocity to (original!) position
107 int j;
108 for(j=0; j<3; j++) pos[i][j] = _s.pos[j];
109 extrapolatePosition(pos[i], currVel, dt, _s.orient, ori[i]);
110
111 // add acceleration to (original!) velocity
112 Math::set3(currAcc, tmp);
113 Math::mul3(dt, tmp, tmp);
114 Math::add3(_s.v, tmp, vel[i]);
115
116 // add rotational acceleration to rotation
117 Math::set3(currRac, tmp);
118 Math::mul3(dt, tmp, tmp);
119 Math::add3(_s.rot, tmp, rot[i]);
120
121 //
122 // Tell the environment to generate new forces on the body,
123 // extract the accelerations, and convert to vectors in the
124 // global frame.
125 //
126 _body->reset();
127
128 // FIXME. Copying into a state object is clumsy! The
129 // per-coordinate arrays should be changed into a single array
130 // of State objects. Ick.
131 State stmp;
132 for(j=0; j<3; j++) {
133 stmp.pos[j] = pos[i][j];
134 stmp.v[j] = vel[i][j];
135 stmp.rot[j] = rot[i][j];
136 }
137 for(j=0; j<9; j++)
138 stmp.orient[j] = ori[i][j];
139 stmp.dt = dt - _dt;
140 _env->calcForces(&stmp);
141
142 _body->getAccel(acc[i]);
143 _body->getAngularAccel(rac[i]);
144 l2gVector(_s.orient, acc[i], acc[i]);
145 l2gVector(_s.orient, rac[i], rac[i]);
146
147 //
148 // Save the resulting derivatives for the next iteration
149 //
150 currVel = vel[i]; currAcc = acc[i];
151 currRot = rot[i]; currRac = rac[i];
152 }
153
154 // Average the resulting derivatives together according to their
155 // weights. Yes, we're "averaging" rotations, which isn't
156 // stricly correct -- rotations live in a non-cartesian space.
157 // But the space is "locally" cartesian.
158 State derivs;
159 float tot = 0;
160 for(i=0; i<NITER; i++) {
161 float wgt = WEIGHTS[i];
162 tot += wgt;
163 int j;
164 for(j=0; j<3; j++) {
165 derivs.v[j] += wgt*vel[i][j]; derivs.rot[j] += wgt*rot[i][j];
166 derivs.acc[j] += wgt*acc[i][j]; derivs.racc[j] += wgt*rac[i][j];
167 }
168 }
169 float itot = 1/tot;
170 for(i=0; i<3; i++) {
171 derivs.v[i] *= itot; derivs.rot[i] *= itot;
172 derivs.acc[i] *= itot; derivs.racc[i] *= itot;
173 }
174
175 // And finally extrapolate once more, using the averaged
176 // derivatives, to the final position and orientation. This code
177 // is essentially identical to the position extrapolation step
178 // inside the loop.
179
180 // save the starting orientation
181 float orient0[9];
182 for(i=0; i<9; i++) orient0[i] = _s.orient[i];
183
184 float rotmat[9];
185 rotMatrix(derivs.rot, _dt, rotmat);
186 Math::mmul33(orient0, rotmat, _s.orient);
187
188 extrapolatePosition(_s.pos, derivs.v, _dt, orient0, _s.orient);
189
190 float tmp[3];
191 Math::mul3(_dt, derivs.acc, tmp);
192 Math::add3(_s.v, tmp, _s.v);
193
194 Math::mul3(_dt, derivs.racc, tmp);
195 Math::add3(_s.rot, tmp, _s.rot);
196
197 for(i=0; i<3; i++) {
198 _s.acc[i] = derivs.acc[i];
199 _s.racc[i] = derivs.racc[i];
200 }
201
202 // Tell the environment about our decision
203 _env->newState(&_s);
204}
205
206// Generates a matrix that rotates about axis r through an angle equal
207// to (|r| * dt). That is, a rotation effected by rotating with rate
208// r for dt "seconds" (or whatever time unit is in use).
209// Implementation shamelessly cribbed from the OpenGL specification.
210//
211// NOTE: we're actually returning the _transpose_ of the rotation
212// matrix! This is becuase we store orientations as global-to-local
213// transformations. Thus, we want to rotate the ROWS of the old
214// matrix to get the new one.
215void Integrator::rotMatrix(float* r, float dt, float* out)
216{
217 // Normalize the vector, and extract the angle through which we'll
218 // rotate.
219 float mag = Math::mag3(r);
220 float angle = dt*mag;
221
222 // Tiny rotations result in degenerate (zero-length) rotation
223 // vectors, so clamp to an identity matrix. 1e-06 radians
224 // per 1/30th of a second (typical dt unit) corresponds to one
225 // revolution per 2.4 days, or significantly less than the
226 // coriolis rotation. And it's still preserves half the floating
227 // point precision of a radian-per-iteration rotation.
228 if(angle < 1e-06) {
229 out[0] = 1; out[1] = 0; out[2] = 0;
230 out[3] = 0; out[4] = 1; out[5] = 0;
231 out[6] = 0; out[7] = 0; out[8] = 1;
232 return;
233 }
234
235 float runit[3];
236 Math::mul3(1/mag, r, runit);
237
238 float s = Math::sin(angle);
239 float c = Math::cos(angle);
240 float c1 = 1-c;
241 float c1rx = c1*runit[0];
242 float c1ry = c1*runit[1];
243 float xx = c1rx*runit[0];
244 float xy = c1rx*runit[1];
245 float xz = c1rx*runit[2];
246 float yy = c1ry*runit[1];
247 float yz = c1ry*runit[2];
248 float zz = c1*runit[2]*runit[2];
249 float xs = runit[0]*s;
250 float ys = runit[1]*s;
251 float zs = runit[2]*s;
252
253 out[0] = xx+c ; out[3] = xy-zs; out[6] = xz+ys;
254 out[1] = xy+zs; out[4] = yy+c ; out[7] = yz-xs;
255 out[2] = xz-ys; out[5] = yz+xs; out[8] = zz+c ;
256}
257
258// Does a Gram-Schmidt orthonormalization on the rows of a
259// global-to-local orientation matrix. The order of normalization
260// here is x, z, y. This is because of the convention of "x" being
261// the forward vector and "z" being up in the body frame. These two
262// vectors are the most important to keep correct.
263void Integrator::orthonormalize(float* m)
264{
265 // The 1st, 2nd and 3rd rows of the matrix store the global frame
266 // equivalents of the x, y, and z unit vectors in the local frame.
267 float *x = m, *y = m+3, *z = m+6;
268
269 Math::unit3(x, x); // x = x/|x|
270
271 float v[3];
272 Math::mul3(Math::dot3(x, z), z, v); // v = z(x dot z)
273 Math::sub3(z, v, z); // z = z - z*(x dot z)
274 Math::unit3(z, z); // z = z/|z|
275
276 Math::cross3(z, x, y); // y = z cross x
277}
278
279}; // namespace yasim
#define i(x)
State
Models an aircraft axis for purposes of trimming.
Definition FGTrimAxis.h:83