mirror of https://github.com/fltk/fltk
534 lines
14 KiB
C++
534 lines
14 KiB
C++
/*
|
|
* fractviewer.cxx [from agviewer.c (version 1.0)]
|
|
*
|
|
* AGV: a glut viewer. Routines for viewing a 3d scene w/ glut
|
|
*
|
|
* See agv_example.c and agviewer.h comments within for more info.
|
|
*
|
|
* I welcome any feedback or improved versions!
|
|
*
|
|
* Philip Winston - 4/11/95
|
|
* pwinston@hmc.edu
|
|
* http://www.cs.hmc.edu/people/pwinston
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#if HAVE_GL && HAVE_GL_GLU_H
|
|
# include <FL/glut.H>
|
|
# include <FL/glu.h>
|
|
|
|
# include <stdio.h>
|
|
# include <stdlib.h>
|
|
# include <math.h>
|
|
# include <sys/types.h>
|
|
# include <time.h>
|
|
# if !defined(_WIN32)
|
|
# include <sys/time.h>
|
|
# endif // !_WIN32
|
|
|
|
# include "fracviewer.h"
|
|
|
|
/* Some <math.h> files do not define M_PI... */
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265
|
|
#endif
|
|
|
|
/***************************************************************/
|
|
/************************** SETTINGS ***************************/
|
|
/***************************************************************/
|
|
|
|
/* Initial polar movement settings */
|
|
#define INIT_POLAR_AZ 0.0
|
|
#define INIT_POLAR_EL 30.0
|
|
#define INIT_DIST 4.0
|
|
#define INIT_AZ_SPIN 0.5
|
|
#define INIT_EL_SPIN 0.0
|
|
|
|
/* Initial flying movement settings */
|
|
#define INIT_EX 0.0
|
|
#define INIT_EY -2.0
|
|
#define INIT_EZ -2.0
|
|
#define INIT_MOVE 0.01
|
|
#define MINMOVE 0.001
|
|
|
|
/* Start in this mode */
|
|
#define INIT_MODE POLAR
|
|
|
|
/* Controls: */
|
|
|
|
/* map 0-9 to an EyeMove value when number key is hit in FLYING mode */
|
|
#define SPEEDFUNCTION(x) ((x)*(x)*0.001)
|
|
|
|
/* Multiply EyeMove by (1+-MOVEFRACTION) when +/- hit in FLYING mode */
|
|
#define MOVEFRACTION 0.25
|
|
|
|
/* What to multiply number of pixels mouse moved by to get rotation amount */
|
|
#define EL_SENS 0.5
|
|
#define AZ_SENS 0.5
|
|
|
|
/* What to multiply number of pixels mouse moved by for movement amounts */
|
|
#define DIST_SENS 0.01
|
|
#define E_SENS 0.01
|
|
|
|
/* Minimum spin to allow in polar (lower forced to zero) */
|
|
#define MIN_AZSPIN 0.1
|
|
#define MIN_ELSPIN 0.1
|
|
|
|
/* Factors used in computing dAz and dEl (which determine AzSpin, ElSpin) */
|
|
#define SLOW_DAZ 0.90
|
|
#define SLOW_DEL 0.90
|
|
#define PREV_DAZ 0.80
|
|
#define PREV_DEL 0.80
|
|
#define CUR_DAZ 0.20
|
|
#define CUR_DEL 0.20
|
|
|
|
/***************************************************************/
|
|
/************************** GLOBALS ****************************/
|
|
/***************************************************************/
|
|
|
|
int MoveMode = INIT_MODE; /* FLYING or POLAR mode? */
|
|
|
|
GLfloat Ex = INIT_EX, /* flying parameters */
|
|
Ey = INIT_EY,
|
|
Ez = INIT_EZ,
|
|
EyeMove = INIT_MOVE,
|
|
|
|
EyeDist = INIT_DIST, /* polar params */
|
|
AzSpin = INIT_AZ_SPIN,
|
|
ElSpin = INIT_EL_SPIN,
|
|
|
|
EyeAz = INIT_POLAR_AZ, /* used by both */
|
|
EyeEl = INIT_POLAR_EL;
|
|
|
|
int agvMoving; /* Currently moving? */
|
|
|
|
int downx, downy, /* for tracking mouse position */
|
|
lastx, lasty,
|
|
downb = -1; /* and button status */
|
|
|
|
GLfloat downDist, downEl, downAz, /* for saving state of things */
|
|
downEx, downEy, downEz, /* when button is pressed */
|
|
downEyeMove;
|
|
|
|
GLfloat dAz, dEl, lastAz, lastEl; /* to calculate spinning w/ polar motion */
|
|
int AdjustingAzEl = 0;
|
|
|
|
int AllowIdle, RedisplayWindow;
|
|
/* If AllowIdle is 1 it means AGV will install its own idle which
|
|
* will update the viewpoint as needed and send glutPostRedisplay() to the
|
|
* window RedisplayWindow which was set in agvInit(). AllowIdle of 0
|
|
* means AGV won't install an idle funciton, and something like
|
|
* "if (agvMoving) agvMove()" should exist at the end of the running
|
|
* idle function.
|
|
*/
|
|
|
|
#define MAX(x,y) (((x) > (y)) ? (x) : (y))
|
|
#define TORAD(x) ((M_PI/180.0)*(x))
|
|
#define TODEG(x) ((180.0/M_PI)*(x))
|
|
|
|
/***************************************************************/
|
|
/************************ PROTOTYPES ***************************/
|
|
/***************************************************************/
|
|
|
|
/*
|
|
* these are functions meant for internal use only
|
|
* the other prototypes are in agviewer.h
|
|
*/
|
|
|
|
void PolarLookFrom(GLfloat dist, GLfloat elevation, GLfloat azimuth);
|
|
void FlyLookFrom(GLfloat x, GLfloat y, GLfloat z,
|
|
GLfloat az, GLfloat el);
|
|
int ConstrainEl(void);
|
|
void MoveOn(int v);
|
|
void SetMove(float newmove);
|
|
static void normalize(GLfloat v[3]);
|
|
void ncrossprod(float v1[3], float v2[3], float cp[3]);
|
|
|
|
|
|
/***************************************************************/
|
|
/************************ agvInit ******************************/
|
|
/***************************************************************/
|
|
|
|
void agvInit(int window)
|
|
{
|
|
glutMouseFunc(agvHandleButton);
|
|
glutMotionFunc(agvHandleMotion);
|
|
glutKeyboardFunc(agvHandleKeys);
|
|
RedisplayWindow = glutGetWindow();
|
|
agvSetAllowIdle(window);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/************************ VIEWPOINT STUFF **********************/
|
|
/***************************************************************/
|
|
|
|
/*
|
|
* viewing transformation modified from page 90 of red book
|
|
*/
|
|
void PolarLookFrom(GLfloat dist, GLfloat elevation, GLfloat azimuth)
|
|
{
|
|
glTranslatef(0, 0, -dist);
|
|
glRotatef(elevation, 1, 0, 0);
|
|
glRotatef(azimuth, 0, 1, 0);
|
|
|
|
}
|
|
|
|
/*
|
|
* I took the idea of tracking eye position in absolute
|
|
* coords and direction looking in Polar form from denis
|
|
*/
|
|
void FlyLookFrom(GLfloat x, GLfloat y, GLfloat z, GLfloat az, GLfloat el)
|
|
{
|
|
float lookat[3], perp[3], up[3];
|
|
|
|
lookat[0] = sin(TORAD(az))*cos(TORAD(el));
|
|
lookat[1] = sin(TORAD(el));
|
|
lookat[2] = -cos(TORAD(az))*cos(TORAD(el));
|
|
normalize(lookat);
|
|
perp[0] = lookat[2];
|
|
perp[1] = 0;
|
|
perp[2] = -lookat[0];
|
|
normalize(perp);
|
|
ncrossprod(lookat, perp, up);
|
|
gluLookAt(x, y, z,
|
|
x+lookat[0], y+lookat[1], z+lookat[2],
|
|
up[0], up[1], up[2]);
|
|
}
|
|
|
|
/*
|
|
* Call viewing transformation based on movement mode
|
|
*/
|
|
void agvViewTransform(void)
|
|
{
|
|
switch (MoveMode) {
|
|
case FLYING:
|
|
FlyLookFrom(Ex, Ey, Ez, EyeAz, EyeEl);
|
|
break;
|
|
case POLAR:
|
|
PolarLookFrom(EyeDist, EyeEl, EyeAz);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* keep them vertical; I think this makes a lot of things easier,
|
|
* but maybe it wouldn't be too hard to adapt things to let you go
|
|
* upside down
|
|
*/
|
|
int ConstrainEl(void)
|
|
{
|
|
if (EyeEl <= -90) {
|
|
EyeEl = -89.99;
|
|
return 1;
|
|
} else if (EyeEl >= 90) {
|
|
EyeEl = 89.99;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Idle Function - moves eyeposition
|
|
*/
|
|
void agvMove(void)
|
|
{
|
|
switch (MoveMode) {
|
|
case FLYING:
|
|
Ex += EyeMove*sin(TORAD(EyeAz))*cos(TORAD(EyeEl));
|
|
Ey += EyeMove*sin(TORAD(EyeEl));
|
|
Ez -= EyeMove*cos(TORAD(EyeAz))*cos(TORAD(EyeEl));
|
|
break;
|
|
|
|
case POLAR:
|
|
EyeEl += ElSpin;
|
|
EyeAz += AzSpin;
|
|
if (ConstrainEl()) { /* weird spin thing to make things look */
|
|
ElSpin = -ElSpin; /* look better when you are kept from going */
|
|
/* upside down while spinning - Isn't great */
|
|
if (fabs(ElSpin) > fabs(AzSpin))
|
|
AzSpin = fabs(ElSpin) * ((AzSpin > 0) ? 1 : -1);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (AdjustingAzEl) {
|
|
dAz *= SLOW_DAZ;
|
|
dEl *= SLOW_DEL;
|
|
}
|
|
|
|
if (AllowIdle) {
|
|
glutSetWindow(RedisplayWindow);
|
|
glutPostRedisplay();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Don't install agvMove as idle unless we will be updating the view
|
|
* and we've been given a RedisplayWindow
|
|
*/
|
|
void MoveOn(int v)
|
|
{
|
|
if (v && ((MoveMode == FLYING && EyeMove != 0) ||
|
|
(MoveMode == POLAR &&
|
|
(AzSpin != 0 || ElSpin != 0 || AdjustingAzEl)))) {
|
|
agvMoving = 1;
|
|
if (AllowIdle)
|
|
glutIdleFunc(agvMove);
|
|
} else {
|
|
agvMoving = 0;
|
|
if (AllowIdle)
|
|
glutIdleFunc(NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* set new redisplay window. If <= 0 it means we are not to install
|
|
* an idle function and will rely on whoever does install one to
|
|
* put statement like "if (agvMoving) agvMove();" at end of it
|
|
*/
|
|
void agvSetAllowIdle(int allowidle)
|
|
{
|
|
if ((AllowIdle = allowidle))
|
|
MoveOn(1);
|
|
}
|
|
|
|
|
|
/*
|
|
* when moving to flying we stay in the same spot, moving to polar we
|
|
* reset since we have to be looking at the origin (though a pivot from
|
|
* current position to look at origin might be cooler)
|
|
*/
|
|
void agvSwitchMoveMode(int move)
|
|
{
|
|
switch (move) {
|
|
case FLYING:
|
|
if (MoveMode == FLYING) return;
|
|
Ex = -EyeDist*sin(TORAD(EyeAz))*cos(TORAD(EyeEl));
|
|
Ey = EyeDist*sin(TORAD(EyeEl));
|
|
Ez = EyeDist*(cos(TORAD(EyeAz))*cos(TORAD(EyeEl)));
|
|
EyeEl = -EyeEl;
|
|
EyeMove = INIT_MOVE;
|
|
break;
|
|
case POLAR:
|
|
EyeDist = INIT_DIST;
|
|
EyeAz = INIT_POLAR_AZ;
|
|
EyeEl = INIT_POLAR_EL;
|
|
AzSpin = INIT_AZ_SPIN;
|
|
ElSpin = INIT_EL_SPIN;
|
|
break;
|
|
}
|
|
MoveMode = move;
|
|
MoveOn(1);
|
|
glutPostRedisplay();
|
|
}
|
|
|
|
/***************************************************************/
|
|
/******************* MOUSE HANDLING ***********************/
|
|
/***************************************************************/
|
|
|
|
void agvHandleButton(int button, int state, int x, int y)
|
|
{
|
|
// deal with mouse wheel events, that fltk sends as buttons 3 or 4
|
|
//if (button > GLUT_RIGHT_BUTTON)return;
|
|
if ((state == GLUT_DOWN) && ((button == 3) || (button == 4))) {
|
|
// attempt to process scrollwheel as zoom in/out
|
|
float deltay = 0.25;
|
|
if (button == 3) {
|
|
deltay = (-0.25);
|
|
}
|
|
downb = -1;
|
|
downDist = EyeDist;
|
|
downEx = Ex;
|
|
downEy = Ey;
|
|
downEz = Ez;
|
|
downEyeMove = EyeMove;
|
|
EyeMove = 0;
|
|
|
|
EyeDist = downDist + deltay;
|
|
Ex = downEx - E_SENS*deltay*sin(TORAD(EyeAz))*cos(TORAD(EyeEl));
|
|
Ey = downEy - E_SENS*deltay*sin(TORAD(EyeEl));
|
|
Ez = downEz + E_SENS*deltay*cos(TORAD(EyeAz))*cos(TORAD(EyeEl));
|
|
|
|
EyeMove = downEyeMove;
|
|
glutPostRedisplay();
|
|
return;
|
|
}
|
|
else if (button > GLUT_RIGHT_BUTTON)return; // ignore any other button...
|
|
|
|
if (state == GLUT_DOWN && downb == -1) {
|
|
lastx = downx = x;
|
|
lasty = downy = y;
|
|
downb = button;
|
|
|
|
switch (button) {
|
|
case GLUT_LEFT_BUTTON:
|
|
lastEl = downEl = EyeEl;
|
|
lastAz = downAz = EyeAz;
|
|
AzSpin = ElSpin = dAz = dEl = 0;
|
|
AdjustingAzEl = 1;
|
|
MoveOn(1);
|
|
break;
|
|
|
|
case GLUT_MIDDLE_BUTTON:
|
|
downDist = EyeDist;
|
|
downEx = Ex;
|
|
downEy = Ey;
|
|
downEz = Ez;
|
|
downEyeMove = EyeMove;
|
|
EyeMove = 0;
|
|
}
|
|
|
|
} else if (state == GLUT_UP && button == downb) {
|
|
|
|
downb = -1;
|
|
|
|
switch (button) {
|
|
case GLUT_LEFT_BUTTON:
|
|
if (MoveMode != FLYING) {
|
|
AzSpin = -dAz;
|
|
if (AzSpin < MIN_AZSPIN && AzSpin > -MIN_AZSPIN)
|
|
AzSpin = 0;
|
|
ElSpin = -dEl;
|
|
if (ElSpin < MIN_ELSPIN && ElSpin > -MIN_ELSPIN)
|
|
ElSpin = 0;
|
|
}
|
|
AdjustingAzEl = 0;
|
|
MoveOn(1);
|
|
break;
|
|
|
|
case GLUT_MIDDLE_BUTTON:
|
|
EyeMove = downEyeMove;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* change EyeEl and EyeAz and position when mouse is moved w/ button down
|
|
*/
|
|
void agvHandleMotion(int x, int y)
|
|
{
|
|
int deltax = x - downx, deltay = y - downy;
|
|
|
|
switch (downb) {
|
|
case GLUT_LEFT_BUTTON:
|
|
EyeEl = downEl + EL_SENS * deltay;
|
|
ConstrainEl();
|
|
EyeAz = downAz + AZ_SENS * deltax;
|
|
dAz = PREV_DAZ*dAz + CUR_DAZ*(lastAz - EyeAz);
|
|
dEl = PREV_DEL*dEl + CUR_DEL*(lastEl - EyeEl);
|
|
lastAz = EyeAz;
|
|
lastEl = EyeEl;
|
|
break;
|
|
case GLUT_MIDDLE_BUTTON:
|
|
EyeDist = downDist + DIST_SENS*deltay;
|
|
Ex = downEx - E_SENS*deltay*sin(TORAD(EyeAz))*cos(TORAD(EyeEl));
|
|
Ey = downEy - E_SENS*deltay*sin(TORAD(EyeEl));
|
|
Ez = downEz + E_SENS*deltay*cos(TORAD(EyeAz))*cos(TORAD(EyeEl));
|
|
break;
|
|
}
|
|
glutPostRedisplay();
|
|
}
|
|
|
|
/***************************************************************/
|
|
/********************* KEYBOARD HANDLING ***********************/
|
|
/***************************************************************/
|
|
|
|
/*
|
|
* set EyeMove (current speed) for FLYING mode
|
|
*/
|
|
void SetMove(float newmove)
|
|
{
|
|
if (newmove > MINMOVE) {
|
|
EyeMove = newmove;
|
|
MoveOn(1);
|
|
} else {
|
|
EyeMove = 0;
|
|
MoveOn(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 0->9 set speed, +/- adjust current speed -- in FLYING mode
|
|
*/
|
|
void agvHandleKeys(unsigned char key, int, int) {
|
|
if (MoveMode != FLYING)
|
|
return;
|
|
|
|
if (key >= '0' && key <= '9')
|
|
SetMove(SPEEDFUNCTION((key-'0')));
|
|
else
|
|
switch(key) {
|
|
case '+':
|
|
if (EyeMove == 0)
|
|
SetMove(MINMOVE);
|
|
else
|
|
SetMove(EyeMove *= (1 + MOVEFRACTION));
|
|
break;
|
|
case '-':
|
|
SetMove(EyeMove *= (1 - MOVEFRACTION));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/*********************** VECTOR STUFF **************************/
|
|
/***************************************************************/
|
|
|
|
/* normalizes v */
|
|
static void normalize(GLfloat v[3])
|
|
{
|
|
GLfloat d = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
|
|
|
|
if (d == 0)
|
|
fprintf(stderr, "Zero length vector in normalize\n");
|
|
else {
|
|
v[0] /= d; v[1] /= d; v[2] /= d;
|
|
}
|
|
}
|
|
|
|
/* calculates a normalized crossproduct to v1, v2 */
|
|
void ncrossprod(float v1[3], float v2[3], float cp[3])
|
|
{
|
|
cp[0] = v1[1]*v2[2] - v1[2]*v2[1];
|
|
cp[1] = v1[2]*v2[0] - v1[0]*v2[2];
|
|
cp[2] = v1[0]*v2[1] - v1[1]*v2[0];
|
|
normalize(cp);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/**************************** AXES *****************************/
|
|
/***************************************************************/
|
|
|
|
|
|
/* draw axes -- was helpful to debug/design things */
|
|
void agvMakeAxesList(int displaylistnum)
|
|
{
|
|
int i,j;
|
|
GLfloat axes_ambuse[] = { 0.5, 0.0, 0.0, 1.0 };
|
|
glNewList(displaylistnum, GL_COMPILE);
|
|
glPushAttrib(GL_LIGHTING_BIT);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, axes_ambuse);
|
|
glBegin(GL_LINES);
|
|
glVertex3f(15, 0, 0); glVertex3f(-15, 0, 0);
|
|
glVertex3f(0, 15, 0); glVertex3f(0, -15, 0);
|
|
glVertex3f(0, 0, 15); glVertex3f(0, 0, -15);
|
|
glEnd();
|
|
for (i = 0; i < 3; i++) {
|
|
glPushMatrix();
|
|
glTranslatef(-10*(i==0), -10*(i==1), -10*(i==2));
|
|
for (j = 0; j < 21; j++) {
|
|
// glutSolidCube(0.1);
|
|
glTranslatef(i==0, i==1, i==2);
|
|
}
|
|
glPopMatrix();
|
|
}
|
|
glPopAttrib();
|
|
glEndList();
|
|
}
|
|
|
|
|
|
#endif // HAVE_GL && HAVE_GL_GLU_H
|