FreeWRL / FreeX3D 4.3.0
Viewer.c
1/*
2
3
4CProto ???
5
6*/
7
8/****************************************************************************
9 This file is part of the FreeWRL/FreeX3D Distribution.
10
11 Copyright 2009 CRC Canada. (http://www.crc.gc.ca)
12
13 FreeWRL/FreeX3D is free software: you can redistribute it and/or modify
14 it under the terms of the GNU Lesser Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 FreeWRL/FreeX3D is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with FreeWRL/FreeX3D. If not, see <http://www.gnu.org/licenses/>.
25****************************************************************************/
26
27
28
29#include <config.h>
30#include <system.h>
31#include <display.h>
32#include <internal.h>
33
34#include <libFreeWRL.h>
35
36#include "../vrml_parser/Structs.h"
37#include "../opengl/OpenGL_Utils.h"
38#include "../opengl/Frustum.h"
39#include "../main/headers.h"
40
41#include "LinearAlgebra.h"
42#include "quaternion.h"
43#include "Viewer.h"
44#include "../x3d_parser/Bindable.h"
45#include "ui/common.h" // for ppcommon
46
47
48//moved to libfreewrl.h
49//enum {
50// CHORD_YAWZ,
51// CHORD_YAWPITCH,
52// CHORD_ROLL,
53// CHORD_XY
54//} input_chords;
55
56static void init_stereodefaults(X3D_Viewer *Viewer)
57{
58 /* must call this before getting values from command line in options.c */
59 Viewer->shutterGlasses = 0;
60 Viewer->anaglyph = 0;
61 Viewer->sidebyside = 0;
62 Viewer->updown = 0;
63 Viewer->isStereo = 0;
64 Viewer->eyedist = 0.065;
65 //For sidebyside: average human eyebase 2.4inches/65mm.
66 // We want it narrower, 57mm or 2.25 inches
67 // for 6" wide viewport: 2.25/6 = .375
68 // for shutter and anaglyph, .5 gets both eyes looking at the
69 // same point on the screen (for nominal distance object)
70 Viewer->screendist = 0.375; //was .8
71 Viewer->stereoParameter = 0.01; //was .4 or toe-in. Toe-in can force your eyes wall-eyed esp. in side-by-side, so set near zero.
72 Viewer->dominantEye = 1; /*0=Left 1=Right used for picking*/
73 Viewer->eitherDominantEye = 1; //1=can pick with either eye depending on which stereoside mouse is in, see setup_pickside()
74 Viewer->iprog[0] = 0; /* left red */
75 Viewer->iprog[1] = 1; /* right green */
76 Viewer->haveQuadbuffer = 0;
77}
78
79
80typedef struct pViewer{
81 int examineCounter;// = 5;
82
83 int viewer_initialized;
88
89 FILE *exfly_in_file;
90 struct point_XYZ viewer_lastP;
91 int exflyMethod; //0 or 1; /* could be a user settable option, which kind of exfly to do */
92 int StereoInitializedOnce;//. = 0;
93 GLboolean acMask[3][3]; //anaglyphChannelMask
94 double old2new[16];
95 double identity[16];
96 double tickFrac;
97 Quaternion sq;
98 double sp[3];
99 int keychord;
100 int dragchord;
101
102}* ppViewer;
103void *Viewer_constructor(){
104 void *v = MALLOCV(sizeof(struct pViewer));
105 memset(v,0,sizeof(struct pViewer));
106 return v;
107}
108void Viewer_init(struct tViewer *t){
109 //public
110 t->stereotype = 0;
111 //private
112 t->prv = Viewer_constructor();
113 {
114 ppViewer p = (ppViewer)t->prv;
115
116 p->examineCounter = 5;
117
118 p->viewer_initialized = FALSE;
119 #ifdef _MSC_VER
120 p->exflyMethod = 1; /* could be a user settable option, which kind of exfly to do */
121 #else
122 p->exflyMethod = 0;
123 #endif
124 p->StereoInitializedOnce = 0;
125 p->acMask[0][0] = (GLboolean)1; // R = 1, 0, 0
126
127 p->acMask[1][1] = (GLboolean)1; // C = 0, 1, 1
128 p->acMask[1][2] = (GLboolean)1;
129
130 /* viewpoint slerping */
131 loadIdentityMatrix(p->old2new);
132 loadIdentityMatrix(p->identity);
133 p->tickFrac = 0.0; //for debugging slowly
134 p->StereoInitializedOnce = 1;
135 p->keychord = CHORD_XY; // default on startup
136 p->dragchord = CHORD_YAWZ;
137 }
138}
139
140
141
142//static void handle_tick_walk(void);
143//static void handle_tick_walk2(double dtime);
144static void handle_tick_fly(void);
145static void handle_tick_exfly(void);
146static void handle_tick_fly2(double dtime);
147
148/* used for EAI calls to get the current speed. Not used for general calcs */
149/* we DO NOT return as a float, as some gccs have trouble with this causing segfaults */
150void getCurrentSpeed() {
152 // OLDCODE UNUSED ppViewer p;
153 ttglobal tg = gglobal();
154 // OLDCODE UNUSED p = (ppViewer)tg->Viewer.prv;
155 viewer = Viewer();
156 tg->Mainloop.BrowserSpeed = tg->Mainloop.BrowserFPS * (fabs(viewer->VPvelocity.x) + fabs(viewer->VPvelocity.y) + fabs(viewer->VPvelocity.z));
157}
158
159void viewer_default0(X3D_Viewer *viewer, int vpnodetype) {
160 //Quaternion q_i;
161 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
162
163 viewer->fieldofview = 45.0;
164 viewer->fovZoom = 1.0;
165
166 viewer->VPvelocity.x = 0.0; viewer->VPvelocity.y = 0.0; viewer->VPvelocity.z = 0.0;
167 viewer->Pos.x = 0; viewer->Pos.y = 0; viewer->Pos.z = 10;
168 viewer->currentPosInModel.x = 0; viewer->currentPosInModel.y = 0; viewer->currentPosInModel.z = 10;
169// viewer->AntiPos.x = 0; viewer->AntiPos.y = 0; viewer->AntiPos.z = 0;
170 viewer->Up.x = 0.0; viewer->Up.y = 1.0; viewer->Up.z = 0.0;
171 vrmlrot_to_quaternion (&viewer->Quat,1.0,0.0,0.0,0.0);
172
173 viewer->vp2rnSaved = FALSE;
174 viewer->headlight = TRUE;
175 viewer->speed = 1.0;
176 viewer->Dist = 10.0;
177 memcpy (&viewer->walk, &p->viewer_walk,sizeof (X3D_Viewer_Walk));
178 memcpy (&viewer->examine, &p->viewer_examine, sizeof (X3D_Viewer_Examine));
179 memcpy (&viewer->fly, &p->viewer_fly, sizeof (X3D_Viewer_Fly));
180 memcpy (&viewer->ypz,&p->viewer_ypz, sizeof (X3D_Viewer_Spherical));
181
182 if(vpnodetype == NODE_OrthoViewpoint){
183 //for LayoutLayer default NONE, Ortho
184 fwl_set_viewer_type0(viewer,VIEWER_NONE);
185 viewer->orthoField[0] = -1.0;
186 viewer->orthoField[1] = -1.0;
187 viewer->orthoField[2] = 1.0;
188 viewer->orthoField[3] = 1.0;
189 viewer->nearPlane = 0.1;
190 viewer->farPlane = 210000.0;
191 viewer->ortho = TRUE;
192 }else{
193 //all other viewpoint types - Viewpoint, GeoViewpoint ...
194 fwl_set_viewer_type0(viewer,VIEWER_EXAMINE);
195 }
196 viewer->LookatMode = 0;
197
198}
199//ppViewer p = (ppViewer)gglobal()->Viewer.prv;
200//X3D_Viewer _Viewer; /* has to be defined somewhere, so it found itself stuck here */
201X3D_Viewer *ViewerByLayerId(int layerid)
202{
204 bindablestack *bstack;
205 ttglobal tg;
206 tg = gglobal();
207 // OLDCODE UNUSED p = (ppViewer)tg->Viewer.prv;
208 //per-layer viewer
209 bstack = getBindableStacksByLayer(tg,layerid);
210 if(!bstack->viewer){
211 int vpnodetype;
212 viewer = MALLOCV(sizeof(X3D_Viewer));
213 memset(viewer,0,sizeof(X3D_Viewer));
214 vpnodetype = bstack->nodetype == NODE_LayoutLayer ? NODE_OrthoViewpoint : NODE_Viewpoint;
215 viewer_default0(viewer,vpnodetype);
216 init_stereodefaults(viewer);
217 bstack->viewer = viewer;
218 }
219 return bstack->viewer;
220}
221X3D_Viewer *Viewer()
222{
223 ttglobal tg;
224 tg = gglobal();
225 return ViewerByLayerId(tg->Bindable.activeLayer);
226}
227
228void viewer_default() {
230 viewer = Viewer();
231 viewer_default0(viewer,NODE_Viewpoint);
232}
233
234void resolve_pos2(X3D_Viewer *viewer);
235void resolve_pos20(X3D_Viewer *viewer);
236void viewer_init (X3D_Viewer *viewer, int type) {
237 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
238
239 /* if we are brand new, set up our defaults */
240 if (!p->viewer_initialized) {
241 p->viewer_initialized = TRUE;
242
243 /* what are we - EXAMINE, FLY, etc... */
244 viewer->type = type;
245
246 viewer->Pos.x = 0; viewer->Pos.y = 0; viewer->Pos.z = 10;
247 viewer->currentPosInModel.x = 0; viewer->currentPosInModel.y = 0; viewer->currentPosInModel.z = 10;
248
249
250 vrmlrot_to_quaternion (&viewer->Quat,1.0,0.0,0.0,0.0);
251
252 viewer->headlight = TRUE;
253 viewer->collision = FALSE;
254 viewer->speed = 1.0;
255 viewer->Dist = 10.0;
256 //viewer->exploreDist = 10.0;
257 memcpy (&viewer->walk, &p->viewer_walk,sizeof (X3D_Viewer_Walk));
258 memcpy (&viewer->examine, &p->viewer_examine, sizeof (X3D_Viewer_Examine));
259 memcpy (&viewer->fly, &p->viewer_fly, sizeof (X3D_Viewer_Fly));
260 memcpy (&viewer->ypz,&p->viewer_ypz, sizeof (X3D_Viewer_Spherical));
261
262
263 /* SLERP code for moving between viewpoints */
264 viewer->SLERPing = FALSE;
265 viewer->startSLERPtime = 0.0;
266 viewer->transitionType = 1; /* assume LINEAR */
267 viewer->transitionTime = 1.0; /* assume 1 second */
268
269 /* Orthographic projections */
270 viewer->ortho = FALSE;
271
272 viewer->doExamineModeDistanceCalculations = FALSE;
273
274 /* orientation - 0 is normal */
275 viewer->screenOrientation = 0;
276
277 viewer->nearPlane=DEFAULT_NEARPLANE; /* near Clip plane - MAKE SURE that statusbar is not in front of this!! */
278 viewer->farPlane=DEFAULT_FARPLANE; /* a good default value */
279 viewer->backgroundPlane = DEFAULT_BACKGROUNDPLANE; /* where Background and TextureBackground nodes go */
280 viewer->fieldofview=45.0;
281 viewer->fovZoom = 1.0;
282
283 viewer->wasBound = FALSE;
284 }
285
286 resolve_pos2(viewer);
287
288}
289
290
291int getCRouteCount();
292void printStatsRoutes()
293{
294 ConsoleMessage("%25s %d\n","Routes count", getCRouteCount());
295}
296
297void printStatsBindingStacks();
298void printStatsResources();
299void printStatsEvents();
300void printStatsNodes();
301void printStats()
302{
303 printMaxStackUsed();
304 printStatsResources();
305 printStatsEvents();
306 printStatsNodes();
307 printStatsRoutes();
308 printStatsBindingStacks();
309}
310
311void
312print_viewer()
313{
315
316 struct orient_XYZA ori;
317 viewer = Viewer();
318
319 quaternion_to_vrmlrot(&(viewer->Quat), &(ori.x),&(ori.y),&(ori.z), &(ori.a));
320 ConsoleMessage("Viewpoint local{\n");
321 ConsoleMessage("\tPosition[%.4f, %.4f, %.4f]\n", (viewer->Pos).x, (viewer->Pos).y, (viewer->Pos).z);
322 ConsoleMessage("\tQuaternion[%.4f, %.4f, %.4f, %.4f]\n", (viewer->Quat).w, (viewer->Quat).x, (viewer->Quat).y, (viewer->Quat).z);
323 ConsoleMessage("\tOrientation[%.4f, %.4f, %.4f, %.4f]\n", ori.x, ori.y, ori.z, ori.a);
324 ConsoleMessage("}\n");
325 getCurrentPosInModelB();
326 ConsoleMessage("World Coordinates of Avatar [%.4f, %.4f %.4f]\n",viewer->currentPosInModel.x,viewer->currentPosInModel.y,viewer->currentPosInModel.z);
327 printStats();
328}
329
330int fwl_get_headlight() {
331 return(Viewer()->headlight);
332}
333
334void fwl_toggle_headlight() {
336 viewer = Viewer();
337
338 if (viewer->headlight == TRUE) {
339 viewer->headlight = FALSE;
340 } else {
341 viewer->headlight = TRUE;
342 }
343}
344/* July 7, 2012 I moved .collision from params to x3d_viewer struct,
345 so its like headlight and navmode */
346void setNoCollision() {
348 viewer = Viewer();
349 viewer->collision = 0;
350}
351int get_collision() {
352 return fwl_getCollision();
353}
354void toggle_collision() {
356 viewer = Viewer();
357 viewer->collision = 1 - viewer->collision;
358}
359
360int fwl_getCollision(){
362 viewer = Viewer();
363 return viewer->collision;
364}
365void fwl_setCollision(int state) {
367 viewer = Viewer();
368 viewer->collision = state;
369}
370
371void fwl_init_StereoDefaults()
372{
374 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
375 viewer = Viewer();
376 if(!p->StereoInitializedOnce)
377 init_stereodefaults(viewer);
378 p->StereoInitializedOnce = 1;
379}
380
381
382void set_eyehalf(const double eyehalf, const double eyehalfangle) {
384 viewer = Viewer();
385
386 viewer->eyehalf = eyehalf;
387 viewer->eyehalfangle = eyehalfangle;
388}
389
390void fwl_set_viewer_type0(X3D_Viewer *viewer, const int type) {
391 ttglobal tg = gglobal();
392
393 if(viewer->type != type){
394 tg->Mainloop.CTRL = FALSE; //turn off any leftover 3-state toggle
395 switch(viewer->type){
396 case VIEWER_LOOKAT:
397 case VIEWER_EXPLORE:
398 viewer->LookatMode = 0; //turn off leftover lookatMode
399 break;
400 default:
401 break;
402 }
403 }
404
405 switch(type) {
406 case VIEWER_EXAMINE:
407 resolve_pos20(viewer);
408 case VIEWER_NONE:
409 case VIEWER_WALK:
410 case VIEWER_EXFLY:
411 //case VIEWER_TPLANE:
412 //case VIEWER_RPLANE:
413 //case VIEWER_TILT:
414 //case VIEWER_FLY2:
415 case VIEWER_TURNTABLE:
416 case VIEWER_DIST:
417 case VIEWER_FLY:
418 viewer->type = type;
419 break;
420 case VIEWER_SPHERICAL:
421 //3 state toggle stack
422 if(viewer->type == type){
423 //this is a request to toggle on/off FOV (field-of-view) adjustment for SPHERICAL mode
424 if(tg->Mainloop.CTRL){
425 tg->Mainloop.CTRL = FALSE;
426 }else{
427 tg->Mainloop.CTRL = TRUE; //in handle_spherical, check if button==3 (RMB) or CTRL + button==1
428 }
429 }else{
430 //request to toggle on EXPLORE mode
431 viewer->type = type;
432 }
433 break;
434
435 case VIEWER_EXPLORE:
436 //3 state toggle stack
437 if(viewer->type == type){
438 //this is a request to toggle on/off CTRL for EXPLORE mode
439 if(tg->Mainloop.CTRL){
440 tg->Mainloop.CTRL = FALSE;
441 viewer->LookatMode = 0;
442 }else{
443 tg->Mainloop.CTRL = TRUE;
444 viewer->LookatMode = 1; //tells mainloop to turn off sensitive
445 }
446 }else{
447 //request to toggle on EXPLORE mode
448 viewer->type = type;
449 }
450 break;
451 case VIEWER_LOOKAT:
452 //2 state toggle
453 if(viewer->type == type){
454 //this is a request to toggle off LOOKAT mode
455 viewer->type = viewer->lastType;
456 viewer->LookatMode = 0;
457 }else{
458 //request to toggle on LOOKAT mode
459 viewer->lastType = viewer->type;
460 viewer->LookatMode = 1; //tells mainloop to turn off sensitive
461 viewer->type = type;
462 }
463 break;
464 default:
465 ConsoleMessage ("Viewer type %d is not supported. See Viewer.h.\n", type);
466 viewer->type = VIEWER_NONE;
467 break;
468 }
469
470 /* set velocity array to zero again - used only for EAI */
471 viewer->VPvelocity.x=0.0; viewer->VPvelocity.y=0.0; viewer->VPvelocity.z=0.0;
472
473 /* can the currently bound viewer type handle this */
474 /* if there is no bound viewer, just ignore (happens on initialization) */
475 if (vectorSize(getActiveBindableStacks(tg)->navigation) >0)
476 if (viewer->oktypes[type]==FALSE) {
477 //setMenuButton_navModes(viewer->type);
478 return;
479 }
480
481 if(1) viewer_init(viewer,type); //feature-EXPLORE
482}
483void fwl_set_viewer_type(const int type) {
485 viewer = Viewer();
486 fwl_set_viewer_type0(viewer, type);
487}
488
489
490
491#ifdef VERBOSE
492#define VIEWER_STRING(type) ( \
493 type == VIEWER_NONE ? "NONE" : ( \
494 type == VIEWER_EXAMINE ? "EXAMINE" : ( \
495 type == VIEWER_WALK ? "WALK" : ( \
496 type == VIEWER_EXFLY ? "EXFLY" : ( \
497 type == VIEWER_SPHERICAL ? "SPHERICAL" : (\
498 type == VIEWER_TURNTABLE ? "TURNTABLE" : (\
499 type == VIEWER_FLY ? "FLY" : "UNKNOWN"))))))
500#endif //VERBOSE
501
502#ifdef _MSC_VER
503#define strcasecmp _stricmp
504#endif
505
506struct navmode {
507 char *key;
508 int type;
509} navmodes [] = {
510 {"NONE",VIEWER_NONE},
511 {"WALK",VIEWER_WALK},
512 {"FLY",VIEWER_FLY},
513 {"EXAMINE",VIEWER_EXAMINE},
514 {"SPHERICAL",VIEWER_SPHERICAL},
515 {"TURNTABLE",VIEWER_TURNTABLE},
516 {"EXPLORE",VIEWER_EXPLORE},
517 {"LOOKAT",VIEWER_LOOKAT},
518 {"YAWZ",VIEWER_YAWZ},
519 {"XY",VIEWER_XY},
520 {"YAWPITCH",VIEWER_YAWPITCH},
521 {"ROLL",VIEWER_ROLL},
522 {"DIST",VIEWER_DIST},
523 {NULL,0},
524};
525char * lookup_navmodestring(int navmode){
526 int i;
527 char *retval;
528 struct navmode *nm;
529 i = 0;
530 retval = NULL;
531 do{
532 nm = &navmodes[i];
533 if(nm->type == navmode){
534 retval = nm->key;
535 break;
536 }
537 i++;
538 }while(navmodes[i].key);
539 if(!retval) retval = "NONE";
540 return retval;
541}
542int lookup_navmode(char *cmode){
543 int i;
544 int retval;
545 struct navmode *nm;
546 i = 0;
547 retval = 0;
548 do{
549 nm = &navmodes[i];
550 if(!strcasecmp(nm->key,cmode)){
551 retval = nm->type;
552 break;
553 }
554 i++;
555 }while(navmodes[i].key);
556 return retval;
557}
558char* fwl_getNavModeStr()
559{
561 viewer = Viewer();
562 return lookup_navmodestring(viewer->type);
563}
564int fwl_getNavMode()
565{
567 viewer = Viewer();
568 return viewer->type;
569}
570int fwl_setNavMode(char *mode){
571 int imode = lookup_navmode(mode);
572 fwl_set_viewer_type(imode);
573 return 0;
574}
575
576
577
578void resolve_pos20(X3D_Viewer *viewer) {
579 /* my($this) = @_; */
580 struct point_XYZ rot, z_axis = { 0, 0, 1 };
581 Quaternion q_inv;
582
583 X3D_Viewer_Examine *examine = &viewer->examine;
584
585 quaternion_inverse(&q_inv, &(viewer->Quat));
586 quaternion_rotation(&rot, &q_inv, &z_axis);
587
588 (examine->Origin).x = (viewer->Pos).x - viewer->Dist * rot.x;
589 (examine->Origin).y = (viewer->Pos).y - viewer->Dist * rot.y;
590 (examine->Origin).z = (viewer->Pos).z - viewer->Dist * rot.z;
591}
592void resolve_pos2(X3D_Viewer *viewer) {
593 viewer_fetch_LCS(viewer);
594 resolve_pos20(viewer);
595}
596double vecangle2(struct point_XYZ* V1, struct point_XYZ* V2, struct point_XYZ* rotaxis) {
597 // similar full circle angle computation as: double matrotate2v()
598
599 double cosine, sine, ulen, vlen, scale, dot, angle;
600 struct point_XYZ cross;
601 /* use dot product to get cosine: cosTheta = (U dot V)/(||u||||v||) */
602 dot = vecdot(V1,V2);
603 ulen = sqrt(vecdot(V1,V1));
604 vlen = sqrt(vecdot(V2,V2));
605 scale = ulen*vlen;
606 if( APPROX(scale, 0.0) )
607 {
608 rotaxis->y = rotaxis->z = 0.0;
609 rotaxis->x = 1.0; //arbitrary axis
610 return 0.0;
611 }
612 cosine = dot/scale;
613 /* use cross product to get sine: ||u X v|| = ||u||||v||sin(theta) or sinTheta = ||uXv||/(||u||||v||)*/
614 veccross(&cross,*V1,*V2);
615 sine = sqrt(vecdot(&cross,&cross))/scale;
616 /* get full circle unambiguous angle using both cosine and sine */
617 angle = atan2(sine,cosine);
618 vecnormal(rotaxis,&cross);
619 return angle;
620}
621void avatar2BoundViewpointVerticalAvatar(GLDOUBLE *matA2BVVA, GLDOUBLE *matBVVA2A)
622{
623 /* goal: make 2 transform matrices to go back and forth from Avatar A to
624 Bound-Viewpoint-Vertical aligned Avatar-centric (no translations or scales - just 2 tilts) coordinates
625 */
627 struct point_XYZ tilted;
628 struct point_XYZ downvec; // = {0.0,-1.0,0.0};
629 double pp[3];
630 // OLDCODE UNUSED ppViewer p = (ppViewer)gglobal()->Viewer.prv;
631 viewer = Viewer();
632 viewer_fetch_user_offsets0(viewer);
633 pointxyz2double(pp,&viewer->Up);
634 vecscaled(pp,pp,-1.0);
635 double2pointxyz(&downvec,pp);
636 //downvec is in bound viewpoint space
637 quaternion_rotation(&tilted, &viewer->Quat, &downvec);
638 //tilted is in avatar space.
639 matrotate2v(matA2BVVA,downvec,tilted);
640 matrotate2v(matBVVA2A,tilted,downvec);
641 //printmatrix2(matA2BVVA,"A2BVVA" );
642 //printmatrix2(matBVVA2A,"BVVA2A");
643 return;
644}
645
646void viewer_level_to_bound()
647{
648/*
649Goal: Gravity as per specs
650Gravity:
651 From specs > abstract > architecture > 23.3.4 NavigationInfo:
652 "The speed, avatarSize and visibilityLimit values are all scaled by the transformation being applied
653 to the currently bound Viewpoint node.
654 If there is no currently bound Viewpoint node, the values are interpreted in the world coordinate system. "
655
656 "For purposes of terrain following, the browser maintains a notion of the down direction (down vector), since gravity
657 is applied in the direction of the down vector. This down vector shall be along the negative Y-axis in the
658 local coordinate system of the currently bound Viewpoint node (i.e., the accumulation of the Viewpoint node's
659 ancestors' transformations, not including the Viewpoint node's orientation field)."
660
661 From specs > abstract > architecture > 23.3.5 Viewpoint
662 "When a Viewpoint node is at the top of the stack, the user's view is
663 conceptually re-parented as a child of the Viewpoint node."
664
665 "Navigation types (see 23.3.4 NavigationInfo) that require a definition of a down vector (e.g., terrain following)
666 shall use the negative Y-axis of the coordinate system of the currently bound Viewpoint node.
667 Likewise, navigation types that require a definition of an up vector shall use the positive Y-axis of the
668 coordinate system of the currently bound Viewpoint node. The orientation field of the Viewpoint node does
669 not affect the definition of the down or up vectors. This allows the author to separate the viewing direction
670 from the gravity direction."
671
672 Implication: if your entire scene is tilted (ie. Z up), along with your viewpoint, you shouldn't notice.
673 Even when terrain following, stepping, colliding.
674
675Transforms:
676World > [TransformStack] > Bound-Viewpoint > [Quat + Pos] > viewer/avatar > [AntiQuat + AntiPos?] > Bound-Viewpoint > Inverse[TransformStack] > World
677Viewer.Quat, Viewer.Pos - local pose of avatar wrt its currently bound viewpoint parent.
678 Includes/contains the viewpoint node's position and orientation field info.
679
680ViewerUpVector: - looks like a global tilt of the avatar - I don't use it here or in collision
681ViewerUpVector computation - see RenderFuncs.c L595
682*/
683
684 /*
685 first attempts at leveling avatar to bound viewpoint:
686 1. Transform a bound-viewpoint-coordinates down vector {0,-1,0} to avatar coords using Quat
687 2. compute tilts of that down vector in avatar space
688 3. apply inverse tilts to end of transform chain ie Quat = Quat*inverse(tilts)
689 */
690 struct point_XYZ rotaxis, tilted;
691 Quaternion q, Quat; //, AntiQuat;
692 double angle;
694 struct point_XYZ downvec;// = {0.0,-1.0,0.0};
695 double pp[3];
696 // OLDCODE UNUSED ppViewer p = (ppViewer)gglobal()->Viewer.prv;
697 viewer = Viewer();
698 viewer_fetch_user_offsets0(viewer);
699 pointxyz2double(pp,&viewer->Up);
700 vecscaled(pp,pp,-1.0);
701 double2pointxyz(&downvec,pp);
702
703 Quat = viewer->Quat;
704 //AntiQuat = Viewer.AntiQuat;
705 quaternion_rotation(&tilted, &Quat, &downvec);
706 //tilted is in avatar space.
707 angle = vecangle2(&downvec,&tilted,&rotaxis);
708 if( APPROX(angle,0.0) ) return; //we're level already
709 vrmlrot_to_quaternion(&q, rotaxis.x, rotaxis.y, rotaxis.z, -angle );
710 quaternion_normalize(&q);
711 quaternion_multiply(&(viewer->Quat), &q, &Quat);
712 quaternion_normalize(&(viewer->Quat));
713
714 viewer_update_user_offsets0(viewer);
715}
716
717void viewer_togl(double fieldofview)
718{
719 /* goal: take the curring Viewer pose (.Pos, .Quat) and set openGL transforms
720 to prepare for a separate call to move the viewpoint -
721 (currently done in Mainloop.c setup_viewpoint())
722 Explanation of AntiPos, AntiQuat:
723 To slerp between viewpoints we need the pose of the old viewpoint in the space of the new viewpoint.
724 - in setup_viewpoint_part2() > prep_viewpoint
725 - we unconditionally apply .position, .orientation (they could be routed to on any frame)
726 - so in setup_viewpoint_part1() > viewer_togl() if slerping we need to initially take off
727 the effect of bind-time .position, .orientation and add in the position, orientation of the
728 last viewpoint.
729 If there's a viewpoint vp, We want to
730 a) navigate away from the initial bind_viewpoint transform + (.position,.orientation) pose
731 b) start navigation from where vp.position, vp.orientation tell us.
732 c) remain responsive if there are changes to .position or .orientation. during run
733 - either javascript or routing may change vp.position, .orientation
734 To accomodate all this we:
735 a) create variables Viewer.Pos, .Quat to hold the navigation
736 b) initially set Viewer.Pos, .Quat to vp.position, .orientation
737 - see INITIALIZE_POSE_ANTIPOSE and its use in bind_viewpoint
738 c) subtract off initially bound .position, .orientation and add on current .position, .orientation
739 - below, AntiPos and AntiQuat hold the original .orientation, .position values set during bind_viewpoint
740 - prep_viewpoint in module Component_Navigation then adds back on
741 the current (javascript/routing changed) .position, .orientation
742 - if no change to .position, .orientation after binding, then these 2
743 (AntiPos,AntiQuat) and (vp.position,vp.orientation) are equal and cancel
744 leaving the .Pos, .Quat -initially with .position, .orientation- in the modelview matrix stack
745 */
747 viewer = Viewer();
748 if (viewer->isStereo) /* buffer != GL_BACK) */
749 set_stereo_offset0(); /*Viewer.iside, Viewer.eyehalf, Viewer.eyehalfangle);*/
750
751
752 if(!viewer->wasBound){
753 //only do these if there's no bound viewpoint
754 //(otherwise prep_viewpoint does .position, .orientation)
755 quaternion_togl(&viewer->Quat);
756 FW_GL_TRANSLATE_D(-(viewer->Pos).x, -(viewer->Pos).y, -(viewer->Pos).z);
757 }
758
759}
760
761/* go through the modelMatrix and see where we are. Notes:
762 - this should ideally be done in prep_Viewpoint, but if there is NO viewpoint... at least
763 here, it gets called. (that is why the antipos is added in here)
764
765 - for X3D Viewpoints, this one adds in the AntiPos; for GeoViewpoints, we do a get after
766 doing Geo transform and rotation that are integral with the GeoViewpoint node.
767*/
768void getCurrentPosInModelB(){
770 double mod[16], modi[16], pp[3];
771 viewer = Viewer();
772
773 vecsetd(pp,0.0,0.0,0.0);
774 FW_GL_GETDOUBLEV(GL_MODELVIEW_MATRIX, mod);
775 matinverseAFFINE(modi,mod);
776 transformAFFINEd(pp,pp,modi);
777 double2pointxyz(&viewer->currentPosInModel,pp);
778}
779
780double quadratic(double x,double a,double b,double c)
781{
782 /* y = a*x*x + b*x + c; */
783 return x*x*a + x*b + c;
784}
785double xsign_quadratic(double x,double a,double b,double c)
786{
787 /* y = sign(x)*(a*abs(x)*abs(x) + b*abs(x) + c); */
788 double xSign;
789 //xSign = _copysign(1.0,x); _MSC_VER
790 if(x < 0.0) xSign = -1.0; else xSign = 1.0;
791 x = fabs(x);
792 return xSign*quadratic(x,a,b,c);
793}
794double cubic(double x,double a,double b,double c, double d){
795 return x*x*x*a + x*x*b + x*c + d;
796}
797double xsign_cubic(double x,double a,double b,double c, double d)
798{
799 /* y = sign(x)*(a*abs(x)*abs(x)*abs(x) + b*abs(x)*abs(x) + c*abs(x) + d); */
800 double xSign;
801 //xSign = _copysign(1.0,x); _MSC_VER
802 if(x < 0.0) xSign = -1.0; else xSign = 1.0;
803 x = fabs(x);
804 return xSign*cubic(x,a,b,c,d);
805}
806double quartic(double x,double a,double b,double c, double d, double e){
807 return x*x*x*x*a + x*x*x*b + x*x*c + x*d +e;
808}
809double xsign_quartic(double x,double a,double b,double c, double d, double e)
810{
811 // (x,signx) = abs(x)
812 // y = signx*(a*x^4 + b*x^3 + c*x^2 + d*x + e
813 double xSign;
814 //xSign = _copysign(1.0,x); _MSC_VER
815 if(x < 0.0) xSign = -1.0; else xSign = 1.0;
816 x = fabs(x);
817 return xSign*quartic(x,a,b,c,d,e);
818}
819double quintic(double x,double a,double b,double c, double d, double e, double f){
820 return x*x*x*x*x*a + x*x*x*x*b + x*x*x*c + x*x*d +x*e + f;
821}
822double xsign_quintic(double x,double a,double b,double c, double d, double e, double f)
823{
824 // (x,signx) = abs(x)
825 // y = signx*(a*x^5 + b*x^4 + c*x^3 + d*x^2 + e*x + f
826 double xSign;
827 //xSign = _copysign(1.0,x); _MSC_VER
828 if(x < 0.0) xSign = -1.0; else xSign = 1.0;
829 x = fabs(x);
830 return xSign*quintic(x,a,b,c,d,e,f);
831}
832static void handle_walk(const int mev, const unsigned int button, const float x, const float y) {
833/*
834 * walk.xd,zd are in a plane parallel to the scene/global horizon.
835 * walk.yd is vertical in the global/scene
836 * walk.rd is an angle in the global/scene horizontal plane (around vertical axis)
837*/
838 ttglobal tg;
839 // OLDCODE UNUSED ppViewer p;
841
842 X3D_Viewer_Walk *walk;
843 tg = gglobal();
844 // OLDCODE UNUSED p = (ppViewer)tg->Viewer.prv;
845 viewer = Viewer();
846 walk = &viewer->walk;
847
848 //new Dec 19, 2017 frame-rate adjustment moved to handle_tick_walk for finer-granularity stutter-smoothing
849
850 if (mev == ButtonPress ) {
851 walk->SY = y;
852 walk->SX = x;
853 } else if (mev == MotionNotify) {
854 if (button == 1) {
855 /* July31,2010 new quadratic speed: allows you slow speed with small mouse motions, or
856 fast speeds with large mouse motions. The .05, 5.0 etc are tuning parameters - I tinkered / experimented
857 using the townsite scene http://dug9.users.sourceforge.net/web3d/townsite_2014/townsite.x3d
858 which has the default navigationInfo speed (1.0) and is to geographic scale in meters.
859 If the tuning params don't work for you please fix/iterate/re-tune/change back/put a switch
860 I find them amply speedy, maybe yaw a bit too fast
861 dug9: button 1 ZD: .05 5.0 0.0 RD: .1 .5 0.0
862 button 3 XD: 5.0 10.0 0.0 YD: 5.0 10.0 0.0
863 */
864 walk->ZD = -xsign_quadratic(y - walk->SY,.05,5.0,0.0)*viewer->speed;
865 walk->RD = xsign_quadratic(x - walk->SX,0.1,0.5,0.0); //a few browsers have a separate rotational speed. We rely on quadratic or cubic drags to cover a good range of rotational speeds
866 //walk->ZD = (y - walk->SY) * Viewer.speed;
867 //walk->RD = (x - walk->SX) * 0.1;
868 } else if (button == 3) {
869 walk->XD = xsign_quadratic(x - walk->SX,5.0,10.0,0.0)*viewer->speed;
870 walk->YD = xsign_quadratic(y - walk->SY,5.0,10.0,0.0)*viewer->speed;
871 //walk->XD = (x - walk->SX) * Viewer.speed;
872 //walk->YD = -(y - walk->SY) * Viewer.speed;
873 }
874 } else if (mev == ButtonRelease) {
875 if (button == 1) {
876 walk->ZD = 0;
877 walk->RD = 0;
878 } else if (button == 3) {
879 walk->XD = 0;
880 walk->YD = 0;
881 }
882 }
883}
884
885
886static double
887 norm(const Quaternion *quat)
888 {
889 return(sqrt(
890 quat->w * quat->w +
891 quat->x * quat->x +
892 quat->y * quat->y +
893 quat->z * quat->z
894 ));
895 }
896
897
898void handle_examine(const int mev, const unsigned int button, float x, float y) {
899 Quaternion q, q_i, arc;
900 struct point_XYZ pp = { 0, 0, 0};
901 double squat_norm;
902 // OLDCODE UNUSED ppViewer p;
904 X3D_Viewer_Examine *examine;
905 // OLDCODE UNUSED p = (ppViewer)gglobal()->Viewer.prv;
906 viewer = Viewer();
907 examine = &viewer->examine;
908 pp.z=viewer->Dist;
909
910 if (mev == ButtonPress) {
911 if (button == 1) {
912 resolve_pos20(viewer);
913/*
914 printf ("\n");
915 printf ("bp, before SQ %4.3f %4.3f %4.3f %4.3f\n",examine->SQuat.x, examine->SQuat.y, examine->SQuat.z, examine->SQuat.w);
916 printf ("bp, before OQ %4.3f %4.3f %4.3f %4.3f\n",examine->OQuat.x, examine->OQuat.y, examine->OQuat.z, examine->OQuat.w);
917 printf ("bp, before Q %4.3f %4.3f %4.3f %4.3f\n",Viewer.Quat.x, Viewer.Quat.y, Viewer.Quat.z, Viewer.Quat.w);
918 printf ("bp, before, pos %4.3f %4.3f %4.3f\n",Viewer.Pos.x, Viewer.Pos.y, Viewer.Pos.z);
919 printf ("bp, before, aps %4.3f %4.3f %4.3f\n",Viewer.AntiPos.x, Viewer.AntiPos.y, Viewer.AntiPos.z);
920*/
921 xy2qua(&(examine->SQuat), x, y);
922 quaternion_set(&(examine->OQuat), &(viewer->Quat));
923/*
924 printf ("bp, after SQ %4.3f %4.3f %4.3f %4.3f\n",examine->SQuat.x, examine->SQuat.y, examine->SQuat.z, examine->SQuat.w);
925 printf ("bp, after OQ %4.3f %4.3f %4.3f %4.3f\n",examine->OQuat.x, examine->OQuat.y, examine->OQuat.z, examine->OQuat.w);
926 printf ("bp, after Q %4.3f %4.3f %4.3f %4.3f\n",Viewer.Quat.x, Viewer.Quat.y, Viewer.Quat.z, Viewer.Quat.w);
927 printf ("bp, after, pos %4.3f %4.3f %4.3f\n",Viewer.Pos.x, Viewer.Pos.y, Viewer.Pos.z);
928 printf ("bp, after, aps %4.3f %4.3f %4.3f\n",Viewer.AntiPos.x, Viewer.AntiPos.y, Viewer.AntiPos.z);
929*/
930
931 }
932 //moved to handle_dist
933 //else if (button == 3) {
934 // examine->SY = y;
935 // examine->ODist = max(0.1,viewer->Dist);
936 //}
937 } else if (mev == MotionNotify) {
938 resolve_pos20(viewer);
939 if (button == 1) {
940 squat_norm = norm(&(examine->SQuat));
941 /* we have missed the press */
942 if (APPROX(squat_norm, 0)) {
943 fprintf(stderr, "Viewer handle_examine: mouse event DRAG - missed press\n");
944 /* $this->{SQuat} = $this->xy2qua($mx,$my); */
945 xy2qua(&(examine->SQuat), x, y);
946 /* $this->{OQuat} = $this->{Quat}; */
947 quaternion_set(&(examine->OQuat), &(viewer->Quat));
948 } else {
949 /* my $q = $this->xy2qua($mx,$my); */
950 xy2qua(&q, x, y);
951 /* my $arc = $q->multiply($this->{SQuat}->invert()); */
952 quaternion_inverse(&q_i, &(examine->SQuat));
953 quaternion_multiply(&arc, &q, &q_i);
954
955
956 /* $this->{Quat} = $arc->multiply($this->{OQuat}); */
957 quaternion_multiply(&(viewer->Quat), &arc, &(examine->OQuat));
958 }
959 }
960 //moved to handle_dist
961 //else if (button == 3) {
962 // #ifndef DISABLER
963 // viewer->Dist = examine->ODist * exp(examine->SY - y);
964 // #else
965 // viewer->Dist = (0 != y) ? examine->ODist * examine->SY / y : 0;
966 // #endif
967 //}
968 }
969
970 quaternion_inverse(&q_i, &(viewer->Quat));
971 quaternion_rotation(&(viewer->Pos), &q_i, &pp);
972
973 //printf ("handle examine after *= quat pos %4.3f %4.3f %4.3f\n",viewer->Pos.x, viewer->Pos.y, viewer->Pos.z);
974
975 viewer->Pos.x += (examine->Origin).x;
976 viewer->Pos.y += (examine->Origin).y;
977 viewer->Pos.z += (examine->Origin).z;
978 //printf ("handle examine after += origin pos %4.3f %4.3f %4.3f\n",viewer->Pos.x, viewer->Pos.y, viewer->Pos.z);
979
980/*
981printf ("examine->origin %4.3f %4.3f %4.3f\n",examine->Origin.x, examine->Origin.y, examine->Origin.z);
982*/
983}
984
985double get_viewer_dist(){
986 return Viewer()->Dist;
987}
988
989void handle_dist(const int mev, const unsigned int button, float x, float y) {
990 /* different than z, this adjusts the viewer->Dist value for examine, turntable, explore, lookat
991 - all without using RMB (right mouse button), so mobile friendly
992 */
993 //examine variant - doesn't move the vp/.pos
994 Quaternion q_i;
995 struct point_XYZ pp = { 0, 0, 0};
996 double yy;
998 // OLDCODE UNUSED ppViewer p;
999 X3D_Viewer_Examine *examine;
1000 // OLDCODE UNUSED p = (ppViewer)gglobal()->Viewer.prv;
1001 viewer = Viewer();
1002 examine = &viewer->examine;
1003 pp.z=viewer->Dist;
1004
1005 //ConsoleMessage("handle_dist but %d mev %d\n", button, mev);
1006 //yy = 1.0 - y;
1007 yy = y;
1008 if (mev == ButtonPress) {
1009 if (button == 1) {
1010 resolve_pos20(viewer);
1011 examine->SY = yy;
1012 examine->ODist = max(0.1,viewer->Dist);
1013 }
1014 } else if (mev == MotionNotify) {
1015 resolve_pos20(viewer);
1016 if (button == 1) {
1017 #ifndef DISABLER
1018 viewer->Dist = examine->ODist * exp(4.0 * (examine->SY - yy));
1019 #else
1020 viewer->Dist = (0 != yy) ? examine->ODist * examine->SY / yy : 0;
1021 #endif
1022 //printf("v.dist=%lf\n",viewer->Dist);
1023 pp.z = viewer->Dist;
1024 }
1025 }
1026 quaternion_inverse(&q_i, &(viewer->Quat));
1027 quaternion_rotation(&(viewer->Pos), &q_i, &pp);
1028
1029 //printf ("handle dist after *= quat pos %4.3f %4.3f %4.3f\n",viewer->Pos.x, viewer->Pos.y, viewer->Pos.z);
1030
1031 viewer->Pos.x += (examine->Origin).x;
1032 viewer->Pos.y += (examine->Origin).y;
1033 viewer->Pos.z += (examine->Origin).z;
1034 //printf ("handle dist after += origin pos %4.3f %4.3f %4.3f\n",viewer->Pos.x, viewer->Pos.y, viewer->Pos.z);
1035
1036
1037}
1038
1039double display_screenRatio();
1040double dclamp(double fval, double fstart, double fend) {
1041 double fret = fval;
1042 fret = fval > fend? fend : fval; //min(fval,fend)
1043 fret = fret < fstart ? fstart : fret; //max(fval,fstart)
1044 return fret;
1045}
1046void handle_turntable(const int mev, const unsigned int button, float x, float y) {
1047 /*
1048 Like handle_spherical, except:
1049 move the viewer->Pos in the opposite direction from where we are looking
1050 */
1051 double frameRateAdjustment;
1054 ttglobal tg = gglobal();
1055 viewer = Viewer();
1056 ypz = &viewer->ypz; //just a place to store last mouse xy during drag
1057
1058 if(APPROX(viewer->Dist,0.0)){
1059 //no pivot point yet
1060 viewer->Dist = 10.0;
1061 }
1062
1063 if( tg->Mainloop.BrowserFPS > 0)
1064 frameRateAdjustment = 20.0 / tg->Mainloop.BrowserFPS; /* lets say 20FPS is our speed benchmark for developing tuning parameters */
1065 else
1066 frameRateAdjustment = 1.0;
1067
1068
1069 if (mev == ButtonPress) {
1070 if (button == 1 || button == 3) {
1071 ypz->x = x;
1072 ypz->y = y;
1073 }
1074 }
1075 else if (mev == MotionNotify)
1076 {
1077 Quaternion qyaw, qpitch;
1078 double dyaw, dpitch;
1079 struct point_XYZ pp, pp2, yaxis;
1080 double yaw, pitch; //dist,
1081 Quaternion quat;
1082
1083 yaw = pitch = 0.0;
1084 if (button == 1 || button == 3){
1085 struct point_XYZ dd,ddr,xx,xxr;
1086 double dist;
1087 yaxis = viewer->Up;
1088 //(examine->Origin).x = (viewer->Pos).x - viewer->Dist * rot.x;
1089 dd.x = dd.y = 0.0; dd.z = viewer->Dist; //exploreDist;
1090 xx.y = xx.z = 0.0; xx.x = 1.0;
1091 quat = viewer->Quat;
1092 quaternion_inverse(&quat,&quat);
1093 quaternion_rotation(&ddr, &quat, &dd);
1094 quaternion_rotation(&xxr, &quat, &xx);
1095 vecdiff(&viewer->examine.Origin,&viewer->Pos,&ddr);
1096
1097 //printf("ddr %f %f, ",ddr.x,ddr.z);
1098 pp = ddr;
1099 vecnormal(&pp, &pp);
1100 pitch = -(acos(dclamp(vecdot(&pp, &yaxis),-1.0,1.0)) - PI*.5);
1101 //euler angles are unstable at pitch 90, when calculated from a verticle ray
1102 //as a trick we switch our yaw calculation above pitch 45 degrees to use a horizontal ray
1103 if(fabs(pitch) > PI*.25){
1104 xxr.y = 0.0;
1105 vecnormal(&xxr,&xxr);
1106 yaw = atan2(xxr.z,xxr.x);
1107 //printf("xx %lf %lf %lf ",xxr.x,xxr.y,xxr.z);
1108 //printf("y1 %lf ",yaw);
1109
1110 }else{
1111 pp2 = pp;
1112 pp2.y = 0.0;
1113 dist = veclength(pp2);
1114 if(dist > 0.0 && fabs(pitch) < (PI *.5 - .001)){
1115 vecnormal(&pp2,&pp2);
1116 yaw = -atan2(pp2.x, pp2.z);
1117 //printf("y1 %lf ",yaw);
1118 }
1119 }
1120 }
1121 if (button == 1) {
1122 dyaw = -(ypz->x - x) * viewer->fieldofview*PI / 180.0*viewer->fovZoom * display_screenRatio(); //tg->display.screenRatio;
1123 dpitch = (ypz->y - y) * viewer->fieldofview*PI / 180.0*viewer->fovZoom;
1124 yaw += dyaw;
1125 pitch += dpitch;
1126 }else if (button == 3) {
1127 //distance drag
1128 //handle_tick_explore quadratic
1129 //double quadratic = -xsign_quadratic(y - ypz->y,5.0,10.0,0.0);
1130 ypz->ypz[1] = -xsign_quadratic(y - ypz->y,100.0,10.0,0.0)*viewer->speed * frameRateAdjustment *.15;
1131 //printf("quad=%f y-y %f s=%f fra=%f\n",quadratic,y-ypz->y,viewer->speed,frameRateAdjustment);
1132 }
1133 if (button == 1 || button == 3)
1134 {
1135 //printf("y4= %lf \n",yaw);
1136 vrmlrot_to_quaternion(&qyaw, 0.0, 1.0, 0.0, yaw);
1137 vrmlrot_to_quaternion(&qpitch, 1.0, 0.0, 0.0, pitch);
1138 quaternion_multiply(&quat, &qpitch, &qyaw);
1139 quaternion_normalize(&quat);
1140
1141 quaternion_set(&(viewer->Quat), &quat);
1142 //move the viewer->pos in the opposite direction that we are looking
1143 quaternion_inverse(&quat, &quat);
1144 pp.x = 0.0;
1145 pp.y = 0.0;
1146 pp.z = viewer->Dist; //dist;
1147 quaternion_rotation(&(viewer->Pos), &quat, &pp);
1148 //remember the last drag coords for next motion
1149 vecadd(&viewer->Pos,&viewer->examine.Origin,&viewer->Pos);
1150 }
1151 if( button == 1){
1152 ypz->x = x;
1153 ypz->y = y;
1154 }
1155 }else if(mev == ButtonRelease) {
1156 if (button == 3) {
1157 ypz->ypz[1] = 0.0;
1158 }
1159 }
1160}
1161
1162
1163void handle_spherical(const int mev, const unsigned int button, float x, float y) {
1164 /* handle_examine almost works except we don't want roll-tilt, and we want to zoom */
1165 int ibutton;
1166 Quaternion qyaw, qpitch;
1167 double dyaw,dpitch;
1170 ttglobal tg = gglobal();
1171 viewer = Viewer();
1172 ypz = &viewer->ypz;
1173 ibutton = button;
1174 if(ibutton == 1 && tg->Mainloop.CTRL) ibutton = 3; //RMB method for mobile/touch
1175
1176 if (mev == ButtonPress) {
1177 if (ibutton == 1 || ibutton == 3) {
1178 ypz->x = x;
1179 ypz->y = y;
1180 }
1181 } else if (mev == MotionNotify) {
1182 if (ibutton == 1) {
1183 double yaw, pitch;
1184 Quaternion quat;
1185 struct point_XYZ dd, ddr, yaxis;
1186
1187 //step 1 convert Viewer.Quat to yaw, pitch (discard any roll)
1188 yaxis = viewer->Up;
1189
1190 dd.x = dd.y = 0.0; dd.z = 1.0;
1191 quat = viewer->Quat;
1192 quaternion_inverse(&quat,&quat);
1193 quaternion_rotation(&ddr, &quat, &dd);
1194 yaw = -atan2(ddr.x,ddr.z);
1195 pitch = -(acos(vecdot(&ddr, &yaxis)) - PI*.5);
1196
1197 //step 2 add on any mouse motion as yaw,pitch chord
1198 dyaw = (ypz->x - x) * viewer->fieldofview*PI/180.0*viewer->fovZoom * display_screenRatio(); //tg->display.screenRatio;
1199 dpitch = -(ypz->y - y) * viewer->fieldofview*PI/180.0*viewer->fovZoom;
1200 yaw += dyaw;
1201 pitch += dpitch;
1202
1203 //step 3 convert yaw, pitch back to Viewer.Quat
1204 vrmlrot_to_quaternion(&qyaw, 0.0, 1.0, 0.0, yaw);
1205 vrmlrot_to_quaternion(&qpitch, 1.0, 0.0, 0.0, pitch);
1206 quaternion_multiply(&quat, &qpitch, &qyaw);
1207 quaternion_normalize(&quat);
1208
1209 quaternion_set(&(viewer->Quat), &quat);
1210
1211 } else if (ibutton == 3) {
1212 double d, fac;
1213 d = -(y - ypz->y)*.5;
1214 fac = pow(10.0,d);
1215 viewer->fovZoom = viewer->fovZoom * fac;
1216 }
1217 if(ibutton == 1 || ibutton == 3){
1218 ypz->x = x;
1219 ypz->y = y;
1220 }
1221 }
1222}
1223
1224
1225
1226/* fly2, tilt, tplane, rplane form a set that replaces keyboard fly for
1227 touch devices. Collision / gravity only differentiates WALK, and treats all
1228 other modes the same as fly.
1229 When FLY mode is set from the scene, the front end (or statusbarHud)
1230 switches to FLY2 which navigates similar to walk mode except with
1231 (default) no gravity and a (default) spherical collision volume.
1232*/
1233void viewer_lastQ_set(Quaternion *lastQ);
1234void handle_fly2(const int mev, const unsigned int button, float x, float y) {
1235 /* there's a handle_tick_fly2() so handle_fly2() must turn on/off the
1236 tick action based on mev (mouse up/down/move)
1237 */
1239 X3D_Viewer_InPlane *inplane;
1240 viewer = Viewer();
1241 inplane = &viewer->inplane;
1242
1243 if (mev == ButtonPress) {
1244 inplane->x = x;
1245 inplane->y = y;
1246 inplane->xx = x;
1247 inplane->yy = y;
1248 inplane->on = 1;
1249 } else if (mev == MotionNotify) {
1250 inplane->xx = x;
1251 inplane->yy = y;
1252 } else if (mev == ButtonRelease ) {
1253 inplane->on = 0;
1254 }
1255
1256}
1257
1258
1259void increment_pos0(struct point_XYZ *vec);
1260void handle_tick_fly2(double dtime) {
1261 ttglobal tg;
1262 X3D_Viewer_InPlane *inplane;
1263 double frameRateAdjustment, xx, yy, yyy, zz, rot, a,b,c;
1264 struct point_XYZ xyz;
1265 Quaternion q, nq;
1267 tg = gglobal();
1268 viewer = Viewer();
1269 inplane = &viewer->inplane;
1270
1271 frameRateAdjustment = dtime * 20.0;
1272
1273 if (inplane->on) {
1274 xx = inplane->xx - inplane->x;
1275 yy = inplane->yy - inplane->y;
1276 //zz = -xsign_quadratic(yy,.05,5.0,0.0)*viewer->speed * frameRateAdjustment;
1277 //zz = -xsign_cubic(yy*10.0,.05,5.0,1.0,0.0)*viewer->speed * dtime;
1278 yyy = yy*1.0;
1279 a = 10000.0;
1280 b = 100.0;
1281 c = 1.0;
1282 zz = -xsign_cubic(yyy,a,b,c,0.0)*viewer->speed * dtime; // * sqrt(viewer->Dist + 1.0);
1283 //if(yy > 0.0){
1284 // double x;
1285 // x = yyy;
1286 // printf("%lf %lf %lf %lf\n",x*x*x*a,x*x*b,x*c,0.0);
1287 //}
1288
1289 //zz *= 0.15;
1290
1291 xyz.x = 0.0;
1292 xyz.y = 0.0;
1293 xyz.z = zz;
1294
1295 //rot = xsign_quadratic(xx,0.1,0.5,0.0)*frameRateAdjustment;
1296 rot = xsign_quadratic(xx,2.0,10.0,0.0)*dtime; //frameRateAdjustment;
1297 //printf("rot=%lf zz=%lf\n",rot,zz);
1298 memcpy(&q,&viewer->Quat,sizeof(Quaternion));
1299 vrmlrot_to_quaternion (&nq,0.0,1.0,0.0,0.4*rot);
1300 viewer_lastQ_set(&nq); //wall penetration - last avatar pose is stored before updating
1301 quaternion_multiply(&(viewer->Quat), &nq, &q); //Quat = walk->RD * Quat
1302 //does the Z gets transformed by the quat?
1303 increment_pos0(&xyz);
1304 }
1305
1306}
1307
1308void handle_lookat(const int mev, const unsigned int button, float x, float y) {
1309 /* do nothing on mouse down or mouse move
1310 on mouse up, trigger node picking action in mainloop
1311 */
1313 viewer = Viewer();
1314
1315 switch(mev){
1316 case ButtonPress:
1317 //trigger a node pick in mainloop, followed by viewpoint transition
1318 viewer->LookatMode = 2;
1319 break;
1320 case MotionNotify:
1321 //do nothing
1322 break;
1323 case ButtonRelease:
1324 //viewer->lookatmode should == 3 coming in here
1325 if(viewer->type == VIEWER_LOOKAT)
1326 fwl_set_viewer_type(VIEWER_LOOKAT); //toggle off LOOKAT
1327 if(viewer->type == VIEWER_EXPLORE)
1328 fwl_set_viewer_type(VIEWER_EXPLORE); //toggle off LOOKAT
1329 viewer->LookatMode = 0; //VIEWER_EXPLORE
1330
1331 break;
1332 }
1333
1334}
1335void handle_tick_lookat() {
1337 //stub in case we need the viewer or viewpoint transition here
1338 viewer = Viewer();
1339 switch(viewer->LookatMode){
1340 case 0: //not in use
1341 case 1: //someone set viewer to lookat mode: mainloop shuts off sensitive, turns on lookat cursor
1342 case 2: //mouseup tells mainloop to pick a node at current mousexy, turn off lookatcursor
1343 case 3: //mainloop picked a node, now transition
1344 case 4: //transition complete, restore previous nav type
1345 break;
1346 }
1347}
1348
1349void handle_explore(const int mev, const unsigned int button, float x, float y) {
1350 /*
1351 Like handle_spherical, except:
1352 move the viewer->Pos in the opposite direction from where we are looking
1353 */
1354 int ctrl;
1356 ttglobal tg = gglobal();
1357 viewer = Viewer();
1358 ctrl = tg->Mainloop.CTRL;
1359
1360
1361 if(ctrl) {
1362 //we're in pick mode - we'll re-use some lookat code
1363 handle_lookat(mev,button,x,y);
1364 return;
1365 }
1366 if(APPROX(viewer->Dist,0.0)){
1367 //no pivot point yet
1368 handle_spherical(mev,button,x,y);
1369 return;
1370 }
1371 handle_turntable(mev, button, x, y);
1372}
1373
1374void handle_tplane(const int mev, const unsigned int button, float x, float y) {
1375 /* handle_walk with 3button mouse, RMB, can do X,Y in plane, but not rotation
1376 for touch screen with one finger, we want a nav mode called InPlane to
1377 do the X,Y shifts and rotation in the plane of the camera screen
1378 (about camera-axis/Z)
1379 */
1381 X3D_Viewer_InPlane *inplane;
1382 viewer = Viewer();
1383 inplane = &viewer->inplane;
1384
1385
1386 if (mev == ButtonPress) {
1387 inplane->x = x; //x;
1388 inplane->y = y; //y;
1389 inplane->on = 1;
1390 } else if (mev == MotionNotify) {
1391 inplane->xx = x; //.15 * xsign_quadratic(x - inplane->x,5.0,10.0,0.0)*viewer->speed * frameRateAdjustment;
1392 inplane->yy = y; //-.15f * xsign_quadratic(y - inplane->y,5.0,10.0,0.0)*viewer->speed * frameRateAdjustment;
1393 } else if(mev == ButtonRelease){
1394 inplane->xx = 0.0f;
1395 inplane->yy = 0.0f;
1396 inplane->on = 0;
1397 }
1398}
1399void handle_tick_tplane(double dtime){
1401 X3D_Viewer_InPlane *inplane;
1402 struct point_XYZ pp;
1403 viewer = Viewer();
1404
1405 inplane = &viewer->inplane;
1406 if(inplane->on){
1407 double xxx,yyy,a,b,c,d,e;
1408 xxx = (inplane->xx - inplane->x)*1.0;
1409 yyy = (inplane->yy - inplane->y)*1.0;
1410 a = 10000.0;
1411 b = 100.0;
1412 c = 1.0;
1413
1414 pp.x = xsign_cubic(xxx,a,b,c,0.0) * dtime * viewer->speed; //sqrt(viewer->Dist + 1.0);
1415 pp.y = xsign_cubic(yyy,a,b,c,0.0) * dtime * viewer->speed; //sqrt(viewer->Dist + 1.0);
1416 //if(xxx > 0.0){
1417 // double x;
1418 // x = xxx;
1419 // printf("xxx %lf %lf %lf %lf\n",x*x*x*a,x*x*b,x*c,0.0);
1420 //}
1421 //if(yyy > 0.0){
1422 // double x;
1423 // x = yyy;
1424 // printf("yyy %lf %lf %lf %lf \n",x*x*x*a,x*x*b,x*c,0.0);
1425 //}
1426
1427 pp.z = 0.0;
1428 increment_pos0(&pp);
1429 }
1430}
1431
1432void handle_rtplane(const int mev, const unsigned int button, float x, float y) {
1433 /* handle_walk with 3button mouse, RMB, can do X,Y in plane, but not rotation
1434 for touch screen with one finger, we want a nav mode called InPlane to
1435 do the X,Y shifts and rotation in the plane of the camera screen
1436 (about camera-axis/Z)
1437 */
1439 X3D_Viewer_InPlane *inplane;
1440 Quaternion nq, q_v;
1441 double xx,yy, frameRateAdjustment;
1442 ttglobal tg = gglobal();
1443 viewer = Viewer();
1444 inplane = &viewer->inplane;
1445
1446 if( tg->Mainloop.BrowserFPS > 0)
1447 frameRateAdjustment = 20.0 / tg->Mainloop.BrowserFPS; /* lets say 20FPS is our speed benchmark for developing tuning parameters */
1448 else
1449 frameRateAdjustment = 1.0;
1450
1451 if (mev == ButtonPress) {
1452 inplane->x = x;
1453 inplane->y = y;
1454 } else if (mev == MotionNotify) {
1455 //handle_tick quadratic drag
1456 inplane->xx = xsign_quadratic(x - inplane->x,0.1,0.5,0.0)*frameRateAdjustment;
1457 inplane->yy = xsign_quadratic(y - inplane->y,0.1,0.5,0.0)*frameRateAdjustment;
1458
1459 } else if (mev == ButtonRelease) {
1460 if (button == 1) {
1461 inplane->xx = 0.0f;
1462 inplane->yy = 0.0f;
1463 }
1464 }
1465}
1466
1467void handle_tick_rplane(double dtime){
1469 X3D_Viewer_InPlane *inplane;
1470 Quaternion quatr;
1471 double roll;
1472 viewer = Viewer();
1473
1474 inplane = &viewer->inplane;
1475 if(inplane->on){
1476 roll = xsign_quadratic(inplane->xx - inplane->x,2.0,2.0,0.0)*dtime;
1477 vrmlrot_to_quaternion (&quatr,0.0,0.0,1.0,roll); //roll about z axis
1478 quaternion_multiply(&(viewer->Quat), &quatr, &(viewer->Quat));
1479 quaternion_normalize(&(viewer->Quat));
1480 }
1481
1482}
1483void handle_tick_tilt(double dtime) {
1485 X3D_Viewer_InPlane *inplane;
1486 Quaternion quatt;
1487 double yaw, pitch;
1488 viewer = Viewer();
1489
1490 inplane = &viewer->inplane;
1491 if(inplane->on){
1492 yaw = xsign_quadratic(inplane->xx - inplane->x,2.0,2.0,0.0)*dtime;
1493 vrmlrot_to_quaternion (&quatt,0.0,1.0,0.0,yaw); //tilt about x axis
1494 quaternion_multiply(&(viewer->Quat), &quatt, &(viewer->Quat));
1495 pitch = -xsign_quadratic(inplane->yy - inplane->y,2.0,2.0,0.0)*dtime;
1496 vrmlrot_to_quaternion (&quatt,1.0,0.0,0.0,pitch); //tilt about x axis
1497 quaternion_multiply(&(viewer->Quat), &quatt, &(viewer->Quat));
1498 quaternion_normalize(&(viewer->Quat));
1499 }
1500}
1501
1502/************************************************************************************/
1503
1504
1505void handle0(const int mev, const unsigned int button, const float x, const float yup)
1506{
1508 viewer = Viewer();
1509
1510 switch(viewer->type){
1511 case VIEWER_WALK:
1512 case VIEWER_FLY:
1513 case VIEWER_SPHERICAL:
1514 case VIEWER_TURNTABLE:
1515 case VIEWER_EXAMINE:
1516 case VIEWER_DIST:
1517 viewer_fetch_user_offsets0(viewer);break;
1518 default:
1519 viewer_fetch_LCS(viewer);break;
1520 }
1521 /* ConsoleMessage("Viewer handle: viewer_type %s, mouse event %d, button %u, x %f, y %f\n",
1522 lookup_navmodestring(viewer->type), mev, button, x, yup); */
1523
1524 if (button == 2) {
1525 return;
1526 }
1527 switch(viewer->type) {
1528 case VIEWER_NONE:
1529 break;
1530 case VIEWER_EXAMINE:
1531 handle_examine(mev, button, ((float) x), ((float) yup));
1532 break;
1533 case VIEWER_WALK:
1534 handle_walk(mev, button, ((float) x), ((float) yup));
1535 break;
1536 case VIEWER_EXFLY:
1537 break;
1538 case VIEWER_FLY:
1539 handle_fly2(mev, button, ((float) x), ((float) yup)); //feature-Navigation_key_and_drag
1540 break;
1541 //I think these were obsolteted by drag chords - see handle_tick(
1542 //case VIEWER_FLY2:
1543 // handle_fly2(mev,button,((float) x),((float)yup));
1544 // break;
1545 //case VIEWER_TILT:
1546 //case VIEWER_RPLANE:
1547 // handle_rtplane(mev,button,((float) x),((float)yup)); //roll, tilt: one uses x, one uses y - separate handle_ticks though
1548 // break;
1549 //case VIEWER_TPLANE:
1550 // handle_tplane(mev,button,((float) x),((float)yup)); //translation in the viewer plane
1551 // break;
1552 case VIEWER_SPHERICAL:
1553 handle_spherical(mev,button,((float) x),((float)yup)); //spherical panorama
1554 break;
1555 case VIEWER_TURNTABLE:
1556 handle_turntable(mev, button, ((float)x), ((float)yup)); //examine without roll around world 0,0,0 origin - like a 3D editor with authoring plane
1557 break;
1558 case VIEWER_LOOKAT:
1559 handle_lookat(mev, button, ((float)x), ((float)yup)); //as per navigationInfo specs, you toggle on, then click an object and it flys you there
1560 break;
1561 case VIEWER_EXPLORE:
1562 handle_explore(mev, button, ((float)x), ((float)yup)); //as per specs, like turntable around any point you pick with CTRL click
1563 break;
1564 case VIEWER_DIST:
1565 handle_dist(mev,button,(float)x,(float)yup);
1566 break;
1567 default:
1568 break;
1569 }
1570 switch(viewer->type){
1571 case VIEWER_WALK:
1572 case VIEWER_FLY:
1573 case VIEWER_SPHERICAL:
1574 case VIEWER_TURNTABLE:
1575 case VIEWER_EXAMINE:
1576 case VIEWER_DIST:
1577 viewer_update_user_offsets0(viewer);break;
1578 default:
1579 viewer_update_LCS(viewer);break;
1580 }
1581
1582}
1583
1584#define FLYREMAP {{'a',NUM0},{'z',NUMDEC},{'j',LEFT_KEY},{'l',RIGHT_KEY},{'p',UP_KEY},{';',DOWN_KEY},{'8',NUM8},{'k',NUM2},{'u',NUM4},{'o',NUM6 },{'7',NUM7},{'9',NUM9}}
1585
1586//BEGIN dug9 Feb2015 >>>
1587//GOAL for this refactoring: CHORD mappings of arrow keys for keyboard navigation and mouse xy drags for FLY navigation
1588// and keeping in mind mobile devices may not want a keyboard on the screen, but they may have 4 arrow keys
1589// - and -if no change to UI menus/no use of chords by user- the default behaviour is what we do now
1590Key FLYREMAP2 [] = {{'a',NUM0},{'z',NUMDEC},{'j',LEFT_KEY},{'l',RIGHT_KEY},{'p',UP_KEY},{';',DOWN_KEY},{'8',NUM8},{'k',NUM2},{'u',NUM4},{'o',NUM6 },{'7',NUM7},{'9',NUM9}};
1591int FLYREMAP2SIZE = 12;
1592Key FLYCHORDREMAP [] = {
1593{'j',LEFT_KEY},{'l',RIGHT_KEY},{'p',UP_KEY},{';',DOWN_KEY}
1594};
1595int arrowkeys [] = {LEFT_KEY,RIGHT_KEY,UP_KEY,DOWN_KEY};
1596//int isArrowkey(int key){
1597// int iret, i;
1598// iret = 0;
1599// for(i=0;i<4;i++)
1600// if(key == arrowkeys[i]) iret = 1;
1601// return iret;
1602//}
1603int indexArrowkey(int key){
1604 int iret, i;
1605 iret = -1;
1606 for(i=0;i<4;i++)
1607 if(key == arrowkeys[i]) iret = i;
1608 return iret;
1609}
1610
1611//movements of the camera (with respect to the scene)
1612enum {
1613 FLY_X_LEFT,
1614 FLY_X_RIGHT,
1615 FLY_Y_DOWN,
1616 FLY_Y_UP,
1617 FLY_Z_FORWARD,
1618 FLY_Z_REVERSE,
1619 FLY_PITCH_UP,
1620 FLY_PITCH_DOWN,
1621 FLY_YAW_LEFT,
1622 FLY_YAW_RIGHT,
1623 FLY_ROLL_COUNTERCLOCKWISE,
1624 FLY_ROLL_CLOCKWISE,
1625} fly_key_command;
1626Key fly_normalkeys [] = {
1627 {'j',FLY_X_LEFT},
1628 {'l',FLY_X_RIGHT},
1629 {';',FLY_Y_DOWN},
1630 {'p',FLY_Y_UP},
1631 {'a',FLY_Z_FORWARD},
1632 {'z',FLY_Z_REVERSE},
1633 {'k',FLY_PITCH_UP},
1634 {'8',FLY_PITCH_DOWN},
1635 {'u',FLY_YAW_LEFT},
1636 {'o',FLY_YAW_RIGHT},
1637 {'7',FLY_ROLL_COUNTERCLOCKWISE},
1638 {'9',FLY_ROLL_CLOCKWISE},
1639};
1640
1641
1642char *chordnames [] = {"YAWZ","YAWPITCH","ROLL","XY"};
1643//the flychord table is bloated with redundancies, but explicit. FLYCHORDMAP2 int[4][4] would be briefer, but harder to trace.
1644typedef struct flychord {
1645 int chord;
1646 Key arrows[4];
1647} flychord;
1648flychord FLYCHORDREMAP2 [] = {
1649 {CHORD_YAWZ, {{FLY_YAW_LEFT,LEFT_KEY},{FLY_YAW_RIGHT,RIGHT_KEY},{FLY_Z_FORWARD,UP_KEY},{FLY_Z_REVERSE,DOWN_KEY}}},
1650 {CHORD_YAWPITCH,{{FLY_YAW_LEFT,LEFT_KEY},{FLY_YAW_RIGHT,RIGHT_KEY},{FLY_PITCH_UP,UP_KEY},{FLY_PITCH_DOWN,DOWN_KEY}}},
1651 {CHORD_ROLL, {{FLY_ROLL_COUNTERCLOCKWISE,LEFT_KEY},{FLY_ROLL_CLOCKWISE,RIGHT_KEY},{FLY_ROLL_COUNTERCLOCKWISE,UP_KEY},{FLY_ROLL_CLOCKWISE,DOWN_KEY}}},
1652 {CHORD_XY, {{FLY_X_LEFT,LEFT_KEY},{FLY_X_RIGHT,RIGHT_KEY},{FLY_Y_UP,UP_KEY},{FLY_Y_DOWN,DOWN_KEY}}},
1653};
1654
1655int viewer_getKeyChord(){
1656 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1657 return p->keychord;
1658}
1659void viewer_setKeyChord(int chord){
1660 int chord1;
1661 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1662 chord1 = chord;
1663 if(chord1 > 3) chord1 = 0;
1664 if(chord1 < 0) chord1 = 3;
1665 p->keychord = chord1;
1666}
1667char *fwl_getKeyChord(){
1668 return chordnames[viewer_getKeyChord()];
1669}
1670
1671int fwl_setKeyChord(char *chordname){
1672 int i, ok;
1673 ok = FALSE;
1674 for(i=0;i<4;i++){
1675 if(!strcasecmp(chordname,chordnames[i])){
1676 viewer_setKeyChord(i); //or should I expand from i to CHORD_YAWZ etc
1677 ok = TRUE;
1678 break;
1679 }
1680 }
1681 return ok;
1682}
1683int viewer_getDragChord(){
1684 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1685 return p->dragchord;
1686}
1687void viewer_setDragChord(int chord){
1688 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1689 p->dragchord = chord;
1690}
1691void viewer_setNextDragChord(){
1692 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1693 p->dragchord = p->dragchord == CHORD_XY ? CHORD_YAWZ : p->dragchord + 1;
1694}
1695char *fwl_getDragChord(){
1696 return chordnames[viewer_getDragChord()];
1697}
1698int fwl_setDragChord(char *chordname){
1699 int i, ok;
1700 ok = FALSE;
1701 for(i=0;i<4;i++){
1702 if(!strcasecmp(chordname,chordnames[i])){
1703 viewer_setDragChord(i); //or should I expand from i to CHORD_YAWZ etc
1704 ok = TRUE;
1705 break;
1706 }
1707 }
1708 return ok;
1709}
1710
1711//next: in lookup_fly_key we would check if its an arrow key, and if so, use the current keychord to lookup the keyfly command.
1712// from that we would look up the normal key
1713int lookup_fly_arrow(int key){
1714 //check if this is an arrow key. If so lookup in the current chord to get the motion command
1715 //and from motion command lookup the 'normal' equivalent key
1716 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1717 int idxarrow, idxnormal;
1718 int iret = 0;
1719 idxarrow = indexArrowkey(key);
1720 if(idxarrow > -1){
1721 //rather than 2 nested loops, comparing, we will trust the ordering and index in
1722 idxnormal = FLYCHORDREMAP2[p->keychord].arrows[idxarrow].key;
1723 //same here - we'll trust the order and index in
1724 iret = fly_normalkeys[idxnormal].key;
1725 }
1726 return iret;
1727}
1728//<<< END dug9 Feb2015
1729char lookup_fly_extended(int key){
1730 int i;
1731 char kp = 0;
1732 Key ps[KEYS_HANDLED] = FLYREMAP;
1733 for(i=0;i<KEYS_HANDLED;i++){
1734 if(key==ps[i].hit){
1735 kp = ps[i].key;
1736 break;
1737 }
1738 }
1739 return kp;
1740}
1741char lookup_fly_key(int key){
1742 //check for special/extended characters related to fly mode, such as numpad and arrow keys
1743 char kp = 0;
1744 kp = lookup_fly_arrow(key); //check arrow keys first
1745 if(!kp)
1746 kp = lookup_fly_extended(key); //else other extended characters
1747 return kp;
1748}
1749static struct flykey_lookup_type {
1750 char key;
1751 int motion; //translation 0, rotation 1
1752 int axis; //0=x,1=y,2=z
1753 int sign; //-1 left 1 right
1754 int command;
1755} flykey_lookup [] = {
1756 {'j', 0, 0, -1, FLY_X_LEFT},
1757 {'l', 0, 0, 1, FLY_X_RIGHT},
1758 {';', 0, 1, -1, FLY_Y_DOWN},
1759 {'p', 0, 1, 1, FLY_Y_UP,},
1760 {'a', 0, 2, -1, FLY_Z_FORWARD},
1761 {'z', 0, 2, 1, FLY_Z_REVERSE},
1762
1763 {'k', 1, 0, -1, FLY_YAW_LEFT},
1764 {'8', 1, 0, 1, FLY_YAW_RIGHT},
1765 {'u', 1, 1, -1, FLY_PITCH_UP},
1766 {'o', 1, 1, 1, FLY_PITCH_DOWN},
1767 {'7', 1, 2, -1, FLY_ROLL_COUNTERCLOCKWISE},
1768 {'9', 1, 2, 1, FLY_ROLL_CLOCKWISE}
1769};
1770
1771
1772struct flykey_lookup_type *getFlyIndex(char key){
1773 struct flykey_lookup_type *flykey;
1774 int index = -1;
1775 flykey = NULL;
1776 for(index=0;index<KEYS_HANDLED;index++){
1777 if(key == flykey_lookup[index].key ) break;
1778 }
1779 if(index > -1)
1780 flykey = &flykey_lookup[index];
1781 return flykey;
1782}
1783int isFlyKey(char key){
1784 int i, index = -1;
1785 index = indexArrowkey(key);
1786 if(index == -1)
1787 for(i=0;i<KEYS_HANDLED;i++)
1788 if(key == flykey_lookup[i].key ){
1789 index = i;
1790 break;
1791 }
1792 return index > -1 ? 1 : 0;
1793}
1794void handle_key(const char key, double keytime)
1795{
1796 char _key;
1798 X3D_Viewer_Fly *fly;
1799 struct flykey_lookup_type *flykey;
1800 viewer = Viewer();
1801
1802 fly = &viewer->fly;
1803 /* $key = lc $key; */
1804 _key = (char) tolower((int) key);
1805 if(!isFlyKey(_key)){
1806 //printf("not fly key\n");
1807 return;
1808 }
1809 //printf("is flykey\n");
1810 flykey = getFlyIndex(_key);
1811 if(flykey){
1812 if(flykey->motion > -1 && flykey->motion < 2 && flykey->axis > -1 && flykey->axis < 3){
1813 fly->down[flykey->motion][flykey->axis].direction = flykey->sign;
1814 fly->down[flykey->motion][flykey->axis].epoch = keytime; //initial keydown
1815 fly->down[flykey->motion][flykey->axis].era = keytime; //will decrement as we apply velocity in fly
1816 fly->down[flykey->motion][flykey->axis].once = 1;
1817 }
1818 }
1819}
1820
1821
1822void handle_keyrelease(const char key, double keytime)
1823{
1824 char _key;
1826 X3D_Viewer_Fly *fly;
1827 struct flykey_lookup_type *flykey;
1828 viewer = Viewer();
1829 /* my($this,$time,$key) = @_; */
1830
1831 fly = &viewer->fly;
1832
1833 /* $key = lc $key; */
1834 _key = (char) tolower((int) key);
1835 if(!isFlyKey(_key)) return;
1836 flykey = getFlyIndex(_key);
1837 if(flykey){
1838 if(flykey->motion > -1 && flykey->motion < 2 && flykey->axis > -1 && flykey->axis < 3){
1839 int *ndown = &fly->ndown[flykey->motion][flykey->axis];
1840 if((*ndown) < 10){
1841 //up to 20 key chirps per axis are stored, with their elapsed time down measured in the keyboard's thread
1842 fly->wasDown[flykey->motion][flykey->axis][*ndown].direction = fly->down[flykey->motion][flykey->axis].direction;
1843 fly->wasDown[flykey->motion][flykey->axis][*ndown].epoch = keytime - fly->down[flykey->motion][flykey->axis].epoch; //total pressedTime
1844 fly->wasDown[flykey->motion][flykey->axis][*ndown].era = keytime - fly->down[flykey->motion][flykey->axis].era; //unused keydown time
1845 fly->wasDown[flykey->motion][flykey->axis][*ndown].once = fly->down[flykey->motion][flykey->axis].once; //a flag for the handle_tick to play with
1846 (*ndown)++;
1847 }
1848 fly->down[flykey->motion][flykey->axis].direction = 0;
1849 }
1850 }
1851}
1852
1853/* wall penetration detection variables
1854 lastP - last avatar position, relative to current avatar position at 0,0,0 in avatar space
1855 - is a sum of walk_tick and collision displacement increment_pos()
1856 lastQ - quaternion increment from walk_tick which applies to previous lastP:
1857 if current frame number is i, and lastP is from i-1, then lastQ applies to i-1 lastP
1858*/
1859//struct point_XYZ viewer_lastP;
1860void viewer_lastP_clear()
1861{
1862 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1863
1864 p->viewer_lastP.x = p->viewer_lastP.y = p->viewer_lastP.z = 0.0;
1865}
1866void viewer_lastQ_set(Quaternion *lastQ)
1867{
1868 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1869 quaternion_rotation(&p->viewer_lastP,lastQ,&p->viewer_lastP);
1870}
1871void viewer_lastP_add(struct point_XYZ *vec)
1872{
1873 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1874 if(get_collision()) /* fw_params.collision use if(1) to test with toggling_collision */
1875 {
1876 VECADD(p->viewer_lastP,*vec);
1877 }
1878 else
1879 viewer_lastP_clear();
1880}
1881
1882struct point_XYZ viewer_lastP_get()
1883{
1884 /* returns a vector from avatar to the last avatar location ie on the last loop, in avatar space */
1885 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
1886
1887 struct point_XYZ nv = p->viewer_lastP;
1888 vecscale(&nv,&nv,-1.0);
1889 return nv;
1890}
1891
1892
1893
1894/*
1895 * handle_tick_walk: called once per frame.
1896 *
1897 * Sets viewer to next expected position.
1898 * This should be called before position sensor calculations
1899 * (and event triggering) take place.
1900 * Position dictated by this routine is NOT final, and is likely to
1901 * change if the viewer is left in a state of collision. (ncoder)
1902 * according to web3d specs, the gravity vector is determined by
1903 * the currently bound viewpoint vertical CBVV
1904 * walk.xd,zd are in a plane parallel to the CBV horizon.
1905 * walk.yd is vertical in the CBVV direction
1906 * walk.rd is an angle in the CBVV horizontal plane (around vertical axis parallel to the CBVV)
1907 */
1908
1909static void handle_tick_walk()
1910{
1912 X3D_Viewer_Walk *walk;
1913 double frame_rate_adjustment;
1914 Quaternion q, nq;
1915 struct point_XYZ pp;
1916 viewer = Viewer();
1917 walk = &viewer->walk;
1918
1919 //new Dec 19, 2017: per-frame dead-reckoning adjustments
1920 //- tuning translation vs rotation: when traveling forward and turning in a circle,
1921 // with mouse held constant on the drag plane (numbers computed once in handle_walk)
1922 // when a stutter / frame-stall / slowdown hits, it should not appear to
1923 // turn more or less sharp. Should still be turning on the same ground circle.
1924 frame_rate_adjustment = 10.0 * (TickTime() - lastTime());
1925
1926 //for normal walking with left button down, only walk->ZD and walk->RD are non-zero
1927 pp.x = frame_rate_adjustment * walk->XD;
1928 pp.y = frame_rate_adjustment * walk->YD;
1929 pp.z = frame_rate_adjustment * walk->ZD;
1931
1932 /* update to walk mode transforms, Feb 2018
1933 - with viewer_fetch_user_offsets() and update_user_offsets()
1934 we copy .position/.orientation <=> Quat, .Pos on each frame
1935 during navigation mouse ticks.
1936 walk mode transforms: (dug9 July 15, 2011)
1937 0.World Coordinates
1938 -- transform stack
1939 ---- 1.viewpoint node - currently bound viewpoint (CBV) gravity direction vector determined here
1940 ------ .position/(.Pos during navigation)
1941 -------- 2.#avatar body proposed - collisions, gravity and wall penetration can be computed here and += to .Pos
1942 ---------- .orientation/(.Quat during navigation) horizontal/pan part (this *= walk.RD)
1943 ------------ 3.(walk->ZD in these coords, and must be transformed by inverse(.Quat) into .Pos delta)
1944 ------------ 3.#avatar body current (BVVA) - collisions, gravity and wall penetration computed here and += to .Pos
1945 -------------- .orientation/.Quat tilts part (up/down and camera z axis tilts)^
1946 ---------------- 4.avatar camera
1947 ^There's no way for the user to tilt in walk mode. To tilt:
1948 a) switch to Fly, tilt with keyboard commands, then switch back to walk,
1949 b) script against viewpoint.orientation, or
1950 c) put non-zero viewpoint orientation in the scene file
1951 # since 2009 the walk avatar collisions,gravity,wall-pen have been done in what has been
1952 called avatar space or BVVA bound viewpoint vertical avatar - same as avatar camera
1953 with tilts removed, but pan applied, so same space as walk->ZD is applied above
1954 However because the avatar collision volume is symmetric around the vertical axis,
1955 it doesn't have to pan-rotate with the avatar to do its job, so it could be done
1956 in .position space, with a few code touch ups. This would also still work in Fly mode
1957 which has a spherically symmetric collision volume.
1958
1959 fly mode transforms:
1960 - simpler - you point, and fly in the direction you pointed, spherical collision volume:
1961 0.World
1962 1. viewpoint
1963 2. avatar position .Pos
1964 3. avatar orientation .Quat
1965 (collisions currently done here), input device XY mapped to XYZ motion here
1966 Notice the order of transforms is the same for Fly mode:
1967 .Pos += inverse(.Quat)*inputXYZ - see increment_pos()
1968
1969 (dug9 May 2015) in a bit more detail:
1970 Shape
1971 Model part of ModelView transform
1972 (world coordinates)
1973 View part of ModelView transform
1974 viewpoint
1975 viewpoint.position
1976 viewpoint.rotation
1977 opengl camera
1978
1979 */
1980
1981 q.w = (viewer->Quat).w;
1982 q.x = (viewer->Quat).x;
1983 q.y = (viewer->Quat).y;
1984 q.z = (viewer->Quat).z;
1985 vrmlrot_to_quaternion (&nq,0.0,1.0,0.0,0.4*walk->RD * 2.0 * frame_rate_adjustment);
1986 viewer_lastQ_set(&nq); //wall penetration - last avatar pose is stored before updating
1987 //split .Quat into horizontal pan and 2 tilts, then:
1988 // .Quat = .Quat * walk->RD (if I reverse the order, the tilts don't rotate with the avatar)
1989 // .Pos += inverse(planar_part(.Quat)) * walk->ZD
1990 //this should rotate the tilts with the avatar
1991 quaternion_multiply(&(viewer->Quat), &q, &nq); //Quat = walk->RD * Quat
1992 quaternion_normalize(&(viewer->Quat));
1993 {
1994 double angle;
1995 struct point_XYZ tilted;
1996 struct point_XYZ rotaxis; // = {0.0, 1.0, 0.0};
1997 Quaternion qlevel,qplanar;
1998 double dd[3];
1999 struct point_XYZ down; // = {0.0, -1.0, 0.0};
2000
2001 pointxyz2double(dd,&viewer->Up);
2002 double2pointxyz(&rotaxis,dd);
2003 vecscaled(dd,dd,-1.0);
2004 double2pointxyz(&down,dd);
2005
2006 //split .Quat into horizontal pan and 2 vertical tilts
2007 quaternion_rotation(&tilted,&q,&down);
2008 angle = vecangle2(&down,&tilted, &rotaxis);
2009 vrmlrot_to_quaternion (&qlevel,rotaxis.x,rotaxis.y,rotaxis.z,-angle);
2010
2011 quaternion_multiply(&qplanar,&qlevel,&q);
2012 //use resulting horizontal pan quat to transform walk->Z
2013 {
2014 //from increment_pos()
2015 struct point_XYZ nv;
2016 struct point_XYZ vec;
2017 Quaternion q_i;
2018 //ppViewer p = (ppViewer)gglobal()->Viewer.prv;
2019 vec.x = pp.x;
2020 vec.y = pp.y;
2021 vec.z = pp.z;
2022 viewer_lastP_add(&vec); //wall penetration - last avatar pose is stored before updating
2023
2024 /* bound-viewpoint-space > Viewer.Pos,Viewer.Quat > avatar-space */
2025 quaternion_inverse(&q_i, &qplanar); //<< I need this in increment_pos
2026 quaternion_rotation(&nv, &q_i, &vec);
2027
2028 /* save velocity calculations for this mode; used for EAI calls only */
2029 viewer->VPvelocity.x = nv.x; viewer->VPvelocity.y = nv.y; viewer->VPvelocity.z = nv.z;
2030 /* and, act on this change of location. */
2031 viewer->Pos.x += nv.x; /* Viewer.Pos must be in bound-viewpoint space */
2032 viewer->Pos.y += nv.y;
2033 viewer->Pos.z += nv.z;
2034
2035 /* printf ("increment_pos; oldpos %4.2f %4.2f %4.2f, anti %4.2f %4.2f %4.2f nv %4.2f %4.2f %4.2f \n",
2036 Viewer.Pos.x, Viewer.Pos.y, Viewer.Pos.z,
2037 Viewer.AntiPos.x, Viewer.AntiPos.y, Viewer.AntiPos.z,
2038 nv.x, nv.y, nv.z); */
2039 }
2040
2041 }
2042
2043}
2044
2045//an external program or app may want to set or get the viewer pose, with no slerping
2046//SSR - these set/getpose are called from _DisplayThread
2047static int negate_pos = TRUE;
2048void viewer_setpose( double *quat4, double *vec3){
2049 /* sign change on pos, but not quat, because freewrl conventions are different
2050 +Quat goes in direction world2vp
2051 -Pos goes in direction world2vp
2052 */
2054 double vec[3];
2055 viewer = Viewer();
2056 viewer_fetch_user_offsets0(viewer);
2057 veccopyd(vec,vec3);
2058 if(negate_pos) vecnegated(vec,vec);
2059 double2pointxyz(&viewer->Pos,vec);
2060 double2quat(&viewer->Quat,quat4);
2061 viewer_update_user_offsets0(viewer);
2062}
2063void viewer_getpose( double *quat4, double *vec3){
2064 /* Freewrl initializes .Quat, .Pos from viewpoint.position, viewpoint.orientation during viewpoint binding
2065 (or gives a default if no bound viewpoint)
2066 Viewer.Quat = inverse(vp.orientation) //changes sense from x3d vp2world, to opengl sense world2vp
2067 Viewer.Pos = vp.position //remains in x3d sense vp2world
2068 */
2070 viewer = Viewer();
2071 viewer_fetch_user_offsets0(viewer);
2072 pointxyz2double(vec3,&viewer->Pos);
2073 if(negate_pos)
2074 vecnegated(vec3,vec3);
2075 quat2double(quat4,&viewer->Quat);
2076}
2077void viewer_fetch_bindtime_pose0(X3D_Viewer *viewer, Quaternion *Quat, struct point_XYZ *Pos);
2078void viewer_getbindpose( double *quat4, double *vec3){
2079/* The bind-time-equivalent viewpoint pose can be got
2080 from the Anti variables intialized by INITIATE_POSITION_ANTIPOSITION macro
2081 which copies the .position, .orientation values from the viewpoint node fields
2082 (if a viewpoint is bound, otherwise defaults are set during startup)
2083*/
2085 viewer = Viewer();
2086 {
2087 Quaternion quat;
2088 struct point_XYZ pos;
2089 viewer_fetch_bindtime_pose0(viewer,&quat,&pos);
2090 quat2double(quat4,&quat);
2091 pointxyz2double(vec3,&pos);
2092
2093 }
2094}
2095void viewer_getview( double *viewMatrix){
2096 /* world - View - Viewpoint - .position - .orientation */
2097 //view matrix includes Transform(s) * viewpoint.position * viewpoint.orientation
2098 //we need to separate the Transforms from the .position and .orientation
2099 FW_GL_GETDOUBLEV(GL_MODELVIEW_MATRIX, viewMatrix);
2100
2101}
2102void viewer_setview( double *viewMatrix){
2103 FW_GL_SETDOUBLEV(GL_MODELVIEW_MATRIX, viewMatrix);
2104}
2105
2106/* formerly package VRML::Viewer::ExFly
2107 * entered via the "f" key.
2108 *
2109 * External input for x,y,z and quat. Reads in file
2110 * /tmp/inpdev (macro IN_FILE), which is a single line file that is
2111 * updated by some external program.
2112 *
2113 * eg:
2114 * 9.67 -1.89 -1.00 0.99923 -0.00219 0.01459 0.03640
2115 *
2116 * Do nothing for the mouse.
2117 */
2118
2119static void handle_tick_exfly()
2120{
2122 size_t len = 0;
2123 char string[STRING_SIZE];
2124 float px,py,pz,q1,q2,q3,q4;
2125 size_t rv; /* unused, but here for compile warnings */
2126 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
2127 viewer = Viewer();
2128
2129 UNUSED(rv); // mitigate compiler warnings
2130
2131 memset(string, 0, STRING_SIZE * sizeof(char));
2132
2133 if ((p->exfly_in_file = fopen(IN_FILE, "r")) == NULL) {
2134 fprintf(stderr,
2135 "Viewer handle_tick_exfly: could not open %s for read, returning to EXAMINE mode.\nSee the FreeWRL man page for further details on the usage of Fly - External Sensor input mode.\n",
2136 IN_FILE);
2137
2138 /* allow the user to continue in default Viewer mode */
2139 viewer->type = VIEWER_EXAMINE;
2140 //setMenuButton_navModes(viewer->type);
2141 return;
2142 }
2143 rv = fread(string, sizeof(char), IN_FILE_BYTES, p->exfly_in_file);
2144 if (ferror(p->exfly_in_file)) {
2145 fprintf(stderr,
2146 "Viewer handle_tick_exfly: error reading from file %s.",
2147 IN_FILE);
2148 fclose(p->exfly_in_file);
2149 return;
2150 }
2151 fclose(p->exfly_in_file);
2152
2153/* if (length($string)>0) */
2154 if ((len = strlen(string)) > 0) {
2155 if(p->exflyMethod == 0)
2156 {
2157 //MUFTI input data
2158 len = sscanf (string, "%f %f %f %f %f %f %f",&px,&py,&pz,
2159 &q1,&q2,&q3,&q4);
2160
2161 /* read error? */
2162 if (len != 7) return;
2163
2164 (viewer->Pos).x = px;
2165 (viewer->Pos).y = py;
2166 (viewer->Pos).z = pz;
2167
2168 (viewer->Quat).w = q1;
2169 (viewer->Quat).x = q2;
2170 (viewer->Quat).y = q3;
2171 (viewer->Quat).z = q4;
2172 }else if(p->exflyMethod == 1){
2173 //dug9 WiiMote data written from a C# program
2174 static int lastbut = 0;
2175 int mev, but;
2176 len = sscanf (string, "%d %f %f ",&but,&px,&py);
2177 if (len != 3) return;
2178 mev = ButtonRelease;
2179 if(but) mev = MotionNotify;
2180 if(but != lastbut)
2181 {
2182 mev = (but==1 || but==4)? ButtonPress : ButtonRelease;
2183 }
2184 // change raw wii values from ( -1 to 1 ) to (0 - 1.0)
2185 //px = (px + 1.0)*.5; //done in wiimote code
2186 //py = 1.0 - (py + 1.0)*.5; //done in wiimote code
2187 handle_walk(mev,but,px,py);
2188 handle_tick_walk();
2189 lastbut = but;
2190 }
2191 }
2192}
2193
2194
2195
2196/* FLY mode change Aug 29, 2014 dug9:
2197 I was having trouble adjusting speeds on a fast computer -
2198 - multiple keystrokes didn't change linear speed
2199 - angluar speed was too fast.
2200 New design:
2201 Goal: slow and fast frame rates both work
2202 Linear: if the user presses a key again/mulitple times before the related speed decay finishes,
2203 those keystrokes are interpreted as a desire to increase speed.
2204 Angular: brief taps do small angle adjustments 1/64 of full circle, holding key down does full rotation in 6 seconds
2205 In both cases, the keydown elapsed time is measured in the keybaord thread, not the display/rendering thread
2206*/
2207
2208static void handle_tick_fly()
2209{
2211 X3D_Viewer_Fly *fly;
2212 Quaternion q_v, nq = { 1, 0, 0, 0 };
2213 struct point_XYZ v;
2214 double changed = 0.0, time_diff = -1.0;
2215 int i;
2216 viewer = Viewer();
2217
2218 fly = &viewer->fly;
2219
2220 //sleep(400); //slow frame rate to test frame-rate-dependent actions
2221 if (fly->lasttime < 0) {
2222 fly->lasttime = TickTime();
2223 return;
2224 } else {
2225 double dtime = TickTime();
2226 time_diff = dtime - fly->lasttime;
2227 if (APPROX(time_diff, 0)) {
2228 return;
2229 }
2230 fly->lasttime = dtime;
2231 if(time_diff < 0.0) return; //skip a frame if the clock wraps around
2232 }
2233
2234
2235 /* has anything changed? if so, then re-render */
2236
2237 /* linear movement */
2238 for (i = 0; i < 3; i++) {
2239 //fade old velocity, using something like exponential decay Ni = N(i-1)*e**(k*t) where k < 0
2240 if(!fly->down[0][i].direction){
2241 double dtime = fly->lasttime - fly->down[0][i].epoch; //fly->ttransition[i][0];
2242 if(dtime > .25) //delay decay, waiting for more speed-indicating keystrokes
2243 fly->Velocity[0][i] *= pow(0.04, time_diff);
2244 }
2245 //if its almost 0, clamp to zero
2246 if(fabs(fly->Velocity[0][i]) < .001){
2247 fly->Velocity[0][i] = 0.0;
2248 }
2249 //if key action, add new velocity
2250 if(fly->down[0][i].direction){
2251 //key is currently down
2252 fly->Velocity[0][i] += (fly->ndown[0][i]+fly->down[0][i].once)*fly->down[0][i].direction * viewer->speed * .1 * max(viewer->Dist,1.0);
2253 fly->down[0][i].once = 0;
2254 fly->ndown[0][i] = 0; //p->translaten[i] = 0;
2255 //fly->ttransition[i][0] = fly->lasttime; //save time of last [i] translate, for delaying decay
2256 }
2257 changed += fly->Velocity[0][i];
2258 }
2259
2260 /* angular movement
2261 key chirp - a quck press and release on a key
2262 Velocity - (not velocity) amount of angle in radians we want to turn on this tick
2263 era - elapsed time between key down and keyup, as measured in the keyboard thread
2264 - used to ramp up angular speed based on how long you hold the key down
2265 - quick chirps on the key will give you smaller 'touch-up' angles
2266 goal: so it works with both fast frame rate/FPS and slow
2267 fast: chirps and instant visual feedback on angle turned with key held down
2268 slow: count your chirps, 64 chirps per full circle/2PI, or hold key down and count seconds 6 seconds = 2PI
2269 */
2270 for (i = 0; i < 3; i++) {
2271 static double radians_per_second = .6; //seems to turn 2x faster than this
2272 fly->Velocity[1][i] = 0.0;
2273 if(!fly->down[1][i].direction){
2274 fly->Velocity[1][i] *= pow(0.04, time_diff);
2275 }else{
2276 //the key is currently being held down, use a bit of it here
2277 double rps = radians_per_second;
2278 //double pressedEra = fly->lasttime - fly->down[1][i].epoch; //rEra[i];
2279 //normally not a chirp, but could be - a chirp here will hardly show, so no harm in double doing chirps here and below
2280 double era = fly->lasttime - fly->down[1][i].era; //- .25; //save a chirp worth because it gets chirped below when the key comes up
2281 fly->Velocity[1][i] += era * fly->down[1][i].direction * rps; // * 0.025;
2282 fly->down[1][i].era += era; //subtract what we just used
2283 //printf("*");
2284 }
2285 if(fly->ndown[1][i]){
2286 //there were some keydowns that happened between ticktimes, add their effects here
2287 int k;
2288 double rps = radians_per_second * .33;
2289 for(k=0; k<fly->ndown[1][i]; k++){
2290 double era = fly->wasDown[1][i][k].era; //unused keydown time
2291 double pressedEra = fly->wasDown[1][i][k].epoch; //total pressedTime
2292 //printf("+%f %f \n",era,pressedTime);
2293 if(pressedEra <= .1)
2294 era = .25; //a key chirp. Which can be too fast to measure in keyboard thread, so we give it a consistent down time (era)
2295 //printf("%d ",fly->wasDown[k][i][1].direction);
2296 fly->Velocity[1][i] += era * fly->wasDown[1][i][k].direction * rps; // * 0.025;
2297 }
2298 fly->ndown[1][i] = 0;
2299 }
2300 if (fabs(fly->Velocity[1][i]) > 0.8) {
2301 fly->Velocity[1][i] /= (fabs(fly->Velocity[1][i]) / 0.8);
2302 }
2303 changed += fly->Velocity[1][i];
2304 /* printf ("avel %d %f\n",i,fly->AVelocity[i]); */
2305 }
2306
2307 /* have we done anything here? */
2308 if (APPROX(changed,0.0)) return;
2309 v.x = fly->Velocity[0][0] * time_diff;
2310 v.y = fly->Velocity[0][1] * time_diff;
2311 v.z = fly->Velocity[0][2] * time_diff;
2312 increment_pos0(&v);
2313
2314 nq.x = fly->Velocity[1][0];// * time_diff;
2315 nq.y = fly->Velocity[1][1]; // * time_diff;
2316 nq.z = fly->Velocity[1][2]; // * time_diff;
2317 quaternion_normalize(&nq);
2318
2319 quaternion_set(&q_v, &(viewer->Quat));
2320 quaternion_multiply(&(viewer->Quat), &nq, &q_v);
2321 quaternion_normalize(&(viewer->Quat));
2322
2323}
2324
2325void
2326handle_tick()
2327{
2329 double dtime;
2330 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
2331 viewer = Viewer();
2332 switch(viewer->type){
2333 case VIEWER_WALK:
2334 case VIEWER_FLY:
2335 case VIEWER_SPHERICAL:
2336 case VIEWER_TURNTABLE:
2337 viewer_fetch_user_offsets0(viewer);break;
2338 default:
2339 viewer_fetch_LCS(viewer);break;
2340 }
2341 dtime = TickTime() - lastTime(); //0.0;
2342
2343 switch(viewer->type) {
2344 case VIEWER_NONE:
2345 break;
2346 case VIEWER_EXAMINE:
2347 break;
2348 case VIEWER_WALK:
2349 handle_tick_walk();
2350 break;
2351 case VIEWER_EXFLY:
2352 handle_tick_exfly();
2353 break;
2354 case VIEWER_FLY:
2355 switch(p->dragchord){
2356 case CHORD_YAWPITCH:
2357 handle_tick_tilt(dtime);
2358 break;
2359 case CHORD_ROLL:
2360 handle_tick_rplane(dtime);
2361 break;
2362 case CHORD_XY:
2363 handle_tick_tplane(dtime);
2364 break;
2365 case CHORD_YAWZ:
2366 default:
2367 handle_tick_fly2(dtime); //fly2 like (WALK - G) except no RMB PAN, drags aligned to Viewer (vs walk aligned to bound Viewpoint vertical)
2368 break;
2369 }
2370 break;
2371 //I think a few of these cases were obsoleted by drag chords above
2372 //case VIEWER_FLY2:
2373 // handle_tick_fly2(dtime); //yawz
2374 // break;
2375 //case VIEWER_TPLANE:
2376 // handle_tick_tplane(dtime);
2377 // break;
2378 //case VIEWER_RPLANE:
2379 // handle_tick_rplane(dtime);
2380 // break;
2381 //case VIEWER_TILT:
2382 // handle_tick_tilt(dtime);
2383 // break;
2384 case VIEWER_LOOKAT:
2385 handle_tick_lookat();
2386 break;
2387 case VIEWER_EXPLORE:
2388 break;
2389 case VIEWER_SPHERICAL:
2390 //do nothing special on tick
2391 break;
2392 case VIEWER_TURNTABLE:
2393 break;
2394 case VIEWER_DIST:
2395 break;
2396 default:
2397 break;
2398 }
2399 switch(viewer->type){
2400 case VIEWER_WALK:
2401 case VIEWER_FLY:
2402 case VIEWER_SPHERICAL:
2403 case VIEWER_TURNTABLE:
2404 viewer_update_user_offsets0(viewer);break;
2405 default:
2406 viewer_update_LCS(viewer);break;
2407 }
2408 if(viewer->type != VIEWER_NONE){
2409 viewer_fetch_user_offsets0(viewer);
2410 handle_tick_fly(); //Navigation-key_and_drag
2411 viewer_update_user_offsets0(viewer);
2412 }
2413 if(0) if (viewer->doExamineModeDistanceCalculations) {
2414 /*
2415 printf ("handle_tick - doing calculations\n");
2416 */
2417 CALCULATE_EXAMINE_DISTANCE
2418 p->examineCounter --;
2419
2420 if (p->examineCounter < 0) {
2421 viewer->doExamineModeDistanceCalculations = FALSE;
2422 p->examineCounter = 5;
2423 }
2424 }
2425
2426
2427}
2428
2429
2430
2431/*
2432 * Semantics: given a viewpoint and orientation,
2433 * we take the center to revolve around to be the closest point to origin
2434 * on the z axis.
2435 * Changed Feb27 2003 JAS - by fixing $d to 10.0, we make the rotation
2436 * point to be 10 metres in front of the user.
2437 */
2438
2439/* ArcCone from TriD */
2440void
2441xy2qua(Quaternion *ret, const double x, const double y)
2442{
2443 double _x = x - 0.5, _y = y - 0.5, _z, dist;
2444 _x *= 2;
2445 _y *= 2;
2446
2447 dist = sqrt((_x * _x) + (_y * _y));
2448
2449 if (dist > 1.0) {
2450 _x /= dist;
2451 _y /= dist;
2452 dist = 1.0;
2453 }
2454 _z = 1 - dist;
2455
2456 ret->w = 0;
2457 ret->x = _x;
2458 ret->y = _y;
2459 ret->z = _z;
2460 quaternion_normalize(ret);
2461}
2462
2463
2464
2465//static GLboolean acMask[2][3]; //anaglyphChannelMask
2466void setmask(GLboolean *mask,int r, int g, int b)
2467{
2468 mask[0] = (GLboolean)r;
2469 mask[1] = (GLboolean)g;
2470 mask[2] = (GLboolean)b;
2471}
2472void Viewer_anaglyph_setSide(int iside)
2473{
2474 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
2475 /* draw in gray */
2476 /* and use channel masks */
2477 GLboolean t = 1;
2478 glColorMask(p->acMask[iside][0],p->acMask[iside][1],p->acMask[iside][2],t);
2479}
2480void Viewer_anaglyph_clearSides()
2481{
2482 glColorMask(1,1,1,1);
2483}
2484//true static:
2485static char * RGBACM = "RGBACM";
2486static int indexRGBACM(int a)
2487{
2488 return (int) (strchr(RGBACM,a)-RGBACM);
2489}
2490int getAnaglyphPrimarySide(int primary, int iside){
2491 //primary red=0, green=1, blue=2
2492 //iside left=0, right=1, neither=2
2493 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
2494 return (int)p->acMask[iside][primary];
2495}
2496
2497void setAnaglyphPrimarySide(int primary, int iside){
2498 //primary red=0, green=1, blue=2
2499 //iside left=0, right=1, neither=2
2500 //it assumes you are setting it to true,
2501 //and turns other sides off the primary automatically
2502 //the user interface should look like this:
2503 //R G B
2504 //* Left
2505 // * Right
2506 // * Neither
2507 //the neither is side=2, and allows the user to turn a primary off all sides
2508 int i;
2509 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
2510 for(i=0;i<3;i++)
2511 if(iside == i)
2512 p->acMask[i][primary] = (GLboolean)1;
2513 else
2514 p->acMask[i][primary] = (GLboolean)0;
2515}
2516void setAnaglyphSideColor(char val, int iside)
2517{
2519 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
2520 viewer = Viewer();
2521 viewer->iprog[iside] = indexRGBACM(val);
2522 if(viewer->iprog[iside] == -1 )
2523 {
2524 printf ("warning, command line anaglyph parameter incorrect - was %c need something like RG\n",val);
2525 viewer->iprog[iside] = iside;
2526 }
2527 /* used for anaglyphMethod==2 */
2528 switch (viewer->iprog[iside]) {
2529 case 0: //'R':
2530 setmask(p->acMask[iside],1,0,0);
2531 break;
2532 case 1: //'G':
2533 setmask(p->acMask[iside],0,1,0);
2534 break;
2535 case 2: //'B':
2536 setmask(p->acMask[iside],0,0,1);
2537 break;
2538 case 3: //'A':
2539 setmask(p->acMask[iside],1,1,0);
2540 break;
2541 case 4: //'C':
2542 setmask(p->acMask[iside],0,1,1);
2543 break;
2544 case 5://'M':
2545 setmask(p->acMask[iside],1,0,1);
2546 break;
2547 }
2548}
2549void fwl_set_AnaglyphParameter(const char *optArg) {
2550/*
2551 NOTE: "const char" means that you wont modify it in the function :)
2552 */
2554 const char* glasses;
2555 int len;
2556 viewer = Viewer();
2557
2558 glasses = optArg;
2559 len = (int) strlen(optArg);
2560 if(len !=2 && len != 3)
2561 {
2562 printf ("warning, command line anaglyph parameter incorrect - was %s need something like RC or LRN\n",optArg);
2563 glasses ="RC"; len = 2;
2564 }
2565 if(len == 2){
2566 setAnaglyphSideColor(glasses[0],0);
2567 setAnaglyphSideColor(glasses[1],1);
2568 }else if(len == 3){
2569 int i, iside;
2570 for(i=0;i<3;i++){
2571 switch(optArg[i]){
2572 case 'L': iside = 0;break;
2573 case 'R': iside = 1;break;
2574 case 'N': iside = 2;break;
2575 default:
2576 iside = 2;
2577 }
2578 setAnaglyphPrimarySide(i,iside);
2579 }
2580 }
2581 viewer->anaglyph = 1; /*0=none 1=active */
2582 viewer->shutterGlasses = 0;
2583 viewer->sidebyside = 0;
2584 viewer->updown = 0;
2585 viewer->isStereo = 1;
2586 setStereoBufferStyle(1);
2587}
2588/* shutter glasses, stereo view from Mufti@rus */
2589/* handle setting shutter from parameters */
2590void fwl_init_Shutter (void)
2591{
2592 /* if you put --shutter on the command line, you'll come in here twice:
2593 first: from options.c but haveQuadbuffer will == 0 because we haven't init gl yet, so don't know
2594 second: post_gl_init - we'll know haveQuadbuffer which might = 1 (if not it goes into flutter mode)
2595 */
2597 ttglobal tg = gglobal();
2598 viewer = Viewer();
2599
2600 tg->display.shutterGlasses = 2;
2601 viewer->shutterGlasses = 2;
2602 setStereoBufferStyle(1);
2603 if(viewer->haveQuadbuffer)
2604 {
2605 tg->display.shutterGlasses = 1; /* platform specific pixelformat/window initialization code should hint PRF_STEREO */
2606 viewer->shutterGlasses = 1;
2607 setStereoBufferStyle(0);
2608 }
2609 viewer->isStereo = 1;
2610
2611}
2612
2613void fwl_init_SideBySide()
2614{
2616 viewer = Viewer();
2617
2618 setStereoBufferStyle(1);
2619 viewer->isStereo = 1;
2620 viewer->sidebyside = 1;
2621 viewer->screendist = min(viewer->screendist,.375);
2622 viewer->stereoParameter = min(viewer->stereoParameter,.01);
2623}
2624void fwl_init_UpDown()
2625{
2627 viewer = Viewer();
2628
2629 setStereoBufferStyle(1);
2630 viewer->isStereo = 1;
2631 viewer->updown = 1;
2632 viewer->screendist = min(viewer->screendist,.375);
2633 viewer->stereoParameter = min(viewer->stereoParameter,.01);
2634}
2635
2636void clear_shader_table();
2637void setAnaglyph()
2638{
2640 viewer = Viewer();
2641
2642 /* called from post_gl_init and hud/options (option.c calls fwl_set_AnaglyphParameter above) */
2643 viewer->anaglyph = 1;
2644 viewer->isStereo = 1;
2645 clear_shader_table();
2646 setStereoBufferStyle(1);
2647}
2648void setMono()
2649{
2651 ttglobal tg = gglobal();
2652 viewer = Viewer();
2653
2654 viewer->isStereo = 0;
2655 if(viewer->anaglyph)
2656 {
2657 glColorMask(1,1,1,1);
2658 clear_shader_table();
2659 }
2660 viewer->anaglyph = 0;
2661 viewer->sidebyside = 0;
2662 viewer->updown = 0;
2663 viewer->shutterGlasses = 0;
2664 tg->display.shutterGlasses = 0;
2665
2666}
2667
2668/*
2669#define VIEWER_STEREO_OFF 0
2670#define VIEWER_STEREO_SHUTTERGLASSES 1
2671#define VIEWER_STEREO_SIDEBYSIDE 2
2672#define VIEWER_STEREO_ANAGLYPH 3
2673#define VIEWER_STEREO_UPDOWN 4
2674*/
2675
2676static void setStereo(int type)
2677{
2678 /* type: 0 off 1 shutterglasses 2 sidebyside 3 analgyph */
2679 /* can only be called after opengl is initialized */
2680 gglobal()->Viewer.stereotype = type;
2681 setMono();
2682 switch(type)
2683 {
2684 case VIEWER_STEREO_OFF: {/*setMono()*/;break;}
2685 case VIEWER_STEREO_SHUTTERGLASSES: {fwl_init_Shutter(); break;}
2686 case VIEWER_STEREO_SIDEBYSIDE: {fwl_init_SideBySide(); break;}
2687 case VIEWER_STEREO_ANAGLYPH: {setAnaglyph(); break;}
2688 case VIEWER_STEREO_UPDOWN: {fwl_init_UpDown(); break;}
2689 default: break;
2690 }
2691}
2692void toggleOrSetStereo(int type)
2693{
2694 /* if user clicks the active stereovision type on a HUD, then it should turn it off - back to mono
2695 if it's not active, then it should be set active*/
2697 int curtype, shut;
2698 viewer = Viewer();
2699
2700 shut = viewer->shutterGlasses ? 1 : 0;
2701 curtype = viewer->isStereo*( (shut)*1 + viewer->sidebyside*2 + viewer->anaglyph*3 + viewer->updown*4);
2702 if(type != curtype) {
2703 setStereo(type);
2704 } else {
2705 setMono();
2706 gglobal()->Viewer.stereotype = 0;
2707 }
2708
2709}
2710void fwl_setPickraySide(int ipreferredSide, int either){
2712 viewer = Viewer();
2713 viewer->dominantEye = ipreferredSide;
2714 viewer->eitherDominantEye = either;
2715
2716}
2717void fwl_getPickraySide(int *ipreferredSide, int *either){
2719 viewer = Viewer();
2720 *ipreferredSide = viewer->dominantEye ;
2721 *either = viewer->eitherDominantEye;
2722}
2723void updateEyehalf()
2724{
2726 viewer = Viewer();
2727 if( viewer->screendist != 0.0)
2728 {
2729 //old semantics (variable meanings)
2730 //eyedist - object space distance between left and right viewpoints
2731 //screendist - distance to toe-in target
2732 //stereoParameter - distance from infinity line to toe-in target
2733 //set_eyehalf( viewer->eyedist/2.0,atan2(viewer->eyedist/2.0,viewer->screendist)*360.0/(2.0*3.1415926));
2734
2735 //new semantics as of March 12, 2012
2736 //eyedist - object space distance between left and right viewpoints
2737 //stereoParameter - tan(toe in angle per side)
2738 // 0=looking at infinity
2739 // 1= 45 degree toe-in per side (90 degree converengence)
2740 // .4 = 22 degree toe-in angle per side
2741 //screendist - distance from viewpoint center to 'central' viewport edge
2742 // - measured in fraction-of side-viewport
2743 // - central viewport edge:
2744 // left edge of right stereo viewport
2745 // right edge of left stereo viewport
2746 // = .5 - for shutterglasses and anaglyph, both sides are centered on the screen
2747 // - for sidebyside, both sides are centered on their respective left and right viewports
2748 // average human eyebase 65mm or 2.5" - we prefer 2.25" or 57mm. For a 6" screen 2.25/6 = .375
2749 set_eyehalf( viewer->eyedist/2.0,atan(viewer->stereoParameter)*180.0/3.1415926);
2750 }
2751}
2752
2753void viewer_postGLinit_init(void)
2754{
2755
2757 int type;
2758 s_renderer_capabilities_t *rdr_caps;
2759 ttglobal tg = gglobal();
2760 viewer = Viewer();
2761 rdr_caps = tg->display.rdr_caps;
2762
2763 // see if we can use quad buffer here or not.
2764 viewer->haveQuadbuffer = (rdr_caps->quadBuffer== GL_TRUE);
2765
2766 //if (viewer->haveQuadbuffer) ConsoleMessage ("viewer_postGLinit_init, HAVE quad buffer"); else ConsoleMessage ("viewer_postGLinit, no quad buffer");
2767
2768 updateEyehalf();
2769
2770 type = VIEWER_STEREO_OFF;
2771 if( viewer->shutterGlasses ) type = VIEWER_STEREO_SHUTTERGLASSES;
2772 if( viewer->sidebyside ) type = VIEWER_STEREO_SIDEBYSIDE;
2773 if( viewer->updown ) type = VIEWER_STEREO_UPDOWN;
2774 if( viewer->anaglyph ==1 ) type = VIEWER_STEREO_ANAGLYPH;
2775
2776 if(type==VIEWER_STEREO_SHUTTERGLASSES)
2777 {
2778 // does this opengl driver/hardware support GL_STEREO? p.469, p.729 RedBook and
2779 // WhiteDune > swt.c L1306
2780 if (!viewer->haveQuadbuffer ) {
2781 ConsoleMessage("Unable to get quadbuffer stereo visual, switching to flutter mode\n");
2782 }
2783 }
2784
2785 setStereo(type);
2786
2787
2788}
2789
2790void fwl_set_StereoParameter (const char *optArg) {
2791
2793 int i;
2794 viewer = Viewer();
2795
2796 i = sscanf(optArg,"%lf",&viewer->stereoParameter);
2797 if (i==0) printf ("warning, command line stereo parameter incorrect - was %s\n",optArg);
2798 else updateEyehalf();
2799}
2800
2801void fwl_set_EyeDist (const char *optArg) {
2802 int i;
2804 viewer = Viewer();
2805
2806 i= sscanf(optArg,"%lf",&viewer->eyedist);
2807 if (i==0) printf ("warning, command line eyedist parameter incorrect - was %s\n",optArg);
2808 else updateEyehalf();
2809}
2810
2811void fwl_set_ScreenDist (const char *optArg) {
2812 int i;
2814 viewer = Viewer();
2815
2816 i= sscanf(optArg,"%lf",&viewer->screendist);
2817 if (i==0) printf ("warning, command line screendist parameter incorrect - was %s\n",optArg);
2818 else updateEyehalf();
2819}
2820/* end of Shutter glasses, stereo mode configure */
2821
2822void set_stereo_offset0() /*int iside, double eyehalf, double eyehalfangle)*/
2823{
2824 double x = 0.0, angle = 0.0;
2826 viewer = Viewer();
2827
2828 if (viewer->iside == 0) {
2829 /* left */
2830 x = viewer->eyehalf;
2831 angle = viewer->eyehalfangle; //old semantics: * viewer->stereoParameter; /*stereoparamter: 0-1 1=toe in to cross-over at Screendist 0=look at infinity, eyes parallel*/
2832 } else if (viewer->iside == 1) {
2833 /* right */
2834 x = -viewer->eyehalf;
2835 angle = -viewer->eyehalfangle; //old semantics: * viewer->stereoParameter;
2836 }
2837 FW_GL_TRANSLATE_D(x, 0.0, 0.0);
2838 FW_GL_ROTATE_D(angle, 0.0, 1.0, 0.0);
2839}
2840void geoviewpoint_fetch_TCS(struct X3D_GeoViewpoint *vp, Quaternion *Quat, struct point_XYZ *Pos);
2841void geoviewpoint_update_TCS(struct X3D_GeoViewpoint *vp, Quaternion *Quat, struct point_XYZ *Pos);
2842
2843void viewer_update_user_offsets0(X3D_Viewer *viewer){
2844 //call this often when navigating
2845 //saves accumulated navigation from bind pose, per viewpoint
2846
2847 struct X3D_Node *boundvp;
2848 boundvp = getActiveLayerBoundViewpoint();
2849 if(boundvp){
2850 switch(boundvp->_nodeType){
2851 case NODE_OrthoViewpoint:
2852 {
2853 double oo[4];
2854 struct X3D_OrthoViewpoint *vp = (struct X3D_OrthoViewpoint*)boundvp;
2855 vecset3f(vp->position.c,viewer->Pos.x,viewer->Pos.y,viewer->Pos.z);
2856 quaternion_to_vrmlrot(&viewer->Quat,&oo[0],&oo[1],&oo[2],&oo[3]);
2857 oo[3] = -oo[3]; //historically all our navigation Quat work was done -ve
2858 double2float(vp->orientation.c,oo,4);
2859 }
2860 break;
2861 case NODE_Viewpoint:
2862 {
2863 double oo[4];
2864 struct X3D_Viewpoint *vp = (struct X3D_Viewpoint*)boundvp;
2865 vecset3f(vp->position.c,viewer->Pos.x,viewer->Pos.y,viewer->Pos.z);
2866 quaternion_to_vrmlrot(&viewer->Quat,&oo[0],&oo[1],&oo[2],&oo[3]);
2867 oo[3] = -oo[3];
2868 double2float(vp->orientation.c,oo,4);
2869 }
2870 break;
2871 case NODE_GeoViewpoint:
2872 {
2873 double pos[3],pos0[3],quat[4],quat0[4];
2874 struct X3D_GeoViewpoint *vp = (struct X3D_GeoViewpoint*)boundvp;
2875 pointxyz2double(pos,&viewer->Pos);
2876 pointxyz2double(pos0,&viewer->Pos0);
2877 quat2double(quat,&viewer->Quat);
2878 quat2double(quat0,&viewer->Quat0);
2879 if(veclengthd(vecdifd(pos,pos,pos0)) > .002 || veclength4d(vecdif4d(quat,quat,quat0)) > .00002)
2880 geoviewpoint_update_TCS(vp,&viewer->Quat,&viewer->Pos);
2881 }
2882 break;
2883 default:
2884 break;
2885 }
2886 }
2887}
2888void viewer_fetch_user_offsets0(X3D_Viewer *viewer){
2889 //call this once, when binding/just after binding, to a viewpoint, if vp->retainUserOffsets == TRUE
2890 //lets user carry on from where they left off with a given viewpoint
2891 struct X3D_Node *boundvp;
2892 boundvp = getActiveLayerBoundViewpoint();
2893 if(boundvp){
2894 switch(boundvp->_nodeType){
2895 case NODE_OrthoViewpoint:
2896 {
2897 struct X3D_OrthoViewpoint *vp = (struct X3D_OrthoViewpoint*)boundvp;
2898 double oo[4], pp[3];
2899 float2double(pp,vp->position.c,3);
2900 double2pointxyz(&viewer->Pos,pp);
2901 //double2pointxyz(&viewer->AntiPos,pp);
2902 float2double(oo,vp->orientation.c,4);
2903 vrmlrot_to_quaternion(&viewer->Quat,oo[0],oo[1],oo[2],-oo[3]);
2904 }
2905 break;
2906 case NODE_Viewpoint:
2907 {
2908 struct X3D_Viewpoint *vp = (struct X3D_Viewpoint*)boundvp;
2909 Quaternion q_i;
2910 double oo[4], pp[3];
2911 float2double(pp,vp->position.c,3);
2912 double2pointxyz(&viewer->Pos,pp);
2913 float2double(oo,vp->orientation.c,4);
2914 vrmlrot_to_quaternion(&viewer->Quat,oo[0],oo[1],oo[2],-oo[3]);
2915 }
2916 break;
2917 case NODE_GeoViewpoint:
2918 {
2919 struct X3D_GeoViewpoint *vp = (struct X3D_GeoViewpoint*)boundvp;
2920 geoviewpoint_fetch_TCS(vp,&viewer->Quat,&viewer->Pos);
2921 //save for noise check on update
2922 viewer->Quat0 = viewer->Quat;
2923 viewer->Pos0 = viewer->Pos;
2924 }
2925 break;
2926 default:
2927 break;
2928 }
2929 }
2930
2931
2932}
2933void geoviewpoint_fetch_LCS(struct X3D_GeoViewpoint *node, Quaternion *Quat, struct point_XYZ *Pos);
2934void geoviewpoint_update_LCS(struct X3D_GeoViewpoint *node, Quaternion *Quat, struct point_XYZ *Pos);
2935void viewer_fetch_LCS(X3D_Viewer *viewer){
2936 //LCS: local coordinate system
2937 //NLS: node-local system
2938 //for regular viewpoint, orthoviewpoint LCS is the same as NLS
2939 //for geoviewpoint, LCS is the shared euclidean system, NLS is local, and changes with gdCoords on each tick
2940 struct X3D_Node *boundvp;
2941 boundvp = getActiveLayerBoundViewpoint();
2942 if(boundvp){
2943 switch(boundvp->_nodeType){
2944 case NODE_OrthoViewpoint:
2945 case NODE_Viewpoint:
2946 viewer_fetch_user_offsets0(viewer);
2947 break;
2948 case NODE_GeoViewpoint:
2949 {
2950 struct X3D_GeoViewpoint *vp = (struct X3D_GeoViewpoint*)boundvp;
2951 geoviewpoint_fetch_LCS(vp,&viewer->Quat,&viewer->Pos);
2952 //save for noise check on update
2953 viewer->Quat0 = viewer->Quat;
2954 viewer->Pos0 = viewer->Pos;
2955
2956 }
2957 break;
2958 default:
2959 break;
2960 }
2961 }
2962}
2963void viewer_update_LCS(X3D_Viewer *viewer){
2964 struct X3D_Node *boundvp;
2965 boundvp = getActiveLayerBoundViewpoint();
2966 if(boundvp){
2967 switch(boundvp->_nodeType){
2968 case NODE_OrthoViewpoint:
2969 case NODE_Viewpoint:
2970 viewer_update_user_offsets0(viewer);
2971 break;
2972 case NODE_GeoViewpoint:
2973 {
2974 double pos[3],pos0[3],quat[4],quat0[4];
2975 struct X3D_GeoViewpoint *vp = (struct X3D_GeoViewpoint*)boundvp;
2976 pointxyz2double(pos,&viewer->Pos);
2977 pointxyz2double(pos0,&viewer->Pos0);
2978 quat2double(quat,&viewer->Quat);
2979 quat2double(quat0,&viewer->Quat0);
2980 if(veclengthd(vecdifd(pos,pos,pos0)) > .002 || veclength4d(vecdif4d(quat,quat,quat0)) > .00002)
2981 geoviewpoint_update_LCS(vp,&viewer->Quat,&viewer->Pos);
2982
2983 }
2984 break;
2985 default:
2986 break;
2987 }
2988 }
2989
2990}
2991void viewer_fetch_bindtime_pose0(X3D_Viewer *viewer, Quaternion *Quat, struct point_XYZ *Pos){
2992 //call this once, when binding/just after binding, to a viewpoint, if vp->retainUserOffsets == TRUE
2993 //lets user carry on from where they left off with a given viewpoint
2994 struct X3D_Node *boundvp;
2995 boundvp = getActiveLayerBoundViewpoint();
2996 if(boundvp){
2997 switch(boundvp->_nodeType){
2998 case NODE_OrthoViewpoint:
2999 {
3000 struct X3D_OrthoViewpoint *vp = (struct X3D_OrthoViewpoint*)boundvp;
3001 double oo[4], pp[3];
3002 float2double(pp,vp->_position.c,3);
3003 double2pointxyz(&viewer->Pos,pp);
3004 //double2pointxyz(&viewer->AntiPos,pp);
3005 float2double(oo,vp->_orientation.c,4);
3006 vrmlrot_to_quaternion(&viewer->Quat,oo[0],oo[1],oo[2],-oo[3]);
3007 }
3008 break;
3009 case NODE_Viewpoint:
3010 {
3011 struct X3D_Viewpoint *vp = (struct X3D_Viewpoint*)boundvp;
3012 Quaternion q_i;
3013 double oo[4], pp[3];
3014 float2double(pp,vp->_position.c,3);
3015 double2pointxyz(&viewer->Pos,pp);
3016 float2double(oo,vp->_orientation.c,4);
3017 vrmlrot_to_quaternion(&viewer->Quat,oo[0],oo[1],oo[2],-oo[3]);
3018 }
3019 break;
3020 case NODE_GeoViewpoint:
3021 {
3022 struct X3D_GeoViewpoint *vp = (struct X3D_GeoViewpoint*)boundvp;
3023 double oo[4];
3024 float2double(oo,vp->_orientation.c,4);
3025 vrmlrot_to_quaternion(Quat,oo[0],oo[1],oo[2], -oo[3]);
3026 double2pointxyz(Pos,vp->_position.c);
3027 }
3028 break;
3029 default:
3030 break;
3031 }
3032 }
3033
3034
3035}
3036/* used to move, in WALK, FLY modes. */
3037void increment_pos0(struct point_XYZ *vec) {
3038 struct point_XYZ nv;
3039 Quaternion q_i;
3041 viewer = Viewer();
3042 viewer_lastP_add(vec);
3043
3044 /* bound-viewpoint-space > Viewer.Pos,Viewer.Quat > avatar-space */
3045 quaternion_inverse(&q_i, &(viewer->Quat));
3046 quaternion_rotation(&nv, &q_i, vec);
3047
3048 /* save velocity calculations for this mode; used for EAI calls only */
3049 viewer->VPvelocity.x = nv.x; viewer->VPvelocity.y = nv.y; viewer->VPvelocity.z = nv.z;
3050 /* and, act on this change of location. */
3051 viewer->Pos.x += nv.x; /* Viewer.Pos must be in bound-viewpoint space */
3052 viewer->Pos.y += nv.y;
3053 viewer->Pos.z += nv.z;
3054
3055 /* printf ("increment_pos; oldpos %4.2f %4.2f %4.2f, anti %4.2f %4.2f %4.2f nv %4.2f %4.2f %4.2f \n",
3056 Viewer.Pos.x, Viewer.Pos.y, Viewer.Pos.z,
3057 Viewer.AntiPos.x, Viewer.AntiPos.y, Viewer.AntiPos.z,
3058 nv.x, nv.y, nv.z); */
3059}
3060void increment_pos(struct point_XYZ *vec) {
3062 viewer = Viewer();
3063 viewer_fetch_user_offsets0(viewer);
3064 increment_pos0(vec);
3065 viewer_update_user_offsets0(viewer);
3066}
3067
3068/* We have a OrthoViewpoint node being bound. (not a GeoViewpoint node) */
3069void bind_OrthoViewpoint (struct X3D_OrthoViewpoint *vp) {
3070 Quaternion q_i;
3071 float xd, yd,zd;
3073 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
3074 viewer = ViewerByLayerId(vp->_layerId);
3075
3076
3077 /* did bind_node tell us we could bind this guy? */
3078 if (!(vp->isBound)) return;
3079 if(!vp->_initializedOnce) {
3080 //save the scene design-time pos,ori for rebinding with no user offsets
3081 veccopy3f(vp->_position.c,vp->position.c);
3082 veccopy4f(vp->_orientation.c,vp->orientation.c);
3083 vp->_initializedOnce = TRUE;
3084 }
3085
3086 if(!vp->retainUserOffsets){
3087 veccopy3f(vp->position.c,vp->_position.c);
3088 veccopy4f(vp->orientation.c,vp->_orientation.c);
3089 }
3090 viewer = ViewerByLayerId(vp->_layerId);
3091 //printf("retained user pose=%lf %lf %lf\n",viewer->Pos.x,viewer->Pos.y,viewer->Pos.z);
3092 //printf("retained user.Quat= %lf %lf %lf %lf\n",viewer->Quat.x,viewer->Quat.y,viewer->Quat.z,viewer->Quat.w);
3093
3094 if (viewer->transitionType != VIEWER_TRANSITION_TELEPORT && viewer->wasBound) {
3095 //save the previous vp pose, in root space, for future slerps
3096 viewer->vp2rnSaved = TRUE; //we bind after prep_viewpoint > setup_viewpoint in rendersceneupdatescene0
3097 //we bind from the root, so this would be setup_viewpoint_1() and _2()
3098 //- the viewmatrix including .position,.orientation,.Pos,.Quat, stereo
3099 {
3100 bindablestack* bstack = getActiveBindableStacks(gglobal());
3101 matcopy(viewer->slerp_viewmatrix,bstack->viewtransformmatrix);
3102 matcopy(viewer->slerp_posorimatrix,bstack->posorimatrix);
3103
3104 }
3105
3106 viewer->SLERPing = FALSE; //TRUE;
3107 viewer->startSLERPtime = TickTime();
3108 /* slerp Mark II */
3109 viewer->SLERPing2 = TRUE;
3110 viewer->SLERPing2justStarted = TRUE;
3111 //printf("binding\n");
3112
3113 } else {
3114 viewer->SLERPing = FALSE;
3115 viewer->SLERPing2 = FALSE;
3116 }
3117 viewer->wasBound = TRUE;
3118
3119 /* calculate distance between the node position and defined centerOfRotation */
3120 INITIATE_POSITION
3121
3122 /* assume Perspective, unless Otrho set */
3123 viewer->ortho=TRUE;
3124 if (vp->fieldOfView.n == 4) {
3125 /* Ortho mapping - glOrtho order left/right/bottom/top
3126 assume X3D says left bottom right top */
3127 viewer->orthoField[0] = (double) vp->fieldOfView.p[0];
3128 viewer->orthoField[1] = (double) vp->fieldOfView.p[1];
3129 viewer->orthoField[2] = (double) vp->fieldOfView.p[2];
3130 viewer->orthoField[3] = (double) vp->fieldOfView.p[3];
3131 } else {
3132 ERROR_MSG("OrthoViewpoint - fieldOfView must have 4 parameters");
3133 viewer->orthoField[0] = -1.0;
3134 viewer->orthoField[1] = -1.0;
3135 viewer->orthoField[2] = 1.0;
3136 viewer->orthoField[3] = 1.0;
3137 }
3138
3139 /* printf ("orthoviewpoint binding distance %f\n",Viewer.Dist); */
3140
3141 /* set the examine mode rotation origin */
3142 INITIATE_ROTATION_ORIGIN
3143
3144 /*
3145
3146 From specs > abstract > architecture > 23.3.5 Viewpoint
3147 "When a Viewpoint node is at the top of the stack, the user's view is
3148 conceptually re-parented as a child of the Viewpoint node. All subsequent changes to the Viewpoint node's
3149 coordinate system change the user's view (e.g., changes to any ancestor transformation nodes or to
3150 the Viewpoint node's position or orientation fields)."
3151
3152 "Navigation types (see 23.3.4 NavigationInfo) that require a definition of a down vector (e.g., terrain following)
3153 shall use the negative Y-axis of the coordinate system of the currently bound Viewpoint node.
3154 Likewise, navigation types that require a definition of an up vector shall use the positive Y-axis of the
3155 coordinate system of the currently bound Viewpoint node. The orientation field of the Viewpoint node does
3156 not affect the definition of the down or up vectors. This allows the author to separate the viewing direction
3157 from the gravity direction."
3158
3159 concept of transformations Jan3,2010:
3160world coords > [Transform stack] > bound Viewpoint > [Viewer.Pos,.Quat] > avatar
3161" < inverse[Transformstack] < " < [AntiPos,AntiQuat] < avatar
3162 gravity (according to specs): Y-down in the (bound Viewpoint local coords).
3163 The viewpoint node orientation field doesn't count toward specs-gravity.
3164 If you want global-gravity, then put your viewpoint node at the scene level, or compute a
3165 per-frame gravity for spherical worlds - see mainloop.c render_collisions.
3166
3167 Implication: the user button LEVEL should level out/cancel/zero the bound-viewpoint's orientation field value.
3168
3169 */
3170
3171 viewer_lastP_clear();
3172 setMenuStatusVP (vp->description->strptr);
3173
3174}
3175
3176/* called from main, after the new viewpoint is setup */
3177int slerp_viewpoint2()
3178{
3179 //slerp_viewpoint method 2: for viewpoint bind-time slerping
3180 int iret, itype;
3182 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
3183 viewer = Viewer();
3184 itype = 2;
3185 iret = 0;
3186 if(viewer->SLERPing2 && viewer->vp2rnSaved && itype==2) {
3187 double mat_to[16],mat_from[16];
3188 bindablestack *bstack;
3189 ttglobal tg = gglobal();
3190 bstack = getActiveBindableStacks(tg);
3191
3192 matmultiplyAFFINE(mat_to,bstack->viewtransformmatrix,bstack->posorimatrix);
3193 matmultiplyAFFINE(mat_from,viewer->slerp_viewmatrix,viewer->slerp_posorimatrix);
3194
3195 //viewpoint slerp-on-bind comes through here
3196 if(0){
3197 //simpler matrix slerp, works, but with distortions during slerp, and complaints from proximity sensors
3198 //theory: we are called from startofloopnodeupdates at the rootnode level
3199 //and we have the prior view matrix (saved in bind_viewpoint) and the current view matrix.
3200 //so we could slerp between the two, and reset the viewmatrix in the matrix stack
3201 //problems with theory: we don't use the opengl matrix stack straightforwardly
3202 //in mainloop.c void render() we call set_viewmatrix() which fetches out of bstack bits of matrices
3203 // stored there in setup_viewpoint1,2,3. and resets the opengl matrix.
3204 //option 1: add (yet another) bstack matrix for slerping
3205 //option 2: apply our slerp difference to one or both of the bstack matrices viewtransformmatrix and/or posorimatrix
3206 // its option 2 viewtransformmatrix we modify.
3207 if(viewer->SLERPing2justStarted)
3208 {
3209 viewer->SLERPing2justStarted = FALSE;
3210 }
3211 {
3212 double tickFrac;
3213 double matnow[16],matdif[16], matid[16], mat_toi[16], matview[16], matnew[16];;
3214 tickFrac = (TickTime() - viewer->startSLERPtime)/viewer->transitionTime;
3215 tickFrac = DOUBLE_MIN(tickFrac,1.0);
3216 tickFrac = DOUBLE_MAX(tickFrac,0.0);
3217 //difference matrix
3218 loadIdentityMatrix(matid);
3219 matinverseAFFINE(mat_toi,mat_to);
3220 matmultiplyAFFINE(matdif,mat_from,mat_toi);
3221 general_slerp(matnow,matdif,matid,16,tickFrac);
3222 matmultiplyAFFINE(matnew,matnow,bstack->viewtransformmatrix);
3223 matcopy(bstack->viewtransformmatrix, matnew);
3224
3225 if(tickFrac > .99)
3226 {
3227 viewer->SLERPing2 = FALSE;
3228 //printf(" done\n");
3229 }
3230
3231 }
3232 }else{
3233 //older way, in theory more elegant with spherical quaternion slerp
3234 if(viewer->SLERPing2justStarted)
3235 {
3236 //rn rootnode space, vpo/vpn old and new viewpoint space
3237 double vpo2rn[16];
3238 //double rn2vpo[16];
3239 double vpn2rn[16],rn2vpn[16];
3240 //double rn2rn[16];
3241 double diffrn[16];
3242 matcopy(vpo2rn,mat_from);
3243 //if(viewer->LookatMode==3){
3244 // matcopy(vpn2rn,p->viewpointnew2rootnode);
3245 //}else{
3246 matcopy(vpn2rn,mat_to);
3247 //}
3248 //matinverse(rn2vpo,vpo2rn);
3249 matinverseAFFINE(rn2vpn,vpn2rn);
3250 //this works a bit:
3251 // diff_RN[rn x rn] = vpo2rn[rn x vpo] * rn2vpn[vpn x rn]
3252 //printmatrix2(vpo2rn,"vpo2rn");
3253 //printmatrix2(rn2vpn,"rn2vpn");
3254 matmultiplyAFFINE(diffrn,vpo2rn,rn2vpn);
3255 //printmatrix2(diffrn,"AFFINE diffrn");
3256 //matmultiplyFULL(diffrn,vpo2rn,rn2vpn);
3257 //printmatrix2(diffrn,"FULL diffrn");
3258
3259 //slerping quat and point_XYZ
3260 matrix_to_quaternion(&p->sq,diffrn);
3261 quaternion_normalize(&p->sq);
3262 p->sp[0] = diffrn[12];
3263 p->sp[1] = diffrn[13];
3264 p->sp[2] = diffrn[14];
3265 viewer->SLERPing2justStarted = FALSE;
3266 //p->tickFrac = 0.0;
3267 //printf("in slerping2juststarted ");
3268 }
3269 //back transform by slerped amount
3270 {
3271 double tickFrac;
3272 Quaternion qdif,qzero;
3273 double vzero[3], vshift[3], matdif[16], matnew[16];
3274
3275 tickFrac = (TickTime() - viewer->startSLERPtime)/viewer->transitionTime;
3276 tickFrac = DOUBLE_MIN(tickFrac,1.0);
3277 tickFrac = DOUBLE_MAX(tickFrac,0.0);
3278 //slerping quat and point
3279 vzero[0] = vzero[1] = vzero[2] = 0.0;
3280 vrmlrot_to_quaternion(&qzero, 0.0,1.0,0.0,0.0); //zero it
3281 quaternion_slerp(&qdif,&p->sq,&qzero,tickFrac);
3282 general_slerp(vshift,p->sp,vzero,3,tickFrac);
3283 FW_GL_PUSH_MATRIX();
3284 FW_GL_LOAD_IDENTITY();
3285 FW_GL_TRANSLATE_D(vshift[0],vshift[1],vshift[2]);
3286 quaternion_togl(&qdif);
3287 fw_glGetDoublev(GL_MODELVIEW_MATRIX, matdif);
3288 FW_GL_POP_MATRIX();
3289 matmultiplyAFFINE(matnew,matdif,bstack->viewtransformmatrix);
3290 matcopy(bstack->viewtransformmatrix, matnew);
3291
3292 if(tickFrac > .99)
3293 viewer->SLERPing2 = FALSE;
3294 }
3295 }
3296 iret = 1;
3297 }
3298 return iret;
3299}
3300int slerp_viewpoint3()
3301{
3302 //type 3 slerping: EXPLORE, LOOKAT
3303
3304 int iret, itype;
3306 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
3307 viewer = Viewer();
3308 itype = 3;
3309 iret = 0;
3310 if(viewer->SLERPing3 && itype==3){
3311 //navigation 'm' LOOKAT and 'g' EXPLORE non-vp-bind slerping comes through here
3312 // slerps: viewer->pos, .quat, .dist
3313 double tickFrac;
3314 tickFrac = (TickTime() - viewer->startSLERPtime)/viewer->transitionTime;
3315 tickFrac = min(1.0,tickFrac); //clamp to max 1.0 otherwise a slow frame rate will overshoot
3316 //viewer_fetch_user_offsets0(viewer);
3317 viewer_fetch_LCS(viewer);
3318 quaternion_slerp(&viewer->Quat,&viewer->startSLERPQuat,&viewer->endSLERPQuat,tickFrac);
3319 point_XYZ_slerp(&viewer->Pos,&viewer->startSLERPPos,&viewer->endSLERPPos,tickFrac);
3320 //viewer_update_user_offsets0(viewer);
3321 viewer_update_LCS(viewer);
3322 general_slerp(&viewer->Dist,&viewer->startSLERPDist,&viewer->endSLERPDist,1,tickFrac);
3323 if(tickFrac >= 1.0) {
3324 viewer->SLERPing3 = 0;
3325 resolve_pos20(viewer); //may not need this if examine etc do it
3326 }
3327 iret = 1;
3328 //now we let normal rendering use the viewer quat, pos, dist during rendering
3329 }
3330 return iret;
3331}
3332
3333void setup_viewpoint_slerp3(double* center, double pivot_radius, double vp_radius){
3334 //slerp3 for EXPLORE, LOOKAT
3335 /* when you don't have a new viewpoint to bind to, but know where you want the viewer to go
3336 with a transform relative to the viewer, instead of bind_viewpoint call
3337 setup_viewpoint_slerp3(pointInEyespace, radiusOfShapeInEyespace)
3338
3339 */
3340 double yaw, pitch;
3341 double C[3];
3342 Quaternion q_i;
3343 Quaternion qyaw, qpitch, qtmp;
3344 struct point_XYZ PC;
3345
3347
3348 double pos[3] = {0.0,0.0,0.0}; //rpos[3],
3349 struct point_XYZ pp,qq;
3350 viewer = Viewer();
3351
3352 veccopyd(pos,center);
3353
3354 vecnormald(pos,pos);
3355 vecscaled(pos,pos,vp_radius); //distance);
3356
3357 viewer->SLERPing3 = 1;
3358
3359 //Dec 2014 another attempt at non-bind viewpoint slerping
3360 //method:
3361 // 1. snapshot the current viewer->quat, viewer->pos, viewer->dist as startSLERP
3362 // 2. compute ending pos, quat, dist and set as endSLERP .Pos, .Quat .Dist
3363 // 3. in viewpoint_slerp(), slerp from starting to ending
3364
3365 // 1. snapshot current viewer quat,pos,dist as startSLERP
3366 viewer_fetch_LCS(viewer);
3367 viewer->startSLERPPos = viewer->Pos;
3368 viewer->startSLERPQuat = viewer->Quat;
3369 viewer->startSLERPDist = viewer->Dist;
3370 viewer->startSLERPtime = TickTime();
3371
3372 // 2. compute end pos,quat,dist as endSLERP
3373 // generally we have a vector center, and a pitch,roll
3374 // end = start + center,pitch,roll
3375 // except we need to do proper transform concatonation:
3376 /*
3377 0.World
3378 1. viewpoint
3379 2. avatar position .Pos
3380 3. avatar orientation .Quat
3381 our center, pitch, yaw are observed here
3382 Order of transforms:
3383 .Pos += inverse(.Quat)*center
3384 */
3385 viewer->endSLERPDist = vp_radius;
3386
3387 // end = start + ...
3388 // endPos = startPos + inverse(startQuat)*center
3389 quaternion_normalize(&viewer->startSLERPQuat);
3390 quaternion_inverse( &q_i,&viewer->startSLERPQuat);
3391 vecdifd(pos,center,pos);
3392 double2pointxyz(&pp,pos);
3393 quaternion_rotation(&qq, &q_i, &pp);
3394 vecadd(&viewer->endSLERPPos,&viewer->startSLERPPos,&qq);
3395
3396 // endQuat = startQuat*toQuat(yaw)*toQuat(pitch)
3397 //when you pick, your pickray and shape object isn't usually dead center in the viewport. In that case,
3398 //besides translating the viewer, you also want to turn the camera to look at the
3399 //center of the shape (turning somewhat toward the pickray direction, but more precisely to the shape object ccenter)
3400 //compute yaw from our pickray
3401 veccopyd(C,pos);
3402 if( APPROX( vecnormald(C,C), 0.0) )
3403 C[2] = 1.0;
3404 //if we are too close, we don't want to turn 180 to move away, we just want to back up
3405 if(C[2] < 0.0)
3406 vecscaled(C,C,-1.0);
3407 yaw = -atan2(C[0],C[2]);
3408 //apply the yaw to the pickray, so that all that's left of the pickray is the pitch part
3409 vrmlrot_to_quaternion(&qyaw, 0.0,1.0,0.0,yaw);
3410 double2pointxyz(&PC,C);
3411 quaternion_rotation(&PC,&qyaw,&PC);
3412 //compute pitch from yaw-transformed pickray
3413 pitch = atan2(PC.y,PC.z);
3414 vrmlrot_to_quaternion(&qpitch,1.0,0.0,0.0,pitch);
3415 //quatEnd = quatPitch*quatYaw*quatStart
3416 quaternion_multiply(&qtmp,&qyaw,&qpitch);
3417 quaternion_multiply(&viewer->endSLERPQuat,&qtmp,&viewer->startSLERPQuat);
3418}
3419
3420void viewer_viewall(){
3421 double dcenter[3], pivot_radius, vp_radius;
3422 float extent6[6];
3423 struct X3D_Node* rn;
3424
3425 rn = rootNode();
3426 if(rn) {
3427 float scene_diameter, vpradius;
3428 double MM[16];
3429 float vpf[3], center[3], vpoffset[3];
3430 FW_GL_GETDOUBLEV(GL_MODELVIEW_MATRIX, MM);
3431 extent6f_copy(extent6,rn->_extent);
3432 extent6f_mattransform4d(extent6,extent6,MM);
3433 //include currently bound viewpoint in scene_diameter?
3434 //-I think it already is part of rootNode extent, no need to add it
3435 vecset3f(vpf,0.0f,0.0f,0.0f);
3436 extent6f_get_center3f(extent6,center);
3437 float2double(dcenter,center,3);
3438 vecdif3f(vpoffset,center,vpf);
3439 pivot_radius = extent6f_get_maxradius(extent6);
3440 vp_radius = vpradius = veclength3f(vpoffset) * 1.5;
3441 Viewer()->Dist = vp_radius; //pivot_radius; // + scene_diameter;
3442
3443 setup_viewpoint_slerp3(dcenter,pivot_radius, vp_radius);
3444 }
3445}
3446/* We have a Viewpoint node being bound. (not a GeoViewpoint node) */
3447void bind_Viewpoint (struct X3D_Viewpoint *vp) {
3448 Quaternion q_i;
3449 float xd, yd,zd;
3451 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
3452
3453 /* did bind_node tell us we could bind this guy? */
3454 if (!(vp->isBound)) return;
3455 if(!vp->_initializedOnce) {
3456 veccopy3f(vp->_position.c,vp->position.c);
3457 veccopy4f(vp->_orientation.c,vp->orientation.c);
3458 vp->_initializedOnce = TRUE;
3459 }
3460
3461 /* SLERPing */
3462 /* record position BEFORE calculating new Viewpoint position */
3463 /*
3464 dug9 - viewpoint slerping: what I see as of July 12, 2011:
3465 in the scene file if there's non-zero position
3466 and orientation values in the fields of the first bindable viewpoint these values are
3467 modified by slerping, during the initial bind. After the initial slerp, slerping code runs,
3468 but accomplishes no effective slerping.
3469 If the fields (.position, .orientation) are zero when the viewpoint first binds,
3470 slerp code runs but no effective slerping.
3471
3472 dug9 - my concept of how a viewpoint slerp should work, as of July 12, 2011:
3473 A smooth transition between current world pose and newly bound viewpoint world pose.
3474 definition of 'pose': 6 parameters consisting of 3 translations and 3 rotations in 3D space
3475 representing the position and direction of an assymetric object
3476 viewpoint pose: transform stack + (.Pos, .Quat)
3477 - on initial binding without viewpoint slerping
3478 (.Pos,.Quat) = (.position,.orientation)
3479 - on initial bind with viewpoint slerping
3480 (.Pos,.Quat) = (.position,.orientation) - pose_difference
3481 pose_difference = (new viewpoint pose) - (last viewpoint pose)
3482 more detail...
3483 1. during a viewpoint bind the 'pose_difference' between the old and new viewpoint poses
3484 is computed from their transform stacks
3485 pose_difference = new_viewpoint_world_pose - old_viewpoint_world_pose
3486 2. the new viewpoint is bound -as normally done without slerping- so it's at the new pose
3487 3. the new viewpoint's pose is multiplied by inverse(pose_difference) effectively
3488 putting the camera part of the new viewpoint back to the old viewpoints camera pose.
3489 This could be done by multiplying the position and orientation fields
3490 4. slerping is started to reduce pose_difference to zero at which point slerping stops
3491 and the camera is at it's viewpoint's final pose
3492 there needs to be variables for the following:
3493 a) pose_difference - a translation and rotation
3494 b) original position and orientation fields
3495 c) modified position and orientation fields
3496 modified_viewpoint_pose = inverse(pose_difference) * bound_viewpoint_pose
3497 the easy part is getting the position and orientation fields, which are simple properties
3498 of viewpoints.
3499 pose_difference:
3500 The hard part: getting the pose_difference which is found by traversing
3501 the scenegraph to both viewpoints, at some point in time in the frame cycle. But when?
3502 Options:
3503 A. as needed during a viewpoint bind, and with slerping on, call a function to
3504 traverse the scenegraph especially for getting the 2 viewpoint global transforms
3505 B. every time a viewpoint is visited on a scenegraph traversal, store its global transform
3506 with it, so it's refreshed often, and becomes a property of the viewpoint which
3507 can be accessed immediately when slerping begins. And hope that's good enough, which
3508 during a very busy event cascade, it might not be.
3509 C. stagger the start of slerping to cover 2 frames
3510 - on the bind frame, we can call a function to invert the current modelview matrix, for
3511 the old viewpoint.
3512 - on the next frame, ditto for the new viewpoint, then start slerping
3513 - problem: there's one frame where the camera jitters to the new pose, then on the
3514 next frame back to the old pose where it starts slerping.
3515 solution: to avoid this, on the second
3516 frame, before starting to draw, perhaps in prep_Viewpoint(), when we have the
3517 current modelview matrix for the new viewpoint, this is the point when we would
3518 compute the pose_difference and the initial pose parameters.
3519 Current process: ==============================================
3520 prep_Viewpoint() in Component_Navigation.c L.80
3521 - does the per-frame slerp increment
3522 - does the viewpoint field values of .orientation,.position
3523 bind_Viewpoint() in Viewer.c L.1925 (here) - sets up the slerping values and flags
3524 viewer_togl() in Viewer.c L.515 - computes slerp increment,
3525 - does part not done by prep_Viewpoint
3526 - so net of that prep_Viewpoint + viewer_togl() combined
3527 pose_difference = (new_world_pose) - (old_world_pose)
3528 pose_increment = slerpIncrement(pose_difference)
3529 - turns off slerping flag when done.
3530
3531 Call stack:
3532 mainloop L.503 (before render geometry)
3533 startOfLoopNodeUpdates() in OpenGL_Utils L.3406
3534 bind_Viewpoint() (here)
3535 mainloop L.630 (before render geometry)
3536 render_pre()
3537 setup_viewpoint()
3538 viewer_togl()
3539 render_hier(rootnode,VF_Viewpoint)
3540 prep_Viewpoint() - only called on the current bound viewpoint
3541 mainloop L.647 (for render_hier(,VF_sensitive) after render geometry)
3542 setup_viewpoint()
3543 ditto
3544 mainloop L.764
3545 SEND_BIND_IF_REQUIRED(tg->ProdCon.setViewpointBindInRender)
3546 prodcon L.604 send_bind_to(X3D_NODE(t->viewpointnodes[i]), 0);
3547 send_bind_to() in Bindables.c L.267
3548 bind_viewpoint()
3549 generally setup_viewpoint() is called when needed before any
3550 non-VF_Viewpoint render_hier() call to update the current pose
3551 -and modelview matrix- of the camera
3552 Variables and what they mean:
3553 Viewer.
3554 .position -viewpoint field, only changes through scripting
3555 .orientation -viewpoint field, only changes through scripting
3556 - when you re-bind to a viewpoint later, these will be the originals or script modified
3557 - transform useage:
3558 ShapeCoordinates
3559 Transform stack shape2world (model part of modelview)
3560 WorldCoordinates (at scene root)
3561 Transform stack to CBV (view part of modelview)
3562 Currently Bound Viewpoint (CBV)
3563 .Pos (== .position after bind, then navigation changes it)
3564 viewpoint avatar
3565 .Quat (== .orientation after bind, then navigation changes it)
3566 viewpoint camera (so called eye coords)
3567 .Pos: on binding, it gets a fresh copy of the .position field of the CBV
3568 - and navigation changes it
3569 .Quat: on binding, it gets a fresh copy of the .orientation field of the CBV
3570 - and navigation changes it
3571 - LEVEL/viewer_level_to_bound() changes it
3572 .bindTimeQuat: == .orientation (of CBV) through lifecycle
3573 - used to un-rotate modelviewmatrix (which includes .Pos,.Quat)
3574 to getcurrentPosInModel() (Q. should it be the whole .Quat?)
3575
3576 .AntiPos
3577 .AntiQuat: == inverse(.orientation)
3578 [.prepVPQuat == .orientation, used in prep_Viewpoint, which is wrong because it's not updated from scripting against .orientation]
3579 .currentPosInModel - used to calculate examine distance, GeoLOD range, debugging
3580 - starts as .position, updated in getCurrentPosInModel()
3581 No-slerping use of variables in prep_Viewpoint():
3582 rotate(prepVPQuat)
3583 translate(viewer->position)
3584 New process: =======================================================
3585 Goal: get both the old and new modelview matrices together, so pose_difference can be
3586 computed and applied to new viewpoint orientation/position before render() on the
3587 2nd loop.
3588 Proposed process:
3589 in bind_viewpoint, set a flag for prep_viewpoint saying its a newly bound viewpoint
3590 - save the current modelview matrix
3591 after prep_viewpoint in mainloop, call a new function:
3592 slerp_viewpoint():
3593 a) the first time in on a newly bound viewpoint
3594 - retrieve the last modelview stored by bind_viewpoint
3595 - get the modelview matrix for the new viewpoint
3596 - compute pose_difference between the old and new viewpoints
3597 pose_difference = last_modelviewmatrix*inverse(newModelViewMatrix)
3598 - modify the position and orientation fields of the new one
3599 with pose_difference ie .Pos, .Orient += slerp(pose_difference)
3600 - setup the slerping numbers
3601 b) subsequent visits on the bound viewpoint
3602 - do slerping increment to reduce pose_difference gradually to zero
3603 - apply to .Pos,.Quat
3604 - shut off slerping when done
3605 */
3606
3607 if(!vp->retainUserOffsets){
3608 veccopy3f(vp->position.c,vp->_position.c);
3609 veccopy4f(vp->orientation.c,vp->_orientation.c);
3610 }
3611 viewer = ViewerByLayerId(vp->_layerId);
3612 //printf("retained user pose=%lf %lf %lf\n",viewer->Pos.x,viewer->Pos.y,viewer->Pos.z);
3613 //printf("retained user.Quat= %lf %lf %lf %lf\n",viewer->Quat.x,viewer->Quat.y,viewer->Quat.z,viewer->Quat.w);
3614
3615 if (viewer->transitionType != VIEWER_TRANSITION_TELEPORT && viewer->wasBound) {
3616 //save the previous vp pose, in root space, for future slerps
3617 viewer->vp2rnSaved = TRUE; //we bind after prep_viewpoint > setup_viewpoint in rendersceneupdatescene0
3618 //we bind from the root, so this would be setup_viewpoint_1() and _2()
3619 //- the viewmatrix including .position,.orientation,.Pos,.Quat, stereo
3620 {
3621 bindablestack* bstack = getActiveBindableStacks(gglobal());
3622 matcopy(viewer->slerp_viewmatrix,bstack->viewtransformmatrix);
3623 matcopy(viewer->slerp_posorimatrix,bstack->posorimatrix);
3624
3625 }
3626
3627 viewer->SLERPing = FALSE; //TRUE;
3628 viewer->startSLERPtime = TickTime();
3629 /* slerp Mark II */
3630 viewer->SLERPing2 = TRUE;
3631 viewer->SLERPing2justStarted = TRUE;
3632 } else {
3633 viewer->SLERPing = FALSE;
3634 viewer->SLERPing2 = FALSE;
3635 }
3636
3637 viewer->wasBound = TRUE;
3638
3639 /* calculate distance between the node position and defined centerOfRotation */
3640 INITIATE_POSITION
3641
3642 /* assume Perspective, unless Otrho set */
3643 viewer->ortho=FALSE;
3644
3645 /* printf ("viewpoint binding distance %f\n",Viewer.Dist); */
3646
3647 /* set the examine mode rotation origin */
3648 INITIATE_ROTATION_ORIGIN
3649
3650 /* set Viewer position and orientation */
3651 /*
3652
3653 From specs > abstract > architecture > 23.3.5 Viewpoint
3654 "When a Viewpoint node is at the top of the stack, the user's view is
3655 conceptually re-parented as a child of the Viewpoint node. All subsequent changes to the Viewpoint node's
3656 coordinate system change the user's view (e.g., changes to any ancestor transformation nodes or to
3657 the Viewpoint node's position or orientation fields)."
3658
3659 "Navigation types (see 23.3.4 NavigationInfo) that require a definition of a down vector (e.g., terrain following)
3660 shall use the negative Y-axis of the coordinate system of the currently bound Viewpoint node.
3661 Likewise, navigation types that require a definition of an up vector shall use the positive Y-axis of the
3662 coordinate system of the currently bound Viewpoint node. The orientation field of the Viewpoint node does
3663 not affect the definition of the down or up vectors. This allows the author to separate the viewing direction
3664 from the gravity direction."
3665
3666 concept of transformations Jan3,2010:
3667world coords > [Transform stack] > bound Viewpoint > [Viewer.Pos,.Quat] > avatar
3668" < inverse[Transformstack] < " < [AntiPos,AntiQuat] < avatar
3669 gravity (according to specs): Y-down in the (bound Viewpoint local coords).
3670 The viewpoint node orientation field doesn't count toward specs-gravity.
3671 If you want global-gravity, then put your viewpoint node at the scene level, or compute a
3672 per-frame gravity for spherical worlds - see mainloop.c render_collisions.
3673
3674 Implication: the user button LEVEL should level out/cancel/zero the bound-viewpoint's orientation field value.
3675
3676 */
3677
3678 viewer_lastP_clear();
3679 setMenuStatusVP (vp->description->strptr);
3680
3681}
3682
3683int fwl_getAnaglyphSide(int whichSide) {
3684 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
3685 //glColorMask(p->acMask[iside][0],p->acMask[iside][1],p->acMask[iside][2],t);
3686
3687 if ((whichSide<0) || (whichSide>1)) {
3688 return 0;
3689 }
3690
3691 /* should return
3692 000 0 Black
3693 001 1 Blue
3694 010 2 Green
3695 011 3 Cyan
3696 100 4 Red
3697 101 5 Magenta
3698 110 6 Yellow
3699 111 7 White
3700 */
3701
3702 return (p->acMask[whichSide][0] << 2) | (p->acMask[whichSide][1] << 1) | (p->acMask[whichSide][2]);
3703}
3704
3705
3706
3707
3708// Android - we are loading in a new file while keeping the system sane.
3709void Android_reset_viewer_to_defaults() {
3710 //ConsoleMessage("********** Android_reset_viewer_to_defaults");
3711 // reset the viewer to initial mode.
3713 ppViewer p = (ppViewer)gglobal()->Viewer.prv;
3714 viewer = Viewer();
3715 p->viewer_initialized = FALSE;
3716
3717 viewer_default();
3718 viewer->SLERPing2 = FALSE;
3719 viewer->SLERPing = FALSE;
3720}
3721
3722int viewer_iside(){
3723 return Viewer()->iside;
3724}
Definition Viewer.h:139