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