1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 #include <Inventor/navigation/SoScXMLZoomTarget.h>
34 
35 /*!
36   \class SoScXMLZoomTarget SoScXMLZoomTarget.h Inventor/scxml/SoScXMLZoomTarget.h
37   \brief SCXML event target service for zoom-behaviour.
38 
39   Events:
40 
41     x-coin-navigation.Zoom.*
42 
43     {
44     BEGIN
45       _sessionid {string}
46       mouseposition {SbVec2f}
47 
48     UPDATE
49       _sessionid {string}
50       mouseposition {SbVec2f}
51 
52     END
53       _sessionid {string}
54       mouseposition {SbVec2f}
55     }
56 
57     ZOOM
58       _sessionid
59       factor {float}
60 
61     ZOOM_IN
62       _sessionid {string}
63       [factor] {float=1.2}
64       [count] {float=1}
65 
66     ZOOM_OUT
67       _sessionid {string}
68       [factor] {float=1.2}
69       [count] {float=1}
70 
71     RESET
72       _sessionid {string}
73 
74   \ingroup navigation
75   \since Coin 3.1
76 */
77 
78 #include <cassert>
79 #include <string>
80 #include <cfloat>
81 #include <cmath>
82 
83 #include <boost/intrusive_ptr.hpp>
84 
85 #include <Inventor/SbVec2f.h>
86 #include <Inventor/SbRotation.h>
87 #include <Inventor/SbViewportRegion.h>
88 #include <Inventor/nodes/SoPerspectiveCamera.h>
89 #include <Inventor/nodes/SoOrthographicCamera.h>
90 #include <Inventor/nodes/SoFrustumCamera.h>
91 #include <Inventor/errors/SoDebugError.h>
92 #include <Inventor/events/SoMouseButtonEvent.h>
93 #include <Inventor/events/SoLocation2Event.h>
94 #include <Inventor/events/SoKeyboardEvent.h>
95 #include <Inventor/scxml/ScXML.h>
96 #include <Inventor/scxml/SoScXMLEvent.h>
97 #include <Inventor/scxml/SoScXMLStateMachine.h>
98 #include <Inventor/C/threads/mutex.h>
99 #include <Inventor/navigation/SoScXMLNavigation.h>
100 #include "threads/threadsutilp.h"
101 #include "scxml/SbStringConvert.h"
102 #include "tidbitsp.h"
103 #include "coindefs.h"
104 #include "SbBasicP.h"
105 
106 namespace {
107 
108 class ZoomData : public SoScXMLNavigationTarget::Data {
109 public:
110   SbVec2f lastposn;
111 
112 };
113 
NewZoomData(void)114 static SoScXMLNavigationTarget::Data * NewZoomData(void) { return new ZoomData; }
115 
116 } // namespace
117 
118 class SoScXMLZoomTarget::PImpl {
119 public:
120   static SbName BEGIN;
121   static SbName UPDATE;
122   static SbName END;
123   static SbName ZOOM;
124   static SbName ZOOM_IN;
125   static SbName ZOOM_OUT;
126   static SbName RESET;
127 };
128 
129 SbName SoScXMLZoomTarget::PImpl::BEGIN;
130 SbName SoScXMLZoomTarget::PImpl::UPDATE;
131 SbName SoScXMLZoomTarget::PImpl::END;
132 SbName SoScXMLZoomTarget::PImpl::ZOOM;
133 SbName SoScXMLZoomTarget::PImpl::ZOOM_IN;
134 SbName SoScXMLZoomTarget::PImpl::ZOOM_OUT;
135 SbName SoScXMLZoomTarget::PImpl::RESET;
136 
137 // *************************************************************************
138 
139 #define PRIVATE
140 
141 SCXML_OBJECT_SOURCE(SoScXMLZoomTarget);
142 
143 void
initClass(void)144 SoScXMLZoomTarget::initClass(void)
145 {
146   SCXML_OBJECT_INIT_CLASS(SoScXMLZoomTarget, SoScXMLNavigationTarget, "SoScXMLNavigationTarget");
147 #define EVENT_PREFIX COIN_NAVIGATION_ZOOM_TARGET_EVENT_PREFIX
148   SoScXMLZoomTarget::PImpl::BEGIN     = SbName(EVENT_PREFIX ".BEGIN");
149   SoScXMLZoomTarget::PImpl::UPDATE    = SbName(EVENT_PREFIX ".UPDATE");
150   SoScXMLZoomTarget::PImpl::END       = SbName(EVENT_PREFIX ".END");
151   SoScXMLZoomTarget::PImpl::ZOOM      = SbName(EVENT_PREFIX ".ZOOM");
152   SoScXMLZoomTarget::PImpl::ZOOM_IN   = SbName(EVENT_PREFIX ".ZOOM_IN");
153   SoScXMLZoomTarget::PImpl::ZOOM_OUT  = SbName(EVENT_PREFIX ".ZOOM_OUT");
154   SoScXMLZoomTarget::PImpl::RESET     = SbName(EVENT_PREFIX ".RESET");
155 #undef EVENT_PREFIX
156 }
157 
158 void
cleanClass(void)159 SoScXMLZoomTarget::cleanClass(void)
160 {
161   SoScXMLZoomTarget::classTypeId = SoType::badType();
162 }
163 
164 SoScXMLZoomTarget * SoScXMLZoomTarget::theSingleton = NULL;
165 
166 SoScXMLZoomTarget *
constructSingleton(void)167 SoScXMLZoomTarget::constructSingleton(void)
168 {
169   assert(SoScXMLZoomTarget::theSingleton == NULL);
170   SoScXMLZoomTarget::theSingleton =
171     static_cast<SoScXMLZoomTarget *>(SoScXMLZoomTarget::classTypeId.createInstance());
172   return SoScXMLZoomTarget::theSingleton;
173 }
174 
175 void
destructSingleton(void)176 SoScXMLZoomTarget::destructSingleton(void)
177 {
178   assert(SoScXMLZoomTarget::theSingleton != NULL);
179   delete SoScXMLZoomTarget::theSingleton;
180   SoScXMLZoomTarget::theSingleton = NULL;
181 }
182 
183 SoScXMLZoomTarget *
singleton(void)184 SoScXMLZoomTarget::singleton(void)
185 {
186   assert(SoScXMLZoomTarget::theSingleton != NULL);
187   return SoScXMLZoomTarget::theSingleton;
188 }
189 
190 const SbName &
BEGIN(void)191 SoScXMLZoomTarget::BEGIN(void)
192 {
193   return SoScXMLZoomTarget::PImpl::BEGIN;
194 }
195 
196 const SbName &
UPDATE(void)197 SoScXMLZoomTarget::UPDATE(void)
198 {
199   return SoScXMLZoomTarget::PImpl::UPDATE;
200 }
201 
202 const SbName &
END(void)203 SoScXMLZoomTarget::END(void)
204 {
205   return SoScXMLZoomTarget::PImpl::END;
206 }
207 
208 const SbName &
ZOOM(void)209 SoScXMLZoomTarget::ZOOM(void)
210 {
211   return SoScXMLZoomTarget::PImpl::ZOOM;
212 }
213 
214 const SbName &
ZOOM_IN(void)215 SoScXMLZoomTarget::ZOOM_IN(void)
216 {
217   return SoScXMLZoomTarget::PImpl::ZOOM_IN;
218 }
219 
220 const SbName &
ZOOM_OUT(void)221 SoScXMLZoomTarget::ZOOM_OUT(void)
222 {
223   return SoScXMLZoomTarget::PImpl::ZOOM_OUT;
224 }
225 
226 const SbName &
RESET(void)227 SoScXMLZoomTarget::RESET(void)
228 {
229   return SoScXMLZoomTarget::PImpl::RESET;
230 }
231 
SoScXMLZoomTarget(void)232 SoScXMLZoomTarget::SoScXMLZoomTarget(void)
233 {
234   this->setEventTargetType(SOSCXML_NAVIGATION_TARGETTYPE);
235   this->setEventTargetName("Zoom");
236 }
237 
~SoScXMLZoomTarget(void)238 SoScXMLZoomTarget::~SoScXMLZoomTarget(void)
239 {
240 }
241 
242 
243 SbBool
processOneEvent(const ScXMLEvent * event)244 SoScXMLZoomTarget::processOneEvent(const ScXMLEvent * event)
245 {
246   assert(event);
247 
248   const SbName sessionid = this->getSessionId(event);
249   if (sessionid == SbName::empty()) { return FALSE; }
250 
251   const SbName & eventname = event->getEventName();
252 
253   if (eventname == BEGIN()) {
254     // _sessionid
255     // mouseposition
256     ZoomData * data = static_cast<ZoomData *>(this->getSessionData(sessionid, NewZoomData));
257     assert(data);
258 
259     SoScXMLStateMachine * statemachine = this->getSoStateMachine(event, sessionid);
260     if unlikely (!statemachine) { return FALSE; }
261 
262     if (!inherited::getEventSbVec2f(event, "mouseposition", data->lastposn)) {
263       return FALSE;
264     }
265 
266     return TRUE;
267   }
268 
269   else if (eventname == UPDATE()) {
270     // _sessionid
271     // mouseposition
272     ZoomData * data = static_cast<ZoomData *>(this->getSessionData(sessionid, NewZoomData));
273     assert(data);
274 
275     SoCamera * camera = inherited::getActiveCamera(event, sessionid);
276     if (!camera) { return FALSE; }
277 
278     SbVec2f prevposn = data->lastposn;
279     if (!inherited::getEventSbVec2f(event, "mouseposition", data->lastposn)) {
280       return FALSE;
281     }
282     SbVec2f thisposn = data->lastposn;
283 
284     // The value 20.0 is just a value found by trial. exp() brings this in the range of <0, ->>.
285     SoScXMLZoomTarget::zoom(camera, float(exp((thisposn[1] - prevposn[1]) * 20.0f)));
286 
287     return TRUE;
288   }
289 
290   else if (eventname == END()) {
291     // _sessionid
292     this->freeSessionData(sessionid);
293     return TRUE;
294   }
295 
296 
297   else if (eventname == ZOOM()) {
298     // _sessionid
299     // factor
300     SoCamera * camera = inherited::getActiveCamera(event, sessionid);
301     if unlikely (!camera) { return FALSE; }
302 
303     double factor = 1.0;
304     if (!inherited::getEventDouble(event, "factor", factor)) {
305       return FALSE;
306     }
307 
308     if (abs(factor) <= FLT_EPSILON) {
309       SoDebugError::post("SoScXMLZoomTarget::processOneEvent",
310                          "while processing %s: can't zoom with a 0 factor.",
311                          eventname.getString());
312       return FALSE;
313     }
314     if (factor < 0.0) {
315       SoDebugError::post("SoScXMLZoomTarget::processOneEvent",
316                          "while processing %s: can't zoom with a negative factor.",
317                          eventname.getString());
318       return FALSE;
319     }
320 
321     SoScXMLZoomTarget::zoom(camera, static_cast<float>(factor));
322     return TRUE;
323   }
324 
325   else if (eventname == ZOOM_IN() || eventname == ZOOM_OUT()) {
326     SoCamera * camera = inherited::getActiveCamera(event, sessionid);
327     if unlikely (!camera) { return FALSE; }
328 
329     double factor = 1.2;
330     inherited::getEventDouble(event, "factor", factor, FALSE);
331 
332     if (abs(factor) <= FLT_EPSILON) {
333       SoDebugError::post("SoScXMLZoomTarget::processOneEvent",
334                          "while processing %s: can't zoom with a 0 factor.",
335                          eventname.getString());
336       return FALSE;
337     }
338     if (factor < 0.0) {
339       SoDebugError::post("SoScXMLZoomTarget::processOneEvent",
340                          "while processing %s: can't zoom with a negative factor.",
341                          eventname.getString());
342       return FALSE;
343     }
344 
345     double count = 1.0;
346     inherited::getEventDouble(event, "count", count, FALSE);
347     if (abs(count) <= FLT_EPSILON) {
348       SoDebugError::post("SoScXMLZoomTarget::processOneEvent",
349                          "while processing %s: can't zoom with a 0 zoom count.",
350                          eventname.getString());
351     }
352     if (count < 0.0) {
353       SoDebugError::post("SoScXMLZoomTarget::processOneEvent",
354                          "while processing %s: can't zoom with a negative zoom count.",
355                          eventname.getString());
356       return FALSE;
357     }
358 
359     double compounded = (eventname == ZOOM_IN()) ?  pow(1.0/factor, count) : pow(factor, count);
360 
361     SoScXMLZoomTarget::zoom(camera, static_cast<float>(compounded));
362     return TRUE;
363   }
364 
365 
366   else if (eventname == RESET()) {
367     SoCamera * camera = inherited::getActiveCamera(event, sessionid);
368     if unlikely (!camera) { return FALSE; }
369 
370     SoScXMLZoomTarget::reset(camera);
371   }
372 
373 
374   else {
375     SoDebugError::post("SoScXMLZoomTarget::processOneEvent",
376                        "received unknown event '%s'",
377                        eventname.getString());
378     return FALSE;
379   }
380 
381   return TRUE;
382 }
383 
384 // *************************************************************************
385 // Dependent on the camera type this will either shrink or expand the
386 // height of the viewport (orthogonal camera) or move the camera
387 // closer or further away from the focal point in the scene.
388 
389 void
zoom(SoCamera * camera,float multiplicator)390 SoScXMLZoomTarget::zoom(SoCamera * camera, float multiplicator)
391 {
392   assert(camera);
393 
394   if (camera->isOfType(SoOrthographicCamera::getClassTypeId())) {
395     // Since there's no perspective, "zooming" in the original sense
396     // of the word won't have any visible effect. So we just increase
397     // or decrease the field-of-view values of the camera instead, to
398     // "shrink" the projection size of the model / scene.
399     SoOrthographicCamera * oc = coin_assert_cast<SoOrthographicCamera *>(camera);
400     oc->height = oc->height.getValue() * multiplicator;
401   }
402 
403   else if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) {
404     SoPerspectiveCamera * pc = coin_assert_cast<SoPerspectiveCamera *>(camera);
405     pc->heightAngle = pc->heightAngle.getValue() * multiplicator;
406   }
407 
408   else if (camera->isOfType(SoFrustumCamera::getClassTypeId())) {
409     // this might not make any sense - debug later (2009-02-15 larsa)
410     SoFrustumCamera * fc = coin_assert_cast<SoFrustumCamera *>(camera);
411     fc->left = fc->left.getValue() * multiplicator;
412     fc->right = fc->right.getValue() * multiplicator;
413     fc->top = fc->top.getValue() * multiplicator;
414     fc->bottom = fc->bottom.getValue() * multiplicator;
415   }
416 
417   else {
418     static SbBool first = TRUE;
419     if (first) {
420       SoDebugError::postWarning("SoScXMLZoomTarget::zoom",
421                                 "Unknown camera type, "
422                                 "will zoom by moving position, "
423                                 "which is not correct.");
424       first = FALSE;
425     }
426 
427     const float oldfocaldist = camera->focalDistance.getValue();
428     const float newfocaldist = oldfocaldist * multiplicator;
429 
430     SbVec3f direction;
431     camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
432 
433     const SbVec3f oldpos = camera->position.getValue();
434     const SbVec3f newpos = oldpos + (newfocaldist - oldfocaldist) * -direction;
435 
436     // This catches a rather common user interface "buglet": if the
437     // user zooms the camera out to a distance from origo larger than
438     // what we still can safely do floating point calculations on
439     // (i.e. without getting NaN or Inf values), the faulty floating
440     // point values will propagate until we start to get debug error
441     // messages and eventually an assert failure from core Coin code.
442     //
443     // With the below bounds check, this problem is avoided.
444     //
445     // (But note that we depend on the input argument ''diffvalue'' to
446     // be small enough that zooming happens gradually. Ideally, we
447     // should also check distorigo with isinf() and isnan() (or
448     // inversely; isinfite()), but those only became standardized with
449     // C99.)
450     const float distorigo = newpos.length();
451     // sqrt(FLT_MAX) == ~ 1e+19, which should be both safe for further
452     // calculations and ok for the end-user and app-programmer.
453     if (distorigo > float(sqrt(FLT_MAX))) {
454 
455       //SoDebugError::postWarning("SoScXMLZoomTarget::zoom",
456       //                          "zoomed too far (distance to origo==%f (%e))",
457       //                          distorigo, distorigo);
458     }
459     else {
460       camera->position = newpos;
461       camera->focalDistance = newfocaldist;
462     }
463   }
464 }
465 
466 
467 /*!
468   This function resets the zooming attributes of the camera to the default
469   values.
470 */
471 
472 void
reset(SoCamera * camera)473 SoScXMLZoomTarget::reset(SoCamera * camera)
474 {
475   assert(camera);
476 
477   SoType cameratype = camera->getTypeId();
478   assert(cameratype.canCreateInstance());
479   boost::intrusive_ptr<SoCamera> defaultcamera = static_cast<SoCamera *>(cameratype.createInstance());
480 
481   if (camera->isOfType(SoOrthographicCamera::getClassTypeId())) {
482     static_cast<SoOrthographicCamera *>(camera)->height =
483       static_cast<SoOrthographicCamera *>(defaultcamera.get())->height.getValue();
484   }
485   else if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) {
486     static_cast<SoPerspectiveCamera *>(camera)->heightAngle =
487       static_cast<SoPerspectiveCamera *>(defaultcamera.get())->heightAngle.getValue();
488   }
489   else if (camera->isOfType(SoFrustumCamera::getClassTypeId())) {
490     SoFrustumCamera * fcamera = static_cast<SoFrustumCamera *>(camera);
491     SoFrustumCamera * fdefaultcamera = static_cast<SoFrustumCamera *>(defaultcamera.get());
492     fcamera->left = fdefaultcamera->left.getValue();
493     fcamera->right = fdefaultcamera->right.getValue();
494     fcamera->top = fdefaultcamera->top.getValue();
495     fcamera->bottom = fdefaultcamera->bottom.getValue();
496   }
497 }
498 
499 #undef PRIVATE
500