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