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