]> git.mxchange.org Git - flightgear.git/blob - src/Model/model.cxx
Allow an empty virtual model, to which other submodels can be attached
[flightgear.git] / src / Model / model.cxx
1 // model.cxx - manage a 3D aircraft model.
2 // Written by David Megginson, started 2002.
3 //
4 // This file is in the Public Domain, and comes with no warranty.
5
6 #ifdef HAVE_CONFIG_H
7 #  include <config.h>
8 #endif
9
10 #include <string.h>             // for strcmp()
11
12 #include <plib/sg.h>
13 #include <plib/ssg.h>
14
15 #include <simgear/compiler.h>
16 #include <simgear/debug/logstream.hxx>
17 #include <simgear/math/interpolater.hxx>
18 #include <simgear/math/point3d.hxx>
19 #include <simgear/math/sg_geodesy.hxx>
20 #include <simgear/misc/exception.hxx>
21 #include <simgear/misc/sg_path.hxx>
22
23 #include <Main/fg_props.hxx>
24 #include <Main/globals.hxx>
25 #include <Main/location.hxx>
26 #include <Scenery/scenery.hxx>
27
28 #include "model.hxx"
29
30
31 \f
32 ////////////////////////////////////////////////////////////////////////
33 // Static utility functions.
34 ////////////////////////////////////////////////////////////////////////
35
36 /**
37  * Locate a named SSG node in a branch.
38  */
39 static ssgEntity *
40 find_named_node (ssgEntity * node, const char * name)
41 {
42   char * node_name = node->getName();
43   if (node_name != 0 && !strcmp(name, node_name))
44     return node;
45   else if (node->isAKindOf(ssgTypeBranch())) {
46     int nKids = node->getNumKids();
47     for (int i = 0; i < nKids; i++) {
48       ssgEntity * result =
49         find_named_node(((ssgBranch*)node)->getKid(i), name);
50       if (result != 0)
51         return result;
52     }
53   } 
54   return 0;
55 }
56
57 /**
58  * Splice a branch in between all child nodes and their parents.
59  */
60 static void
61 splice_branch (ssgBranch * branch, ssgEntity * child)
62 {
63   int nParents = child->getNumParents();
64   branch->addKid(child);
65   for (int i = 0; i < nParents; i++) {
66     ssgBranch * parent = child->getParent(i);
67     parent->replaceKid(child, branch);
68   }
69 }
70
71 /**
72  * Set up the transform matrix for a spin or rotation.
73  */
74 static void
75 set_rotation (sgMat4 &matrix, double position_deg,
76               sgVec3 &center, sgVec3 &axis)
77 {
78  float temp_angle = -position_deg * SG_DEGREES_TO_RADIANS ;
79  
80  float s = (float) sin ( temp_angle ) ;
81  float c = (float) cos ( temp_angle ) ;
82  float t = SG_ONE - c ;
83
84  // axis was normalized at load time 
85  // hint to the compiler to put these into FP registers
86  float x = axis[0];
87  float y = axis[1];
88  float z = axis[2];
89
90  matrix[0][0] = t * x * x + c ;
91  matrix[0][1] = t * y * x - s * z ;
92  matrix[0][2] = t * z * x + s * y ;
93  matrix[0][3] = SG_ZERO;
94  
95  matrix[1][0] = t * x * y + s * z ;
96  matrix[1][1] = t * y * y + c ;
97  matrix[1][2] = t * z * y - s * x ;
98  matrix[1][3] = SG_ZERO;
99  
100  matrix[2][0] = t * x * z - s * y ;
101  matrix[2][1] = t * y * z + s * x ;
102  matrix[2][2] = t * z * z + c ;
103  matrix[2][3] = SG_ZERO;
104
105   // hint to the compiler to put these into FP registers
106  x = center[0];
107  y = center[1];
108  z = center[2];
109  
110  matrix[3][0] = x - x*matrix[0][0] - y*matrix[1][0] - z*matrix[2][0];
111  matrix[3][1] = y - x*matrix[0][1] - y*matrix[1][1] - z*matrix[2][1];
112  matrix[3][2] = z - x*matrix[0][2] - y*matrix[1][2] - z*matrix[2][2];
113  matrix[3][3] = SG_ONE;
114 }
115
116 /**
117  * Set up the transform matrix for a translation.
118  */
119 static void
120 set_translation (sgMat4 &matrix, double position_m, sgVec3 &axis)
121 {
122   sgVec3 xyz;
123   sgScaleVec3(xyz, axis, position_m);
124   sgMakeTransMat4(matrix, xyz);
125 }
126
127
128 /**
129  * Make an offset matrix from rotations and position offset.
130  */
131 static void
132 make_offsets_matrix (sgMat4 * result, double h_rot, double p_rot, double r_rot,
133                      double x_off, double y_off, double z_off)
134 {
135   sgMat4 rot_matrix;
136   sgMat4 pos_matrix;
137   sgMakeRotMat4(rot_matrix, h_rot, p_rot, r_rot);
138   sgMakeTransMat4(pos_matrix, x_off, y_off, z_off);
139   sgMultMat4(*result, pos_matrix, rot_matrix);
140 }
141
142
143 /**
144  * Read an interpolation table from properties.
145  */
146 static SGInterpTable *
147 read_interpolation_table (const SGPropertyNode * props)
148 {
149   const SGPropertyNode * table_node = props->getNode("interpolation");
150   if (table_node != 0) {
151     SGInterpTable * table = new SGInterpTable();
152     vector<SGPropertyNode_ptr> entries = table_node->getChildren("entry");
153     for (int i = 0; i < entries.size(); i++)
154       table->addEntry(entries[i]->getDoubleValue("ind", 0.0),
155                       entries[i]->getDoubleValue("dep", 0.0));
156     return table;
157   } else {
158     return 0;
159   }
160 }
161
162
163 \f
164 ////////////////////////////////////////////////////////////////////////
165 // Implementation of FG3DModel
166 ////////////////////////////////////////////////////////////////////////
167
168 FG3DModel::FG3DModel ()
169   : _model(0)
170 {
171 }
172
173 FG3DModel::~FG3DModel ()
174 {
175   // since the nodes are attached to the scene graph, they'll be
176   // deleted automatically
177
178   int i;
179   for (i = 0; i < _animations.size(); i++)
180     delete _animations[i];
181 }
182
183 void 
184 FG3DModel::init (const string &path)
185 {
186   SGPropertyNode props;
187
188                                 // Load the 3D aircraft object itself
189   SGPath xmlpath = globals->get_fg_root();
190   SGPath modelpath = path;
191   xmlpath.append(modelpath.str());
192
193                                 // Check for an XML wrapper
194   if (xmlpath.str().substr(xmlpath.str().size() - 4, 4) == ".xml") {
195     readProperties(xmlpath.str(), &props);
196     if (props.hasValue("/path")) {
197       modelpath = modelpath.dir();
198       modelpath.append(props.getStringValue("/path"));
199     } else {
200       if (_model == 0)
201         _model = new ssgBranch;
202     }
203   }
204
205                                 // Assume that textures are in
206                                 // the same location as the XML file.
207   if (_model == 0) {
208     ssgTexturePath((char *)xmlpath.dir().c_str());
209     _model = (ssgBranch *)ssgLoad((char *)modelpath.c_str());
210     if (_model == 0)
211       throw sg_exception("Failed to load 3D model");
212   }
213
214                                 // Set up the alignment node
215   ssgTransform * align = new ssgTransform;
216   align->addKid(_model);
217   sgMat4 res_matrix;
218   make_offsets_matrix(&res_matrix,
219                       props.getFloatValue("/offsets/heading-deg", 0.0),
220                       props.getFloatValue("/offsets/roll-deg", 0.0),
221                       props.getFloatValue("/offsets/pitch-deg", 0.0),
222                       props.getFloatValue("/offsets/x-m", 0.0),
223                       props.getFloatValue("/offsets/y-m", 0.0),
224                       props.getFloatValue("/offsets/z-m", 0.0));
225   align->setTransform(res_matrix);
226
227                                 // Load animations
228   vector<SGPropertyNode_ptr> animation_nodes = props.getChildren("animation");
229   unsigned int i;
230   for (i = 0; i < animation_nodes.size(); i++) {
231     vector<SGPropertyNode_ptr> name_nodes =
232       animation_nodes[i]->getChildren("object-name");
233     if (name_nodes.size() < 1) {
234       Animation * animation = make_animation(0, animation_nodes[i]);
235       if (animation != 0)
236         _animations.push_back(animation);
237     } else {
238       for (unsigned int j = 0; j < name_nodes.size(); j++) {
239         Animation * animation =
240           make_animation(name_nodes[j]->getStringValue(), animation_nodes[i]);
241         if (animation != 0)
242           _animations.push_back(animation);
243       }
244     }
245   }
246
247                                 // Load sub-models
248   vector<SGPropertyNode_ptr> model_nodes = props.getChildren("model");
249   for (i = 0; i < model_nodes.size(); i++) {
250     SGPropertyNode_ptr node = model_nodes[i];
251     ssgTransform * align = new ssgTransform;
252     sgMat4 res_matrix;
253     make_offsets_matrix(&res_matrix,
254                         node->getFloatValue("offsets/heading-deg", 0.0),
255                         node->getFloatValue("offsets/roll-deg", 0.0),
256                         node->getFloatValue("offsets/pitch-deg", 0.0),
257                         node->getFloatValue("offsets/x-m", 0.0),
258                         node->getFloatValue("offsets/y-m", 0.0),
259                         node->getFloatValue("offsets/z-m", 0.0));
260     align->setTransform(res_matrix);
261     FG3DModel * kid = new FG3DModel;
262     kid->init(node->getStringValue("path"));
263     align->addKid(kid->getSceneGraph());
264     _model->addKid(align);
265     _children.push_back(kid);
266   }
267 }
268
269 void
270 FG3DModel::update (double dt)
271 {
272   unsigned int i;
273
274   for (i = 0; i < _children.size(); i++)
275     _children[i]->update(dt);
276   for (i = 0; i < _animations.size(); i++)
277     _animations[i]->update(dt);
278 }
279
280 FG3DModel::Animation *
281 FG3DModel::make_animation (const char * object_name,
282                            SGPropertyNode * node)
283 {
284   Animation * animation = 0;
285   const char * type = node->getStringValue("type");
286   if (!strcmp("none", type)) {
287     animation = new NullAnimation();
288   } else if (!strcmp("range", type)) {
289     animation = new RangeAnimation();
290   } else if (!strcmp("billboard", type)) {
291     animation = new BillboardAnimation();
292   } else if (!strcmp("select", type)) {
293     animation = new SelectAnimation();
294   } else if (!strcmp("spin", type)) {
295     animation = new SpinAnimation();
296   } else if (!strcmp("rotate", type)) {
297     animation = new RotateAnimation();
298   } else if (!strcmp("translate", type)) {
299     animation = new TranslateAnimation();
300   } else {
301     animation = new NullAnimation();
302     SG_LOG(SG_INPUT, SG_WARN, "Unknown animation type " << type);
303   }
304
305   ssgEntity * object;
306   if (object_name != 0) {
307     object = find_named_node(_model, object_name);
308     if (object == 0) {
309       SG_LOG(SG_INPUT, SG_WARN, "Object " << object_name << " not found");
310       delete animation;
311       animation = 0;
312     }
313   } else {
314     object = _model;
315   }
316
317   if (animation != 0)
318     animation->init(object, node);
319   return animation;
320 }
321
322
323 \f
324 ////////////////////////////////////////////////////////////////////////
325 // Implementation of FG3DModel::Animation
326 ////////////////////////////////////////////////////////////////////////
327
328 FG3DModel::Animation::Animation ()
329 {
330 }
331
332 FG3DModel::Animation::~Animation ()
333 {
334 }
335
336
337 \f
338 ////////////////////////////////////////////////////////////////////////
339 // Implementation of FG3DModel::NullAnimation
340 ////////////////////////////////////////////////////////////////////////
341
342 FG3DModel::NullAnimation::NullAnimation ()
343   : _branch(new ssgBranch)
344 {
345 }
346
347 FG3DModel::NullAnimation::~NullAnimation ()
348 {
349   _branch = 0;
350 }
351
352 void
353 FG3DModel::NullAnimation::init (ssgEntity * object,
354                                       SGPropertyNode * props)
355 {
356   splice_branch(_branch, object);
357   _branch->setName(props->getStringValue("name", 0));
358 }
359
360 void
361 FG3DModel::NullAnimation::update (double dt)
362 {
363 }
364
365
366 \f
367 ////////////////////////////////////////////////////////////////////////
368 // Implementation of FG3DModel::RangeAnimation
369 ////////////////////////////////////////////////////////////////////////
370
371 FG3DModel::RangeAnimation::RangeAnimation ()
372   : _branch(new ssgRangeSelector)
373 {
374 }
375
376 FG3DModel::RangeAnimation::~RangeAnimation ()
377 {
378   _branch = 0;
379 }
380
381 void
382 FG3DModel::RangeAnimation::init (ssgEntity * object,
383                                       SGPropertyNode * props)
384 {
385   float ranges[2];
386   splice_branch(_branch, object);
387   _branch->setName(props->getStringValue("name", 0));
388   ranges[0] = props->getFloatValue("min-m", 0);
389   ranges[1] = props->getFloatValue("max-m", 5000);
390   _branch->setRanges(ranges, 2);
391 }
392
393 void
394 FG3DModel::RangeAnimation::update (double dt)
395 {
396 }
397
398
399 \f
400 ////////////////////////////////////////////////////////////////////////
401 // Implementation of FG3DModel::BillboardAnimation
402 ////////////////////////////////////////////////////////////////////////
403
404 FG3DModel::BillboardAnimation::BillboardAnimation ()
405   : _branch(0)
406 {
407   // Note: we cannot allocate the branch until we know whether
408   // it can rotate around the x axis as well as the z axis.
409 }
410
411 FG3DModel::BillboardAnimation::~BillboardAnimation ()
412 {
413   _branch = 0;
414 }
415
416 void
417 FG3DModel::BillboardAnimation::init (ssgEntity * object,
418                                      SGPropertyNode * props)
419 {
420   _branch = new ssgCutout(props->getBoolValue("spherical", true));
421   splice_branch(_branch, object);
422   _branch->setName(props->getStringValue("name", 0));
423 }
424
425 void
426 FG3DModel::BillboardAnimation::update (double dt)
427 {
428 }
429
430
431 \f
432 ////////////////////////////////////////////////////////////////////////
433 // Implementation of FG3DModel::SelectAnimation
434 ////////////////////////////////////////////////////////////////////////
435
436 FG3DModel::SelectAnimation::SelectAnimation ()
437   : _condition(0),
438     _selector(new ssgSelector)
439 {
440 }
441
442 FG3DModel::SelectAnimation::~SelectAnimation ()
443 {
444   delete _condition;
445   _selector = 0;
446 }
447
448 void
449 FG3DModel::SelectAnimation::init (ssgEntity * object,
450                                       SGPropertyNode * props)
451 {
452   splice_branch(_selector, object);
453   _selector->setName(props->getStringValue("name", 0));
454   SGPropertyNode * node = props->getChild("condition");
455   if (node != 0) {
456     _condition = fgReadCondition(node);
457   }
458 }
459
460 void
461 FG3DModel::SelectAnimation::update (double dt)
462 {
463   if (_condition != 0 && _condition->test()) 
464     _selector->select(0xffff);
465   else
466     _selector->select(0x0000);
467 }
468
469
470 \f
471 ////////////////////////////////////////////////////////////////////////
472 // Implementation of FG3DModel::SpinAnimation
473 ////////////////////////////////////////////////////////////////////////
474
475 FG3DModel::SpinAnimation::SpinAnimation ()
476   : _prop(0),
477     _factor(0),
478     _position_deg(0),
479     _transform(new ssgTransform)
480 {
481 }
482
483 FG3DModel::SpinAnimation::~SpinAnimation ()
484 {
485   _transform = 0;
486 }
487
488 void
489 FG3DModel::SpinAnimation::init (ssgEntity * object,
490                                       SGPropertyNode * props)
491 {
492                                 // Splice in the new transform node
493   splice_branch(_transform, object);
494   _transform->setName(props->getStringValue("name", 0));
495   _prop = fgGetNode(props->getStringValue("property", "/null"), true);
496   _factor = props->getDoubleValue("factor", 1.0);
497   _position_deg = props->getDoubleValue("starting-position-deg", 0);
498   _center[0] = props->getFloatValue("center/x-m", 0);
499   _center[1] = props->getFloatValue("center/y-m", 0);
500   _center[2] = props->getFloatValue("center/z-m", 0);
501   _axis[0] = props->getFloatValue("axis/x", 0);
502   _axis[1] = props->getFloatValue("axis/y", 0);
503   _axis[2] = props->getFloatValue("axis/z", 0);
504   sgNormalizeVec3(_axis);
505 }
506
507 void
508 FG3DModel::SpinAnimation::update (double dt)
509 {
510   float velocity_rpms = (_prop->getDoubleValue() * _factor / 60.0);
511   _position_deg += (dt * velocity_rpms * 360);
512   while (_position_deg < 0)
513     _position_deg += 360.0;
514   while (_position_deg >= 360.0)
515     _position_deg -= 360.0;
516   set_rotation(_matrix, _position_deg, _center, _axis);
517   _transform->setTransform(_matrix);
518 }
519
520
521 \f
522 ////////////////////////////////////////////////////////////////////////
523 // Implementation of FG3DModel::RotateAnimation
524 ////////////////////////////////////////////////////////////////////////
525
526 FG3DModel::RotateAnimation::RotateAnimation ()
527   : _prop(0),
528     _offset_deg(0.0),
529     _factor(1.0),
530     _table(0),
531     _has_min(false),
532     _min_deg(0.0),
533     _has_max(false),
534     _max_deg(1.0),
535     _position_deg(0.0),
536     _transform(new ssgTransform)
537 {
538 }
539
540 FG3DModel::RotateAnimation::~RotateAnimation ()
541 {
542   delete _table;
543   _transform = 0;
544 }
545
546 void
547 FG3DModel::RotateAnimation::init (ssgEntity * object,
548                                   SGPropertyNode * props)
549 {
550                                 // Splice in the new transform node
551   splice_branch(_transform, object);
552   _transform->setName(props->getStringValue("name", 0));
553   _prop = fgGetNode(props->getStringValue("property", "/null"), true);
554   _offset_deg = props->getDoubleValue("offset-deg", 0.0);
555   _factor = props->getDoubleValue("factor", 1.0);
556   _table = read_interpolation_table(props);
557   if (props->hasValue("min-deg")) {
558     _has_min = true;
559     _min_deg = props->getDoubleValue("min-deg");
560   }
561   if (props->hasValue("max-deg")) {
562     _has_max = true;
563     _max_deg = props->getDoubleValue("max-deg");
564   }
565   _position_deg = props->getDoubleValue("starting-position-deg", 0);
566   _center[0] = props->getFloatValue("center/x-m", 0);
567   _center[1] = props->getFloatValue("center/y-m", 0);
568   _center[2] = props->getFloatValue("center/z-m", 0);
569   _axis[0] = props->getFloatValue("axis/x", 0);
570   _axis[1] = props->getFloatValue("axis/y", 0);
571   _axis[2] = props->getFloatValue("axis/z", 0);
572   sgNormalizeVec3(_axis);
573 }
574
575 void
576 FG3DModel::RotateAnimation::update (double dt)
577 {
578   if (_table == 0) {
579     _position_deg = (_prop->getDoubleValue() + _offset_deg) * _factor;
580    if (_has_min && _position_deg < _min_deg)
581      _position_deg = _min_deg;
582    if (_has_max && _position_deg > _max_deg)
583      _position_deg = _max_deg;
584   } else {
585     _position_deg = _table->interpolate(_prop->getDoubleValue());
586   }
587   set_rotation(_matrix, _position_deg, _center, _axis);
588   _transform->setTransform(_matrix);
589 }
590
591
592 \f
593 ////////////////////////////////////////////////////////////////////////
594 // Implementation of FG3DModel::TranslateAnimation
595 ////////////////////////////////////////////////////////////////////////
596
597 FG3DModel::TranslateAnimation::TranslateAnimation ()
598   : _prop(0),
599     _offset_m(0.0),
600     _factor(1.0),
601     _table(0),
602     _has_min(false),
603     _min_m(0.0),
604     _has_max(false),
605     _max_m(1.0),
606     _position_m(0.0),
607     _transform(new ssgTransform)
608 {
609 }
610
611 FG3DModel::TranslateAnimation::~TranslateAnimation ()
612 {
613   delete _table;
614   _transform = 0;
615 }
616
617 void
618 FG3DModel::TranslateAnimation::init (ssgEntity * object,
619                                      SGPropertyNode * props)
620 {
621                                 // Splice in the new transform node
622   splice_branch(_transform, object);
623   _transform->setName(props->getStringValue("name", 0));
624   _prop = fgGetNode(props->getStringValue("property", "/null"), true);
625   _offset_m = props->getDoubleValue("offset-m", 0.0);
626   _factor = props->getDoubleValue("factor", 1.0);
627   _table = read_interpolation_table(props);
628   if (props->hasValue("min-m")) {
629     _has_min = true;
630     _min_m = props->getDoubleValue("min-m");
631   }
632   if (props->hasValue("max-m")) {
633     _has_max = true;
634     _max_m = props->getDoubleValue("max-m");
635   }
636   _position_m = props->getDoubleValue("starting-position-m", 0);
637   _axis[0] = props->getFloatValue("axis/x", 0);
638   _axis[1] = props->getFloatValue("axis/y", 0);
639   _axis[2] = props->getFloatValue("axis/z", 0);
640   sgNormalizeVec3(_axis);
641 }
642
643 void
644 FG3DModel::TranslateAnimation::update (double dt)
645 {
646   if (_table == 0) {
647     _position_m = (_prop->getDoubleValue() + _offset_m) * _factor;
648     if (_has_min && _position_m < _min_m)
649       _position_m = _min_m;
650     if (_has_max && _position_m > _max_m)
651       _position_m = _max_m;
652   } else {
653     _position_m = _table->interpolate(_prop->getDoubleValue());
654   }
655   set_translation(_matrix, _position_m, _axis);
656   _transform->setTransform(_matrix);
657 }
658
659
660 \f
661 ////////////////////////////////////////////////////////////////////////
662 // Implementation of FGModelPlacement.
663 ////////////////////////////////////////////////////////////////////////
664
665 FGModelPlacement::FGModelPlacement ()
666   : _model(new FG3DModel),
667     _lon_deg(0),
668     _lat_deg(0),
669     _elev_ft(0),
670     _roll_deg(0),
671     _pitch_deg(0),
672     _heading_deg(0),
673     _selector(new ssgSelector),
674     _position(new ssgTransform),
675     _location(new FGLocation)
676 {
677 }
678
679 FGModelPlacement::~FGModelPlacement ()
680 {
681   delete _model;
682   delete _selector;
683 }
684
685 void
686 FGModelPlacement::init (const string &path)
687 {
688   _model->init(path);
689   _position->addKid(_model->getSceneGraph());
690   _selector->addKid(_position);
691   _selector->clrTraversalMaskBits(SSGTRAV_HOT);
692 }
693
694 void
695 FGModelPlacement::update (double dt)
696 {
697   _model->update(dt);
698
699   _location->setPosition( _lon_deg, _lat_deg, _elev_ft );
700   _location->setOrientation( _roll_deg, _pitch_deg, _heading_deg );
701
702   sgMat4 POS;
703   sgCopyMat4(POS, _location->getTransformMatrix());
704   
705   sgVec3 trans;
706   sgCopyVec3(trans, _location->get_view_pos());
707
708   for(int i = 0; i < 4; i++) {
709     float tmp = POS[i][3];
710     for( int j=0; j<3; j++ ) {
711       POS[i][j] += (tmp * trans[j]);
712     }
713   }
714   _position->setTransform(POS);
715 }
716
717 bool
718 FGModelPlacement::getVisible () const
719 {
720   return (_selector->getSelect() != 0);
721 }
722
723 void
724 FGModelPlacement::setVisible (bool visible)
725 {
726   _selector->select(visible);
727 }
728
729 void
730 FGModelPlacement::setLongitudeDeg (double lon_deg)
731 {
732   _lon_deg = lon_deg;
733 }
734
735 void
736 FGModelPlacement::setLatitudeDeg (double lat_deg)
737 {
738   _lat_deg = lat_deg;
739 }
740
741 void
742 FGModelPlacement::setElevationFt (double elev_ft)
743 {
744   _elev_ft = elev_ft;
745 }
746
747 void
748 FGModelPlacement::setPosition (double lon_deg, double lat_deg, double elev_ft)
749 {
750   _lon_deg = lon_deg;
751   _lat_deg = lat_deg;
752   _elev_ft = elev_ft;
753 }
754
755 void
756 FGModelPlacement::setRollDeg (double roll_deg)
757 {
758   _roll_deg = roll_deg;
759 }
760
761 void
762 FGModelPlacement::setPitchDeg (double pitch_deg)
763 {
764   _pitch_deg = pitch_deg;
765 }
766
767 void
768 FGModelPlacement::setHeadingDeg (double heading_deg)
769 {
770   _heading_deg = heading_deg;
771 }
772
773 void
774 FGModelPlacement::setOrientation (double roll_deg, double pitch_deg,
775                                   double heading_deg)
776 {
777   _roll_deg = roll_deg;
778   _pitch_deg = pitch_deg;
779   _heading_deg = heading_deg;
780 }
781
782 // end of model.cxx