1 /*
2  * Copyright (C) 2001-2005, R3vis Corporation.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA,
17  * or visit http://www.gnu.org/copyleft/gpl.html.
18  *
19  * Contributor(s):
20  *   Wes Bethel, R3vis Corporation, Marin County, California
21  *
22  * The OpenRM project is located at http://openrm.sourceforge.net/.
23  */
24 /*
25  * $Id: offscreen.c,v 1.11 2005/07/04 18:12:48 wes Exp $
26  * $Revision: 1.11 $
27  * $Name: OpenRM-1-6-0-2-c $
28  * $Log: offscreen.c,v $
29  * Revision 1.11  2005/07/04 18:12:48  wes
30  * Fix w32 compile problems.
31  *
32  * Revision 1.10  2005/06/08 17:13:20  wes
33  * Code cleanup to eliminate compiler warnings.
34  *
35  * Revision 1.9  2003/07/25 21:56:43  wes
36  * Bug fix: post-render callback wasn't being assigned under Win32.
37  *
38  * Revision 1.8  2003/04/13 18:13:23  wes
39  * Updated copyright dates.
40  *
41  * Revision 1.7  2003/01/27 05:07:07  wes
42  * Changes to RMpipe initialization sequence APIs. Tested for GLX, but not WGL.
43  *
44  * Revision 1.6  2003/01/16 22:22:45  wes
45  * Updated all source files to reflect new organization of header files: all
46  * headers that were formerly located in include/rmaux, include/rmv and
47  * include/rmi are now located in include/rm.
48  *
49  * Revision 1.5  2002/06/17 00:39:58  wes
50  * replaced rmSubtreeFrame with rmFrame.
51  *
52  * Revision 1.4  2001/10/15 00:23:52  wes
53  * Use new rmPipeSetOffscreenWindow() routine.
54  *
55  * Revision 1.3  2001/07/15 22:33:19  wes
56  * Added rmPipeDelete to the end of all demo progs. For those that use
57  * an initfunc, added a new RMnode * parm (which is unused, except for rm2screen).
58  *
59  * Revision 1.2  2001/06/03 19:41:18  wes
60  * Replace call to create an AVS format image with a call to
61  * create a JPEG image.
62  *
63  * Revision 1.1  2001/03/31 17:26:14  wes
64  * Initial entry.
65  *
66  */
67 
68 #include <rm/rm.h>
69 #include <rm/rmaux.h>
70 #include <rm/rmv.h>
71 #include <rm/rmi.h>
72 #include "libdio.h"
73 #include "procmode.h"
74 
75 int imgWidth=800,imgHeight=600;
76 
77 static char MyRootName[]={"MyRoot"};
78 static RMnode *MyRoot;
79 char datafilename[256]={"data/volume.dio"};
80 dioDataObject *mydataobj=NULL;
81 int do_color=0;
82 int do_print=0;
83 int vis_technique=0;
84 int my_linewidth = RM_LINEWIDTH_MEDIUM;
85 int my_linestyle = RM_LINES_SOLID;
86 
87 float isolevel=0.5;
88 
89 int           use_secondary=0;
90 dioDataObject *secondary=NULL;
91 RMvisMap      *vmap=NULL;
92 
93 #define DO_OFFSCREEN 1
94 
95 /*
96  * Colorization notes:
97  * 1. when a secondary dataset is provided, the isosurface will be
98  *    vertex-colorized by:
99  * 2. at each isosurface triangle vertex, the rmv routine that generates
100  *    the isosurface will invoke the app callback to retrieve
101  *    data values from the secondary dataset. the RGB(A) color value
102  *    from the visualization colormap that corresponds to the data value
103  *    from the secondary dataset will be used as the vertex color.
104  * 3. since it is an app callback that provides the secondary data values
105  *    via a callback, there is no restriction that says the actual
106  *    size, or even dimensionality, of the secondary dataset must be
107  *    the same as the primary dataset.
108  * 4. this demo maps the min/max of the secondary data set to the min/max
109  *    transfer function values.
110  * 5. this demo uses the primary dataset as the source of the secondary
111  *    dataset. that means that we should see an isosurface of constant
112  *    color, where the color will essentially be a function of the
113  *    isocontouring level.
114  */
115 
116 void
usage(char * av[])117 usage(char *av[])
118 {
119     char buf[256];
120     sprintf(buf," usage: %s [-i datafilename (defaults to data/volume.dio) [-w imgWidth] [-h imgHeight] [-p (print the scene graph to stderr)] [-l isolevel (isocontouring level, default is 0.5, use a value in range 0..1.0 with the default data set)] \n",av[0]);
121 
122 #ifdef RM_WIN
123     MessageBox(NULL,buf,"vis3d",MB_OK);
124 #else
125     fprintf(stderr,"%s",buf);
126 #endif
127 }
128 
129 void
parse_args(int ac,char * av[])130 parse_args(int ac,
131 	   char *av[])
132 {
133     int i;
134 
135     i = 1;
136     while (i < ac)
137     {
138 	if (strcmp(av[i],"-w") == 0)
139 	{
140 	    i++;
141 	    sscanf(av[i],"%d",&imgWidth);
142 	}
143 	else if (strcmp(av[i],"-h") == 0)
144         {
145 	    i++;
146 	    sscanf(av[i],"%d",&imgHeight);
147 	}
148 	else if (strcmp(av[i],"-i") == 0)
149 	{
150 	    i++;
151 	    strcpy(datafilename,av[i]);
152 	}
153 	else if (strcmp(av[i],"-2") == 0)
154         {
155 	    use_secondary=1;
156 	}
157 	else if (strcmp(av[i],"-l") == 0)
158         {
159 	    i++;
160 	    sscanf(av[i],"%f",&isolevel);
161 	}
162 	else if (strcmp(av[i],"-c") == 0)
163 	{
164 	    do_color=1;
165 	}
166 	else if (strcmp(av[i],"-p") == 0)
167 	    do_print = 1;
168 	else
169 	{
170 	    usage(av);
171 	    exit(-1);
172 	}
173 	i++;
174     }
175 }
176 
177 
178 void
my_read_data(char * datafilename)179 my_read_data(char *datafilename)
180 {
181     mydataobj = dioReadDataObject(datafilename);
182     if (mydataobj == NULL)
183     {
184 	fprintf(stderr," error reading input data file. exiting. \n");
185 	exit(-1);
186     }
187     dioObjectConditioner(mydataobj);
188 }
189 
190 void
my_set_scene(int stereo_format)191 my_set_scene(int stereo_format)
192 {
193     RMcamera3D *c=rmCamera3DNew();
194 
195     rmDefaultCamera3D(c);		/* assign it some default values. */
196 
197     rmCamera3DComputeViewFromGeometry(c,MyRoot, imgWidth, imgHeight);
198 
199     if (stereo_format != RM_MONO_CHANNEL)
200     {
201 	rmCamera3DSetStereo(c,RM_TRUE);
202 	rmCamera3DSetEyeSeparation(c,2.5F);
203 	rmCamera3DSetFocalDistance (c,0.707F);
204 
205     }
206 
207     /* add the 3D camera as a scene parameter to rmRootNode() */
208     rmNodeSetSceneCamera3D(rmRootNode(),c);
209 
210     rmCamera3DDelete(c);
211 
212 
213     /* use RM's default lighting model */
214     rmDefaultLighting(rmRootNode());
215 
216 }
217 
218 /*
219  * the following two routines are the interface between the RMV
220  * vis tools and the local data model. RMV wants us to supply routines
221  * which will tell the vis tool what the (x,y,z) point is at some grid
222  * location, and what the data point is at some grid location.
223  *
224  * the local data model is very simple, so we can make simplifying
225  * assumptions resulting in very terse routines.
226  */
227 
228 RMvertex3D
mygridfunc_uvw(int i,int j,int k,int isize,int jsize,int ksize,float * baseX,float * baseY,float * baseZ)229 mygridfunc_uvw(int i,
230 	       int j,
231 	       int k,
232 	       int isize,
233 	       int jsize,
234 	       int ksize,
235 	       float *baseX,
236 	       float *baseY,
237 	       float *baseZ)
238 {
239     /*
240      * tell RMV what this grid (x,y,z) point is at location (i,j,k).
241      * we assume the data model is sufficiently intelligent to know
242      * it's own dimensions, and is capable of dealing with a three-dimensional
243      * indexing system.
244      *
245      * we assume that the "i" index maps to width, and that "j"
246      * maps to height, and k maps to depth in the local data model.
247      */
248     RMvertex3D temp3d;
249     int indx;
250 
251     /*    indx = mydataobj->width * j + i; */
252 /*    indx = mydataobj->dims[0] * j + i + mydataobj->dims[0]*mydataobj->dims[1]*k; */
253     indx = isize * j + i + isize*jsize*k;
254 
255     temp3d.x = baseX[indx];
256     temp3d.y = baseY[indx];
257     temp3d.z = baseZ[indx];
258 
259     /* foil compiler warning */
260     ksize = 0;
261 
262     return(temp3d);
263 }
264 
265 float
mydatafunc_uvw(int i,int j,int k,int isize,int jsize,int ksize,float * baseData)266 mydatafunc_uvw(int i,
267 	       int j,
268 	       int k,
269 	       int isize,
270 	       int jsize,
271 	       int ksize,
272 	       float *baseData)
273 {
274     /*
275      * tell RMV what the data value is at grid location (i,j,k). we
276      * assume an n-dimensional array can be accesses as if it
277      * were a one-d array.
278      */
279     int indx;
280 
281 /*    indx = mydataobj->dims[0] * j + i + mydataobj->dims[0]*mydataobj->dims[1]*k; */
282     indx = isize * j + i + isize*jsize*k;
283 
284     /* foil compiler warning */
285     ksize = 0;
286 
287     return(baseData[indx]);
288 }
289 
290 
291 void
my_build_objs(void)292 my_build_objs(void)
293 {
294     RMnode *visnode;
295 
296     MyRoot = rmNodeNew(MyRootName,RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
297     rmNodeAddChild(rmRootNode(),MyRoot);
298 
299     visnode = rmNodeNew("vis",RM_RENDERPASS_3D, RM_RENDERPASS_OPAQUE);
300 
301     rmvK3MarchingCubes(mygridfunc_uvw,
302 		       mydatafunc_uvw,
303 		       /* note this is a hack - we should really be using
304 			a secondary dataset that is different from the
305 			primary dataset. since we're just reusing the
306 			primary dataset, what we'll get is a constant-
307 			colored isosurface, and the color changes when
308 			we change the isocontouring level via the command line*/
309 		       (secondary != NULL) ? mydatafunc_uvw : NULL,
310 		       vmap,
311 		       mydataobj->width,
312 		       mydataobj->height,
313 		       mydataobj->depth,
314 		       isolevel,
315 		       visnode,
316 		       mydataobj->xcoords,
317 		       mydataobj->ycoords,
318 		       mydataobj->zcoords,
319 		       mydataobj->rawdata);
320 
321 
322     rmNodeAddChild(MyRoot,visnode);
323 
324     /*
325      * set the bounding box for the visnode as a function of
326      * the extents of the underlying grid, not the extents of
327      * the derived isosurface.
328      */
329 
330     rmNodeSetBoundingBox(visnode,
331 			 (RMvertex3D *)&(mydataobj->corners[0]),
332 			 (RMvertex3D *)&(mydataobj->corners[1]));
333     rmNodeComputeCenterFromBoundingBox(visnode);
334 
335     rmNodeUnionAllBoxes(rmRootNode());
336 
337     rmNodeComputeCenterFromBoundingBox(MyRoot);
338 
339     {
340 	RMcolor4D bgcolor={0.2,0.2,0.3,1.0};
341 	/*
342 	 * assign a background color to take effect at "MyRoot"
343 	 */
344 	rmNodeSetSceneBackgroundColor(MyRoot,&bgcolor);
345     }
346 
347     dioDeleteDataObject(mydataobj);
348 
349 }
350 
351 void
my_idle_func(RMpipe * p,int ix,int iy)352 my_idle_func(RMpipe *p,
353 	     int ix,
354 	     int iy)
355 {
356     RMmatrix m,old;
357     double d,c,s;
358     rmMatrixIdentity(&m);
359     d = RM_DEGREES_TO_RADIANS(1.0);
360     c = cos(d);
361     s = sin(d);
362     m.m[0][0] = m.m[2][2] = c;
363     m.m[0][2] = -s;
364     m.m[2][0] = s;
365 
366     if (rmNodeGetRotateMatrix(MyRoot,&old) == RM_WHACKED)
367 	rmMatrixIdentity(&old);
368     rmMatrixMultiply(&old,&m,&old);
369     rmNodeSetRotateMatrix(MyRoot,&old);
370 
371     rmFrame(p, rmRootNode());
372 
373     /* foil compiler warning */
374     ix = iy = 0;
375 }
376 
377 void
setup_secondary_dataobj()378 setup_secondary_dataobj()
379 {
380     secondary = mydataobj;
381     vmap = rmDefaultVismap();
382     rmVismapSetTfMin(vmap,mydataobj->datamin);
383     rmVismapSetTfMax(vmap,mydataobj->datamax);
384 }
385 
386 void
myDumpImageFunc(const RMimage * img,RMenum whichPass)387 myDumpImageFunc(const RMimage *img,
388 		RMenum whichPass)
389 {
390     char fname[]={"offscreen.jpg"};
391 
392     printf(" Writing JPEG image file named %s ...", fname);
393     fflush(stdout);
394 
395     rmiWriteJPEG(fname, 100, img);
396 
397     printf(" ..done \n");
398     fflush(stdout);
399 
400     /* foil compiler warning */
401     whichPass = RM_TRUE;
402 }
403 
404 void
myinitfunc(RMpipe * p)405 myinitfunc(RMpipe *p)
406 {
407     my_read_data(datafilename);
408 
409     if (use_secondary == 1)
410 	setup_secondary_dataobj();
411 
412     my_build_objs();
413     my_set_scene(rmPipeGetChannelFormat(p));
414 
415     /*
416      * set up the event handler to apply geometric transformations at
417      * MyRoot. note that the lights and cameras are placed at rmRootNode().
418      * therefore, the rotations & scaling applied at MyRoot do not affect
419      * the cameras & lights since they are at a higher level in the
420      * scene graph than MyRoot.
421      */
422     rmauxSetGeomTransform(MyRoot,p);
423     rmauxSetCamera3DTransform(rmRootNode(), p);
424 
425 
426     if (do_print == 1)		/* won't see output in win32 unless you change
427 				 NULL to a char string with a filename, then
428 				 the output will be written in ASCII format
429 				 to that file. */
430 	rmPrintSceneGraph(rmRootNode(),RM_PRINT_VERBOSE,NULL);
431 
432 }
433 
434 void
myrenderfunc(RMpipe * p,RMnode * n)435 myrenderfunc(RMpipe *p, RMnode *n)
436 {
437     rmStatsStartTime();
438 
439     rmFrame(p, n);
440 
441     rmStatsEndTime();
442     rmStatsPrint();
443 }
444 
445 #ifdef RM_WIN
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdLine,int nCmdShow)446 int WINAPI WinMain (HINSTANCE hInstance,
447 		    HINSTANCE hPrevInstance,
448                     LPSTR lpszCmdLine, int nCmdShow)
449 {
450     MSG      msg;
451     HWND     hWnd;
452     void *fptr;
453     RMpipe *lone_pipe = NULL;
454     RMenum targetPlatform = RM_PIPE_WGL;
455     RMenum processingMode = RM_PIPE_MULTISTAGE; /* don't use MP mode! */
456     RMenum channelFormat;
457 
458     parse_args(__argc, __argv);
459 
460 #else  /* assume RM_X */
461 int
462 main(int ac,
463      char *av[])
464 {
465     RMpipe *lone_pipe = NULL;
466     RMenum processingMode = RM_PIPE_MULTISTAGE; /* don't use MP mode! */
467     RMenum targetPlatform = RM_PIPE_GLX;
468     RMenum channelFormat;
469 #if DO_OFFSCREEN
470 #else
471     void *msg=NULL;			/* needed for rmauxEventLoop
472 				 win32/unix API consistency */
473 #endif
474     parse_args(ac, av);
475 #endif
476 
477     /*
478      * pick a stereo format:
479      * RM_MONO_CHANNEL - plain old single-view
480      * RM_REDBLUE_STEREO_CHANNEL - left channel in red, right channel in cyan
481      * RM_BLUERED_STEREO_CHANNEL - left in cyan, right in red
482      * RM_MBUF_STEREO_CHANNEL - multibuffered stereo, requires special
483      *    hardware.
484      */
485 #if DO_OFFSCREEN
486     channelFormat = RM_OFFSCREEN_MONO_CHANNEL;
487 #else
488     channelFormat = RM_MONO_CHANNEL;
489 #endif
490 
491 
492     /*
493      * first stage of RM initialization.
494      */
495     rmInit();
496 
497     /*
498      * create the rendering pipe. this step is required in both
499      * Win32 and X.
500      */
501     lone_pipe = rmPipeNew(targetPlatform);
502     rmPipeSetChannelFormat(lone_pipe, channelFormat);
503 
504     rmPipeSetProcessingMode(lone_pipe, processingMode);
505 
506 #if DO_OFFSCREEN
507 
508 #ifdef RM_WIN
509     {
510         /*
511 	 * Win32: when a window is created, we have to tell windows the
512 	 * name of the "WndProc," the procedure that gets called by
513 	 * windows with events (the event loop) (contrast to the X model
514 	 * where the name of the event loop is not part of the window).
515 	 * Since we're using RMaux, we know about the event handling
516 	 * procedure named "rmauxWndProc" and we provide that here.
517 	 */
518         fptr = (void *)(rmauxWndProc);
519 	hWnd = rmauxCreateOffscreenDrawable(lone_pipe,
520 					    imgWidth,imgHeight,16,
521 					    hInstance,fptr);
522 	if (hWnd == 0)
523 	  exit(-1);
524 
525 	/*
526 	 * assign the new window handle to the rendering pipe.
527 	 */
528 	rmPipeSetWindow(lone_pipe,hWnd, imgWidth, imgHeight);
529 
530     }
531 #endif
532 #ifdef RM_X
533     {
534 	GLXPixmap glxp;
535 	glxp = rmauxCreateOffscreenDrawable(lone_pipe,
536 					    imgWidth, imgHeight,
537 					    DefaultDepth(rmxPipeGetDisplay(lone_pipe), DefaultScreen(rmxPipeGetDisplay(lone_pipe))));
538 
539 	/*
540 	 * assign the offscreen window to the rendering pipe.
541 	 */
542 	rmPipeSetOffscreenWindow(lone_pipe, glxp, imgWidth, imgHeight);
543     }
544 #endif
545     rmPipeSetPostRenderFunc(lone_pipe, myDumpImageFunc);
546 
547 #else  /* do interactive stuff */
548 
549 #ifdef RM_WIN
550     {
551         /*
552 	 * Win32: when a window is created, we have to tell windows the
553 	 * name of the "WndProc," the procedure that gets called by
554 	 * windows with events (the event loop) (contrast to the X model
555 	 * where the name of the event loop is not part of the window).
556 	 * Since we're using RMaux, we know about the event handling
557 	 * procedure named "rmauxWndProc" and we provide that here.
558 	 */
559         fptr = (void *)(rmauxWndProc);
560 	hWnd = rmauxCreateW32Window(lone_pipe,
561 			       NULL, /* no parent window */
562 			       20,20,imgWidth,imgHeight,"RM for Windows",
563 			       hInstance,fptr);
564 	if (hWnd == 0)
565 	  exit(-1);
566 
567 	/*
568 	 * assign the new window handle to the rendering pipe.
569 	 */
570 	rmPipeSetWindow(lone_pipe,hWnd, imgWidth, imgHeight);
571     }
572 #endif
573 #ifdef RM_X
574     {
575 	Window w;
576 	int managed = RM_TRUE;
577 
578 	w = rmauxCreateXWindow(lone_pipe,
579 			       (Window)NULL, /* parent window */
580 			       0,0,imgWidth,imgHeight,
581 			       "RM for X-Windows","icon-title",managed);
582 	/*
583 	 * assign the window to the rendering pipe.
584 	 */
585 
586 	rmPipeSetWindow(lone_pipe,w,imgWidth,imgHeight);
587     }
588 #endif
589 
590 #endif
591 
592     rmPipeMakeCurrent(lone_pipe);
593 
594     myinitfunc(lone_pipe);
595 
596 #if DO_OFFSCREEN
597     rmFrame(lone_pipe, rmRootNode());
598 #else
599 
600     rmFrame(lone_pipe, rmRootNode());
601     rmauxSetKeyFunc(lone_pipe, rmauxDefaultKeyFunc);
602     rmauxSetRenderFunc(myrenderfunc);
603     rmauxEventLoop(lone_pipe,rmRootNode(), &msg);
604 #endif
605 
606     rmPipeDelete(lone_pipe);
607     rmFinish();
608 
609 #ifdef RM_WIN
610     return( msg.wParam );
611 #else
612     return(1);
613 #endif
614 }
615 
616