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/SoScXMLRotateTarget.h>
34 
35 /*!
36   \class SoScXMLRotateTarget SoScXMLRotateTarget.h Inventor/scxml/SoScXMLRotateTarget.h
37   \brief Navigation system event target for rotating operations.
38 
39   \ingroup navigation
40 */
41 
42 #
43 #include <cassert>
44 #include <cmath>
45 #include <cfloat>
46 
47 #include <boost/scoped_ptr.hpp>
48 #include <boost/intrusive_ptr.hpp>
49 
50 #include <Inventor/SbViewVolume.h>
51 #include <Inventor/SbRotation.h>
52 #include <Inventor/SbPlane.h>
53 #include <Inventor/SbLine.h>
54 #include <Inventor/errors/SoDebugError.h>
55 #include <Inventor/nodes/SoOrthographicCamera.h>
56 #include <Inventor/nodes/SoPerspectiveCamera.h>
57 #include <Inventor/fields/SoSFVec3d.h>
58 #include <Inventor/SbTime.h>
59 #include <Inventor/SbVec2f.h>
60 #include <Inventor/SbViewVolume.h>
61 #include <Inventor/errors/SoDebugError.h>
62 #include <Inventor/projectors/SbSphereSheetProjector.h>
63 #include <Inventor/nodes/SoCamera.h>
64 #include <Inventor/scxml/ScXMLEvent.h>
65 #include <Inventor/scxml/SoScXMLStateMachine.h>
66 #include <Inventor/C/tidbits.h>
67 #include <Inventor/navigation/SoScXMLNavigation.h>
68 #include <Inventor/navigation/SoScXMLFlightControlTarget.h>
69 #include <Inventor/navigation/SoScXMLDollyTarget.h>
70 
71 #include "scxml/SbStringConvert.h"
72 #include "SbBasicP.h"
73 #include "coindefs.h"
74 #include "base/coinString.h"
75 
76 namespace {
77 
78 class RotateData : public SoScXMLNavigationTarget::Data {
79 // sendspinstart
80 // should be persistent over rotations, but individually settable per session
81 // or better; per event origin point
82 public:
RotateData(void)83   RotateData(void) {
84     this->projector.reset(new SbSphereSheetProjector);
85     SbViewVolume volume;
86     volume.ortho(-1, 1, -1, 1, -1, 1);
87     this->projector->setViewVolume(volume);
88     this->logsize = 0;
89   }
90 
91   SbVec2f downposn;
92   boost::intrusive_ptr<SoCamera> cameraclone;
93   boost::scoped_ptr<SbSphereSheetProjector> projector;
94 
95   struct log {
96     SbVec2f posn;
97     SbTime time;
98   } mouselog[3];
99   int logsize;
100 };
101 
NewRotateData(void)102 static SoScXMLNavigationTarget::Data * NewRotateData(void) { return new RotateData; }
103 
104 } // namespace
105 
106 class SoScXMLRotateTarget::PImpl {
107 public:
108   // received
109   static SbName BEGIN;
110   static SbName UPDATE;
111   static SbName END;
112   static SbName SET_FOCAL_POINT;
113   // sent
114   static SbName TRIGGER_SPIN;
115 };
116 
117 SbName SoScXMLRotateTarget::PImpl::BEGIN;
118 SbName SoScXMLRotateTarget::PImpl::UPDATE;
119 SbName SoScXMLRotateTarget::PImpl::END;
120 SbName SoScXMLRotateTarget::PImpl::SET_FOCAL_POINT;
121 SbName SoScXMLRotateTarget::PImpl::TRIGGER_SPIN;
122 
123 // *************************************************************************
124 
125 #define PRIVATE
126 
127 SCXML_OBJECT_SOURCE(SoScXMLRotateTarget);
128 
129 void
initClass(void)130 SoScXMLRotateTarget::initClass(void)
131 {
132   SCXML_OBJECT_INIT_CLASS(SoScXMLRotateTarget, SoScXMLNavigationTarget, "SoScXMLNavigationTarget");
133 
134 #define EVENT_PREFIX COIN_NAVIGATION_ROTATE_TARGET_EVENT_PREFIX
135   PImpl::BEGIN            = SbName(EVENT_PREFIX ".BEGIN");
136   PImpl::UPDATE           = SbName(EVENT_PREFIX ".UPDATE");
137   PImpl::END              = SbName(EVENT_PREFIX ".END");
138   PImpl::SET_FOCAL_POINT  = SbName(EVENT_PREFIX ".SET_FOCAL_POINT");
139   PImpl::TRIGGER_SPIN     = SbName(EVENT_PREFIX ".TRIGGER_SPIN");
140 #undef EVENT_PREFIX
141 }
142 
143 void
cleanClass(void)144 SoScXMLRotateTarget::cleanClass(void)
145 {
146   SoScXMLRotateTarget::classTypeId = SoType::badType();
147 }
148 
149 SoScXMLRotateTarget * SoScXMLRotateTarget::theSingleton = NULL;
150 
151 SoScXMLRotateTarget *
constructSingleton(void)152 SoScXMLRotateTarget::constructSingleton(void)
153 {
154   assert(SoScXMLRotateTarget::theSingleton == NULL);
155   SoScXMLRotateTarget::theSingleton =
156     static_cast<SoScXMLRotateTarget *>(SoScXMLRotateTarget::classTypeId.createInstance());
157   return SoScXMLRotateTarget::theSingleton;
158 }
159 
160 void
destructSingleton(void)161 SoScXMLRotateTarget::destructSingleton(void)
162 {
163   assert(SoScXMLRotateTarget::theSingleton != NULL);
164   delete SoScXMLRotateTarget::theSingleton;
165   SoScXMLRotateTarget::theSingleton = NULL;
166 }
167 
168 SoScXMLRotateTarget *
singleton(void)169 SoScXMLRotateTarget::singleton(void)
170 {
171   assert(SoScXMLRotateTarget::theSingleton != NULL);
172   return SoScXMLRotateTarget::theSingleton;
173 }
174 
175 const SbName &
BEGIN(void)176 SoScXMLRotateTarget::BEGIN(void)
177 {
178   return PImpl::BEGIN;
179 }
180 
181 const SbName &
UPDATE(void)182 SoScXMLRotateTarget::UPDATE(void)
183 {
184   return PImpl::UPDATE;
185 }
186 
187 const SbName &
END(void)188 SoScXMLRotateTarget::END(void)
189 {
190   return PImpl::END;
191 }
192 
193 const SbName &
SET_FOCAL_POINT(void)194 SoScXMLRotateTarget::SET_FOCAL_POINT(void)
195 {
196   return PImpl::SET_FOCAL_POINT;
197 }
198 
199 const SbName &
TRIGGER_SPIN(void)200 SoScXMLRotateTarget::TRIGGER_SPIN(void)
201 {
202   return PImpl::TRIGGER_SPIN;
203 }
204 
SoScXMLRotateTarget(void)205 SoScXMLRotateTarget::SoScXMLRotateTarget(void)
206 {
207   this->setEventTargetType(SOSCXML_NAVIGATION_TARGETTYPE);
208   this->setEventTargetName("Rotate");
209 }
210 
~SoScXMLRotateTarget(void)211 SoScXMLRotateTarget::~SoScXMLRotateTarget(void)
212 {
213 }
214 
215 
216 SbBool
processOneEvent(const ScXMLEvent * event)217 SoScXMLRotateTarget::processOneEvent(const ScXMLEvent * event)
218 {
219   assert(event);
220 
221   SbName sessionid = this->getSessionId(event);
222   if (sessionid == SbName::empty()) { return FALSE; }
223 
224   const SbName & eventname = event->getEventName();
225 
226   if (eventname == BEGIN()) {
227     RotateData * data =
228       static_cast<RotateData *>(this->getSessionData(sessionid, NewRotateData));
229     assert(data);
230 
231     SoScXMLStateMachine * statemachine = inherited::getSoStateMachine(event, sessionid);
232     if (!statemachine) { return FALSE; }
233 
234     if (!inherited::getEventSbVec2f(event, "mouseposition", data->downposn)) {
235       return FALSE;
236     }
237 
238     data->mouselog[0].posn = data->downposn;
239     data->mouselog[0].time = SbTime::getTimeOfDay();
240     data->logsize = 1;
241 
242     SoCamera * camera = inherited::getActiveCamera(event, sessionid);
243     if unlikely (!camera) { return FALSE; }
244 
245     // store current camera position
246     data->cameraclone = static_cast<SoCamera *>(camera->copy());
247 
248     return TRUE;
249   }
250 
251   else if (eventname == UPDATE()) {
252     RotateData * data = static_cast<RotateData *>(this->getSessionData(sessionid, NewRotateData));
253     assert(data);
254 
255     SoScXMLStateMachine * statemachine = inherited::getSoStateMachine(event, sessionid);
256     if (!statemachine) { return FALSE; }
257 
258     SoCamera * camera = statemachine->getActiveCamera();
259     if unlikely (!camera) {
260       SoDebugError::post("SoScXMLRotateTarget::processOneEvent",
261                          "while processing %s: no current camera",
262                          eventname.getString());
263       return FALSE;
264     }
265 
266     assert(data->cameraclone.get());
267     if unlikely (camera->getTypeId() != data->cameraclone->getTypeId()) {
268       SoDebugError::post("SoScXMLRotateTarget::processOneEvent",
269                          "while processing %s: camera type was changed",
270                          eventname.getString());
271       return FALSE;
272     }
273 
274     // get mouse position
275     SbVec2f currentpos;
276     if (!inherited::getEventSbVec2f(event, "mouseposition", currentpos)) {
277       return FALSE;
278     }
279 
280     // update mouse log
281     data->mouselog[2].time = data->mouselog[1].time;
282     data->mouselog[2].posn = data->mouselog[1].posn;
283     data->mouselog[1].time = data->mouselog[0].time;
284     data->mouselog[1].posn = data->mouselog[0].posn;
285     data->mouselog[0].posn = currentpos;
286     data->mouselog[0].time = SbTime::getTimeOfDay();
287     data->logsize += 1;
288 
289     // find rotation
290     data->projector->project(data->downposn);
291     SbRotation rot;
292     data->projector->projectAndGetRotation(currentpos, rot);
293     rot.invert();
294 
295     // restore camera to original position and do full reorientation
296     camera->copyFieldValues(data->cameraclone.get());
297     reorientCamera(camera, rot);
298 
299     return TRUE;
300   }
301 
302   else if (eventname == END()) {
303     SbBool triggerspincheck = FALSE; // default if not set
304     inherited::getEventSbBool(event, "triggerspin", triggerspincheck, FALSE);
305 
306     if (triggerspincheck) {
307       RotateData * data = static_cast<RotateData *>(this->getSessionData(sessionid, NewRotateData));
308       assert(data);
309 
310       SbBool triggerspin = FALSE;
311       SbRotation spinrotation;
312 
313       if (data->logsize > 2) {
314         SbTime stoptime = (SbTime::getTimeOfDay() - data->mouselog[0].time);
315         if (stoptime.getValue() < 0.100) {
316           SbVec3f from = data->projector->project(data->mouselog[2].posn);
317           SbVec3f to = data->projector->project(data->mouselog[0].posn);
318           spinrotation = data->projector->getRotation(from, to);
319 
320           SbTime delta = data->mouselog[0].time - data->mouselog[2].time;
321           double deltatime = delta.getValue();
322           spinrotation.invert();
323           spinrotation.scaleAngle(float(0.200 / deltatime));
324 
325           SbVec3f axis;
326           float radians;
327           spinrotation.getValue(axis, radians);
328           if ((radians > 0.01f) && (deltatime < 0.300)) {
329             triggerspin = TRUE;
330           }
331         }
332       }
333 
334       if (triggerspin) {
335         SoScXMLStateMachine * statemachine = inherited::getSoStateMachine(event, sessionid);
336         if (!statemachine) { return FALSE; }
337         SbString rotationstr;
338         rotationstr = SbStringConvert::toString(spinrotation);
339 
340         SbString updatetimestr;
341         double fromtime = SbTime::getTimeOfDay().getValue();
342         updatetimestr = SbStringConvert::toString(fromtime);
343 
344         ScXMLEvent event;
345         event.setEventName(TRIGGER_SPIN());
346         event.setAssociation("rotation", rotationstr.getString());
347         event.setAssociation("from", updatetimestr.getString());
348         statemachine->queueEvent(&event);
349       }
350     }
351 
352     this->freeSessionData(sessionid);
353     return TRUE;
354   }
355 
356   else if (eventname == SET_FOCAL_POINT()) {
357     // _sessionid
358     // worldspace {SbVec3f}
359     // [upvector] {SbVec3f}
360     // [focaldistance] {double}
361 
362     SoScXMLStateMachine * statemachine = inherited::getSoStateMachine(event, sessionid);
363     if (!statemachine) { return FALSE; }
364 
365     SoCamera * camera = inherited::getActiveCamera(event, sessionid);
366     if unlikely (!camera) { return FALSE; }
367 
368     SbVec3f worldspace(0.0f, 0.0f, 0.0f);
369     if (event->getAssociation("worldspace")) {
370       SbString valuestr = event->getAssociation("worldspace");
371       if (SbStringConvert::typeOf(valuestr) == SbStringConvert::SBVEC3F) {
372         if (!inherited::getEventSbVec3f(event, "worldspace", worldspace)) {
373           return FALSE;
374         }
375       } else {
376         return FALSE;
377       }
378     }
379 
380     SbVec3f upvector(0.0f, 0.0f, 0.0f);
381     SbBool useupvector = inherited::getEventSbVec3f(event, "upvector", upvector, FALSE);
382 
383     double focaldistance = 0.0f;
384     SbBool usefocaldistance = inherited::getEventDouble(event, "focaldistance", focaldistance, FALSE);
385 
386     if (!useupvector) {
387       // camera->pointAt() will turn the model away from its current up vector, so we
388       // try to preserve the existing up vector here instead of calling the up-vector-less
389       // version.
390       camera->orientation.getValue().multVec(SbVec3f(0.0f, 1.0f, 0.0f), upvector);
391       useupvector = TRUE;
392     }
393 
394     if (!useupvector) {
395       SoScXMLRotateTarget::setFocalPoint(camera, worldspace);
396     } else {
397       SoScXMLRotateTarget::setFocalPoint(camera, worldspace, upvector);
398     }
399     if (usefocaldistance) {
400       SoScXMLDollyTarget::jump(camera, float(focaldistance));
401     }
402 
403     return TRUE;
404   }
405 
406   else {
407     SoDebugError::post("SoScXMLRotateTarget::processOneEvent",
408                        "processing %s: unknown event",
409                        eventname.getString());
410     return FALSE;
411   }
412 
413   return TRUE;
414 }
415 
416 // Rotate camera around its focal point.
417 void
reorientCamera(SoCamera * camera,const SbRotation & rot)418 SoScXMLRotateTarget::reorientCamera(SoCamera * camera, const SbRotation & rot)
419 {
420   if (camera == NULL) return;
421 
422   // Find global coordinates of focal point.
423   SbVec3f direction;
424   camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
425   SbVec3f focalpoint = camera->position.getValue() +
426     camera->focalDistance.getValue() * direction;
427 
428   // Set new orientation value by accumulating the new rotation.
429   camera->orientation = rot * camera->orientation.getValue();
430 
431   // Reposition camera so we are still pointing at the same old focal point.
432   camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
433   camera->position = focalpoint - camera->focalDistance.getValue() * direction;
434 
435   // some custom code to support UTMCamera cameras
436   static const SoType utmcamtype(SoType::fromName("UTMCamera"));
437   if (utmcamtype != SoType::badType() && camera->isOfType(utmcamtype)) {
438     SbVec3d offset;
439     offset.setValue(camera->position.getValue());
440     SoSFVec3d * utmpositionfield = coin_assert_cast<SoSFVec3d *>(camera->getField("utmposition"));
441     utmpositionfield->setValue(utmpositionfield->getValue()+offset);
442     camera->position.setValue(0.0f, 0.0f, 0.0f);
443   }
444 }
445 
446 void
setFocalPoint(SoCamera * camera,const SbVec3f & worldspace)447 SoScXMLRotateTarget::setFocalPoint(SoCamera * camera, const SbVec3f & worldspace)
448 {
449   camera->pointAt(worldspace);
450 }
451 
452 void
setFocalPoint(SoCamera * camera,const SbVec3f & worldspace,const SbVec3f & upvector)453 SoScXMLRotateTarget::setFocalPoint(SoCamera * camera, const SbVec3f & worldspace, const SbVec3f & upvector)
454 {
455   camera->pointAt(worldspace, upvector);
456   SoScXMLFlightControlTarget::resetRoll(camera, upvector);
457 }
458 
459 #undef PRIVATE
460