Henrik Tramberend (henrik++at++wertheim)
Fri, 3 Feb 1995 12:38:23 +0001 (MET)
> Hello.
>
> I'm trying to make a method of rendering for the spherical projector.
>
Hi,
a few month ago I wrote this little demo which uses method 2 you
described. It renders to the il buffer and updates texture memory directly
form there. On an ONYX RE2 with one RM4 it renders 14 HZ on the
esprit.flt model. The code is rather unpolished, but maybe it helps.
ciao
henrik
/*
* complex.c
*
* IRIS Performer example using cull and draw process callbacks.
* Mouse and keyboard go through GL which is simpler than mixed
* model (GLX), but does incur some overhead in the draw process.
*
* $Revision: 1.35 $
* $Date: 1993/08/22 11:06:15 $
*
* Run-time controls:
* ESC-key: exits
* F1-key: profile
* Left-mouse: advance
* Middle-mouse: stop
* Right-mouse: retreat
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <getopt.h> /* for cmdline handler */
#include <gl/device.h>
#include "pf.h"
#include "pfsgi.h"
static void ilbCullChannel(pfChannel *chan, void *data);
static void ilbDrawChannel(pfChannel *chan, void *data);
static void winCullChannel(pfChannel *chan, void *data);
static void winDrawChannel(pfChannel *chan, void *data);
static void OpenPipeline(pfPipe *p);
static void UpdateView(void);
static void GetGLInput(void);
static void usage(void);
static pfNode* genTexQuad(void);
static void DrawTexQuad(void);
static void CopyFbToTex(long xs, long ys, long tid);
static void CopyFbToMemToTex(long xs, long ys, long tid);
#define Button1Mask 0x01
#define Button2Mask 0x02
#define Button3Mask 0x04
#define HISPEED 0.1f
#define LOSPEED 0.001f
/*
* structure that resides in shared memory so that the
* application, cull, and draw processes can access it.
*/
typedef struct
{
long exitFlag;
long inWindow;
float mouseX;
float mouseY;
long mouseButtons;
long shiftKey;
long winId;
long winOriginX;
long winOriginY;
long winSizeX;
long winSizeY;
long win2Id;
long win2OriginX;
long win2OriginY;
long win2SizeX;
long win2SizeY;
long texId;
unsigned long* texbuf;
pfCoord ilbview;
pfCoord winview;
float ilbsceneSize;
float winsceneSize;
int drawStats;
} SharedData;
SharedData* Shared;
/* light source created and updated in draw-process */
static pfLight *Sun;
/* for configuring multi-process */
static long ProcSplit = PFMP_APPCULLDRAW;
/* write out scene upon read-in - uses pfDebugPrint */
static long WriteScene = 0;
char ProgName[PF_MAXSTRING];
static ulong* texMem;
static void gentex (int xs, int ys, unsigned long *texbuf)
{
int pixcount;
unsigned long *pixel;
pixcount = xs * ys;
pixel = texbuf;
while (--pixcount) {
*(pixel++) = lrand48 () | 0xff000000;
}
}
/*
* Usage() -- print usage advice and exit. This procedure
* is executed in the application process.
*/
static void
Usage (void)
{
fprintf(stderr, "Usage: %s [-p procSplit] [file.flt ...]\n", ProgName);
pfExit();
exit(1);
}
/*
* docmdline() -- use getopt to get command-line arguments,
* executed at the start of the application process.
*/
static long
docmdline(int argc, char *argv[])
{
long opt;
strcpy(ProgName, argv[0]);
/* process command-line arguments */
while ((opt = getopt(argc, argv, "wp:?")) != -1)
{
switch (opt)
{
case 'w':
WriteScene = 1;
break;
case 'p':
ProcSplit = atoi(optarg);
break;
case '?':
Usage();
}
}
return optind;
}
/*
* main() -- program entry point. this procedure
* is executed in the application process.
*/
int
main (int argc, char *argv[])
{
int arg;
int found;
pfNode *root;
pfChannel *ilbchan;
pfChannel *winchan;
pfScene *ilbscene;
pfScene *winscene;
pfPipe *p;
pfEarthSky *eSky;
pfSphere ilbbsphere;
pfCoord ilbview;
pfCoord winview;
float ilbfar = 10000.0f;
float winfar = 10000.0f;
if (argc < 2)
Usage();
arg = docmdline(argc, argv);
pfInit();
/* configure multi-process selection */
pfMultiprocess(ProcSplit);
/* allocate shared before fork()'ing parallel processes */
Shared = (SharedData*)pfMalloc(sizeof(SharedData), pfGetSharedArena());
Shared->inWindow = 0;
Shared->exitFlag = 0;
Shared->drawStats = 0;
/* initiate multi-processing mode set in pfMultiprocess call */
pfConfig();
ilbscene = pfNewScene();
winscene = pfNewScene();
/* specify directories where geometry and textures exist */
if (!(getenv("PFPATH")))
pfFilePath(
"."
":./data"
":../data"
":../../data"
":/usr/src/Performer/data"
);
fprintf(stderr,"FilePath: %s\n", pfGetFilePath());
/* load files named by command line arguments */
for (found = 0; arg < argc; arg++)
{
if ((root = LoadFile(argv[arg], NULL)) != NULL)
{
pfAddChild(ilbscene, root);
found++;
}
}
/* if no files successfully loaded, terminate program */
if (!found)
Usage();
/* generate winscene */
root = genTexQuad();
pfAddChild(winscene, root);
/* determine extent of scene's geometry */
pfGetNodeBSphere(ilbscene, &ilbbsphere);
p = pfGetPipe(0);
pfPhase(PFPHASE_FREE_RUN);
/* Open and configure full screen GL window. */
pfInitPipe(p, OpenPipeline);
pfFrameRate(60.0f);
ilbchan = pfNewChan(p);
pfChanCullFunc(ilbchan, ilbCullChannel);
pfChanDrawFunc(ilbchan, ilbDrawChannel);
pfChanScene(ilbchan, ilbscene);
pfChanNearFar(ilbchan, 0.1f, ilbfar);
winchan = pfNewChan(p);
pfChanCullFunc(winchan, winCullChannel);
pfChanDrawFunc(winchan, winDrawChannel);
pfChanScene(winchan, winscene);
pfChanAutoAspect(winchan, PFFRUST_CALC_NONE);
pfMakeOrthoFrust(winchan, -1.0, 1.0, -1.0, 1.0);
pfFrustNearFar(winchan, -1.0, 1.0);
/* Create an earth/sky model that draws sky/ground/horizon */
eSky = pfNewESky();
pfESkyMode(eSky, PFES_BUFFER_CLEAR, PFES_SKY_GRND);
pfESkyAttr(eSky, PFES_GRND_HT, -10.0f);
pfChanESky(ilbchan, eSky);
{
float sceneSize;
/* Set initial view to be "in front" of scene */
/* view point at center of bbox */
pfCopyVec3(ilbview.xyz, ilbbsphere.center);
/* find max dimension */
sceneSize = ilbbsphere.radius * 2.0f;
Shared->ilbsceneSize = sceneSize;
/* offset so all is visible */
Shared->ilbview.xyz[PF_Y] -= sceneSize;
Shared->ilbview.xyz[PF_Z] += 0.25f*sceneSize;
pfSetVec3(Shared->ilbview.hpr, 0.0f, 0.0f, 0.0f);
pfChanView(ilbchan, Shared->ilbview.xyz, Shared->ilbview.hpr);
}
/* main simulation loop */
while (!Shared->exitFlag)
{
/* wait until next frame boundary */
pfSync();
/* Set view parameters. */
UpdateView();
pfChanView(ilbchan, Shared->ilbview.xyz, Shared->ilbview.hpr);
/* initiate traversal using current state */
pfFrame();
}
/* terminate cull and draw processes (if they exist) */
pfExit();
/* exit to operating system */
exit(0);
}
/*
* UpdateView() updates the eyepoint based on the information
* placed in shared memory by GetGLInput().
*/
static void
UpdateView(void)
{
static float head = 0.0f;
static float pitch = 90.0f;
static float speed = 0.0f;
static float speedLimit = 4.0f;
pfCoord *view;
float cp;
view = &Shared->ilbview;
if (!Shared->inWindow)
{
speed = 0;
return;
}
switch (Shared->mouseButtons)
{
case Button1Mask: /* LEFTMOUSE: faster forward or slower backward*/
if (speed > 0.0f)
speed *= 1.2f;
else
speed /= 1.2f;
if (PF_ABSLT(speed, LOSPEED * Shared->ilbsceneSize))
speed = LOSPEED * Shared->ilbsceneSize;
else if (speed >= HISPEED * Shared->ilbsceneSize)
speed = HISPEED * Shared->ilbsceneSize;
break;
case Button2Mask: /* MIDDLEMOUSE: stop moving and pick */
speed = 0.0f;
break;
case Button3Mask: /* RIGHTMOUSE: faster backward or slower foreward*/
if (speed < 0.0f)
speed *= 1.2f;
else
speed /= 1.2f;
if (PF_ABSLT(speed, LOSPEED * Shared->ilbsceneSize))
speed = -LOSPEED * Shared->ilbsceneSize;
else if (speed <= -HISPEED * Shared->ilbsceneSize)
speed = -HISPEED * Shared->ilbsceneSize;
break;
}
/* update view direction */
view->hpr[PF_H] -= Shared->mouseX * PF_ABS(Shared->mouseX);
view->hpr[PF_P] = Shared->mouseY * PF_ABS(Shared->mouseY) * 90.0f;
view->hpr[PF_R] = 0.0f;
/* update view position */
cp = cosf(PF_DEG2RAD(view->hpr[PF_P]));
view->xyz[PF_X] += speed*sinf(-PF_DEG2RAD(view->hpr[PF_H])*cp);
view->xyz[PF_Y] += speed*cosf(-PF_DEG2RAD(view->hpr[PF_H])*cp);
view->xyz[PF_Z] += speed*sinf( PF_DEG2RAD(view->hpr[PF_P]));
}
/*
* OpenPipeline() -- create a pipeline: setup the window system,
* the IRIS GL, and IRIS Performer. this procedure is executed in
* the draw process (when there is a separate draw process).
*/
static void
OpenPipeline(pfPipe *p)
{
float xSize = 512;
float ySize = 512;
/* negotiate with window-manager */
scrnselect(0);
foreground();
prefsize(xSize, ySize);
Shared->winId = winopen("IRIS Performer");
winconstraints();
keepaspect(1, 1);
winconstraints();
/* register events of note with event-queue manager */
qdevice(ESCKEY);
qdevice(F1KEY);
qdevice(LEFTSHIFTKEY);
/* negotiate with GL */
pfInitGfx(p);
/* create a light source in the "south-west" (QIII) */
Sun = pfNewLight(NULL);
pfLightPos(Sun, -0.3f, -0.3f, 1.0f, 0.0f);
/* create a default texture environment */
pfApplyTEnv(pfNewTEnv(NULL));
/* create a default lighting model */
pfApplyLModel(pfNewLModel(NULL));
pfApplyMtl(pfNewMtl(NULL));
/* enable culling of back-facing polygons */
pfCullFace(PFCF_OFF);
/*
* These enables should be set to reflect the majority of the
* database. If most geometry is not textured, then texture
* should be disabled. However, you then need to change the
* FLIGHT-format file reader. (pfflt.c)
*/
pfEnable(PFEN_TEXTURE);
pfEnable(PFEN_LIGHTING);
pfEnable(PFEN_FOG);
/* get ilbuffer */
if (ilbuffer(1) != 1)
printf("ilbuffer(1): failed.\n");
else
printf("ilbuffer(1): succeeded.\n");
}
/*
* CullChannel() -- traverse the scene graph and generate a
* display list for the draw process. This procedure is
* executed in the cull process.
*/
static void
ilbCullChannel(pfChannel *channel, void *data)
{
/*
* pfDrawGeoSet or other display listable Performer routines
* could be invoked before or after pfCull()
*/
pfCull();
}
static void
winCullChannel(pfChannel *channel, void *data)
{
/*
* pfDrawGeoSet or other display listable Performer routines
* could be invoked before or after pfCull()
*/
pfCull();
}
/*
* DrawChannel() -- draw a channel and read input queue. this
* procedure is executed in the draw process (when there is a
* separate draw process).
*/
static void
ilbDrawChannel (pfChannel *channel, void *data)
{
float left, right, bottom, top;
int tilesx, tilesy;
int j, i;
int x0, y0;
float s0, s1, t0, t1;
/* read window origin and size (it may have changed) */
pfGetPipeSize(pfGetChanPipe(channel),
&Shared->winSizeX, &Shared->winSizeY);
pfGetPipeOrigin(pfGetChanPipe(channel),
&Shared->winOriginX, &Shared->winOriginY);
left = 0.0;
right = 512.0 / (float)Shared->winSizeX;
bottom = 0.0;
top = 512.0 / (float)Shared->winSizeY;
pfChanViewport(channel, left, right, bottom, top);
/*
printf("ilbChannel: %d %d %d %d\n",
(int)left, (int)(right * Shared->winSizeX),
(int)bottom, (int)(top * Shared->winSizeY));
*/
/* switch to ilbuffer */
ildraw(1);
backbuffer(FALSE);
/* rebind light so it stays fixed in position */
pfLightOn(Sun);
/* erase framebuffer and draw Earth-Sky model */
pfClearChan(channel);
/* invoke Performer draw-processing for this frame */
pfDraw();
/* switch off ilbuffer */
ildraw(0);
backbuffer(TRUE);
}
static void
winDrawChannel (pfChannel *channel, void *data)
{
static unsigned long tilebuf[128*128];
pfVec4 color;
int i, j;
int tilesx, tilesy;
float s0, s1, t0, t1;
long x0, y0;
texbind (TX_TEXTURE_0, Shared->texId);
readsource(SRC_ILBUFFER_1);
tilesx = 512 / 128;
tilesy = 512 / 128;
for (i=0; i<tilesx; i++) {
s0 = (float) i / (float) (tilesx);
s1 = (float) (i+1) / (float) (tilesx);
x0 = (i * 128) + Shared->winOriginX;
for (j=0; j<tilesy; j++) {
t0 = (float) j / (float) (tilesy);
t1 = (float) (j+1) / (float) (tilesy);
y0 = (j * 128) + Shared->winOriginY;
fbsubtexload(x0, y0,
TX_TEXTURE_0, Shared->texId,
s0, s1, t0, t1,
2);
}
}
pfPushState();
pfBasicState();
zbuffer(FALSE);
pfDraw();
zbuffer(TRUE);
pfPopState();
GetGLInput();
if (Shared->drawStats)
pfDrawChanStats(channel);
}
#define SPH_COMPL 80
#define CUBE_SIZE 1.0f
pfGeoSet*
MakeTexSphere(void)
{
pfGeoSet *gset;
pfGeoState *gst;
pfTexture *tex;
pfTexEnv *tenv;
void *arena;
static pfVec3* verts;
static pfVec2* tcoords;
static ushort* vind;
static ushort* tind;
pfVec3* v;
pfVec2* t;
ushort* vi;
ushort* ti;
float x, y, i, j;
long nverts, nquads;
ulong* dat;
arena = pfGetSharedArena();
gset = pfNewGSet(arena);
nverts = (SPH_COMPL+1) * (SPH_COMPL+1);
nquads = SPH_COMPL * SPH_COMPL;
verts = pfMalloc(nverts * sizeof(pfVec3), arena);
tcoords = pfMalloc(nverts * sizeof(pfVec2), arena);
vind = pfMalloc(nquads * 4 * sizeof(ushort), arena);
tind = pfMalloc(nquads * 4 * sizeof(ushort), arena);
v = verts;
t = tcoords;
for (y = -1.0; y <= 1.0; y += 2.0 / SPH_COMPL) {
for (x = -1.0; x <= 1.0; x += 2.0 / SPH_COMPL) {
float l, a, b, ls, xs, ys, xt, yt;
l = sqrtf(x*x + y*y);
if (l <= 1.0) {
a = asinf(l);
b = atan2f(y, x);
ls = (2*a)/M_PI;
xs = cosf(b)*ls;
ys = sinf(b)*ls;
xt = (xs+1.0)/2.0;
yt = (ys+1.0)/2.0;
} else {
xt = (x+1.0)/2.0;
yt = (y+1.0)/2.0;
}
/* z is up*/
(*v)[0] = x; (*v)[1] = 0.0; (*v)[2] = y; v++;
(*t)[0] = xt; (*t)[1] = yt; t++;
/*
printf ("l: %.2f ls: %.2f x: %.2f y: %.2f xt: %.2f yt: %.2f\n", l, ls, x, y, xt, yt);
*/
}
}
vi = vind;
ti = tind;
for (j=0; j<SPH_COMPL; j++) {
for (i=0; i<SPH_COMPL; i++) {
*vi = j*(SPH_COMPL+1)+i; vi++;
*vi = j*(SPH_COMPL+1)+i+1; vi++;
*vi = (j+1)*(SPH_COMPL+1)+i+1; vi++;
*vi = (j+1)*(SPH_COMPL+1)+i; vi++;
*ti = j*(SPH_COMPL+1)+i; ti++;
*ti = j*(SPH_COMPL+1)+i+1; ti++;
*ti = (j+1)*(SPH_COMPL+1)+i+1; ti++;
*ti = (j+1)*(SPH_COMPL+1)+i; ti++;
}
}
texMem = (ulong*) pfMalloc(512 * 512 * sizeof(ulong), arena);
gentex(512, 512, texMem);
/*
* set the coordinate, normal and color arrays
* and their cooresponding index arrays
*/
pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, verts, vind);
pfGSetAttr(gset, PFGS_TEXCOORD2, PFGS_PER_VERTEX, tcoords, tind);
pfGSetPrimType(gset, PFGS_QUADS);
pfGSetNumPrims(gset, nquads);
/*
* create a geostate from shared memory, enable
* texturing (in case that's not the default), and
* set the geostate for this geoset
*/
gst = pfNewGState(arena);
pfGStateMode(gst, PFSTATE_ENTEXTURE, 1);
pfGSetGState(gset, gst);
/*
* create a new texture from shared memory,
* load a texture file and add texture to geostate
*/
tex = pfNewTex(arena);
pfTexFormat(tex, PFTEX_INTERNAL_FORMAT, PFTEX_RGBA_8);
pfTexFormat(tex, PFTEX_EXTERNAL_FORMAT, PFTEX_PACK_8);
pfTexFormat(tex, PFTEX_FAST_DEFINE, PF_ON);
pfTexFilter(tex, PFTEX_MINFILTER, PFTEX_POINT);
pfTexFilter(tex, PFTEX_MAGFILTER, PFTEX_POINT);
pfTexImage(tex, texMem, 3, 512, 512, 0);
dat = (ulong *)tex;
Shared->texId = *(dat+2);
pfGStateAttr(gst, PFSTATE_TEXTURE, tex);
/*
* create a new texture environment from shared memory,
* decal the texture since the geoset has no color to
* modulate, set the texture environment for this geostate
*/
tenv = pfNewTEnv(arena);
pfTEnvMode(tenv, PFTE_DECAL);
pfGStateAttr(gst, PFSTATE_TEXENV, tenv);
return gset;
}
pfGeoSet*
MakeTexCube(void)
{
pfGeoSet *gset;
pfGeoState *gst;
pfTexture *tex;
pfTexEnv *tenv;
void *arena;
static pfVec3 verts[] ={{-CUBE_SIZE,-CUBE_SIZE, CUBE_SIZE},
{ CUBE_SIZE,-CUBE_SIZE, CUBE_SIZE},
{ CUBE_SIZE, CUBE_SIZE, CUBE_SIZE},
{-CUBE_SIZE, CUBE_SIZE, CUBE_SIZE},
{-CUBE_SIZE,-CUBE_SIZE, -CUBE_SIZE},
{ CUBE_SIZE,-CUBE_SIZE, -CUBE_SIZE},
{ CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE},
{-CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}};
static ushort vindex[] ={0, 1, 2, 3, /* front */
0, 3, 7, 4, /* left */
4, 7, 6, 5, /* back */
1, 5, 6, 2, /* right */
3, 2, 6, 7, /* top */
0, 4, 5, 1}; /* bottom */
static pfVec3 norms[] ={{ 0.0f, 0.0f, 1.0f},
{ 0.0f, 0.0f,-1.0f},
{ 0.0f, 1.0f, 0.0f},
{ 0.0f,-1.0f, 0.0f},
{ 1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f}};
static ushort nindex[] ={0, 5, 1, 4, 2, 3};
static pfVec2 tcoords[] ={{0.0f, 0.0f},
{1.0f, 0.0f},
{1.0f, 1.0f},
{0.0f, 1.0f}};
static ushort tindex[] ={0, 1, 2, 3,
0, 1, 2, 3,
0, 1, 2, 3,
0, 1, 2, 3,
0, 1, 2, 3,
0, 1, 2, 3};
ulong* dat;
arena = pfGetSharedArena();
gset = pfNewGSet(arena);
texMem = (ulong*) pfMalloc(512 * 512 * sizeof(ulong), arena);
gentex(512, 512, texMem);
/*
* set the coordinate, normal and color arrays
* and their cooresponding index arrays
*/
pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, verts, vindex);
pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, norms, nindex);
pfGSetAttr(gset, PFGS_TEXCOORD2, PFGS_PER_VERTEX, tcoords, tindex);
pfGSetPrimType(gset, PFGS_QUADS);
pfGSetNumPrims(gset, 6);
/*
* create a geostate from shared memory, enable
* texturing (in case that's not the default), and
* set the geostate for this geoset
*/
gst = pfNewGState(arena);
pfGStateMode(gst, PFSTATE_ENTEXTURE, 1);
pfGSetGState(gset, gst);
/*
* create a new texture from shared memory,
* load a texture file and add texture to geostate
*/
tex = pfNewTex(arena);
pfTexFormat(tex, PFTEX_INTERNAL_FORMAT, PFTEX_RGBA_8);
pfTexFormat(tex, PFTEX_EXTERNAL_FORMAT, PFTEX_PACK_8);
pfTexFormat(tex, PFTEX_FAST_DEFINE, PF_ON);
pfTexFilter(tex, PFTEX_MINFILTER, PFTEX_BILINEAR);
pfTexFilter(tex, PFTEX_MAGFILTER, PFTEX_BILINEAR);
pfTexImage(tex, texMem, 3, 512, 512, 0);
dat = (ulong *)tex;
Shared->texId = *(dat+2);
printf("texId: %d\n", Shared->texId);
pfGStateAttr(gst, PFSTATE_TEXTURE, tex);
/*
* create a new texture environment from shared memory,
* decal the texture since the geoset has no color to
* modulate, set the texture environment for this geostate
*/
tenv = pfNewTEnv(arena);
pfTEnvMode(tenv, PFTE_DECAL);
pfGStateAttr(gst, PFSTATE_TEXENV, tenv);
return gset;
}
static pfNode*
genTexQuad(void)
{
pfGroup* group = pfNewGroup();
pfGeode* geode = pfNewGeode();
pfGeoSet* gset = MakeTexSphere();
pfAddGSet(geode, gset);
pfAddChild(group, geode);
return (pfNode*) group;
}
static void
GetGLInput(void)
{
long x, y;
while (qtest())
{
short value;
long device = qread(&value);
/* only act on key-down transitions */
if (value)
{
switch (device)
{
/* ESC-key signals end of simulation */
case ESCKEY:
Shared->exitFlag = 1;
break;
/* F1-key toggles channel-stats display */
case F1KEY:
Shared->drawStats = !Shared->drawStats;
break;
}
}
}
/* read cursor position (may be outside our window) */
x = getvaluator(MOUSEX);
y = getvaluator(MOUSEY);
Shared->inWindow = 0;
/* update cursor virtual position when cursor inside window */
if (x >= Shared->winOriginX &&
x < (Shared->winOriginX + Shared->winSizeX) &&
y >= Shared->winOriginY &&
y < (Shared->winOriginY + Shared->winSizeY))
{
Shared->inWindow = 1;
Shared->mouseX = 2.0f * ((x - Shared->winOriginX) /
(float)Shared->winSizeX) - 1.0f;
Shared->mouseY = 2.0f * ((y - Shared->winOriginY) /
(float)Shared->winSizeY) - 1.0f;
Shared->mouseButtons = ((getbutton(LEFTMOUSE) ? Button1Mask : 0) |
(getbutton(MIDDLEMOUSE) ? Button2Mask : 0) |
(getbutton(RIGHTMOUSE) ? Button3Mask : 0));
}
Shared->shiftKey = getbutton(LEFTSHIFTKEY);
}
------------------------------------------------------------------------
Henrik Tramberend Phone: +49(30)25417.3
Art+Com e.V. Fax: +49(30)25417.555
Berlin, Germany E-Mail: henrik++at++artcom.de
This archive was generated by hypermail 2.0b2 on Mon Aug 10 1998 - 17:50:56 PDT