fltk/test/fracviewer.cxx

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