1 // viewmgr.cxx -- class for managing all the views in the flightgear world.
2 //
3 // Written by Curtis Olson, started October 2000.
4 //   partially rewritten by Jim Wilson March 2002
5 //
6 // Copyright (C) 2000  Curtis L. Olson  - http://www.flightgear.org/~curt
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23 
24 #include "config.h"
25 
26 #include <algorithm> // for std::clamp
27 
28 #include "viewmgr.hxx"
29 #include "ViewPropertyEvaluator.hxx"
30 
31 #include <simgear/compiler.h>
32 #include <simgear/scene/util/OsgMath.hxx>
33 #include <simgear/structure/exception.hxx>
34 
35 #include <Main/fg_props.hxx>
36 #include "view.hxx"
37 
38 #include "CameraGroup.hxx"
39 
40 namespace {
41 
42 template <typename T>
not_std_clamp(const T & v,const T & lower,const T & upper)43 T not_std_clamp(const T& v, const T& lower, const T& upper)
44 {
45     return (v < lower) ? lower : ((v > upper) ? upper : v);
46 }
47 
48 } // namespace
49 
50 // Constructor
FGViewMgr(void)51 FGViewMgr::FGViewMgr( void ) :
52   config_list(fgGetNode("/sim", true)->getChildren("view"))
53 {
54     _current = fgGetInt("/sim/current-view/view-number");
55 }
56 
57 // Destructor
~FGViewMgr(void)58 FGViewMgr::~FGViewMgr( void )
59 {
60 }
61 
62 void
init()63 FGViewMgr::init ()
64 {
65     if (_inited) {
66         SG_LOG(SG_VIEW, SG_WARN, "duplicate init of view manager");
67         return;
68     }
69 
70     _inited = true;
71 
72     // Ensure that /sim/chase-distance-m is negative if it is specified.
73     // E.g. see https://sourceforge.net/p/flightgear/codetickets/2454/
74     //
75     SGPropertyNode* chase_distance_node = fgGetNode("/sim/chase-distance-m");
76     if (chase_distance_node) {
77         double chase_distance = chase_distance_node->getDoubleValue();
78         if (chase_distance > 0) {
79             chase_distance = -chase_distance;
80             SG_LOG(SG_VIEW, SG_ALERT, "sim/chase-distance-m is positive; correcting to " << chase_distance);
81             chase_distance_node->setDoubleValue(chase_distance);
82         }
83     }
84 
85     config_list = fgGetNode("/sim", true)->getChildren("view");
86     _current = fgGetInt("/sim/current-view/view-number");
87     if (_current != 0 && (_current < 0 || _current >= (int) views.size())) {
88         SG_LOG(SG_VIEW, SG_ALERT,
89                 "Invalid /sim/current-view/view-number=" << _current
90                 << ". views.size()=" << views.size()
91                 << ". Will assert false and use zero."
92                 );
93         assert(0);
94         _current = 0;
95     }
96 
97     for (unsigned int i = 0; i < config_list.size(); i++) {
98         SGPropertyNode* n = config_list[i];
99         SGPropertyNode* config = n->getChild("config", 0, true);
100 
101         flightgear::View* v = flightgear::View::createFromProperties(config, n->getIndex());
102         if (v) {
103             add_view(v);
104         } else {
105             SG_LOG(SG_VIEW, SG_DEV_WARN, "Failed to create view from:" << config->getPath());
106         }
107   }
108 
109   if (get_current_view()) {
110       get_current_view()->bind();
111   } else {
112       SG_LOG(SG_VIEW, SG_DEV_WARN, "FGViewMgr::init: current view " << _current << " failed to create");
113   }
114 }
115 
116 void
postinit()117 FGViewMgr::postinit()
118 {
119     // force update now so many properties of the current view are valid,
120     // eg view position and orientation (as exposed via globals)
121     update(0.0);
122 }
123 
124 void
shutdown()125 FGViewMgr::shutdown()
126 {
127     if (!_inited) {
128         return;
129     }
130 
131     _inited = false;
132     views.clear();
133 }
134 
135 void
reinit()136 FGViewMgr::reinit ()
137 {
138     viewer_list::iterator it;
139     for (it = views.begin(); it != views.end(); ++it) {
140         (*it)->resetOffsetsAndFOV();
141     }
142 }
143 
144 void
bind()145 FGViewMgr::bind()
146 {
147     // these are bound to the current view properties
148     _tiedProperties.setRoot(fgGetNode("/sim/current-view", true));
149 
150 
151     _tiedProperties.Tie("view-number", this,
152                         &FGViewMgr::getCurrentViewIndex,
153                         &FGViewMgr::setCurrentViewIndex, false);
154     _viewNumberProp = _tiedProperties.getRoot()->getNode("view-number");
155     _viewNumberProp->setAttribute(SGPropertyNode::ARCHIVE, false);
156     _viewNumberProp->setAttribute(SGPropertyNode::PRESERVE, true);
157     _viewNumberProp->setAttribute(SGPropertyNode::LISTENER_SAFE, true);
158 }
159 
160 
161 void
unbind()162 FGViewMgr::unbind ()
163 {
164     flightgear::View* v = get_current_view();
165     if (v) {
166         v->unbind();
167     }
168 
169   _tiedProperties.Untie();
170     _viewNumberProp.clear();
171 
172     ViewPropertyEvaluator::clear();
173 }
174 
175 void
update(double dt)176 FGViewMgr::update (double dt)
177 {
178     flightgear::View* currentView = get_current_view();
179     if (!currentView) {
180         return;
181     }
182 
183   // Update the current view
184   currentView->update(dt);
185 
186 // update the camera now
187     osg::ref_ptr<flightgear::CameraGroup> cameraGroup = flightgear::CameraGroup::getDefault();
188     if (!cameraGroup) {
189         // attempting to diagnose the cause of FLIGHTGEAR-H9F
190         throw sg_exception("FGViewMgr::update: no camera group exists");
191     }
192 
193     cameraGroup->setCameraParameters(currentView->get_v_fov(),
194                                         cameraGroup->getMasterAspectRatio());
195     cameraGroup->update(toOsg(currentView->getViewPosition()),
196                         toOsg(currentView->getViewOrientation()));
197 }
198 
clear()199 void FGViewMgr::clear()
200 {
201     views.clear();
202 }
203 
204 flightgear::View*
get_current_view()205 FGViewMgr::get_current_view()
206 {
207     if (views.empty())
208         return nullptr;
209     assert(_current >= 0 && _current < (int) views.size());
210     return views[_current];
211 }
212 
213 const flightgear::View*
get_current_view() const214 FGViewMgr::get_current_view() const
215 {
216     return const_cast<FGViewMgr*>(this)->get_current_view();
217 }
218 
219 
220 flightgear::View*
get_view(int i)221 FGViewMgr::get_view( int i )
222 {
223     const auto lastView = static_cast<int>(views.size()) - 1;
224     const int c = not_std_clamp(i, 0, lastView);
225     return views.at(c);
226 }
227 
228 const flightgear::View*
get_view(int i) const229 FGViewMgr::get_view( int i ) const
230 {
231     const auto lastView = static_cast<int>(views.size()) - 1;
232     const int c = not_std_clamp(i, 0, lastView);
233     return views.at(c);
234 }
235 
236 flightgear::View*
next_view()237 FGViewMgr::next_view()
238 {
239     const auto numViews = static_cast<int>(views.size());
240     setCurrentViewIndex((_current + 1) % numViews);
241     _viewNumberProp->fireValueChanged();
242     return get_current_view();
243 }
244 
245 flightgear::View*
prev_view()246 FGViewMgr::prev_view()
247 {
248     const auto numViews = static_cast<int>(views.size());
249     // subtract 1, but add a full numViews, to ensure the integer
250     // modulo returns a +ve result; negative values mean something
251     // else to setCurrentViewIndex
252     setCurrentViewIndex((_current - 1 + numViews) % numViews);
253     _viewNumberProp->fireValueChanged();
254     return get_current_view();
255 }
256 
257 void
add_view(flightgear::View * v)258 FGViewMgr::add_view( flightgear::View * v )
259 {
260   views.push_back(v);
261   v->init();
262 }
263 
getCurrentViewIndex() const264 int FGViewMgr::getCurrentViewIndex() const
265 {
266     return _current;
267 }
268 
setCurrentViewIndex(int newview)269 void FGViewMgr::setCurrentViewIndex(int newview)
270 {
271     if (newview == _current) {
272         return;
273     }
274     // negative numbers -> set view with node index -newview
275     if (newview < 0) {
276         for (int i = 0; i < (int)config_list.size(); i++) {
277             int index = -config_list[i]->getIndex();
278             if (index == newview)
279                 newview = i;
280         }
281         if (newview < 0) {
282             SG_LOG(SG_VIEW, SG_ALERT,
283                     "Failed to find -ve newview=" << newview
284                     << ". Will assert false and ignore."
285                     );
286             assert(0);
287             return;
288         }
289     }
290     if (newview < 0 || newview >= (int) views.size()) {
291         SG_LOG(SG_VIEW, SG_ALERT, "Invalid newview=" << newview
292                 << ". views.size()=" << views.size()
293                 << ". Will assert false and ignore."
294                 );
295         assert(0);
296         return;
297     }
298 
299     if (get_current_view()) {
300 	    get_current_view()->unbind();
301     }
302 
303     _current = newview;
304 
305     if (get_current_view()) {
306         get_current_view()->bind();
307     }
308 }
309 
310 
311 // Register the subsystem.
312 SGSubsystemMgr::Registrant<FGViewMgr> registrantFGViewMgr(
313     SGSubsystemMgr::DISPLAY);
314