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