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