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