]> git.mxchange.org Git - flightgear.git/blob - src/Main/viewer.cxx
a40f8227522e044b4dcc34eaf92a2a1ba9631543
[flightgear.git] / src / Main / viewer.cxx
1 // viewer.cxx -- class for managing a viewer in the flightgear world.
2 //
3 // Written by Curtis Olson, started August 1997.
4 //                          overhaul started October 2000.
5 //   partially rewritten by Jim Wilson jim@kelcomaine.com using interface
6 //                          by David Megginson March 2002
7 //
8 // Copyright (C) 1997 - 2000  Curtis L. Olson  - curt@flightgear.org
9 //
10 // This program is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU General Public License as
12 // published by the Free Software Foundation; either version 2 of the
13 // License, or (at your option) any later version.
14 //
15 // This program is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 // General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 //
24 // $Id$
25
26
27 #include <simgear/compiler.h>
28
29 #ifdef HAVE_CONFIG_H
30 #  include <config.h>
31 #endif
32
33 #include <simgear/debug/logstream.hxx>
34 #include <simgear/constants.h>
35 #include <simgear/math/point3d.hxx>
36 #include <simgear/math/polar3d.hxx>
37 #include <simgear/math/sg_geodesy.hxx>
38
39 #include <Scenery/scenery.hxx>
40
41 /* from lookat */
42 #include <simgear/math/vector.hxx>
43 #include "globals.hxx"
44 /* end from lookat */
45
46 #include "viewer.hxx"
47
48 \f
49 ////////////////////////////////////////////////////////////////////////
50 // Implementation of FGViewer.
51 ////////////////////////////////////////////////////////////////////////
52
53 // Constructor
54 FGViewer::FGViewer( void ):
55     scalingType(FG_SCALING_MAX),
56     fov(55.0),
57     _dirty(true),
58     _lon_deg(0),
59     _lat_deg(0),
60     _alt_ft(0),
61     _target_lon_deg(0),
62     _target_lat_deg(0),
63     _target_alt_ft(0),
64     _roll_deg(0),
65     _pitch_deg(0),
66     _heading_deg(0),
67     _x_offset_m(0),
68     _y_offset_m(0),
69     _z_offset_m(0),
70     _heading_offset_deg(0),
71     _pitch_offset_deg(0),
72     _roll_offset_deg(0),
73     _goal_heading_offset_deg(0.0),
74     _goal_pitch_offset_deg(0.0)
75 {
76     sgdZeroVec3(_absolute_view_pos);
77     sea_level_radius = SG_EQUATORIAL_RADIUS_M; 
78     //a reasonable guess for init, so that the math doesn't blow up
79 }
80
81
82 // Destructor
83 FGViewer::~FGViewer( void ) {
84 }
85
86 void
87 FGViewer::init ()
88 {
89   if ( _type == FG_LOOKAT ) {
90       set_reverse_view_offset(true);
91   }
92
93   if ( _type == FG_RPH ) {
94       set_reverse_view_offset(false);
95   }
96 }
97
98 void
99 FGViewer::bind ()
100 {
101 }
102
103 void
104 FGViewer::unbind ()
105 {
106 }
107
108 void
109 FGViewer::setType ( int type )
110 {
111   if (type == 0)
112     _type = FG_RPH;
113   if (type == 1)
114     _type = FG_LOOKAT;
115 }
116
117 void
118 FGViewer::setLongitude_deg (double lon_deg)
119 {
120   _dirty = true;
121   _lon_deg = lon_deg;
122 }
123
124 void
125 FGViewer::setLatitude_deg (double lat_deg)
126 {
127   _dirty = true;
128   _lat_deg = lat_deg;
129 }
130
131 void
132 FGViewer::setAltitude_ft (double alt_ft)
133 {
134   _dirty = true;
135   _alt_ft = alt_ft;
136 }
137
138 void
139 FGViewer::setPosition (double lon_deg, double lat_deg, double alt_ft)
140 {
141   _dirty = true;
142   _lon_deg = lon_deg;
143   _lat_deg = lat_deg;
144   _alt_ft = alt_ft;
145 }
146
147 void
148 FGViewer::setTargetLongitude_deg (double lon_deg)
149 {
150   _dirty = true;
151   _target_lon_deg = lon_deg;
152 }
153
154 void
155 FGViewer::setTargetLatitude_deg (double lat_deg)
156 {
157   _dirty = true;
158   _target_lat_deg = lat_deg;
159 }
160
161 void
162 FGViewer::setTargetAltitude_ft (double alt_ft)
163 {
164   _dirty = true;
165   _target_alt_ft = alt_ft;
166 }
167
168 void
169 FGViewer::setTargetPosition (double lon_deg, double lat_deg, double alt_ft)
170 {
171   _dirty = true;
172   _target_lon_deg = lon_deg;
173   _target_lat_deg = lat_deg;
174   _target_alt_ft = alt_ft;
175 }
176
177 void
178 FGViewer::setRoll_deg (double roll_deg)
179 {
180   _dirty = true;
181   _roll_deg = roll_deg;
182 }
183
184 void
185 FGViewer::setPitch_deg (double pitch_deg)
186 {
187   _dirty = true;
188   _pitch_deg = pitch_deg;
189 }
190
191 void
192 FGViewer::setHeading_deg (double heading_deg)
193 {
194   _dirty = true;
195   _heading_deg = heading_deg;
196 }
197
198 void
199 FGViewer::setOrientation (double roll_deg, double pitch_deg, double heading_deg)
200 {
201   _dirty = true;
202   _roll_deg = roll_deg;
203   _pitch_deg = pitch_deg;
204   _heading_deg = heading_deg;
205 }
206
207 void
208 FGViewer::setXOffset_m (double x_offset_m)
209 {
210   _dirty = true;
211   _x_offset_m = x_offset_m;
212 }
213
214 void
215 FGViewer::setYOffset_m (double y_offset_m)
216 {
217   _dirty = true;
218   _y_offset_m = y_offset_m;
219 }
220
221 void
222 FGViewer::setZOffset_m (double z_offset_m)
223 {
224   _dirty = true;
225   _z_offset_m = z_offset_m;
226 }
227
228 void
229 FGViewer::setPositionOffsets (double x_offset_m, double y_offset_m, double z_offset_m)
230 {
231   _dirty = true;
232   _x_offset_m = x_offset_m;
233   _y_offset_m = y_offset_m;
234   _z_offset_m = z_offset_m;
235 }
236
237 void
238 FGViewer::setRollOffset_deg (double roll_offset_deg)
239 {
240   _dirty = true;
241   _roll_offset_deg = roll_offset_deg;
242 }
243
244 void
245 FGViewer::setPitchOffset_deg (double pitch_offset_deg)
246 {
247   _dirty = true;
248   _pitch_offset_deg = pitch_offset_deg;
249 }
250
251 void
252 FGViewer::setHeadingOffset_deg (double heading_offset_deg)
253 {
254   _dirty = true;
255   _heading_offset_deg = heading_offset_deg;
256 }
257
258 void
259 FGViewer::setGoalRollOffset_deg (double goal_roll_offset_deg)
260 {
261   _dirty = true;
262   _goal_roll_offset_deg = goal_roll_offset_deg;
263 }
264
265 void
266 FGViewer::setGoalPitchOffset_deg (double goal_pitch_offset_deg)
267 {
268   _dirty = true;
269   _goal_pitch_offset_deg = goal_pitch_offset_deg;
270   while ( _goal_pitch_offset_deg < -90 ) {
271     _goal_pitch_offset_deg = -90.0;
272   }
273   while ( _goal_pitch_offset_deg > 90.0 ) {
274     _goal_pitch_offset_deg = 90.0;
275   }
276
277 }
278
279 void
280 FGViewer::setGoalHeadingOffset_deg (double goal_heading_offset_deg)
281 {
282   _dirty = true;
283   _goal_heading_offset_deg = goal_heading_offset_deg;
284   while ( _goal_heading_offset_deg < 0.0 ) {
285     _goal_heading_offset_deg += 360;
286   }
287   while ( _goal_heading_offset_deg > 360 ) {
288     _goal_heading_offset_deg -= 360;
289   }
290 }
291
292 void
293 FGViewer::setOrientationOffsets (double roll_offset_deg, double pitch_offset_deg, double heading_offset_deg)
294 {
295   _dirty = true;
296   _roll_offset_deg = roll_offset_deg;
297   _pitch_offset_deg = pitch_offset_deg;
298   _heading_offset_deg = heading_offset_deg;
299 }
300
301 double *
302 FGViewer::get_absolute_view_pos () 
303 {
304   if (_dirty)
305     recalc();
306   return _absolute_view_pos;
307 }
308
309 float *
310 FGViewer::getRelativeViewPos () 
311 {
312   if (_dirty)
313     recalc();
314   return _relative_view_pos;
315 }
316
317 float *
318 FGViewer::getZeroElevViewPos () 
319 {
320   if (_dirty)
321     recalc();
322   return _zero_elev_view_pos;
323 }
324
325
326 // recalc() is done every time one of the setters is called (making the 
327 // cached data "dirty") on the next "get".  It calculates all the outputs 
328 // for viewer.
329 void
330 FGViewer::recalc ()
331 {
332   sgVec3 minus_z, right, forward, tilt;
333   sgMat4 VIEWo;
334   sgMat4 tmpROT;  // temp rotation work matrices
335   sgVec3 tmpVec3;  // temp work vector (3)
336
337
338   // The position vectors originate from the view point or target location
339   // depending on the type of view.
340
341   if (_type == FG_RPH) {
342     recalcPositionVectors( _lon_deg, _lat_deg, _alt_ft );
343   } else {
344     recalcPositionVectors( _target_lon_deg, _target_lat_deg, _target_alt_ft );
345   }
346
347   sgCopyVec3(zero_elev, _zero_elev_view_pos);
348   sgCopyVec3(view_pos, _relative_view_pos);
349
350   if (_type == FG_LOOKAT) {
351
352     // Make the world up rotation matrix for lookat
353     sgMakeRotMat4( UP, _target_lon_deg, 0.0, -_target_lat_deg );
354
355     // get the world up verctor from the worldup rotation matrix
356     sgSetVec3( world_up, UP[0][0], UP[0][1], UP[0][2] );
357
358     sgCopyVec3( view_up, world_up );
359     
360
361     // create offset vector
362     sgVec3 lookat_offset;
363     sgSetVec3( lookat_offset, _x_offset_m, _y_offset_m, _z_offset_m );
364
365     // Apply heading orientation and orientation offset to lookat_offset...
366     sgMakeRotMat4( tmpROT, _heading_offset_deg -_heading_deg, world_up);
367     sgXformVec3( lookat_offset, lookat_offset, UP );
368     sgXformVec3( lookat_offset, lookat_offset, tmpROT );
369
370     // Apply orientation offset tilt...
371     // FIXME: Need to get and use a "right" vector instead of 1-0-0
372     sgSetVec3 (tmpVec3, 1, 0, 0);
373     sgMakeRotMat4( tmpROT, _pitch_offset_deg, tmpVec3 );
374     sgXformPnt3( lookat_offset, lookat_offset, tmpROT );
375
376     // add the offsets including rotations to the coordinates
377     sgAddVec3( view_pos, lookat_offset );
378
379     // Make the VIEW matrix.
380     fgMakeLookAtMat4( VIEW, view_pos, view_forward, view_up );
381
382
383     // the VIEW matrix includes both rotation and translation.  Let's
384     // knock out the translation part to make the VIEW_ROT matrix
385     sgCopyMat4( VIEW_ROT, VIEW );
386     VIEW_ROT[3][0] = VIEW_ROT[3][1] = VIEW_ROT[3][2] = 0.0;
387
388   }
389
390   if (_type == FG_RPH) {
391
392     // code to calculate LOCAL matrix calculated from Phi, Theta, and
393     // Psi (roll, pitch, yaw) in case we aren't running LaRCsim as our
394     // flight model
395         
396     fgMakeLOCAL( LOCAL, _pitch_deg * SG_DEGREES_TO_RADIANS,
397                         _roll_deg * SG_DEGREES_TO_RADIANS,
398                         -_heading_deg * SG_DEGREES_TO_RADIANS);
399         
400     // Make the world up rotation matrix for pilot view
401     sgMakeRotMat4( UP, _lon_deg, 0.0, -_lat_deg );
402
403     // get the world up verctor from the worldup rotation matrix
404     sgSetVec3( world_up, UP[0][0], UP[0][1], UP[0][2] );
405
406     // VIEWo becomes the rotation matrix with world_up incorporated
407     sgCopyMat4( VIEWo, LOCAL );
408     sgPostMultMat4( VIEWo, UP );
409
410     // generate the sg view up and forward vectors
411     sgSetVec3( view_up, VIEWo[0][0], VIEWo[0][1], VIEWo[0][2] );
412     sgSetVec3( right, VIEWo[1][0], VIEWo[1][1], VIEWo[1][2] );
413     sgSetVec3( forward, VIEWo[2][0], VIEWo[2][1], VIEWo[2][2] );
414
415     // apply the offsets in world coordinates
416     sgVec3 pilot_offset_world;
417     sgSetVec3( pilot_offset_world, 
418                _z_offset_m, _y_offset_m, -_x_offset_m );
419     sgXformVec3( pilot_offset_world, pilot_offset_world, VIEWo );
420
421     // generate the view offset matrix using orientation offset (heading)
422     sgMakeRotMat4( VIEW_OFFSET, _heading_offset_deg, view_up );
423
424     // create a tilt matrix using orientation offset (pitch)
425     sgMat4 VIEW_TILT;
426     sgMakeRotMat4( VIEW_TILT, _pitch_offset_deg, right );
427     sgPreMultMat4(VIEW_OFFSET, VIEW_TILT);
428     sgXformVec3( view_forward, forward, VIEW_OFFSET );
429     SG_LOG( SG_VIEW, SG_DEBUG, "(RPH) view forward = "
430             << view_forward[0] << "," << view_forward[1] << ","
431             << view_forward[2] );
432         
433     // VIEW_ROT = LARC_TO_SSG * ( VIEWo * VIEW_OFFSET )
434     fgMakeViewRot( VIEW_ROT, VIEW_OFFSET, VIEWo );
435
436     sgVec3 trans_vec;
437     sgAddVec3( trans_vec, view_pos, pilot_offset_world );
438
439     // VIEW = VIEW_ROT * TRANS
440     sgCopyMat4( VIEW, VIEW_ROT );
441     sgPostMultMat4ByTransMat4( VIEW, trans_vec );
442
443   }
444
445   // Given a vector pointing straight down (-Z), map into onto the
446   // local plane representing "horizontal".  This should give us the
447   // local direction for moving "south".
448   sgSetVec3( minus_z, 0.0, 0.0, -1.0 );
449
450   sgmap_vec_onto_cur_surface_plane(world_up, view_pos, minus_z,
451                                      surface_south);
452   sgNormalizeVec3(surface_south);
453
454   // now calculate the surface east vector
455   sgVec3 world_down;
456   sgNegateVec3(world_down, world_up);
457   sgVectorProductVec3(surface_east, surface_south, world_down);
458
459   set_clean();
460 }
461
462 void
463 FGViewer::recalcPositionVectors (double lon_deg, double lat_deg, double alt_ft) const
464 {
465   double sea_level_radius_m;
466   double lat_geoc_rad;
467
468
469                                 // Convert from geodetic to geocentric
470                                 // coordinates.
471   sgGeodToGeoc(lat_deg * SGD_DEGREES_TO_RADIANS,
472                alt_ft * SG_FEET_TO_METER,
473                &sea_level_radius_m,
474                &lat_geoc_rad);
475
476                                 // Calculate the cartesian coordinates
477                                 // of point directly below at sea level.
478                                 // aka Zero Elevation Position
479   Point3D p = Point3D(lon_deg * SG_DEGREES_TO_RADIANS,
480                       lat_geoc_rad,
481                       sea_level_radius_m);
482   Point3D tmp = sgPolarToCart3d(p) - scenery.get_next_center();
483   sgSetVec3(_zero_elev_view_pos, tmp[0], tmp[1], tmp[2]);
484
485                                 // Calculate the absolute view position
486                                 // in fgfs coordinates.
487                                 // aka Absolute View Position
488   p.setz(p.radius() + alt_ft * SG_FEET_TO_METER);
489   tmp = sgPolarToCart3d(p);
490   sgdSetVec3(_absolute_view_pos, tmp[0], tmp[1], tmp[2]);
491
492                                 // Calculate the relative view position
493                                 // from the scenery center.
494                                 // aka Relative View Position
495   sgdVec3 scenery_center;
496   sgdSetVec3(scenery_center,
497              scenery.get_next_center().x(),
498              scenery.get_next_center().y(),
499              scenery.get_next_center().z());
500   sgdVec3 view_pos;
501   sgdSubVec3(view_pos, _absolute_view_pos, scenery_center);
502   sgSetVec3(_relative_view_pos, view_pos);
503
504 }
505
506 double
507 FGViewer::get_h_fov()
508 {
509     switch (scalingType) {
510     case FG_SCALING_WIDTH:  // h_fov == fov
511         return fov;
512     case FG_SCALING_MAX:
513         if (aspect_ratio < 1.0) {
514             // h_fov == fov
515             return fov;
516         } else {
517             // v_fov == fov
518             return atan(tan(fov/2 * SG_DEGREES_TO_RADIANS) / aspect_ratio) *
519                 SG_RADIANS_TO_DEGREES * 2;
520         }
521     default:
522         assert(false);
523     }
524 }
525
526 double
527 FGViewer::get_v_fov()
528 {
529     switch (scalingType) {
530     case FG_SCALING_WIDTH:  // h_fov == fov
531         return atan(tan(fov/2 * SG_DEGREES_TO_RADIANS) * aspect_ratio) *
532             SG_RADIANS_TO_DEGREES * 2;
533     case FG_SCALING_MAX:
534         if (aspect_ratio < 1.0) {
535             // h_fov == fov
536             return atan(tan(fov/2 * SG_DEGREES_TO_RADIANS) * aspect_ratio) *
537                 SG_RADIANS_TO_DEGREES * 2;
538         } else {
539             // v_fov == fov
540             return fov;
541         }
542     default:
543         assert(false);
544     }
545 }
546
547 void
548 FGViewer::update (int dt)
549 {
550   int i;
551   for ( i = 0; i < dt; i++ ) {
552     if ( fabs( _goal_heading_offset_deg - _heading_offset_deg) < 1 ) {
553       setHeadingOffset_deg( _goal_heading_offset_deg );
554       break;
555     } else {
556       // move current_view.headingoffset towards
557       // current_view.goal_view_offset
558       if ( _goal_heading_offset_deg > _heading_offset_deg )
559         {
560           if ( _goal_heading_offset_deg - _heading_offset_deg < 180 ){
561             inc_view_offset( 0.5 );
562           } else {
563             inc_view_offset( -0.5 );
564           }
565         } else {
566           if ( _heading_offset_deg - _goal_heading_offset_deg < 180 ){
567             inc_view_offset( -0.5 );
568           } else {
569             inc_view_offset( 0.5 );
570           }
571         }
572       if ( _heading_offset_deg > 360 ) {
573         inc_view_offset( -360 );
574       } else if ( _heading_offset_deg < 0 ) {
575         inc_view_offset( 360 );
576       }
577     }
578   }
579
580   for ( i = 0; i < dt; i++ ) {
581     if ( fabs( _goal_pitch_offset_deg - _pitch_offset_deg ) < 1 ) {
582       setPitchOffset_deg( _goal_pitch_offset_deg );
583       break;
584     } else {
585       // move current_view.pitch_offset_deg towards
586       // current_view.goal_view_tilt
587       if ( _goal_pitch_offset_deg > _pitch_offset_deg )
588         {
589           if ( _goal_pitch_offset_deg - _pitch_offset_deg < 0 ){
590             inc_view_tilt( 1.0 );
591           } else {
592             inc_view_tilt( -1.0 );
593           }
594         } else {
595           if ( _pitch_offset_deg - _goal_pitch_offset_deg < 0 ){
596             inc_view_tilt( -1.0 );
597           } else {
598             inc_view_tilt( 1.0 );
599           }
600         }
601       if ( _pitch_offset_deg > 90 ) {
602         setPitchOffset_deg(90);
603       } else if ( _pitch_offset_deg < -90 ) {
604         setPitchOffset_deg( -90 );
605       }
606     }
607   }
608 }
609
610
611 void FGViewer::fgMakeLookAtMat4 ( sgMat4 dst, const sgVec3 eye, const sgVec3 center,
612                         const sgVec3 up )
613 {
614   // Caveats:
615   // 1) In order to compute the line of sight, the eye point must not be equal
616   //    to the center point.
617   // 2) The up vector must not be parallel to the line of sight from the eye
618   //    to the center point.
619
620   /* Compute the direction vectors */
621   sgVec3 x,y,z;
622
623   /* Y vector = center - eye */
624   sgSubVec3 ( y, center, eye ) ;
625
626   /* Z vector = up */
627   sgCopyVec3 ( z, up ) ;
628
629   /* X vector = Y cross Z */
630   sgVectorProductVec3 ( x, y, z ) ;
631
632   /* Recompute Z = X cross Y */
633   sgVectorProductVec3 ( z, x, y ) ;
634
635   /* Normalize everything */
636   sgNormaliseVec3 ( x ) ;
637   sgNormaliseVec3 ( y ) ;
638   sgNormaliseVec3 ( z ) ;
639
640   /* Build the matrix */
641 #define M(row,col)  dst[row][col]
642   M(0,0) = x[0];    M(0,1) = x[1];    M(0,2) = x[2];    M(0,3) = 0.0;
643   M(1,0) = y[0];    M(1,1) = y[1];    M(1,2) = y[2];    M(1,3) = 0.0;
644   M(2,0) = z[0];    M(2,1) = z[1];    M(2,2) = z[2];    M(2,3) = 0.0;
645   M(3,0) = eye[0];  M(3,1) = eye[1];  M(3,2) = eye[2];  M(3,3) = 1.0;
646 #undef M
647 }
648 /* end from lookat */
649
650 /* from rph */
651 // VIEW_ROT = LARC_TO_SSG * ( VIEWo * VIEW_OFFSET )
652 // This takes advantage of the fact that VIEWo and VIEW_OFFSET
653 // only have entries in the upper 3x3 block
654 // and that LARC_TO_SSG is just a shift of rows   NHV
655 void FGViewer::fgMakeViewRot( sgMat4 dst, const sgMat4 m1, const sgMat4 m2 )
656 {
657     for ( int j = 0 ; j < 3 ; j++ ) {
658         dst[2][j] = m2[0][0] * m1[0][j] +
659             m2[0][1] * m1[1][j] +
660             m2[0][2] * m1[2][j];
661
662         dst[0][j] = m2[1][0] * m1[0][j] +
663             m2[1][1] * m1[1][j] +
664             m2[1][2] * m1[2][j];
665
666         dst[1][j] = m2[2][0] * m1[0][j] +
667             m2[2][1] * m1[1][j] +
668             m2[2][2] * m1[2][j];
669     }
670     dst[0][3] = 
671         dst[1][3] = 
672         dst[2][3] = 
673         dst[3][0] = 
674         dst[3][1] = 
675         dst[3][2] = SG_ZERO;
676     dst[3][3] = SG_ONE;
677 }
678
679
680 void FGViewer::fgMakeLOCAL( sgMat4 dst, const double Theta,
681                                 const double Phi, const double Psi)
682 {
683     SGfloat cosTheta = (SGfloat) cos(Theta);
684     SGfloat sinTheta = (SGfloat) sin(Theta);
685     SGfloat cosPhi   = (SGfloat) cos(Phi);
686     SGfloat sinPhi   = (SGfloat) sin(Phi);
687     SGfloat sinPsi   = (SGfloat) sin(Psi) ;
688     SGfloat cosPsi   = (SGfloat) cos(Psi) ;
689         
690     dst[0][0] = cosPhi * cosTheta;
691     dst[0][1] = sinPhi * cosPsi + cosPhi * -sinTheta * -sinPsi;
692     dst[0][2] = sinPhi * sinPsi + cosPhi * -sinTheta * cosPsi;
693     dst[0][3] = SG_ZERO;
694
695     dst[1][0] = -sinPhi * cosTheta;
696     dst[1][1] = cosPhi * cosPsi + -sinPhi * -sinTheta * -sinPsi;
697     dst[1][2] = cosPhi * sinPsi + -sinPhi * -sinTheta * cosPsi;
698     dst[1][3] = SG_ZERO ;
699         
700     dst[2][0] = sinTheta;
701     dst[2][1] = cosTheta * -sinPsi;
702     dst[2][2] = cosTheta * cosPsi;
703     dst[2][3] = SG_ZERO;
704         
705     dst[3][0] = SG_ZERO;
706     dst[3][1] = SG_ZERO;
707     dst[3][2] = SG_ZERO;
708     dst[3][3] = SG_ONE ;
709 }
710
711 /* end from rph */
712
713