/* * Copyright (C) 1997-2003, R3vis Corporation. * * 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 2 * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA, * or visit http://www.gnu.org/copyleft/gpl.html. * * Contributor(s): * Wes Bethel, R3vis Corporation, Marin County, California * * The OpenRM project is located at http://openrm.sourceforge.net/. */ /* * $Id: trans2d.c,v 1.12 2003/04/13 18:13:23 wes Exp $ * $Revision: 1.12 $ * $Name: OpenRM-1-5-2-RC1 $ * $Log: trans2d.c,v $ * Revision 1.12 2003/04/13 18:13:23 wes * Updated copyright dates. * * Revision 1.11 2003/01/27 05:07:07 wes * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL. * * Revision 1.10 2003/01/16 22:22:45 wes * Updated all source files to reflect new organization of header files: all * headers that were formerly located in include/rmaux, include/rmv and * include/rmi are now located in include/rm. * * Revision 1.9 2002/06/17 00:45:46 wes * Updated copyright line. * * Revision 1.8 2001/07/15 22:33:19 wes * Added rmPipeDelete to the end of all demo progs. For those that use * an initfunc, added a new RMnode * parm (which is unused, except for rm2screen). * * Revision 1.7 2001/06/03 19:44:05 wes * Add calls to new rmaux routines to handle window resize events, and * for keyboard event handling. * * Revision 1.6 2001/03/31 16:55:18 wes * Added procmode.h, which defines an RMpipe processing mode used in * most demonstration programs. The default processing mode is * RM_PIPE_MULTISTAGE_VIEW_PARALLEL. * * Revision 1.5 2000/12/02 17:24:32 wes * Version 1.4.0-alpha-1 checkin. See the RELEASENOTES file for * a summary of changes. With this checkin, these demo programs * are no longer compatible with versions of the OpenRM API that * are pre-1.4.0. * * Revision 1.4 2000/08/28 01:38:18 wes * Updated rmaux* interfaces - rmauxEventLoop now takes two additional * parameters (keypress and window resize app callbacks); replaced * rmauxUI with rmauxSetGeomTransform and, where appropriate, * rmauxSetCamera3DTransform. * * Revision 1.3 2000/04/27 03:12:05 wes * Minor code cleanup. * * Revision 1.2 2000/04/20 18:04:34 wes * Minor tweaks and reorg for OpenRM 1.2.1. * * Revision 1.1.1.1 2000/02/28 21:55:30 wes * OpenRM 1.2 Release * * Revision 1.11 2000/02/28 17:21:55 wes * RM 1.2, pre-OpenRM * */ #include <rm/rm.h> #include <rm/rmaux.h> #include "libdio.h" #include "procmode.h" /* * this program generates a bunch of 2D objects, lets you pick one * and translate it around in the (x,y) plane. * * since translation can be applied to RMnodes, and not RMprimitives, each * object is put into a different node (VRML style). * * the only command line args are those that let you set the * display image window size: "-w xxx -h yyy" */ static RMnode *MyRoot; int img_width=400,img_height=300; static char image_filename[128]={"data/doghead.x"}; #define DIVISOR 1.F/(float)(RAND_MAX) #include <time.h> void initrand() { srand(time(NULL)); } float RandToFloat(float fmin, float fmax) { /* * generate a random number, convert it to some floating point * range, and return it. */ float mag,t; int r1; r1 = rand(); /* input rand numbers in range 0..32767 */ /* convert to range 0..1 */ t = (float)r1 * DIVISOR; mag = fmax-fmin; t = t*mag + fmin; return(t); } void my_set_scene(RMnode *camNode) { RMcamera2D *c = rmCamera2DNew(); float vp[4]; /* * assume that all objects lie in -1..1 in x&y. all their vertices * will lie inside that area, however they may hang off the edges * due to radius sizes, etc. */ rmCamera2DSetExtents(c,-1.0F, -1.0F, 1.0F, 1.0F); vp[0] = vp[1] = 0.0; vp[2] = vp[3] = 1.0; rmCamera2DResetAspectRatio(c,vp, img_width,img_height); /* set the camera parm on MyRoot */ rmNodeSetSceneCamera2D(camNode,c); rmCamera2DDelete(c); /* don't need it any more..a copy is made */ } void create_boxes(RMnode *parent, int nboxes) { int i; RMprimitive *p; RMvertex2D *v; RMnode *n; RMcolor3D *c; v = rmVertex2DNew(nboxes*2); c = rmColor3DNew(nboxes); for (i=0;i<nboxes*2;i++) { v[i].x = RandToFloat(-1.0F, 1.0F); v[i].y = RandToFloat(-1.0F, 1.0F); } for (i=0;i<nboxes;i++) { c[i].r = c[i].g = 0.0; c[i].b = (float)i/(float)(nboxes-1); } n = rmNodeNew("boxes",RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE); p = rmPrimitiveNew(RM_BOX2D); rmPrimitiveSetVertex2D(p,nboxes*2,v,RM_COPY_DATA,NULL); rmPrimitiveSetColor3D(p,nboxes,c, RM_COPY_DATA, NULL); rmNodeAddPrimitive(n,p); rmNodeAddChild(parent,n); rmVertex2DDelete(v); rmColor3DDelete(c); } void create_circles(RMnode *parent, int ncircles) { int i; RMprimitive *p; RMvertex2D *v; RMnode *n; RMcolor3D *c; float *r; v = rmVertex2DNew(ncircles); r = rmFloatNew(ncircles); c = rmColor3DNew(ncircles); for (i=0;i<ncircles;i++) { v[i].x = RandToFloat(-1.0F, 1.0F); v[i].y = RandToFloat(-1.0F, 1.0F); r[i] = RandToFloat(0.1,0.3); c[i].r = (float)i/(float)(ncircles-1); c[i].g = 0.0; c[i].b = 0.0; } n = rmNodeNew("circles",RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE); p = rmPrimitiveNew(RM_CIRCLE2D); rmPrimitiveSetVertex2D(p,ncircles,v,RM_COPY_DATA,NULL); rmPrimitiveSetColor3D(p,ncircles,c, RM_COPY_DATA, NULL); rmPrimitiveSetRadii(p,ncircles,r, RM_COPY_DATA, NULL); rmNodeSetPolygonDrawMode(n,RM_FRONT_AND_BACK, RM_LINE); rmNodeSetLineStyle(n,RM_LINES_DASHED); rmNodeSetLineWidth(n,RM_LINEWIDTH_HEAVY); rmNodeAddPrimitive(n,p); rmNodeAddChild(parent,n); rmVertex2DDelete(v); rmColor3DDelete(c); rmFloatDelete(r); } void create_ellipses(RMnode *parent, int n) { int i; RMprimitive *p; RMvertex2D *v; RMnode *node; RMcolor3D *c; float *r; float *rotatevals; v = rmVertex2DNew(n); r = rmFloatNew(n*2); c = rmColor3DNew(n); rotatevals = rmFloatNew(n); for (i=0;i<n;i++) { v[i].x = RandToFloat(-1.0F, 1.0F); v[i].y = RandToFloat(-1.0F, 1.0F); c[i].r = 0.0; c[i].g = (float)(n-i) /(float)(n); c[i].b = 0.0; } for (i=0;i<n;i++) { /* * rotation values are specified in 10th's of degrees. therefore, * a value of 3600 specifies 360 degrees of rotation. for this example, * we constain rotation to +/- 180 degrees. */ rotatevals[i] = RandToFloat(-1800.0F, 1800.0F); } for (i=0;i<n*2;i++) { if (i & 1) r[i] = RandToFloat(0.05F, 0.1F); else r[i] = RandToFloat(0.2F, 0.3F); } node = rmNodeNew("ellipses",RM_RENDERPASS_2D,RM_RENDERPASS_OPAQUE); p = rmPrimitiveNew(RM_ELLIPSE2D); rmPrimitiveSetVertex2D(p,n,v,RM_COPY_DATA,NULL); rmPrimitiveSetColor3D(p,n,c, RM_COPY_DATA, NULL); rmPrimitiveSetRadii(p,n*2,r, RM_COPY_DATA, NULL); rmPrimitiveSetEllipse2DRotate(p,n,rotatevals,RM_COPY_DATA,NULL); rmNodeSetPolygonDrawMode(node,RM_FRONT_AND_BACK, RM_LINE); rmNodeAddPrimitive(node,p); rmNodeAddChild(parent,node); rmVertex2DDelete(v); rmColor3DDelete(c); rmFloatDelete(r); rmFloatDelete(rotatevals); } void create_sprite(RMnode *parent) { RMimage *orig_img, *sprite_img; RMnode *sprite_node; RMvertex2D v; RMprimitive *sp; /* * first, read in an image. */ orig_img = dioReadAVSImage(image_filename); #if 0 /* * next,build a new image that is pretty small in size, and * resize the original image. */ sprite_img = rmImageNew(2,100,100,1,rmImageGetFormat(orig_img), RM_UNSIGNED_BYTE, RM_COPY_DATA); rmImageResize(orig_img,sprite_img,RM_HARDWARE); /* throw away the first image because we don't need it anymore */ rmImageDelete(orig_img); #endif /* * alternately, instead of resizing the image we could just set it's * pixel zoom parameters. take your pick. you don't need both, so * if you uncomment the following, comment out the part above * where we resize the image. */ rmImageSetPixelZoom(orig_img,0.25F, 0.25F); sprite_img = orig_img; v.x = RandToFloat(-1.0F,1.0F); v.y = RandToFloat(-1.0F,1.0F); sprite_node = rmNodeNew("sprite",RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE); sp = rmPrimitiveNew(RM_SPRITE); rmPrimitiveSetVertex2D(sp,1,&v,RM_COPY_DATA,NULL); rmPrimitiveSetSprites(sp, 1, &sprite_img); rmNodeAddPrimitive(sprite_node,sp); rmNodeAddChild(parent,sprite_node); } #define ONE_OVER_255 0.0039215686 void create_bitmap(RMnode *parent) { RMimage *orig_img; RMbitmap *bmap; RMnode *bitmap_node; RMvertex2D v; RMprimitive *bp; int w,h; RMcolor3D c = {1.0F, 1.0F, 0.0F}; /* * first, read in an image. */ orig_img = dioReadAVSImage("data/wu.x"); /* * next, create a bitmap, and turn on bits based upon the * luminance component within the AVS image. this chunk of * code makes use of a priori knowledge about: * 1. the format of bytes in an RMimage buffer. */ rmImageGetImageSize(orig_img,NULL,&w,&h,NULL,NULL,NULL); bmap = rmBitmapNew(w,h); /* * the bitmap data starts out as all zeros. now, we'll go through and * turn bits on when the computed luminance of the source image * exceeds 0.5. */ { unsigned char *s; int i,j,k; int w,h; /* * we're assuming the image is in RGBA format. */ k = rmImageGetFormat(orig_img); s = rmImageGetPixelData(orig_img); rmImageGetImageSize(orig_img,NULL,&w,&h,NULL,NULL,NULL); for (j=0;j<h;j++) { for (i=0;i<w;i++) { float r,g,b,a; /* luminance = 0.3*r,0.6*g,0.1*b, input is unsigned bytes */ if (k == RM_IMAGE_RGBA) { r = (float)(*s++) * ONE_OVER_255; g = (float)(*s++) * ONE_OVER_255; b = (float)(*s++) * ONE_OVER_255; a = (float)(*s++) * ONE_OVER_255; r = r * 0.3 + g*0.6 + b*0.1; if (r > 0.5) rmBitmapSetBit(bmap,i,j); } else /* assume k==1 */ { /* * the AVS images are always crunched to RGB format, so * if we're here, there's a big error of some type. */ if (*s++ > 0x80) rmBitmapSetBit(bmap,i,j); } } } } /* throw away the first image because we don't need it anymore */ rmImageDelete(orig_img); v.x = RandToFloat(-1.0F,1.0F); v.y = RandToFloat(-1.0F,1.0F); bitmap_node = rmNodeNew("bitmap",RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE); bp = rmPrimitiveNew(RM_BITMAP); rmPrimitiveSetVertex2D(bp,1,&v,RM_COPY_DATA,NULL); rmPrimitiveSetColor3D(bp,1,&c,RM_COPY_DATA,NULL); rmPrimitiveSetBitmaps(bp,1,&bmap); rmNodeAddPrimitive(bitmap_node,bp); rmNodeAddChild(parent,bitmap_node); } void create_text(RMnode *parent) { RMnode *textnode; RMprimitive *p; RMtextProps *tp; RMvertex2D v; RMcolor3D white={1.0,1.0,1.0}; char string[32]={"woof!"}; char *c[1]; textnode = rmNodeNew("text",RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE); p = rmPrimitiveNew(RM_TEXT); v.x = RandToFloat(-0.75,0.75); v.y = RandToFloat(-0.75,0.75); rmPrimitiveSetVertex2D(p,1,&v,RM_COPY_DATA,NULL); tp = rmTextPropsNew(); rmTextPropsSetAttribs(tp, RM_FONT_SANS, RM_FONT_L, RM_FALSE,RM_FALSE, RM_LEFT, RM_BOTTOM); rmNodeSetSceneTextProps(textnode,tp); rmTextPropsDelete(tp); c[0] = string; rmPrimitiveSetText(p,1,c); rmPrimitiveSetColor3D(p,1,&white, RM_COPY_DATA,NULL); rmNodeAddPrimitive(textnode,p); rmNodeAddChild(parent,textnode); } void my_build_objs() { MyRoot = rmNodeNew("MyRoot",RM_RENDERPASS_2D, RM_RENDERPASS_OPAQUE); initrand(); create_boxes(MyRoot,3); create_circles(MyRoot, 3); create_ellipses(MyRoot, 3); create_sprite(MyRoot); create_bitmap(MyRoot); create_text(MyRoot); rmNodeAddChild(rmRootNode(),MyRoot); { RMcolor4D bgcolor={0.2,0.2,0.3,1.0}; rmNodeSetSceneBackgroundColor(MyRoot, &bgcolor); } } void usage(char *s) { char buf[128]; sprintf(buf," usage: %s [-w xxx (image width)] [-h yyy (image height)] \n",s); rmError(buf); exit(-1); } void parse_args(int ac, char *av[]) { int i; i = 1; while (i < ac) { if (strcmp(av[i],"-w") == 0) { i++; sscanf(av[i],"%d",&img_width); } else if (strcmp(av[i],"-h") == 0) { i++; sscanf(av[i],"%d",&img_height); } else { usage(av[0]); exit(-1); } i++; } } RMnode *picked_node=NULL; int start_x, start_y; RMvertex3D save_translate_vector; float sx,sy; float camera_yscale,camera_xscale; int mypickfunc(RMpipe *p, int ix, int iy) { RMpick *pick_results; pick_results = rmFramePick(p, rmRootNode(), ix,iy); if (pick_results != NULL) { fprintf(stderr," name of picked object is %s \n",rmPickedNodeName(pick_results)); picked_node = rmPickedNode(pick_results); if (rmNodeGetTranslateVector(picked_node,&save_translate_vector) == RM_WHACKED) { save_translate_vector.x = save_translate_vector.y = save_translate_vector.z = 0.; } start_x = ix; start_y = iy; /* * the following code is needed to compute the camera_xscale and * camera_yscale parms. these parms are used to attenuate the * translations in the presence of non-square windows. */ { RMcamera2D *c; float xmin,ymin,xmax,ymax; float aspect; rmNodeGetSceneCamera2D(MyRoot,&c); rmCamera2DGetExtents(c,&xmin,&ymin,&xmax,&ymax); rmCamera2DGetAspectRatio(c, &aspect); camera_yscale = ymax-ymin; /* y-span of camera in world coords */ camera_xscale = xmax-xmin; /* x-span of camera in world coords */ camera_xscale *= aspect; rmPipeGetWindowSize(p, &img_width, &img_height); camera_yscale /= img_height; camera_xscale /= img_width; rmCamera2DDelete(c); } } else { rmNotice("no objects picked. try again!"); picked_node = NULL; } return(1); /* necessary, otherwise the event loop will exit*/ } int mytranslatefunc(RMpipe *p, int ix, int iy) { float tx,ty; float sx,sy; RMvertex3D new_translate_vector; if (picked_node == NULL) return(1); sx = camera_xscale; sy = camera_yscale; tx = (ix - start_x) * sx; ty = (start_y - iy) * sy; /* flip y */ new_translate_vector.x = save_translate_vector.x + tx; new_translate_vector.y = save_translate_vector.y + ty; new_translate_vector.z = 0.F; rmNodeSetTranslateVector(picked_node,&new_translate_vector); /* redraw the frame */ rmFrame(p, rmRootNode()); return(1); /* necessary, otherwise the event loop will exit*/ } void myrenderfunc(RMpipe *p, RMnode *n) { rmFrame(p, n); } int myendpickfunc(RMpipe *p, int ix, int iy) { /* * this routine is needed in multistage_*_parallel mode to push * the last accured transformation down the rendering pipeline. */ rmFrame(p, rmRootNode()); return(1); } void myinitfunc(RMpipe *p, RMnode *n) { my_build_objs(); my_set_scene(MyRoot); /* enable only the 2D rendering pass */ rmPipeSetRenderPassEnable(p, RM_FALSE, RM_FALSE, RM_TRUE); /* * set button functions to do pick & translation */ rmauxSetButtonDownFunc (RM_BUTTON1,RM_NO_MODIFIER, mypickfunc); rmauxSetButtonMotionFunc (RM_BUTTON1,RM_NO_MODIFIER, mytranslatefunc); rmauxSetButtonUpFunc (RM_BUTTON1, RM_NO_MODIFIER, myendpickfunc); /* * set handler to reset aspect ratio when the window is resized. */ rmauxSetResizeFunc(p, MyRoot, rmauxDefaultResizeFunc); if (rmPipeProcessingModeIsMultithreaded(p) == RM_TRUE) rmFrame(p, rmRootNode()); rmFrame(p, rmRootNode()); } #ifdef RM_WIN int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; HWND hWnd; void *fptr; RMpipe *lone_pipe=NULL; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMenum targetPlatform = RM_PIPE_WGL; int status; parse_args(__argc,__argv); #else /* assume RM_X */ int main(int ac, char *av[]) { RMpipe *lone_pipe=NULL; RMenum processingMode = DEFAULT_PROCESSING_MODE; /* in procmode.h */ RMenum targetPlatform = RM_PIPE_GLX; int status; void *msg; /* needed for rmauxEventLoop win32/unix API consistency */ parse_args(ac,av); #endif /* * first stage of RM initialization. */ rmInit(); /* * create the rendering pipe. this step is required in both * Win32 and X. */ lone_pipe = rmPipeNew(targetPlatform); rmPipeSetProcessingMode(lone_pipe, processingMode); #ifdef RM_WIN { /* * Win32: when a window is created, we have to tell windows the * name of the "WndProc," the procedure that gets called by * windows with events (the event loop) (contrast to the X model * where the name of the event loop is not part of the window). * Since we're using RMaux, we know about the event handling * procedure named "rmauxWndProc" and we provide that here. */ fptr = (void *)(rmauxWndProc); hWnd = rmauxCreateW32Window(lone_pipe, NULL, /* no parent window */ 20,20,img_width,img_height,"RM for Windows", hInstance,fptr); if (hWnd == 0) exit(-1); /* * assign the new window handle to the rendering pipe. */ rmPipeSetWindow(lone_pipe,hWnd, img_width, img_height); } #endif #ifdef RM_X { Window w; w = rmauxCreateXWindow(lone_pipe, (Window)NULL, /* parent window */ 0,0,img_width,img_height, "RM for X-Windows","RM",RM_TRUE); /* * assign the window to the rendering pipe. */ rmPipeSetWindow(lone_pipe,w,img_width,img_height); } #endif /* * specify the name of the "init" function. the "init" function is * mandatory in the Win32 world, and optional in the X world. * * in Win32, we don't want to call RM services until OpenGL is * ready. we can be assured of readiness by using an init function * with RMaux. * * in X, at this point, the window is mapped and OpenGL is ready, * and we could call our init function directly. */ rmauxSetInitFunc(myinitfunc); /* * X-ism: once the window is created and assigned to the * rendering pipe, rmPipeMakeCurrent makes the OpenGL rendering context * current for the pipe+window combination. * * this step is required for X. in these demo programs, it is not * strictly required by Win32, as we made the newly created context * current as part of the OpenGL initialization sequence. */ rmPipeMakeCurrent(lone_pipe); rmauxSetRenderFunc(myrenderfunc); /* * set key handler function so this prog will exit on "q" key. */ rmauxSetKeyFunc(lone_pipe, rmauxDefaultKeyFunc); rmauxEventLoop(lone_pipe,rmRootNode(), &msg); rmPipeDelete(lone_pipe); rmFinish(); #ifdef RM_WIN return( msg.wParam ); #else return(1); #endif }