/*
This file is part of EZRide.
Copyright 2007 Dwight Barkley,
Copyright 2010 Vadim Biktashev & Andrew Foulkes.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ezride.h"
#include "ezgraphGL.h"
/* -------------------------------------------------------------------------
* This file contains all graphics manipulation routines.
* The important things to know about this file are:
*
* (1) X11 is used to open the graphics window and handle the events
* (i.e. key presses and pointer motions within the window). InitX() is a
* long routine that opens the window; Event_check() contains the event loop
* that looks for window events; QuitX() quits X obviously. You should be
* able to switch over to a higher-level method with only minor
* modifications except for these routines.
*
* (2) After the window is open, OpenGL is used to handle all the rendering.
* myReshape() must be called before anything can be plotted. To understand
* this function see the OpenGL manual.
*
* The routines near the top of this file handle the interactive graphics
* through OpenGL calls. Note: to add a new interactive feature, add to the
* event loop in Event_check() and add a corresponding routine to perform the
* desired task.
*
* (3) Note on the graphics modes.
*
* There are 2 modes the program can be in:
* MODE_SIMULATING Simulation progresses through time (it cannot be
* rotated while in this mode)
* MODE_VIEWING Simulation is paused.
* ------------------------------------------------------------------------- */
/*
* Global variables for this file only
* ----------------------------------- */
static Display *theDisplay;
static int theScreen;
static Window theWindow;
static GLXContext theGLXContext;
static XTextProperty theWindowName;
static int WindowWidth;
static int WindowHeight;
static int field; /* Field being viewed */
static int save_images=FALSE;/* Save every image flag */
static int ezmode; /* Mode flag */
static Real half_width,
half_height;
static Real plot_length[2]; /* Lengths of the simulation volume in
graphics coordinates (see
Draw_ini()). */
/*
* Private functions
* ----------------- */
static int setColor (int i, int j);
static void Draw_tips (void);
static void Draw_pins (void);
static void Restart (void);
static void Pause (void);
static void View_u_field (void);
static void View_v_field (void);
static void View_p_field (void);
static void View_no_field (void);
static void Toggle_tip_plotting (void);
static void Toggle_BC (void);
static void Toggle_pert (void);
static void myReshape (int w, int h);
static void Mover (int m_x, int m_y);
static void Shift_fields (int index1, int index2);
static void InitX (int winx, int winy, int width, int height);
static Bool WaitForNotify (Display *d, XEvent *e, char *arg);
static void Save_image (void);
/* ========================================================================= */
void Draw (void)
{
/* Main plotting routine */
int i,j;
Real x1, x2, y1, y2, rect_h=plot_length[0]/(NX-1);
char windowname[256];
char *name=&(windowname[0]);
if(GRAPHICS) {
/* Clear the color buffer and draw a blue rectangle the size of
the simulation area */
glClear(GL_COLOR_BUFFER_BIT);
GLCOLOR3(0.,0.,1.);
GLRECT(-half_width, -half_height, half_width, half_height);
}
sprintf(windowname,"EZRide: %s i=%u t=%.2f",ride_on?"on":"off",istep,istep*dt);
XStringListToTextProperty(&name,1,&theWindowName);
XSetWMName(theDisplay, theWindow, &theWindowName);
if(field != NO_FIELD) {
y1 = -half_height;
y2 = y1 + rect_h;
for(j=1;j0.9): Red */
GLCOLOR3(1.,0.,0.);
}
break;
case V_FIELD : /* Set the v_color */
/* Scale the v-field between 0 and 1. The maximum (VMAX) and minimum
* (VMIN) values used for scaling are somewhat ad hoc. The max and min
* functions are used to ensure 0 <= scaled_v < 1. */
scaled_v = (V(i,j)-VMIN)/(VMAX-VMIN);
red = 1.-(scaled_v*scaled_v);
green = scaled_v*scaled_v/2.0;
blue = scaled_v*scaled_v;
GLCOLOR3(red, green, blue);
break;
case P_FIELD : /* Set the "phase" u-v color */
/* Scale the u-field between 0 and 1, to make the red component,
* and the v-field as above, to make the green component. */
scaled_u = (U(i,j)-UMIN)/(UMAX-UMIN);
scaled_v = (V(i,j)-VMIN)/(VMAX-VMIN);
red = scaled_u;
green = scaled_v;
blue = scaled_v; /* scaled_u*scaled_v; */
GLCOLOR3(red, green, blue);
break;
}
return(1);
}
/* ========================================================================= */
static void Draw_tips (void)
{
Real rect_h=plot_length[0]/(NX-1);
int i;
glLineWidth(TIP_WT);
glBegin(TIP_PLOT_TYPE);
GLCOLOR3(TIP_R, TIP_G, TIP_B);
for(i=0;i2) printf("will save every image now\n");
}
}
/* ========================================================================= */
static void Toggle_tip_plotting (void)
{
if (show_tip) {
show_tip = FALSE;
} else {
show_tip = TRUE;
ntips = 0; /* set to zero to "erase" previous tips */
}
if (ezmode != MODE_SIMULATING) Draw();
}
/* ========================================================================= */
static void Toggle_BC (void)
{
if (NBC)
{NBC=0;
printf("Using Dirichlet Boundary COnditions,\n\n");
}
else
{NBC=1;
printf("Using Neumann Boundary Conditions,\n\n");
}
}
/* ========================================================================= */
static void Toggle_pert (void)
{
if (perturb_on) {
perturb_on=FALSE;
printf("\nPerturbation is switched off,\n");
} else {
perturb_on=TRUE;
printf("\nPerturbation is switched on,\n");
}
}
/* ========================================================================= */
void Center (void)
{
/* #define DDD printf("(XX,YY)=%d,%d\n",XX,YY) */
Find_tips();
/* DDD; */
while(XXNX2+1) { Mover(-1,0); XX--; /* DDD; */}
while(YYNY2+1) { Mover(0,-1); YY--; /* DDD; */}
Find_tips();
/* DDD; */
if (ezmode != MODE_SIMULATING) Draw();
}
/* ========================================================================= */
static void Toggle_riding (void)
{
if (ride_on) {
ride_on=0;
printf("ride off\n");
} else {
Center();
ride_on=1;
printf("ride on\n");
}
if (ezmode != MODE_SIMULATING) {
Draw();
}
}
/* ========================================================================= */
static void myReshape(int w, int h)
{
/* half_width and half_height define the area viewed in GL. In general if
* these are large, then simulation area will appear small, and vice versa.
* PLOT_SIZE in ezgraphGL.h allows adjustment of this without changing any
* of the code below. */
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (-PLOT_SIZE*half_width, PLOT_SIZE*half_width,
-PLOT_SIZE*half_height, PLOT_SIZE*half_height, -20., 20.);
glMatrixMode (GL_MODELVIEW);
glViewport (0, 0, w, h);
/* Remember the window size for Save_image */
WindowWidth=w; WindowHeight=h;
}
/* ========================================================================= */
void Draw_ini (int initial_field)
{
/* Initialize everything necessary for plotting. */
field = initial_field;
/* The lengths of the simulation area in graphics coordinates are set. I
* choose to have the largest plot_length=1. Thus the simulation area lies
* inside the unit square in graphics coordinates. */
{
int nmax = max(NX,NY);
plot_length[0] = (NX-1.)/(nmax-1.);
plot_length[1] = (NY-1.)/(nmax-1.);
}
half_width = 0.5*plot_length[0];
half_height = 0.5*plot_length[1];
/* At this point everything has been initialized for finding tips
* without graphics. Can return after setting a few things. Setting field
* to NO_FIELD and ezmode to MODE_SIMULATING will give only minimum OpenGL
* calls. */
if( !GRAPHICS ) {
field = NO_FIELD;
ezmode = MODE_SIMULATING;
return;
}
/* Apparently this should make xwd in save_image work properly, as advised by */
/* http://publib.boulder.ibm.com/infocenter/pseries/v5r3/index.jsp?topic=/com.ibm.aix.aixwnpgd/doc/aixwnpgd/xvfb.htm */
setenv("_OGL_MIXED_MODE_RENDERING","1",1);
/* Create X window and prepares for use by OpenGL */
InitX(WINX,WINY,WINSIZE*plot_length[0],WINSIZE*plot_length[1]);
/* Set the shade model to Flat. The default Smooth (GL_SMOOTH)
* can take too long on some systems and I see no difference. */
glShadeModel(GL_FLAT);
/* Makes little difference. */
glEnable(GL_DITHER);
/* Set the background color. Here: red=green=blue=BACKGROUND. One can
* set any values one chooses so long as 0 <= red, green, blue <=1 */
glClearColor(BACKGROUND,BACKGROUND,BACKGROUND,0.0);
/* Set starting mode */
if (autostart) {
ezmode = MODE_SIMULATING;
} else {
ezmode = MODE_VIEWING;
}
}
/* ========================================================================= */
void Mover (int m_x, int m_y)
{
/* Moves spiral in response to a key press */
/* Keeps track of the moves in the (X1,Y1) coords of the riding FoR */
int i, i_start, i_stop, i_step;
int j, j_start, j_stop, j_step;
/* Check for out-of-range. It is necessary to move at most one grid point
* in each direction to avoid problems at domain boundaries. */
if( (abs(m_x)>1)||(abs(m_y)>1) ) {
fprintf(stderr,"out-of-range in Mover(): m_x, m_y = %d, %d\n", m_x, m_y);
exit(1);
}
if(m_x > 0) {i_start = NX; i_stop = 1; i_step = -1;}
else {i_start = 1; i_stop = NX; i_step = 1;}
if(m_y > 0) {j_start = NY; j_stop = 1; j_step = -1;}
else {j_start = 1; j_stop = NY; j_step = 1;}
for(i=i_start;i!=(i_stop+i_step);i+=i_step) {
for(j=j_start;j!=(j_stop+j_step);j+=j_step) {
Shift_fields(INDEX(i,j), INDEX(i+m_x,j+m_y));
}
}
/* Wrap-around for periodic bcs */
#if PBC_x
if(m_x!=0) {
for(j=j_start;j!=(j_stop+j_step);j+=j_step) {
if(m_x>0)
Shift_fields(INDEX(NX,j), INDEX(1,j+m_y));
else
Shift_fields(INDEX(1,j), INDEX(NX,j+m_y));
}
}
#endif
#if PBC_y
if(m_y!=0) {
for(i=i_start;i!=(i_stop+i_step);i+=i_step) {
if(m_y>0)
Shift_fields(INDEX(i,NY),INDEX(i+m_x,1));
else
Shift_fields(INDEX(i,1), INDEX(i+m_x,NY));
}
}
#endif
#if GRAPHICS
if (ezmode != MODE_SIMULATING) {
Draw();
}
#endif
X1 -= m_x*grid_h;
Y1 -= m_y*grid_h;
}
/* ========================================================================= */
void Shift_fields (int index1, int index2)
{
/* There is unnecessary copying of the spatial sums here. If s1 and s2
* from ezstep were known, then the unnecessary copying could be
* eliminated. I don't move the spiral that much so prefer to keep the
* code simpler. */
u[index2] = u[index1];
v[index2] = v[index1];
sigma_u[index2] = sigma_u[index1];
sigma_u[FIELD_SIZE+index2] = sigma_u[FIELD_SIZE+index1];
#if V_DIFF_ON
sigma_v[index2] = sigma_v[index1];
sigma_v[FIELD_SIZE+index2] = sigma_v[FIELD_SIZE+index1];
#endif
}
/* ========================================================================= */
int Event_check (void)
{
static XEvent theEvent;
static KeySym theKeySym;
static int theKeyBufferMaxLen = 64;
static char theKeyBuffer[65];
static XComposeStatus theComposeStatus;
int write_tip_save;
if( !GRAPHICS ) return(0);
/* Save write_tip, and then turn tip writing is turned off. This is
* done so that no tips are written as a result of graphics calls in
* the event loop. At the end, the write_tip is restored. */
write_tip_save = write_tip;
write_tip = FALSE;
/* X Event Loop
*
* If there is something in the queue then each event is processed in
* turn. When the queue is empty, and the mode (which may have been changed
* by the events just processed) is MODE_SIMULATING then control is
* returned (presumably to the main loop in main()). However, when the
* queue is empty and the mode is either MODE_ROTATING or MODE_VIEWING then
* XNextEvent is called which blocks until the next X event is received
* (eg. the space bar being pressed which sets the mode back to
* MODE_SIMULATING and so control returns to main()). */
while(XPending(theDisplay) || (ezmode != MODE_SIMULATING)) {
XNextEvent(theDisplay, &theEvent);
switch(theEvent.type) { /* switch according to X event */
case KeyPress: /* A KeyPress event has occurred. */
XLookupString((XKeyEvent *) &theEvent, theKeyBuffer,
theKeyBufferMaxLen, &theKeySym, &theComposeStatus);
switch(theKeySym) { /* switch according to the pressed key */
case XK_Escape: exit(0); /* hard exit, nothing saved */
case XK_Q: case XK_q: return(1); /* Quit: soft exit */
case XK_P: case XK_p: Pause(); Draw(); break; /* Pause */
case XK_space: Restart(); break; /* continue calculations */
case XK_Right: Mover(1,0); break; /* move +x */
case XK_Left: Mover(-1,0); break; /* move -x */
case XK_Up: Mover(0,1); break; /* move +y */
case XK_Down: Mover(0,-1); break; /* move -y */
case XK_C: case XK_c: Center(); break; /* Bring tip to center */
case XK_F: case XK_f: View_p_field(); break; /* Faze view */
case XK_U: case XK_u: View_u_field(); break; /* U-view */
case XK_V: case XK_v: View_v_field(); break; /* V-view */
case XK_N: case XK_n: View_no_field(); break; /* No view */
case XK_B: case XK_b: Toggle_BC(); break; /* dirichlet/neumann Boundary conditions */
case XK_H: case XK_h: Toggle_pert(); break; /* perturbation H() on/off */
case XK_I: case XK_i: Toggle_image_saving(); break; /* of automatic X11 window saving */
case XK_M: case XK_m: Make_images(); break; /* Make an ad hoc ppm image independently of X11 if it works */
case XK_R: case XK_r: Toggle_riding(); break; /* Ride on/off */
case XK_S: case XK_s: Save_image(); break; /* Save the current X11 window image via xwd utility if it works */
case XK_T: case XK_t: Toggle_tip_plotting(); break; /* Tip on/off */
} /* switch(theKeySym) */
break;
case EnterNotify:
/* This case is necessary for window managers which do not set keyboard
* focus to theWindow when the pointer enters theWindow. */
XSetInputFocus(theDisplay, theWindow, RevertToPointerRoot, CurrentTime);
break;
case Expose:
/* Window mapped for the first time and if you uncover some part of the
* window. If you start paused and you see nothing in the window then
* its possible that the problem is that the first Expose event is not
* being caught for some reason. */
Draw();
break;
case ConfigureNotify:
/* Window size is changed by the user (or the window is initially
* opened). Note that InitX contains code that constrains the window
* to be square. */
myReshape(theEvent.xconfigure.width, theEvent.xconfigure.height);
break;
} /* switch (theEvent.type) */
} /* while (XPending(theDisplay) || (ezmode != MODE_SIMULATING)) */
/* restore write_tip */
write_tip = write_tip_save;
return(0);
}
/* ========================================================================= */
static void InitX (int winx, int winy, int width, int height)
{
/* Initializes X and opens a window. */
static XVisualInfo *theVisualInfo;
static Colormap theColormap;
static int theDepth;
static int theDWidth;
static int theDHeight;
static char *theDisplayName = NULL;
static XEvent event;
static Atom del_atom;
static XSizeHints theSizeHints;
static XSetWindowAttributes theSWA;
static char *name = WINDOW_TITLE;
static XTextProperty theWindowName, theIconName;
static int num1,num2;
static int list[] = {GLX_RGBA,
GLX_DOUBLEBUFFER,
GLX_RED_SIZE, 1,
GLX_GREEN_SIZE, 1,
GLX_BLUE_SIZE, 1,
GLX_DEPTH_SIZE, 1,
None } ;
/* DOUBLE is the important one. Per5haps need to add error checking when
* DOUBLE_BUFFER not available. In the aux library the first entry in list
* was GLX_LEVEL, 0, */
/* Open the display */
if ((theDisplay = XOpenDisplay(NULL)) == NULL) {
fprintf(stderr,
"ERROR: Could not open a connection to X on display %s\n",
XDisplayName(theDisplayName));
exit(1);
}
if (!glXQueryExtension(theDisplay, &num1, &num2)) {
fprintf(stderr,
"ERROR: No glx extension on display %s\n",
XDisplayName(theDisplayName));
exit(1);
}
theScreen = DefaultScreen(theDisplay);
theDepth = DefaultDepth (theDisplay, theScreen);
theDWidth = DisplayWidth (theDisplay, theScreen);
theDHeight = DisplayHeight(theDisplay, theScreen);
if (!(theVisualInfo = glXChooseVisual(theDisplay, theScreen, list))) {
fprintf(stderr, "ERROR: Couldn't find visual");
exit(-1);
}
if (!(theGLXContext = glXCreateContext(theDisplay, theVisualInfo,
None, GL_TRUE))) {
/* Last parameter indicates that, if possible, then render directly to
* graphics hardware and bypass the X server. This should be faster. */
fprintf(stderr, "ERROR: Can not create a context!\n");
exit(-1);
}
theColormap = XCreateColormap(theDisplay,
RootWindow(theDisplay, theVisualInfo->screen),
theVisualInfo->visual, AllocNone);
/* AllocAll would generate a BadMatch. */
if (!(theColormap)) {
fprintf(stderr, "ERROR: couldn't create Colormap\n");
exit(-1);
}
theSWA.colormap = theColormap;
theSWA.border_pixel = 0;
theSWA.event_mask = (EnterWindowMask | KeyPressMask | StructureNotifyMask |
ButtonPressMask | ButtonReleaseMask | ExposureMask |
PointerMotionMask);
theWindow = XCreateWindow(theDisplay,
RootWindow(theDisplay, theVisualInfo->screen),
winx, winy, width, height, 0,
theVisualInfo->depth, InputOutput,
theVisualInfo->visual,
CWBorderPixel|CWColormap|CWEventMask, &theSWA);
if (!(theWindow)) {
fprintf(stderr, "ERROR: couldn't create X window\n");
exit(-1);
}
/* Remember the window size for Save_image */
WindowWidth=width; WindowHeight=height;
/* Set standard window properties. theWindowName and theIconName are set
* to Name, which unless you change it (in ezgraph.h), it will be
* EZride. */
XStringListToTextProperty(&name,1,&theWindowName);
XStringListToTextProperty(&name,1,&theIconName);
theSizeHints.base_width = width;
theSizeHints.base_height = height;
theSizeHints.min_aspect.x = width; /* Maintain x:y ratio */
theSizeHints.max_aspect.x = width;
theSizeHints.min_aspect.y = height;
theSizeHints.max_aspect.y = height;
theSizeHints.flags = PSize|PAspect;
if(!(WM_CTRLS_POS)) theSizeHints.flags |= USPosition;
/* Setting USPosition here seems to make the WM honor the x and y
* specified by XCreateWindow above. Not setting this should give control
* of position to the WM. Note that the root window of an application is
* special in that the WM has special privileges over its properties so
* this may not work on all platforms. */
XSetWMProperties(theDisplay, theWindow, &theWindowName, &theIconName,
NULL, 0, &theSizeHints, NULL, NULL);
/* Express interest in WM killing this application */
if ((del_atom = XInternAtom(theDisplay, "WM_DELETE_WINDOW", TRUE)) != None) {
XSetWMProtocols(theDisplay, theWindow, &del_atom, 1);
}
XMapWindow(theDisplay, theWindow);
XIfEvent(theDisplay, &event, WaitForNotify, (char *)theWindow);
glXMakeCurrent(theDisplay, theWindow, theGLXContext);
/* Print useful information. I suggest printing this at least once */
if(verbose>1) {
printf("%s version %d of the X Window System, X%d R%d\n",
ServerVendor (theDisplay),
VendorRelease (theDisplay),
ProtocolVersion (theDisplay),
ProtocolRevision(theDisplay));
if(theDepth==1) {
printf("Color plane depth...........%d (monochrome)\n", theDepth);
}
else {
printf("Color plane depth...........%d \n", theDepth);
}
printf("Display Width...............%d \n", theDWidth);
printf("Display Height..............%d \n", theDHeight);
printf("The display %s\n", XDisplayName(theDisplayName));
}
}
/* ========================================================================= */
static Bool WaitForNotify(Display *d, XEvent *e, char *arg)
{
/* As seen in the Porting Guide. */
return (e->type == MapNotify) && (e->xmap.window == (Window)arg);
}
/* ========================================================================= */
void QuitX (void)
{
if(GRAPHICS)
XCloseDisplay(theDisplay);
return;
}
/* ========================================================================= */
/* Save to a file a png image of what is currently on the screen using glReadPixels + netpbm converter. */
static void Save_image (void) {
static int num = 0;
static int istep_prev = 0;
static char cmd[4096];
int bufsize=WindowWidth*WindowHeight*3;
unsigned char *buf;
char *fifoname=tempnam(NULL,NULL);
pid_t child_pid;
FILE *fifo;
int status;
if (!theWindow) return;
if (-1==mkfifo(fifoname,0600)) {perror("Save_image could not make a fifo"); return;}
if (istep != istep_prev) num=0;
switch (child_pid=fork()) {
case -1:
perror("pipeto could not fork");
return;
case 0:
sprintf(cmd,"cat %s | pnmflip -tb | pnmtopng > %s%06d-%02d.png",fifoname,"snap",istep,num);
system(cmd);
_exit(0);
default:
if (NULL==(fifo=fopen(fifoname,"w"))) {perror("Save_image could not write to fifo"); return;}
fprintf(fifo,"P6\n%d %d\n%d\n",WindowWidth,WindowHeight,255);
XRaiseWindow(theDisplay,theWindow);
glFlush(); /* printf("glFlush done\n"); */
buf=malloc(bufsize);
glReadPixels(0,0,WindowWidth,WindowHeight,GL_RGB,GL_UNSIGNED_BYTE,buf);
fwrite(buf,1,bufsize,fifo);
if (0!=fclose(fifo)) perror("Save_image could not close fifo");
waitpid(child_pid,&status,0);
if (0==(WIFEXITED(status))) fprintf(stderr,"Save_image child status %08x\n",status);
free(buf);
unlink(fifoname);
istep_prev=istep;
num++;
}
}
/* ========================================================================= */