source: trunk/packages/vizservers/nanovis/Command.cpp @ 3605

Last change on this file since 3605 was 3605, checked in by ldelgass, 12 years ago

Add writer thread to nanovis (set USE_THREADS in Makefile), more refactoring.

  • Property svn:eol-style set to native
File size: 73.0 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * ----------------------------------------------------------------------
4 * Command.cpp
5 *
6 *      This modules creates the Tcl interface to the nanovis server.  The
7 *      communication protocol of the server is the Tcl language.  Commands
8 *      given to the server by clients are executed in a safe interpreter and
9 *      the resulting image rendered offscreen is returned as BMP-formatted
10 *      image data.
11 *
12 * ======================================================================
13 *  AUTHOR:  Wei Qiao <qiaow@purdue.edu>
14 *           Michael McLennan <mmclennan@purdue.edu>
15 *           Purdue Rendering and Perceptualization Lab (PURPL)
16 *
17 *  Copyright (c) 2004-2013  HUBzero Foundation, LLC
18 *
19 *  See the file "license.terms" for information on usage and
20 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
21 * ======================================================================
22 */
23
24/*
25 * TODO:  In no particular order...
26 *        o Use Tcl command option parser to reduce size of procedures, remove
27 *          lots of extra error checking code. (almost there)
28 *        o Add bookkeeping for volumes, heightmaps, flows, etc. to track
29 *          1) simulation # 2) include/exclude.  The include/exclude
30 *          is to indicate whether the item should contribute to the overall
31 *          limits of the axes.
32 */
33
34#include <assert.h>
35#include <stdlib.h>
36#include <unistd.h>                     /* Needed for getpid, gethostname,
37                                         * write, etc. */
38#include <tcl.h>
39
40#include <RpField1D.h>
41#include <RpEncode.h>
42#include <RpOutcome.h>
43#include <RpBuffer.h>
44
45#include <vrmath/Vector3f.h>
46
47#include "nanovisServer.h"
48#include "nanovis.h"
49#include "ReadBuffer.h"
50#ifdef USE_THREADS
51#include "ResponseQueue.h"
52#endif
53#include "Command.h"
54#include "CmdProc.h"
55#include "FlowCmd.h"
56#include "dxReader.h"
57#include "VtkReader.h"
58#include "BMPWriter.h"
59#include "PPMWriter.h"
60#include "Grid.h"
61#include "HeightMap.h"
62#include "NvCamera.h"
63#include "NvZincBlendeReconstructor.h"
64#include "OrientationIndicator.h"
65#include "Unirect.h"
66#include "Volume.h"
67#include "VolumeRenderer.h"
68#include "Trace.h"
69
70using namespace nv;
71using namespace nv::graphics;
72using namespace vrmath;
73
74// default transfer function
75static const char def_transfunc[] =
76    "transfunc define default {\n\
77  0.00  0 0 1\n\
78  0.25  0 1 1\n\
79  0.50  0 1 0\n\
80  0.75  1 1 0\n\
81  1.00  1 0 0\n\
82} {\n\
83  0.000000 0.0\n\
84  0.107847 0.0\n\
85  0.107857 1.0\n\
86  0.177857 1.0\n\
87  0.177867 0.0\n\
88  0.250704 0.0\n\
89  0.250714 1.0\n\
90  0.320714 1.0\n\
91  0.320724 0.0\n\
92  0.393561 0.0\n\
93  0.393571 1.0\n\
94  0.463571 1.0\n\
95  0.463581 0.0\n\
96  0.536419 0.0\n\
97  0.536429 1.0\n\
98  0.606429 1.0\n\
99  0.606439 0.0\n\
100  0.679276 0.0\n\
101  0.679286 1.0\n\
102  0.749286 1.0\n\
103  0.749296 0.0\n\
104  0.822133 0.0\n\
105  0.822143 1.0\n\
106  0.892143 1.0\n\
107  0.892153 0.0\n\
108  1.000000 0.0\n\
109}";
110
111static int lastCmdStatus;
112
113#ifdef USE_THREADS
114void
115nv::queueResponse(const void *bytes, size_t len,
116                  Response::AllocationType allocType,
117                  Response::ResponseType type)
118{
119    Response *response = new Response(type);
120    response->setMessage((unsigned char *)bytes, len, allocType);
121    g_queue->enqueue(response);
122}
123#else
124
125ssize_t
126nv::SocketWrite(const void *bytes, size_t len)
127{
128    size_t ofs = 0;
129    ssize_t bytesWritten;
130    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
131        ofs += bytesWritten;
132        if (ofs == len)
133            break;
134    }
135    if (bytesWritten < 0) {
136        ERROR("write: %s", strerror(errno));
137    }
138    return bytesWritten;
139}
140
141#endif  /*USE_THREADS*/
142
143bool
144nv::SocketRead(char *bytes, size_t len)
145{
146    ReadBuffer::BufferStatus status;
147    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
148    TRACE("followingData status: %d", status);
149    return (status == ReadBuffer::OK);
150}
151
152bool
153nv::SocketRead(Rappture::Buffer &buf, size_t len)
154{
155    ReadBuffer::BufferStatus status;
156    status = g_inBufPtr->followingData(buf, len);
157    TRACE("followingData status: %d", status);
158    return (status == ReadBuffer::OK);
159}
160
161static int
162ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
163{
164    int result;
165#ifdef WANT_TRACE
166    char *str = Tcl_DStringValue(dsPtr);
167    std::string cmd(str);
168    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
169    TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str());
170#endif
171    lastCmdStatus = TCL_OK;
172    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
173                        Tcl_DStringLength(dsPtr),
174                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
175    Tcl_DStringSetLength(dsPtr, 0);
176    if (lastCmdStatus == TCL_BREAK) {
177        return TCL_BREAK;
178    }
179    lastCmdStatus = result;
180    if (result != TCL_OK) {
181        TRACE("Error: %d", result);
182    }
183    return result;
184}
185
186bool
187GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
188{
189    int value;
190
191    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
192        return TCL_ERROR;
193    }
194    *boolPtr = (bool)value;
195    return TCL_OK;
196}
197
198int
199GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
200{
201    double value;
202
203    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
204        return TCL_ERROR;
205    }
206    *valuePtr = (float)value;
207    return TCL_OK;
208}
209
210static int
211GetCullMode(Tcl_Interp *interp, Tcl_Obj *objPtr,
212            RenderContext::CullMode *modePtr)
213{
214    const char *string = Tcl_GetString(objPtr);
215    if (strcmp(string, "none") == 0) {
216        *modePtr = RenderContext::NO_CULL;
217    } else if (strcmp(string, "front") == 0) {
218        *modePtr = RenderContext::FRONT;
219    } else if (strcmp(string, "back") == 0) {
220        *modePtr = RenderContext::BACK;
221    } else {
222        Tcl_AppendResult(interp, "invalid cull mode \"", string,
223                         "\": should be front, back, or none\"", (char *)NULL);
224        return TCL_ERROR;
225    }
226    return TCL_OK;
227}
228
229static int
230GetShadingModel(Tcl_Interp *interp, Tcl_Obj *objPtr,
231                RenderContext::ShadingModel *modelPtr)
232{
233    const char *string = Tcl_GetString(objPtr);
234
235    if (strcmp(string,"flat") == 0) {
236        *modelPtr = RenderContext::FLAT;
237    } else if (strcmp(string,"smooth") == 0) {
238        *modelPtr = RenderContext::SMOOTH;
239    } else {
240        Tcl_AppendResult(interp, "bad shading model \"", string,
241                         "\": should be flat or smooth", (char *)NULL);
242        return TCL_ERROR;
243    }
244    return TCL_OK;
245}
246
247static int
248GetPolygonMode(Tcl_Interp *interp, Tcl_Obj *objPtr,
249               RenderContext::PolygonMode *modePtr)
250{
251    const char *string = Tcl_GetString(objPtr);
252
253    if (strcmp(string,"wireframe") == 0) {
254        *modePtr = RenderContext::LINE;
255    } else if (strcmp(string,"fill") == 0) {
256        *modePtr = RenderContext::FILL;
257    } else {
258        Tcl_AppendResult(interp, "invalid polygon mode \"", string,
259                         "\": should be wireframe or fill\"", (char *)NULL);
260        return TCL_ERROR;
261    }
262    return TCL_OK;
263}
264
265/**
266 * Creates a heightmap from the given the data. The format of the data
267 * should be as follows:
268 *
269 *     xMin, xMax, xNum, yMin, yMax, yNum, heights...
270 *
271 * xNum and yNum must be integer values, all others are real numbers.
272 * The number of heights must be xNum * yNum;
273 */
274static HeightMap *
275CreateHeightMap(ClientData clientData, Tcl_Interp *interp, int objc,
276                Tcl_Obj *const *objv)
277{
278    float xMin, yMin, xMax, yMax;
279    int xNum, yNum;
280
281    if (objc != 7) {
282        Tcl_AppendResult(interp,
283        "wrong # of values: should be xMin yMin xMax yMax xNum yNum heights",
284        (char *)NULL);
285        return NULL;
286    }
287    if ((GetFloatFromObj(interp, objv[0], &xMin) != TCL_OK) ||
288        (GetFloatFromObj(interp, objv[1], &yMin) != TCL_OK) ||
289        (GetFloatFromObj(interp, objv[2], &xMax) != TCL_OK) ||
290        (GetFloatFromObj(interp, objv[3], &yMax) != TCL_OK) ||
291        (Tcl_GetIntFromObj(interp, objv[4], &xNum) != TCL_OK) ||
292        (Tcl_GetIntFromObj(interp, objv[5], &yNum) != TCL_OK)) {
293        return NULL;
294    }
295    int nValues;
296    Tcl_Obj **elem;
297    if (Tcl_ListObjGetElements(interp, objv[6], &nValues, &elem) != TCL_OK) {
298        return NULL;
299    }
300    if ((xNum <= 0) || (yNum <= 0)) {
301        Tcl_AppendResult(interp, "bad number of x or y values", (char *)NULL);
302        return NULL;
303    }
304    if (nValues != (xNum * yNum)) {
305        Tcl_AppendResult(interp, "wrong # of heights", (char *)NULL);
306        return NULL;
307    }
308
309    float *heights;
310    heights = new float[nValues];
311    if (heights == NULL) {
312        Tcl_AppendResult(interp, "can't allocate array of heights",
313                         (char *)NULL);
314        return NULL;
315    }
316
317    int i;
318    for (i = 0; i < nValues; i++) {
319        if (GetFloatFromObj(interp, elem[i], heights + i) != TCL_OK) {
320            delete [] heights;
321            return NULL;
322        }
323    }
324    HeightMap *heightMap = new HeightMap();
325    heightMap->setHeight(xMin, yMin, xMax, yMax, xNum, yNum, heights);
326    heightMap->transferFunction(NanoVis::getTransferFunction("default"));
327    heightMap->setVisible(true);
328    heightMap->setLineContourVisible(true);
329    delete [] heights;
330    return heightMap;
331}
332
333static int
334GetHeightMapFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, HeightMap **hmPtrPtr)
335{
336    const char *string = Tcl_GetString(objPtr);
337
338    NanoVis::HeightMapHashmap::iterator itr = NanoVis::heightMapTable.find(string);
339    if (itr == NanoVis::heightMapTable.end()) {
340        if (interp != NULL) {
341            Tcl_AppendResult(interp, "can't find a heightmap named \"",
342                         string, "\"", (char*)NULL);
343        }
344        return TCL_ERROR;
345    }
346    *hmPtrPtr = itr->second;
347    return TCL_OK;
348}
349
350
351/**
352 * Used internally to decode a series of volume index values and
353 * store then in the specified vector.  If there are no volume index
354 * arguments, this means "all volumes" to most commands, so all
355 * active volume indices are stored in the vector.
356 *
357 * Updates pushes index values into the vector.  Returns TCL_OK or
358 * TCL_ERROR to indicate an error.
359 */
360int
361GetVolumeFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Volume **volPtrPtr)
362{
363    const char *string = Tcl_GetString(objPtr);
364
365    NanoVis::VolumeHashmap::iterator itr = NanoVis::volumeTable.find(string);
366    if (itr == NanoVis::volumeTable.end()) {
367        if (interp != NULL) {
368            Tcl_AppendResult(interp, "can't find a volume named \"",
369                         string, "\"", (char*)NULL);
370        }
371        return TCL_ERROR;
372    }
373    *volPtrPtr = itr->second;
374    return TCL_OK;
375}
376
377/**
378 * Used internally to decode a series of volume index values and
379 * store then in the specified vector.  If there are no volume index
380 * arguments, this means "all volumes" to most commands, so all
381 * active volume indices are stored in the vector.
382 *
383 * Updates pushes index values into the vector.  Returns TCL_OK or
384 * TCL_ERROR to indicate an error.
385 */
386static int
387GetVolumes(Tcl_Interp *interp, int objc, Tcl_Obj *const *objv,
388           std::vector<Volume *>* vectorPtr)
389{
390    if (objc == 0) {
391        // No arguments. Get all volumes.
392        NanoVis::VolumeHashmap::iterator itr;
393        for (itr = NanoVis::volumeTable.begin();
394             itr != NanoVis::volumeTable.end(); ++itr) {
395            vectorPtr->push_back(itr->second);
396        }
397    } else {
398        // Get the volumes associated with the given index arguments.
399        for (int n = 0; n < objc; n++) {
400            Volume *volume;
401            if (GetVolumeFromObj(interp, objv[n], &volume) != TCL_OK) {
402                return TCL_ERROR;
403            }
404            vectorPtr->push_back(volume);
405        }
406    }
407    return TCL_OK;
408}
409
410/**
411 * Used internally to decode a series of volume index values and
412 * store then in the specified vector.  If there are no volume index
413 * arguments, this means "all volumes" to most commands, so all
414 * active volume indices are stored in the vector.
415 *
416 * Updates pushes index values into the vector.  Returns TCL_OK or
417 * TCL_ERROR to indicate an error.
418 */
419static int
420GetHeightMaps(Tcl_Interp *interp, int objc, Tcl_Obj *const *objv,
421              std::vector<HeightMap *>* vectorPtr)
422{
423    if (objc == 0) {
424        // No arguments. Get all heightmaps.
425        NanoVis::HeightMapHashmap::iterator itr;
426        for (itr = NanoVis::heightMapTable.begin();
427             itr != NanoVis::heightMapTable.end(); ++itr) {
428            vectorPtr->push_back(itr->second);
429        }
430    } else {
431        for (int n = 0; n < objc; n++) {
432            HeightMap *heightMap;
433            if (GetHeightMapFromObj(interp, objv[n], &heightMap) != TCL_OK) {
434                return TCL_ERROR;
435            }
436            vectorPtr->push_back(heightMap);
437        }
438    }
439    return TCL_OK;
440}
441
442/**
443 * Used internally to decode an axis value from a string ("x", "y",
444 * or "z") to its index (0, 1, or 2).  Returns TCL_OK if successful,
445 * along with a value in valPtr.  Otherwise, it returns TCL_ERROR
446 * and an error message in the interpreter.
447 */
448static int
449GetAxis(Tcl_Interp *interp, const char *string, int *indexPtr)
450{
451    if (string[1] == '\0') {
452        char c;
453
454        c = tolower((unsigned char)string[0]);
455        if (c == 'x') {
456            *indexPtr = 0;
457            return TCL_OK;
458        } else if (c == 'y') {
459            *indexPtr = 1;
460            return TCL_OK;
461        } else if (c == 'z') {
462            *indexPtr = 2;
463            return TCL_OK;
464        }
465        /*FALLTHRU*/
466    }
467    Tcl_AppendResult(interp, "bad axis \"", string,
468                     "\": should be x, y, or z", (char*)NULL);
469    return TCL_ERROR;
470}
471
472/**
473 * Used internally to decode an axis value from a string ("x", "y",
474 * or "z") to its index (0, 1, or 2).  Returns TCL_OK if successful,
475 * along with a value in indexPtr.  Otherwise, it returns TCL_ERROR
476 * and an error message in the interpreter.
477 */
478int
479GetAxisFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *indexPtr)
480{
481    return GetAxis(interp, Tcl_GetString(objPtr), indexPtr);
482}
483
484/**
485 * Used internally to decode an axis value from a string ("x", "y",
486 * or "z") to its index (0, 1, or 2).  Returns TCL_OK if successful,
487 * along with a value in indexPtr.  Otherwise, it returns TCL_ERROR
488 * and an error message in the interpreter.
489 */
490static int
491GetAxisDirFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *indexPtr, int *dirPtr)
492{
493    const char *string = Tcl_GetString(objPtr);
494
495    int sign = 1;
496    if (*string == '-') {
497        sign = -1;
498        string++;
499    }
500    if (GetAxis(interp, string, indexPtr) != TCL_OK) {
501        return TCL_ERROR;
502    }
503    if (dirPtr != NULL) {
504        *dirPtr = sign;
505    }
506    return TCL_OK;
507}
508
509/**
510 * Used internally to decode a color value from a string ("R G B")
511 * as a list of three numbers 0-1.  Returns TCL_OK if successful,
512 * along with RGB values in rgbPtr.  Otherwise, it returns TCL_ERROR
513 * and an error message in the interpreter.
514 */
515static int
516GetColor(Tcl_Interp *interp, int objc, Tcl_Obj *const *objv, float *rgbPtr)
517{
518    if (objc < 3) {
519        Tcl_AppendResult(interp, "missing color values\": ",
520                         "should list of R G B values 0.0 - 1.0", (char*)NULL);
521        return TCL_ERROR;
522    }
523    if ((GetFloatFromObj(interp, objv[0], rgbPtr + 0) != TCL_OK) ||
524        (GetFloatFromObj(interp, objv[1], rgbPtr + 1) != TCL_OK) ||
525        (GetFloatFromObj(interp, objv[2], rgbPtr + 2) != TCL_OK)) {
526        return TCL_ERROR;
527    }
528    return TCL_OK;
529}
530
531/**
532 * Read the requested number of bytes from standard input into the given
533 * buffer.  The buffer is then decompressed and decoded.
534 */
535int
536GetDataStream(Tcl_Interp *interp, Rappture::Buffer &buf, int nBytes)
537{
538    if (!SocketRead(buf, nBytes)) {
539        return TCL_ERROR;
540    }
541    Rappture::Outcome err;
542    TRACE("Checking header[%.13s]", buf.bytes());
543    if (strncmp (buf.bytes(), "@@RP-ENC:", 9) == 0) {
544        /* There's a header on the buffer, use it to decode the data. */
545        if (!Rappture::encoding::decode(err, buf, RPENC_HDR)) {
546            Tcl_AppendResult(interp, err.remark(), (char*)NULL);
547            return TCL_ERROR;
548        }
549    } else if (Rappture::encoding::isBase64(buf.bytes(), buf.size())) {
550        /* No header, but it's base64 encoded.  It's likely that it's both
551         * base64 encoded and compressed. */
552        if (!Rappture::encoding::decode(err, buf, RPENC_B64 | RPENC_Z)) {
553            Tcl_AppendResult(interp, err.remark(), (char*)NULL);
554            return TCL_ERROR;
555        }
556    }
557    return TCL_OK;
558}
559
560static int
561CameraAngleOp(ClientData clientData, Tcl_Interp *interp, int objc,
562              Tcl_Obj *const *objv)
563{
564    float theta, phi, psi;
565    if ((GetFloatFromObj(interp, objv[2], &phi) != TCL_OK) ||
566        (GetFloatFromObj(interp, objv[3], &theta) != TCL_OK) ||
567        (GetFloatFromObj(interp, objv[4], &psi) != TCL_OK)) {
568        return TCL_ERROR;
569    }
570    NanoVis::cam->rotate(phi, theta, psi);
571    return TCL_OK;
572}
573
574static int
575CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
576               Tcl_Obj *const *objv)
577{
578    double quat[4];
579    if ((Tcl_GetDoubleFromObj(interp, objv[2], &quat[3]) != TCL_OK) ||
580        (Tcl_GetDoubleFromObj(interp, objv[3], &quat[0]) != TCL_OK) ||
581        (Tcl_GetDoubleFromObj(interp, objv[4], &quat[1]) != TCL_OK) ||
582        (Tcl_GetDoubleFromObj(interp, objv[5], &quat[2]) != TCL_OK)) {
583        return TCL_ERROR;
584    }
585    NanoVis::cam->rotate(quat);
586    return TCL_OK;
587}
588
589static int
590CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
591             Tcl_Obj *const *objv)
592{
593    float x, y;
594    if ((GetFloatFromObj(interp, objv[2], &x) != TCL_OK) ||
595        (GetFloatFromObj(interp, objv[3], &y) != TCL_OK)) {
596        return TCL_ERROR;
597    }
598    NanoVis::pan(x, y);
599    return TCL_OK;
600}
601
602static int
603CameraPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
604                 Tcl_Obj *const *objv)
605{
606    float x, y, z;
607    if ((GetFloatFromObj(interp, objv[2], &x) != TCL_OK) ||
608        (GetFloatFromObj(interp, objv[3], &y) != TCL_OK) ||
609        (GetFloatFromObj(interp, objv[4], &z) != TCL_OK)) {
610        return TCL_ERROR;
611    }
612    NanoVis::cam->x(x);
613    NanoVis::cam->y(y);
614    NanoVis::cam->z(z);
615    return TCL_OK;
616}
617
618static int
619CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
620              Tcl_Obj *const *objv)
621{
622    bool all = false;
623    if (objc == 3) {
624        const char *string = Tcl_GetString(objv[2]);
625        char c = string[0];
626        if ((c != 'a') || (strcmp(string, "all") != 0)) {
627            Tcl_AppendResult(interp, "bad camera reset option \"", string,
628                             "\": should be all", (char*)NULL);
629            return TCL_ERROR;
630        }
631        all = true;
632    }
633    NanoVis::resetCamera(all);
634    return TCL_OK;
635}
636
637static int
638CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
639             Tcl_Obj *const *objv)
640{
641    float z;
642    if (GetFloatFromObj(interp, objv[2], &z) != TCL_OK) {
643        return TCL_ERROR;
644    }
645    NanoVis::zoom(z);
646    return TCL_OK;
647}
648
649static Rappture::CmdSpec cameraOps[] = {
650    {"angle",   2, CameraAngleOp,    5, 5, "xAngle yAngle zAngle",},
651    {"orient",  1, CameraOrientOp,   6, 6, "qw qx qy qz",},
652    {"pan",     2, CameraPanOp,      4, 4, "x y",},
653    {"pos",     2, CameraPositionOp, 5, 5, "x y z",},
654    {"reset",   1, CameraResetOp,    2, 3, "?all?",},
655    {"zoom",    1, CameraZoomOp,     3, 3, "factor",},
656};
657static int nCameraOps = NumCmdSpecs(cameraOps);
658
659static int
660CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
661          Tcl_Obj *const *objv)
662{
663    Tcl_ObjCmdProc *proc;
664
665    proc = Rappture::GetOpFromObj(interp, nCameraOps, cameraOps,
666                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
667    if (proc == NULL) {
668        return TCL_ERROR;
669    }
670    return (*proc) (clientData, interp, objc, objv);
671}
672
673static int
674SnapshotCmd(ClientData clientData, Tcl_Interp *interp, int objc,
675            Tcl_Obj *const *objv)
676{
677    int origWidth, origHeight, width, height;
678
679    origWidth = NanoVis::winWidth;
680    origHeight = NanoVis::winHeight;
681    width = 2048;
682    height = 2048;
683
684    NanoVis::resizeOffscreenBuffer(width, height);
685    NanoVis::bindOffscreenBuffer();
686    NanoVis::render();
687    NanoVis::readScreen();
688#ifdef USE_THREADS
689    queuePPM(g_queue, "nv>image -type print -bytes",
690             NanoVis::screenBuffer, width, height);
691#else
692    writePPM(g_fdOut, "nv>image -type print -bytes",
693             NanoVis::screenBuffer, width, height);
694#endif
695    NanoVis::resizeOffscreenBuffer(origWidth, origHeight);
696
697    return TCL_OK;
698}
699
700static int
701CutplanePositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
702                   Tcl_Obj *const *objv)
703{
704    float relval;
705    if (GetFloatFromObj(interp, objv[2], &relval) != TCL_OK) {
706        return TCL_ERROR;
707    }
708
709    // keep this just inside the volume so it doesn't disappear
710    if (relval < 0.01f) {
711        relval = 0.01f;
712    } else if (relval > 0.99f) {
713        relval = 0.99f;
714    }
715
716    int axis;
717    if (GetAxisFromObj(interp, objv[3], &axis) != TCL_OK) {
718        return TCL_ERROR;
719    }
720
721    std::vector<Volume *> ivol;
722    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
723        return TCL_ERROR;
724    }
725    std::vector<Volume *>::iterator iter;
726    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
727        (*iter)->moveCutplane(axis, relval);
728    }
729    return TCL_OK;
730}
731
732/*
733 * cutplane state $bool $axis vol,,,
734 */
735static int
736CutplaneStateOp(ClientData clientData, Tcl_Interp *interp, int objc,
737                Tcl_Obj *const *objv)
738{
739    bool state;
740    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
741        return TCL_ERROR;
742    }
743
744    int axis;
745    if (GetAxisFromObj(interp, objv[3], &axis) != TCL_OK) {
746        return TCL_ERROR;
747    }
748
749    std::vector<Volume *> ivol;
750    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
751        return TCL_ERROR;
752    }
753    if (state) {
754        std::vector<Volume *>::iterator iter;
755        for (iter = ivol.begin(); iter != ivol.end(); iter++) {
756            (*iter)->enableCutplane(axis);
757        }
758    } else {
759        std::vector<Volume *>::iterator iter;
760        for (iter = ivol.begin(); iter != ivol.end(); iter++) {
761            (*iter)->disableCutplane(axis);
762        }
763    }
764    return TCL_OK;
765}
766
767static Rappture::CmdSpec cutplaneOps[] = {
768    {"position", 1, CutplanePositionOp, 4, 0, "relval axis ?indices?",},
769    {"state",    1, CutplaneStateOp,    4, 0, "bool axis ?indices?",},
770};
771static int nCutplaneOps = NumCmdSpecs(cutplaneOps);
772
773/*
774 * ----------------------------------------------------------------------
775 * CLIENT COMMAND:
776 *   cutplane state on|off <axis> ?<volume>...?
777 *   cutplane position <relvalue> <axis> ?<volume>...?
778 *
779 * Clients send these commands to manipulate the cutplanes in one or
780 * more data volumes.  The "state" command turns a cutplane on or
781 * off.  The "position" command changes the position to a relative
782 * value in the range 0-1.  The <axis> can be x, y, or z.  These
783 * options are applied to the volumes represented by one or more
784 * <volume> indices.  If no volumes are specified, then all volumes
785 * are updated.
786 * ----------------------------------------------------------------------
787 */
788static int
789CutplaneCmd(ClientData clientData, Tcl_Interp *interp, int objc,
790            Tcl_Obj *const *objv)
791{
792    Tcl_ObjCmdProc *proc;
793
794    proc = Rappture::GetOpFromObj(interp, nCutplaneOps, cutplaneOps,
795                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
796    if (proc == NULL) {
797        return TCL_ERROR;
798    }
799    return (*proc) (clientData, interp, objc, objv);
800}
801
802/*
803 * ClientInfoCmd --
804 *
805 *      Log initial values to stats file.  The first time this is called
806 *      "render_start" is written into the stats file.  Afterwards, it
807 *      is "render_info".
808 *       
809 *         clientinfo list
810 */
811static int
812ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
813              Tcl_Obj *const *objv)
814{
815    Tcl_DString ds;
816    Tcl_Obj *objPtr, *listObjPtr, **items;
817    int result;
818    int i, numItems, length;
819    char buf[BUFSIZ];
820    const char *string;
821    static int first = 1;
822
823    if (objc != 2) {
824        Tcl_AppendResult(interp, "wrong # of arguments: should be \"",
825                Tcl_GetString(objv[0]), " list\"", (char *)NULL);
826        return TCL_ERROR;
827    }
828#ifdef KEEPSTATS
829    /* Use the initial client key value pairs as the parts for a generating
830     * a unique file name. */
831    int f = nv::getStatsFile(interp, objv[1]);
832    if (f < 0) {
833        Tcl_AppendResult(interp, "can't open stats file: ",
834                         Tcl_PosixError(interp), (char *)NULL);
835        return TCL_ERROR;
836    }
837#endif
838    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
839    Tcl_IncrRefCount(listObjPtr);
840    if (first) {
841        first = false;
842        objPtr = Tcl_NewStringObj("render_start", 12);
843        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
844        /* server */
845        objPtr = Tcl_NewStringObj("server", 6);
846        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
847        objPtr = Tcl_NewStringObj("nanovis", 7);
848        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
849        /* pid */
850        objPtr = Tcl_NewStringObj("pid", 3);
851        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
852        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
853        /* machine */
854        objPtr = Tcl_NewStringObj("machine", 7);
855        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
856        gethostname(buf, BUFSIZ-1);
857        buf[BUFSIZ-1] = '\0';
858        objPtr = Tcl_NewStringObj(buf, -1);
859        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
860    } else {
861        objPtr = Tcl_NewStringObj("render_info", 11);
862        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
863    }
864    Tcl_DStringInit(&ds);
865    /* date */
866    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
867    strcpy(buf, ctime(&nv::g_stats.start.tv_sec));
868    buf[strlen(buf) - 1] = '\0';
869    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
870    /* date_secs */
871    Tcl_ListObjAppendElement(interp, listObjPtr,
872                Tcl_NewStringObj("date_secs", 9));
873    Tcl_ListObjAppendElement(interp, listObjPtr,
874                Tcl_NewLongObj(nv::g_stats.start.tv_sec));
875    /* Client arguments. */
876    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK) {
877        return TCL_ERROR;
878    }
879    for (i = 0; i < numItems; i++) {
880        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
881    }
882    Tcl_DStringInit(&ds);
883    string = Tcl_GetStringFromObj(listObjPtr, &length);
884    Tcl_DStringAppend(&ds, string, length);
885    Tcl_DStringAppend(&ds, "\n", 1);
886#ifdef KEEPSTATS
887    result = nv::writeToStatsFile(f, Tcl_DStringValue(&ds),
888                                  Tcl_DStringLength(&ds));
889#else
890    TRACE("clientinfo: %s", Tcl_DStringValue(&ds));
891#endif
892    Tcl_DStringFree(&ds);
893    Tcl_DecrRefCount(listObjPtr);
894    return result;
895}
896
897/*
898 * ----------------------------------------------------------------------
899 * CLIENT COMMAND:
900 *   legend <volumeIndex> <width> <height>
901 *
902 * Clients use this to generate a legend image for the specified
903 * transfer function.  The legend image is a color gradient from 0
904 * to one, drawn in the given transfer function.  The resulting image
905 * is returned in the size <width> x <height>.
906 * ----------------------------------------------------------------------
907 */
908static int
909LegendCmd(ClientData clientData, Tcl_Interp *interp, int objc,
910          Tcl_Obj *const *objv)
911{
912    if (objc != 4) {
913        Tcl_AppendResult(interp, "wrong # args: should be \"",
914            Tcl_GetString(objv[0]), " transfunc width height\"", (char*)NULL);
915        return TCL_ERROR;
916    }
917
918    const char *tfName = Tcl_GetString(objv[1]);
919    TransferFunction *tf = NanoVis::getTransferFunction(tfName);
920    if (tf == NULL) {
921        Tcl_AppendResult(interp, "unknown transfer function \"", tfName, "\"",
922                             (char*)NULL);
923        return TCL_ERROR;
924    }
925    int w, h;
926    if ((Tcl_GetIntFromObj(interp, objv[2], &w) != TCL_OK) ||
927        (Tcl_GetIntFromObj(interp, objv[3], &h) != TCL_OK)) {
928        return TCL_ERROR;
929    }
930    if (Volume::updatePending) {
931        NanoVis::setVolumeRanges();
932    }
933    NanoVis::renderLegend(tf, Volume::valueMin, Volume::valueMax, w, h, tfName);
934    return TCL_OK;
935}
936
937static int
938ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
939                Tcl_Obj *const *objv)
940{
941    float rgb[3];
942    if ((GetFloatFromObj(interp, objv[2], &rgb[0]) != TCL_OK) ||
943        (GetFloatFromObj(interp, objv[3], &rgb[1]) != TCL_OK) ||
944        (GetFloatFromObj(interp, objv[4], &rgb[2]) != TCL_OK)) {
945        return TCL_ERROR;
946    }
947    NanoVis::setBgColor(rgb);
948    return TCL_OK;
949}
950
951/*
952 * ----------------------------------------------------------------------
953 * CLIENT COMMAND:
954 *   screen size <width> <height>
955 *
956 * Clients send this command to set the size of the rendering area.
957 * Future images are generated at the specified width/height.
958 * ----------------------------------------------------------------------
959 */
960static int
961ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
962             Tcl_Obj *const *objv)
963{
964    int w, h;
965    if ((Tcl_GetIntFromObj(interp, objv[2], &w) != TCL_OK) ||
966        (Tcl_GetIntFromObj(interp, objv[3], &h) != TCL_OK)) {
967        return TCL_ERROR;
968    }
969    NanoVis::resizeOffscreenBuffer(w, h);
970    return TCL_OK;
971}
972
973static Rappture::CmdSpec screenOps[] = {
974    {"bgcolor",  1, ScreenBgColorOp,  5, 5, "r g b",},
975    {"size",     1, ScreenSizeOp, 4, 4, "width height",},
976};
977static int nScreenOps = NumCmdSpecs(screenOps);
978
979static int
980ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
981          Tcl_Obj *const *objv)
982{
983    Tcl_ObjCmdProc *proc;
984
985    proc = Rappture::GetOpFromObj(interp, nScreenOps, screenOps,
986                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
987    if (proc == NULL) {
988        return TCL_ERROR;
989    }
990    return (*proc) (clientData, interp, objc, objv);
991}
992
993/*
994 * ----------------------------------------------------------------------
995 * CLIENT COMMAND:
996 *   transfunc define <name> <colormap> <alphamap>
997 *     where <colormap> = { <v> <r> <g> <b> ... }
998 *           <alphamap> = { <v> <w> ... }
999 *
1000 * Clients send these commands to manipulate the transfer functions.
1001 * ----------------------------------------------------------------------
1002 */
1003static int
1004TransfuncCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1005             Tcl_Obj *const *objv)
1006{
1007    if (objc < 2) {
1008        Tcl_AppendResult(interp, "wrong # args: should be \"",
1009                Tcl_GetString(objv[0]), " option arg arg...\"", (char*)NULL);
1010        return TCL_ERROR;
1011    }
1012
1013    const char *string = Tcl_GetString(objv[1]);
1014    char c = string[0];
1015    if ((c == 'd') && (strcmp(string, "define") == 0)) {
1016        if (objc != 5) {
1017            Tcl_AppendResult(interp, "wrong # args: should be \"",
1018                Tcl_GetString(objv[0]), " define name colorMap alphaMap\"",
1019                (char*)NULL);
1020            return TCL_ERROR;
1021        }
1022
1023        // decode the data and store in a series of fields
1024        Rappture::Field1D rFunc, gFunc, bFunc, wFunc;
1025        int cmapc, wmapc, i;
1026        Tcl_Obj **cmapv;
1027        Tcl_Obj **wmapv;
1028
1029        wmapv = cmapv = NULL;
1030        if (Tcl_ListObjGetElements(interp, objv[3], &cmapc, &cmapv) != TCL_OK) {
1031            return TCL_ERROR;
1032        }
1033        if ((cmapc % 4) != 0) {
1034            Tcl_AppendResult(interp, "wrong # elements is colormap: should be ",
1035                "{ v r g b ... }", (char*)NULL);
1036            return TCL_ERROR;
1037        }
1038        if (Tcl_ListObjGetElements(interp, objv[4], &wmapc, &wmapv) != TCL_OK) {
1039            return TCL_ERROR;
1040        }
1041        if ((wmapc % 2) != 0) {
1042            Tcl_AppendResult(interp, "wrong # elements in alphamap: should be ",
1043                " { v w ... }", (char*)NULL);
1044            return TCL_ERROR;
1045        }
1046        for (i = 0; i < cmapc; i += 4) {
1047            int j;
1048            double q[4];
1049
1050            for (j=0; j < 4; j++) {
1051                if (Tcl_GetDoubleFromObj(interp, cmapv[i+j], &q[j]) != TCL_OK) {
1052                    return TCL_ERROR;
1053                }
1054                if ((q[j] < 0.0) || (q[j] > 1.0)) {
1055                    Tcl_AppendResult(interp, "bad colormap value \"",
1056                        Tcl_GetString(cmapv[i+j]),
1057                        "\": should be in the range 0-1", (char*)NULL);
1058                    return TCL_ERROR;
1059                }
1060            }
1061            rFunc.define(q[0], q[1]);
1062            gFunc.define(q[0], q[2]);
1063            bFunc.define(q[0], q[3]);
1064        }
1065        for (i=0; i < wmapc; i += 2) {
1066            double q[2];
1067            int j;
1068
1069            for (j=0; j < 2; j++) {
1070                if (Tcl_GetDoubleFromObj(interp, wmapv[i+j], &q[j]) != TCL_OK) {
1071                    return TCL_ERROR;
1072                }
1073                if ((q[j] < 0.0) || (q[j] > 1.0)) {
1074                    Tcl_AppendResult(interp, "bad alphamap value \"",
1075                        Tcl_GetString(wmapv[i+j]),
1076                        "\": should be in the range 0-1", (char*)NULL);
1077                    return TCL_ERROR;
1078                }
1079            }
1080            wFunc.define(q[0], q[1]);
1081        }
1082        // sample the given function into discrete slots
1083        const int nslots = 256;
1084        float data[4*nslots];
1085        for (i=0; i < nslots; i++) {
1086            double x = double(i)/(nslots-1);
1087            data[4*i]   = rFunc.value(x);
1088            data[4*i+1] = gFunc.value(x);
1089            data[4*i+2] = bFunc.value(x);
1090            data[4*i+3] = wFunc.value(x);
1091        }
1092        // find or create this transfer function
1093        NanoVis::defineTransferFunction(Tcl_GetString(objv[2]), nslots, data);
1094    } else {
1095        Tcl_AppendResult(interp, "bad option \"", string,
1096                "\": should be define", (char*)NULL);
1097        return TCL_ERROR;
1098    }
1099    return TCL_OK;
1100}
1101
1102/*
1103 * ----------------------------------------------------------------------
1104 * CLIENT COMMAND:
1105 *   up axis
1106 *
1107 * Clients use this to set the "up" direction for all volumes.  Volumes
1108 * are oriented such that this direction points upward.
1109 * ----------------------------------------------------------------------
1110 */
1111static int
1112UpCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
1113{
1114    if (objc != 2) {
1115        Tcl_AppendResult(interp, "wrong # args: should be \"",
1116                         Tcl_GetString(objv[0]), " x|y|z|-x|-y|-z\"", (char*)NULL);
1117        return TCL_ERROR;
1118    }
1119
1120    int sign;
1121    int axis;
1122    if (GetAxisDirFromObj(interp, objv[1], &axis, &sign) != TCL_OK) {
1123        return TCL_ERROR;
1124    }
1125    NanoVis::updir = (axis+1)*sign;
1126    return TCL_OK;
1127}
1128
1129static int
1130VolumeAnimationCaptureOp(ClientData clientData, Tcl_Interp *interp, int objc,
1131                         Tcl_Obj *const *objv)
1132{
1133    int total;
1134    if (Tcl_GetIntFromObj(interp, objv[3], &total) != TCL_OK) {
1135        return TCL_ERROR;
1136    }
1137    VolumeInterpolator *interpolator =
1138        NanoVis::volRenderer->getVolumeInterpolator();
1139    interpolator->start();
1140    if (interpolator->isStarted()) {
1141        const char *dirName = (objc < 5) ? NULL : Tcl_GetString(objv[4]);
1142        for (int frameNum = 0; frameNum < total; ++frameNum) {
1143            float fraction;
1144
1145            fraction = ((float)frameNum) / (total - 1);
1146            TRACE("fraction : %f", fraction);
1147            interpolator->update(fraction);
1148
1149            int fboOrig;
1150            glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
1151
1152            NanoVis::bindOffscreenBuffer();  //enable offscreen render
1153
1154            NanoVis::render();
1155            NanoVis::readScreen();
1156
1157            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
1158
1159            /* FIXME: this function requires 4-byte aligned RGB rows,
1160             * but screen buffer is now 1 byte aligned for PPM images.
1161             */
1162            nv::writeBMPFile(frameNum, dirName,
1163                             NanoVis::screenBuffer,
1164                             NanoVis::winWidth, NanoVis::winHeight);
1165        }
1166    }
1167    return TCL_OK;
1168}
1169
1170static int
1171VolumeAnimationClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
1172                       Tcl_Obj *const *objv)
1173{
1174    NanoVis::volRenderer->clearAnimatedVolumeInfo();
1175    return TCL_OK;
1176}
1177
1178static int
1179VolumeAnimationStartOp(ClientData clientData, Tcl_Interp *interp, int objc,
1180                       Tcl_Obj *const *objv)
1181{
1182    NanoVis::volRenderer->startVolumeAnimation();
1183    return TCL_OK;
1184}
1185
1186static int
1187VolumeAnimationStopOp(ClientData clientData, Tcl_Interp *interp, int objc,
1188                      Tcl_Obj *const *objv)
1189{
1190    NanoVis::volRenderer->stopVolumeAnimation();
1191    return TCL_OK;
1192}
1193
1194static int
1195VolumeAnimationVolumesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1196                         Tcl_Obj *const *objv)
1197{
1198    std::vector<Volume *> volumes;
1199    if (GetVolumes(interp, objc - 3, objv + 3, &volumes) != TCL_OK) {
1200        return TCL_ERROR;
1201    }
1202    TRACE("parsing volume data identifier");
1203    NanoVis::VolumeHashmap::iterator itr;
1204    for (itr = NanoVis::volumeTable.begin();
1205         itr != NanoVis::volumeTable.end(); ++itr) {
1206        NanoVis::volRenderer->addAnimatedVolume(itr->second);
1207    }
1208    return TCL_OK;
1209}
1210
1211static Rappture::CmdSpec volumeAnimationOps[] = {
1212    {"capture",   2, VolumeAnimationCaptureOp,  4, 5, "numframes ?filename?",},
1213    {"clear",     2, VolumeAnimationClearOp,    3, 3, "",},
1214    {"start",     3, VolumeAnimationStartOp,    3, 3, "",},
1215    {"stop",      3, VolumeAnimationStopOp,     3, 3, "",},
1216    {"volumes",   1, VolumeAnimationVolumesOp,  3, 0, "?indices?",},
1217};
1218
1219static int nVolumeAnimationOps = NumCmdSpecs(volumeAnimationOps);
1220
1221static int
1222VolumeAnimationOp(ClientData clientData, Tcl_Interp *interp, int objc,
1223                  Tcl_Obj *const *objv)
1224{
1225    Tcl_ObjCmdProc *proc;
1226
1227    proc = Rappture::GetOpFromObj(interp, nVolumeAnimationOps,
1228                volumeAnimationOps, Rappture::CMDSPEC_ARG2, objc, objv, 0);
1229    if (proc == NULL) {
1230        return TCL_ERROR;
1231    }
1232    return (*proc) (clientData, interp, objc, objv);
1233}
1234
1235static int
1236VolumeDataFollowsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1237                    Tcl_Obj *const *objv)
1238{
1239    TRACE("Data Loading");
1240
1241    int nbytes;
1242    if (Tcl_GetIntFromObj(interp, objv[3], &nbytes) != TCL_OK) {
1243        return TCL_ERROR;
1244    }
1245    const char *tag = Tcl_GetString(objv[4]);
1246
1247    Rappture::Buffer buf(nbytes);
1248    if (GetDataStream(interp, buf, nbytes) != TCL_OK) {
1249        return TCL_ERROR;
1250    }
1251    const char *bytes = buf.bytes();
1252    size_t nBytes = buf.size();
1253
1254    TRACE("Checking header[%.20s]", bytes);
1255
1256    Volume *volume = NULL;
1257
1258    if ((nBytes > 5) && (strncmp(bytes, "<HDR>", 5) == 0)) {
1259        TRACE("ZincBlende Stream loading...");
1260        volume = NvZincBlendeReconstructor::getInstance()->loadFromMemory(const_cast<char *>(bytes));
1261        if (volume == NULL) {
1262            Tcl_AppendResult(interp, "can't get volume instance", (char *)NULL);
1263            return TCL_ERROR;
1264        }
1265        TRACE("finish loading");
1266
1267        Vector3f scale = volume->getPhysicalScaling();
1268        Vector3f loc(scale);
1269        loc *= -0.5;
1270        volume->location(loc);
1271
1272        NanoVis::VolumeHashmap::iterator itr = NanoVis::volumeTable.find(tag);
1273        if (itr != NanoVis::volumeTable.end()) {
1274            Tcl_AppendResult(interp, "volume \"", tag, "\" already exists.",
1275                             (char *)NULL);
1276            return TCL_ERROR;
1277        }
1278        NanoVis::volumeTable[tag] = volume;
1279        volume->name(tag);
1280    } else if ((nBytes > 14) && (strncmp(bytes, "# vtk DataFile", 14) == 0)) {
1281        TRACE("VTK loading...");
1282        std::stringstream fdata;
1283        fdata.write(bytes, nBytes);
1284        if (nBytes <= 0) {
1285            ERROR("data buffer is empty");
1286            abort();
1287        }
1288        Rappture::Outcome context;
1289        volume = load_vtk_volume_stream(context, tag, fdata);
1290        if (volume == NULL) {
1291            Tcl_AppendResult(interp, context.remark(), (char*)NULL);
1292            return TCL_ERROR;
1293        }
1294    } else {
1295        // **Deprecated** OpenDX format
1296        if ((nBytes > 5) && (strncmp(bytes, "<ODX>", 5) == 0)) {
1297            bytes += 5;
1298            nBytes -= 5;
1299        }
1300        TRACE("DX loading...");
1301        std::stringstream fdata;
1302        fdata.write(bytes, nBytes);
1303        if (nBytes <= 0) {
1304            ERROR("data buffer is empty");
1305            abort();
1306        }
1307        Rappture::Outcome context;
1308        volume = load_dx_volume_stream(context, tag, fdata);
1309        if (volume == NULL) {
1310            Tcl_AppendResult(interp, context.remark(), (char*)NULL);
1311            return TCL_ERROR;
1312        }
1313    }
1314
1315    if (volume != NULL) {
1316        volume->disableCutplane(0);
1317        volume->disableCutplane(1);
1318        volume->disableCutplane(2);
1319        volume->transferFunction(NanoVis::getTransferFunction("default"));
1320        volume->visible(true);
1321
1322        if (Volume::updatePending) {
1323            NanoVis::setVolumeRanges();
1324        }
1325
1326        char info[1024];
1327        // FIXME: strlen(info) is the return value of sprintf
1328        int cmdLength =
1329            sprintf(info, "nv>data tag %s min %g max %g vmin %g vmax %g\n", tag,
1330                    volume->wAxis.min(), volume->wAxis.max(),
1331                    Volume::valueMin, Volume::valueMax);
1332#ifdef USE_THREADS
1333        queueResponse(info, cmdLength, Response::VOLATILE);
1334#else
1335        ssize_t nWritten  = SocketWrite(info, (size_t)cmdLength);
1336        if (nWritten != (ssize_t)cmdLength) {
1337            ERROR("Short write");
1338             return TCL_ERROR;
1339        }
1340#endif
1341    }
1342
1343    return TCL_OK;
1344}
1345
1346static int
1347VolumeDataStateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1348                  Tcl_Obj *const *objv)
1349{
1350    bool state;
1351    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1352        return TCL_ERROR;
1353    }
1354    std::vector<Volume *> ivol;
1355    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1356        return TCL_ERROR;
1357    }
1358    std::vector<Volume *>::iterator iter;
1359    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1360        (*iter)->dataEnabled(state);
1361    }
1362    return TCL_OK;
1363}
1364
1365static Rappture::CmdSpec volumeDataOps[] = {
1366    {"follows",   1, VolumeDataFollowsOp, 5, 5, "nbytes tag",},
1367    {"state",     1, VolumeDataStateOp,   4, 0, "bool ?indices?",},
1368};
1369static int nVolumeDataOps = NumCmdSpecs(volumeDataOps);
1370
1371static int
1372VolumeDataOp(ClientData clientData, Tcl_Interp *interp, int objc,
1373             Tcl_Obj *const *objv)
1374{
1375    Tcl_ObjCmdProc *proc;
1376
1377    proc = Rappture::GetOpFromObj(interp, nVolumeDataOps, volumeDataOps,
1378                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
1379    if (proc == NULL) {
1380        return TCL_ERROR;
1381    }
1382    return (*proc) (clientData, interp, objc, objv);
1383}
1384
1385static int
1386VolumeDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1387               Tcl_Obj *const *objv)
1388{
1389    for (int i = 2; i < objc; i++) {
1390        Volume *volume;
1391        if (GetVolumeFromObj(interp, objv[i], &volume) != TCL_OK) {
1392            return TCL_ERROR;
1393        }
1394        NanoVis::removeVolume(volume);
1395    }
1396    NanoVis::eventuallyRedraw();
1397    return TCL_OK;
1398}
1399
1400static int
1401VolumeExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1402               Tcl_Obj *const *objv)
1403{
1404    bool value;
1405    Volume *volume;
1406
1407    value = false;
1408    if (GetVolumeFromObj(NULL, objv[2], &volume) == TCL_OK) {
1409        value = true;
1410    }
1411    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), (int)value);
1412    return TCL_OK;
1413}
1414
1415static int
1416VolumeNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1417              Tcl_Obj *const *objv)
1418{
1419    Tcl_Obj *listObjPtr;
1420    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
1421    NanoVis::VolumeHashmap::iterator itr;
1422    for (itr = NanoVis::volumeTable.begin();
1423         itr != NanoVis::volumeTable.end(); ++itr) {
1424        Tcl_Obj *objPtr = Tcl_NewStringObj(itr->second->name(), -1);
1425        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1426    }
1427    Tcl_SetObjResult(interp, listObjPtr);
1428    return TCL_OK;
1429}
1430
1431static int
1432VolumeOutlineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1433                     Tcl_Obj *const *objv)
1434{
1435    float rgb[3];
1436    if (GetColor(interp, objc - 3, objv + 3, rgb) != TCL_OK) {
1437        return TCL_ERROR;
1438    }
1439    std::vector<Volume *> ivol;
1440    if (GetVolumes(interp, objc - 6, objv + 6, &ivol) != TCL_OK) {
1441        return TCL_ERROR;
1442    }
1443    std::vector<Volume *>::iterator iter;
1444    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1445        (*iter)->setOutlineColor(rgb);
1446    }
1447    return TCL_OK;
1448}
1449
1450static int
1451VolumeOutlineStateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1452                     Tcl_Obj *const *objv)
1453{
1454    bool state;
1455    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1456        return TCL_ERROR;
1457    }
1458    std::vector<Volume *> ivol;
1459    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1460        return TCL_ERROR;
1461    }
1462    std::vector<Volume *>::iterator iter;
1463    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1464        (*iter)->outline(state);
1465    }
1466    return TCL_OK;
1467}
1468
1469static Rappture::CmdSpec volumeOutlineOps[] = {
1470    {"color",     1, VolumeOutlineColorOp,  6, 0, "r g b ?indices?",},
1471    {"state",     1, VolumeOutlineStateOp,  4, 0, "bool ?indices?",},
1472    {"visible",   1, VolumeOutlineStateOp,  4, 0, "bool ?indices?",},
1473};
1474static int nVolumeOutlineOps = NumCmdSpecs(volumeOutlineOps);
1475
1476static int
1477VolumeOutlineOp(ClientData clientData, Tcl_Interp *interp, int objc,
1478                Tcl_Obj *const *objv)
1479{
1480    Tcl_ObjCmdProc *proc;
1481
1482    proc = Rappture::GetOpFromObj(interp, nVolumeOutlineOps, volumeOutlineOps,
1483        Rappture::CMDSPEC_ARG2, objc, objv, 0);
1484    if (proc == NULL) {
1485        return TCL_ERROR;
1486    }
1487    return (*proc) (clientData, interp, objc, objv);
1488}
1489
1490static int
1491VolumeShadingAmbientOp(ClientData clientData, Tcl_Interp *interp, int objc,
1492                       Tcl_Obj *const *objv)
1493{
1494    float ambient;
1495    if (GetFloatFromObj(interp, objv[3], &ambient) != TCL_OK) {
1496        return TCL_ERROR;
1497    }
1498    if (ambient < 0.0f || ambient > 1.0f) {
1499        WARN("Invalid ambient coefficient requested: %g, should be [0,1]", ambient);
1500    }
1501    std::vector<Volume *> ivol;
1502    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1503        return TCL_ERROR;
1504    }
1505    std::vector<Volume *>::iterator iter;
1506    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1507        (*iter)->ambient(ambient);
1508    }
1509    return TCL_OK;
1510}
1511
1512static int
1513VolumeShadingDiffuseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1514                       Tcl_Obj *const *objv)
1515{
1516    float diffuse;
1517    if (GetFloatFromObj(interp, objv[3], &diffuse) != TCL_OK) {
1518        return TCL_ERROR;
1519    }
1520    if (diffuse < 0.0f || diffuse > 1.0f) {
1521        WARN("Invalid diffuse coefficient requested: %g, should be [0,1]", diffuse);
1522    }
1523    std::vector<Volume *> ivol;
1524    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1525        return TCL_ERROR;
1526    }
1527    std::vector<Volume *>::iterator iter;
1528    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1529        (*iter)->diffuse(diffuse);
1530    }
1531    return TCL_OK;
1532}
1533
1534static int
1535VolumeShadingIsosurfaceOp(ClientData clientData, Tcl_Interp *interp, int objc,
1536                          Tcl_Obj *const *objv)
1537{
1538    bool iso_surface;
1539    if (GetBooleanFromObj(interp, objv[3], &iso_surface) != TCL_OK) {
1540        return TCL_ERROR;
1541    }
1542    std::vector<Volume *> ivol;
1543    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1544        return TCL_ERROR;
1545    }
1546    std::vector<Volume *>::iterator iter;
1547    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1548        (*iter)->isosurface(iso_surface);
1549    }
1550    return TCL_OK;
1551}
1552
1553static int
1554VolumeShadingLight2SideOp(ClientData clientData, Tcl_Interp *interp, int objc,
1555                          Tcl_Obj *const *objv)
1556{
1557    bool twoSidedLighting;
1558    if (GetBooleanFromObj(interp, objv[3], &twoSidedLighting) != TCL_OK) {
1559        return TCL_ERROR;
1560    }
1561    std::vector<Volume *> ivol;
1562    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1563        return TCL_ERROR;
1564    }
1565    std::vector<Volume *>::iterator iter;
1566    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1567        (*iter)->twoSidedLighting(twoSidedLighting);
1568    }
1569    return TCL_OK;
1570}
1571
1572static int
1573VolumeShadingOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
1574                       Tcl_Obj *const *objv)
1575{
1576
1577    float opacity;
1578    if (GetFloatFromObj(interp, objv[3], &opacity) != TCL_OK) {
1579        return TCL_ERROR;
1580    }
1581    TRACE("set opacity %f", opacity);
1582    std::vector<Volume *> ivol;
1583    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1584        return TCL_ERROR;
1585    }
1586    std::vector<Volume *>::iterator iter;
1587    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1588        (*iter)->opacityScale(opacity);
1589    }
1590    return TCL_OK;
1591}
1592
1593static int
1594VolumeShadingSpecularOp(ClientData clientData, Tcl_Interp *interp, int objc,
1595                        Tcl_Obj *const *objv)
1596{
1597    float specular;
1598    if (GetFloatFromObj(interp, objv[3], &specular) != TCL_OK) {
1599        return TCL_ERROR;
1600    }
1601    if (specular < 0.0f || specular > 1.0f) {
1602        WARN("Invalid specular coefficient requested: %g, should be [0,1]", specular);
1603    }
1604    std::vector<Volume *> ivol;
1605    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1606        return TCL_ERROR;
1607    }
1608    std::vector<Volume *>::iterator iter;
1609    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1610        (*iter)->specularLevel(specular);
1611    }
1612    return TCL_OK;
1613}
1614
1615static int
1616VolumeShadingSpecularExpOp(ClientData clientData, Tcl_Interp *interp, int objc,
1617                           Tcl_Obj *const *objv)
1618{
1619    float specularExp;
1620    if (GetFloatFromObj(interp, objv[3], &specularExp) != TCL_OK) {
1621        return TCL_ERROR;
1622    }
1623    if (specularExp < 0.0f || specularExp > 128.0f) {
1624        WARN("Invalid specular exponent requested: %g, should be [0,128]", specularExp);
1625    }
1626    std::vector<Volume *> ivol;
1627    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1628        return TCL_ERROR;
1629    }
1630    std::vector<Volume *>::iterator iter;
1631    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1632        (*iter)->specularExponent(specularExp);
1633    }
1634    return TCL_OK;
1635}
1636
1637static int
1638VolumeShadingTransFuncOp(ClientData clientData, Tcl_Interp *interp, int objc,
1639                         Tcl_Obj *const *objv)
1640{
1641    const char *name = Tcl_GetString(objv[3]);
1642    TransferFunction *tf = NanoVis::getTransferFunction(name);
1643    if (tf == NULL) {
1644        Tcl_AppendResult(interp, "transfer function \"", name,
1645                         "\" is not defined", (char*)NULL);
1646        return TCL_ERROR;
1647    }
1648    std::vector<Volume *> ivol;
1649    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1650        return TCL_ERROR;
1651    }
1652    std::vector<Volume *>::iterator iter;
1653    for (iter = ivol.begin(); iter != ivol.end(); ++iter) {
1654        TRACE("setting %s with transfer function %s", (*iter)->name(),
1655               tf->name());
1656        (*iter)->transferFunction(tf);
1657    }
1658    return TCL_OK;
1659}
1660
1661static Rappture::CmdSpec volumeShadingOps[] = {
1662    {"ambient",       1, VolumeShadingAmbientOp,     4, 0, "value ?indices?",},
1663    {"diffuse",       1, VolumeShadingDiffuseOp,     4, 0, "value ?indices?",},
1664    {"isosurface",    1, VolumeShadingIsosurfaceOp,  4, 0, "bool ?indices?",},
1665    {"light2side",    1, VolumeShadingLight2SideOp,  4, 0, "bool ?indices?",},
1666    {"opacity",       1, VolumeShadingOpacityOp,     4, 0, "value ?indices?",},
1667    {"specularExp",   9, VolumeShadingSpecularExpOp, 4, 0, "value ?indices?",},
1668    {"specularLevel", 9, VolumeShadingSpecularOp,    4, 0, "value ?indices?",},
1669    {"transfunc",     1, VolumeShadingTransFuncOp,   4, 0, "funcName ?indices?",},
1670};
1671static int nVolumeShadingOps = NumCmdSpecs(volumeShadingOps);
1672
1673static int
1674VolumeShadingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1675                Tcl_Obj *const *objv)
1676{
1677    Tcl_ObjCmdProc *proc;
1678
1679    proc = Rappture::GetOpFromObj(interp, nVolumeShadingOps, volumeShadingOps,
1680        Rappture::CMDSPEC_ARG2, objc, objv, 0);
1681    if (proc == NULL) {
1682        return TCL_ERROR;
1683    }
1684    return (*proc) (clientData, interp, objc, objv);
1685}
1686
1687static int
1688VolumeStateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1689              Tcl_Obj *const *objv)
1690{
1691    bool state;
1692    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1693        return TCL_ERROR;
1694    }
1695    std::vector<Volume *> ivol;
1696    if (GetVolumes(interp, objc - 3, objv + 3, &ivol) != TCL_OK) {
1697        return TCL_ERROR;
1698    }
1699    std::vector<Volume *>::iterator iter;
1700    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1701        (*iter)->visible(state);
1702    }
1703    return TCL_OK;
1704}
1705
1706static Rappture::CmdSpec volumeOps[] = {
1707    {"animation", 2, VolumeAnimationOp,   3, 0, "oper ?args?",},
1708    {"data",      2, VolumeDataOp,        3, 0, "oper ?args?",},
1709    {"delete",    2, VolumeDeleteOp,      3, 0, "?name...?",},
1710    {"exists",    1, VolumeExistsOp,      3, 3, "name",},
1711    {"names",     1, VolumeNamesOp,       2, 2, "",},
1712    {"outline",   1, VolumeOutlineOp,     3, 0, "oper ?args?",},
1713    {"shading",   2, VolumeShadingOp,     3, 0, "oper ?args?",},
1714    {"state",     2, VolumeStateOp,       3, 0, "bool ?indices?",},
1715};
1716static int nVolumeOps = NumCmdSpecs(volumeOps);
1717
1718static int
1719VolumeCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1720          Tcl_Obj *const *objv)
1721{
1722    Tcl_ObjCmdProc *proc;
1723
1724    proc = Rappture::GetOpFromObj(interp, nVolumeOps, volumeOps,
1725        Rappture::CMDSPEC_ARG1, objc, objv, 0);
1726    if (proc == NULL) {
1727        return TCL_ERROR;
1728    }
1729    return (*proc) (clientData, interp, objc, objv);
1730}
1731
1732static int
1733HeightMapDataFollowsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1734                       Tcl_Obj *const *objv)
1735{
1736    int nBytes;
1737    if (Tcl_GetIntFromObj(interp, objv[3], &nBytes) != TCL_OK) {
1738        return TCL_ERROR;
1739    }
1740    const char *tag = Tcl_GetString(objv[4]);
1741
1742    Rappture::Buffer buf(nBytes);
1743    if (GetDataStream(interp, buf, nBytes) != TCL_OK) {
1744        return TCL_ERROR;
1745    }
1746    Rappture::Unirect2d data(1);
1747    if (data.parseBuffer(interp, buf) != TCL_OK) {
1748        return TCL_ERROR;
1749    }
1750    if (data.nValues() == 0) {
1751        Tcl_AppendResult(interp, "no data found in stream", (char *)NULL);
1752        return TCL_ERROR;
1753    }
1754    if (!data.isInitialized()) {
1755        return TCL_ERROR;
1756    }
1757    HeightMap *heightMap;
1758    NanoVis::HeightMapHashmap::iterator itr = NanoVis::heightMapTable.find(tag);
1759    if (itr != NanoVis::heightMapTable.end()) {
1760        heightMap = itr->second;
1761    } else {
1762        heightMap = new HeightMap();
1763        NanoVis::heightMapTable[tag] = heightMap;
1764    }
1765    TRACE("Number of heightmaps=%d", NanoVis::heightMapTable.size());
1766    // Must set units before the heights.
1767    heightMap->xAxis.units(data.xUnits());
1768    heightMap->yAxis.units(data.yUnits());
1769    heightMap->zAxis.units(data.vUnits());
1770    heightMap->wAxis.units(data.yUnits());
1771    heightMap->setHeight(data.xMin(), data.yMin(), data.xMax(), data.yMax(),
1772                         data.xNum(), data.yNum(), data.transferValues());
1773    heightMap->transferFunction(NanoVis::getTransferFunction("default"));
1774    heightMap->setVisible(true);
1775    heightMap->setLineContourVisible(true);
1776    NanoVis::eventuallyRedraw();
1777    return TCL_OK;
1778}
1779
1780static int
1781HeightMapDataVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1782                       Tcl_Obj *const *objv)
1783{
1784    bool visible;
1785    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1786        return TCL_ERROR;
1787    }
1788    std::vector<HeightMap *> imap;
1789    if (GetHeightMaps(interp, objc - 4, objv + 4, &imap) != TCL_OK) {
1790        return TCL_ERROR;
1791    }
1792    std::vector<HeightMap *>::iterator iter;
1793    for (iter = imap.begin(); iter != imap.end(); iter++) {
1794        (*iter)->setVisible(visible);
1795    }
1796    NanoVis::eventuallyRedraw();
1797    return TCL_OK;
1798}
1799
1800static Rappture::CmdSpec heightMapDataOps[] = {
1801    {"follows",  1, HeightMapDataFollowsOp, 5, 5, "size heightmapName",},
1802    {"visible",  1, HeightMapDataVisibleOp, 4, 0, "bool ?heightmapNames...?",},
1803};
1804static int nHeightMapDataOps = NumCmdSpecs(heightMapDataOps);
1805
1806static int
1807HeightMapDataOp(ClientData clientData, Tcl_Interp *interp, int objc,
1808                Tcl_Obj *const *objv)
1809{
1810    Tcl_ObjCmdProc *proc;
1811
1812    proc = Rappture::GetOpFromObj(interp, nHeightMapDataOps, heightMapDataOps,
1813                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
1814    if (proc == NULL) {
1815        return TCL_ERROR;
1816    }
1817    return (*proc) (clientData, interp, objc, objv);
1818}
1819
1820
1821static int
1822HeightMapLineContourColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1823                            Tcl_Obj *const *objv)
1824{
1825    float rgb[3];
1826    if (GetColor(interp, objc - 3, objv + 3, rgb) != TCL_OK) {
1827        return TCL_ERROR;
1828    }
1829    std::vector<HeightMap *> imap;
1830    if (GetHeightMaps(interp, objc - 6, objv + 6, &imap) != TCL_OK) {
1831        return TCL_ERROR;
1832    }
1833    std::vector<HeightMap *>::iterator iter;
1834    for (iter = imap.begin(); iter != imap.end(); iter++) {
1835        (*iter)->setLineContourColor(rgb);
1836    }
1837    NanoVis::eventuallyRedraw();
1838    return TCL_OK;
1839}
1840
1841static int
1842HeightMapLineContourVisibleOp(ClientData clientData, Tcl_Interp *interp,
1843                              int objc, Tcl_Obj *const *objv)
1844{
1845    bool visible;
1846    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1847        return TCL_ERROR;
1848    }
1849    std::vector<HeightMap *> imap;
1850    if (GetHeightMaps(interp, objc - 4, objv + 4, &imap) != TCL_OK) {
1851        return TCL_ERROR;
1852    }
1853    std::vector<HeightMap *>::iterator iter;
1854    for (iter = imap.begin(); iter != imap.end(); iter++) {
1855        (*iter)->setLineContourVisible(visible);
1856    }
1857    NanoVis::eventuallyRedraw();
1858    return TCL_OK;
1859}
1860
1861static Rappture::CmdSpec heightMapLineContourOps[] = {
1862    {"color",   1, HeightMapLineContourColorOp,   6, 0, "r g b ?heightmapNames...?",},
1863    {"visible", 1, HeightMapLineContourVisibleOp, 4, 0, "bool ?heightmapNames...?",},
1864};
1865static int nHeightMapLineContourOps = NumCmdSpecs(heightMapLineContourOps);
1866
1867static int
1868HeightMapLineContourOp(ClientData clientData, Tcl_Interp *interp, int objc,
1869                       Tcl_Obj *const *objv)
1870{
1871    Tcl_ObjCmdProc *proc;
1872
1873    proc = Rappture::GetOpFromObj(interp, nHeightMapLineContourOps,
1874        heightMapLineContourOps, Rappture::CMDSPEC_ARG2, objc, objv, 0);
1875    if (proc == NULL) {
1876        return TCL_ERROR;
1877    }
1878    return (*proc) (clientData, interp, objc, objv);
1879}
1880
1881static int
1882HeightMapCullOp(ClientData clientData, Tcl_Interp *interp, int objc,
1883                Tcl_Obj *const *objv)
1884{
1885    RenderContext::CullMode mode;
1886    if (GetCullMode(interp, objv[2], &mode) != TCL_OK) {
1887        return TCL_ERROR;
1888    }
1889    NanoVis::renderContext->setCullMode(mode);
1890    NanoVis::eventuallyRedraw();
1891    return TCL_OK;
1892}
1893
1894static int
1895HeightMapCreateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1896                  Tcl_Obj *const *objv)
1897{
1898    const char *tag = Tcl_GetString(objv[2]);
1899    NanoVis::HeightMapHashmap::iterator itr = NanoVis::heightMapTable.find(tag);
1900    if (itr != NanoVis::heightMapTable.end()) {
1901        Tcl_AppendResult(interp, "heightmap \"", tag, "\" already exists.",
1902                         (char *)NULL);
1903        return TCL_ERROR;
1904    }
1905    /* heightmap create xmin ymin xmax ymax xnum ynum values */
1906    HeightMap *heightMap = CreateHeightMap(clientData, interp, objc - 3, objv + 3);
1907    if (heightMap == NULL) {
1908        return TCL_ERROR;
1909    }
1910    NanoVis::heightMapTable[tag] = heightMap;
1911    NanoVis::eventuallyRedraw();
1912    TRACE("Number of heightmaps=%d", NanoVis::heightMapTable.size());
1913    return TCL_OK;
1914}
1915
1916static int
1917HeightMapLegendOp(ClientData clientData, Tcl_Interp *interp, int objc,
1918                  Tcl_Obj *const *objv)
1919{
1920    HeightMap *hmPtr;
1921    if (GetHeightMapFromObj(interp, objv[2], &hmPtr) != TCL_OK) {
1922        return TCL_ERROR;
1923    }
1924    const char *tag;
1925    tag = Tcl_GetString(objv[2]);
1926    TransferFunction *tfPtr;
1927    tfPtr = hmPtr->transferFunction();
1928    if (tfPtr == NULL) {
1929        Tcl_AppendResult(interp, "no transfer function defined for heightmap"
1930                         " \"", tag, "\"", (char*)NULL);
1931        return TCL_ERROR;
1932    }
1933    int w, h;
1934    if ((Tcl_GetIntFromObj(interp, objv[3], &w) != TCL_OK) ||
1935        (Tcl_GetIntFromObj(interp, objv[4], &h) != TCL_OK)) {
1936        return TCL_ERROR;
1937    }
1938    if (HeightMap::updatePending) {
1939        NanoVis::setHeightmapRanges();
1940    }
1941    NanoVis::renderLegend(tfPtr, HeightMap::valueMin, HeightMap::valueMax,
1942                          w, h, tag);
1943    return TCL_OK;
1944}
1945
1946static int
1947HeightMapPolygonOp(ClientData clientData, Tcl_Interp *interp, int objc,
1948                   Tcl_Obj *const *objv)
1949{
1950    RenderContext::PolygonMode mode;
1951    if (GetPolygonMode(interp, objv[2], &mode) != TCL_OK) {
1952        return TCL_ERROR;
1953    }
1954    NanoVis::renderContext->setPolygonMode(mode);
1955    NanoVis::eventuallyRedraw();
1956    return TCL_OK;
1957}
1958
1959static int
1960HeightMapShadingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1961                 Tcl_Obj *const *objv)
1962{
1963    RenderContext::ShadingModel model;
1964    if (GetShadingModel(interp, objv[2], &model) != TCL_OK) {
1965        return TCL_ERROR;
1966    }
1967    NanoVis::renderContext->setShadingModel(model);
1968    NanoVis::eventuallyRedraw();
1969    return TCL_OK;
1970}
1971
1972static int
1973HeightMapTransFuncOp(ClientData clientData, Tcl_Interp *interp, int objc,
1974                     Tcl_Obj *const *objv)
1975{
1976    const char *name;
1977    name = Tcl_GetString(objv[2]);
1978    TransferFunction *tf = NanoVis::getTransferFunction(name);
1979    if (tf == NULL) {
1980        Tcl_AppendResult(interp, "transfer function \"", name,
1981                         "\" is not defined", (char*)NULL);
1982        return TCL_ERROR;
1983    }
1984    std::vector<HeightMap *> imap;
1985    if (GetHeightMaps(interp, objc - 3, objv + 3, &imap) != TCL_OK) {
1986        return TCL_ERROR;
1987    }
1988    std::vector<HeightMap *>::iterator iter;
1989    for (iter = imap.begin(); iter != imap.end(); iter++) {
1990        (*iter)->transferFunction(tf);
1991    }
1992    NanoVis::eventuallyRedraw();
1993    return TCL_OK;
1994}
1995
1996static int
1997HeightMapOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
1998                   Tcl_Obj *const *objv)
1999{
2000    float opacity;
2001    if (GetFloatFromObj(interp, objv[2], &opacity) != TCL_OK) {
2002        return TCL_ERROR;
2003    }
2004    std::vector<HeightMap *> heightmaps;
2005    if (GetHeightMaps(interp, objc - 3, objv + 3, &heightmaps) != TCL_OK) {
2006        return TCL_ERROR;
2007    }
2008    std::vector<HeightMap *>::iterator iter;
2009    for (iter = heightmaps.begin(); iter != heightmaps.end(); iter++) {
2010        (*iter)->opacity(opacity);
2011    }
2012    NanoVis::eventuallyRedraw();
2013    return TCL_OK;
2014}
2015
2016static Rappture::CmdSpec heightMapOps[] = {
2017    {"create",       2, HeightMapCreateOp,      10, 10, "heightmapName xmin ymin xmax ymax xnum ynum values",},
2018    {"cull",         2, HeightMapCullOp,        3, 3, "mode",},
2019    {"data",         1, HeightMapDataOp,        3, 0, "oper ?args?",},
2020    {"legend",       2, HeightMapLegendOp,      5, 5, "heightmapName width height",},
2021    {"linecontour",  2, HeightMapLineContourOp, 2, 0, "oper ?args?",},
2022    {"opacity",      1, HeightMapOpacityOp,     3, 0, "value ?heightmapNames...? ",},
2023    {"polygon",      1, HeightMapPolygonOp,     3, 3, "mode",},
2024    {"shading",      1, HeightMapShadingOp,     3, 3, "model",},
2025    {"transfunc",    2, HeightMapTransFuncOp,   3, 0, "name ?heightmapNames...?",},
2026};
2027static int nHeightMapOps = NumCmdSpecs(heightMapOps);
2028
2029static int
2030HeightMapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2031             Tcl_Obj *const *objv)
2032{
2033    Tcl_ObjCmdProc *proc;
2034
2035    proc = Rappture::GetOpFromObj(interp, nHeightMapOps, heightMapOps,
2036                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
2037    if (proc == NULL) {
2038        return TCL_ERROR;
2039    }
2040    return (*proc) (clientData, interp, objc, objv);
2041}
2042
2043static int
2044GridAxisColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2045                Tcl_Obj *const *objv)
2046{
2047    float r, g, b, a;
2048    if ((GetFloatFromObj(interp, objv[2], &r) != TCL_OK) ||
2049        (GetFloatFromObj(interp, objv[3], &g) != TCL_OK) ||
2050        (GetFloatFromObj(interp, objv[4], &b) != TCL_OK)) {
2051        return TCL_ERROR;
2052    }
2053    a = 1.0f;
2054    if ((objc == 6) && (GetFloatFromObj(interp, objv[5], &a) != TCL_OK)) {
2055        return TCL_ERROR;
2056    }
2057    if (NanoVis::grid) {
2058        NanoVis::grid->setAxisColor(r, g, b, a);
2059    }
2060    return TCL_OK;
2061}
2062
2063static int
2064GridAxisNameOp(ClientData clientData, Tcl_Interp *interp, int objc,
2065               Tcl_Obj *const *objv)
2066{
2067    int axis;
2068    if (GetAxisFromObj(interp, objv[2], &axis) != TCL_OK) {
2069        return TCL_ERROR;
2070    }
2071    if (NanoVis::grid != NULL) {
2072        Axis *axisPtr;
2073
2074        axisPtr = NULL;     /* Suppress compiler warning. */
2075        switch (axis) {
2076        case 0: axisPtr = &NanoVis::grid->xAxis; break;
2077        case 1: axisPtr = &NanoVis::grid->yAxis; break;
2078        case 2: axisPtr = &NanoVis::grid->zAxis; break;
2079        }
2080        axisPtr->name(Tcl_GetString(objv[3]));
2081        axisPtr->units(Tcl_GetString(objv[4]));
2082    }
2083    return TCL_OK;
2084}
2085
2086static int
2087GridLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2088                Tcl_Obj *const *objv)
2089{
2090    float r, g, b, a;
2091    if ((GetFloatFromObj(interp, objv[2], &r) != TCL_OK) ||
2092        (GetFloatFromObj(interp, objv[3], &g) != TCL_OK) ||
2093        (GetFloatFromObj(interp, objv[4], &b) != TCL_OK)) {
2094        return TCL_ERROR;
2095    }
2096    a = 1.0f;
2097    if ((objc == 6) && (GetFloatFromObj(interp, objv[5], &a) != TCL_OK)) {
2098        return TCL_ERROR;
2099    }
2100    if (NanoVis::grid) {
2101        NanoVis::grid->setLineColor(r, g, b, a);
2102    }
2103    return TCL_OK;
2104}
2105
2106static int
2107GridVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
2108              Tcl_Obj *const *objv)
2109{
2110    bool visible;
2111    if (GetBooleanFromObj(interp, objv[2], &visible) != TCL_OK) {
2112        return TCL_ERROR;
2113    }
2114    NanoVis::grid->setVisible(visible);
2115    return TCL_OK;
2116}
2117
2118static Rappture::CmdSpec gridOps[] = {
2119    {"axiscolor",  5, GridAxisColorOp,  5, 6, "r g b ?a?",},
2120    {"axisname",   5, GridAxisNameOp,   5, 5, "index title units",},
2121    {"linecolor",  7, GridLineColorOp,  5, 6, "r g b ?a?",},
2122    {"visible",    1, GridVisibleOp,    3, 3, "bool",},
2123};
2124static int nGridOps = NumCmdSpecs(gridOps);
2125
2126static int
2127GridCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2128        Tcl_Obj *const *objv)
2129{
2130    Tcl_ObjCmdProc *proc;
2131
2132    proc = Rappture::GetOpFromObj(interp, nGridOps, gridOps,
2133                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
2134    if (proc == NULL) {
2135        return TCL_ERROR;
2136    }
2137    return (*proc) (clientData, interp, objc, objv);
2138}
2139
2140static int
2141AxisCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2142        Tcl_Obj *const *objv)
2143{
2144    if (objc < 2) {
2145        Tcl_AppendResult(interp, "wrong # args: should be \"",
2146                Tcl_GetString(objv[0]), " option arg arg...\"", (char*)NULL);
2147        return TCL_ERROR;
2148    }
2149    const char *string = Tcl_GetString(objv[1]);
2150    char c = string[0];
2151    if ((c == 'v') && (strcmp(string, "visible") == 0)) {
2152        bool visible;
2153
2154        if (GetBooleanFromObj(interp, objv[2], &visible) != TCL_OK) {
2155            return TCL_ERROR;
2156        }
2157        NanoVis::orientationIndicator->setVisible(visible);
2158    } else {
2159        Tcl_AppendResult(interp, "bad axis option \"", string,
2160                         "\": should be visible", (char*)NULL);
2161        return TCL_ERROR;
2162    }
2163    return TCL_OK;
2164}
2165
2166static int
2167ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2168              Tcl_Obj *const *objv)
2169{
2170    lastCmdStatus = TCL_BREAK;
2171    return TCL_OK;
2172}
2173
2174/**
2175 * \brief Execute commands from client in Tcl interpreter
2176 *
2177 * In this threaded model, the select call is for event compression.  We
2178 * want to execute render server commands as long as they keep coming. 
2179 * This lets us execute a stream of many commands but render once.  This
2180 * benefits camera movements, screen resizing, and opacity changes
2181 * (using a slider on the client).  The down side is you don't render
2182 * until there's a lull in the command stream.  If the client needs an
2183 * image, it can issue an "imgflush" command.  That breaks us out of the
2184 * read loop.
2185 */
2186int
2187nv::processCommands(Tcl_Interp *interp,
2188                    ReadBuffer *inBufPtr, int fdOut)
2189{
2190    int ret = 1;
2191    int status = TCL_OK;
2192
2193    Tcl_DString command;
2194    Tcl_DStringInit(&command);
2195    fd_set readFds;
2196    struct timeval tv, *tvPtr;
2197
2198    FD_ZERO(&readFds);
2199    FD_SET(inBufPtr->file(), &readFds);
2200    tvPtr = NULL;                       /* Wait for the first read. This is so
2201                                         * that we don't spin when no data is
2202                                         * available. */
2203    while (inBufPtr->isLineAvailable() ||
2204           (select(1, &readFds, NULL, NULL, tvPtr) > 0)) {
2205        size_t numBytes;
2206        unsigned char *buffer;
2207
2208        /* A short read is treated as an error here because we assume that we
2209         * will always get commands line by line. */
2210        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2211            /* Terminate the server if we can't communicate with the client
2212             * anymore. */
2213            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2214                TRACE("Exiting server on EOF from client");
2215                return -1;
2216            } else {
2217                ERROR("Exiting server, failed to read from client: %s",
2218                      strerror(errno));
2219                return -1;
2220            }
2221        }
2222        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
2223        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
2224            struct timeval start, finish;
2225            gettimeofday(&start, NULL);
2226            status = ExecuteCommand(interp, &command);
2227            gettimeofday(&finish, NULL);
2228            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
2229            g_stats.nCommands++;
2230            if (status == TCL_BREAK) {
2231                return 1;               /* This was caused by a "imgflush"
2232                                         * command. Break out of the read loop
2233                                         * and allow a new image to be
2234                                         * rendered. */
2235            } else { //if (status != TCL_OK) {
2236                ret = 0;
2237                if (handleError(interp, status, fdOut) < 0) {
2238                    return -1;
2239                }
2240            }
2241        }
2242
2243        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
2244                                         * if no data is available. */
2245        FD_SET(inBufPtr->file(), &readFds);
2246        tvPtr = &tv;
2247    }
2248
2249    return ret;
2250}
2251
2252/**
2253 * \brief Send error message to client socket
2254 */
2255int
2256nv::handleError(Tcl_Interp *interp, int status, int fdOut)
2257{
2258    const char *string;
2259    int nBytes;
2260
2261    if (status != TCL_OK) {
2262        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
2263        nBytes = strlen(string);
2264        if (nBytes > 0) {
2265            TRACE("status=%d errorInfo=(%s)", status, string);
2266
2267            std::ostringstream oss;
2268            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2269            nBytes = oss.str().length();
2270
2271#ifdef USE_THREADS
2272            queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR);
2273#else
2274            if (write(fdOut, oss.str().c_str(), nBytes) < 0) {
2275                ERROR("write failed: %s", strerror(errno));
2276                return -1;
2277            }
2278#endif
2279        }
2280    }
2281
2282    string = getUserMessages();
2283    nBytes = strlen(string);
2284    if (nBytes > 0) {
2285        TRACE("userError=(%s)", string);
2286
2287        std::ostringstream oss;
2288        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2289        nBytes = oss.str().length();
2290
2291#ifdef USE_THREADS
2292        queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR);
2293#else
2294        if (write(fdOut, oss.str().c_str(), nBytes) < 0) {
2295            ERROR("write failed: %s", strerror(errno));
2296            return -1;
2297        }
2298#endif
2299        clearUserMessages();
2300    }
2301
2302    return 0;
2303}
2304
2305void
2306nv::initTcl(Tcl_Interp *interp, ClientData clientData)
2307{
2308    /*
2309     * Ideally the connection is authenticated by nanoscale.  I still like the
2310     * idea of creating a non-safe master interpreter with a safe slave
2311     * interpreter.  Alias all the nanovis commands in the slave. That way we
2312     * can still run Tcl code within nanovis.  The eventual goal is to create
2313     * a test harness through the interpreter for nanovis.
2314     */
2315
2316    Tcl_MakeSafe(interp);
2317
2318    Tcl_CreateObjCommand(interp, "axis",        AxisCmd,        clientData, NULL);
2319    Tcl_CreateObjCommand(interp, "camera",      CameraCmd,      clientData, NULL);
2320    Tcl_CreateObjCommand(interp, "clientinfo",  ClientInfoCmd,  clientData, NULL);
2321    Tcl_CreateObjCommand(interp, "cutplane",    CutplaneCmd,    clientData, NULL);
2322    FlowCmdInitProc(interp, clientData);
2323    Tcl_CreateObjCommand(interp, "grid",        GridCmd,        clientData, NULL);
2324    Tcl_CreateObjCommand(interp, "heightmap",   HeightMapCmd,   clientData, NULL);
2325    Tcl_CreateObjCommand(interp, "imgflush",    ImageFlushCmd,  clientData, NULL);
2326    Tcl_CreateObjCommand(interp, "legend",      LegendCmd,      clientData, NULL);
2327    Tcl_CreateObjCommand(interp, "screen",      ScreenCmd,      clientData, NULL);
2328    Tcl_CreateObjCommand(interp, "snapshot",    SnapshotCmd,    clientData, NULL);
2329    Tcl_CreateObjCommand(interp, "transfunc",   TransfuncCmd,   clientData, NULL);
2330    Tcl_CreateObjCommand(interp, "up",          UpCmd,          clientData, NULL);
2331    Tcl_CreateObjCommand(interp, "volume",      VolumeCmd,      clientData, NULL);
2332
2333    // create a default transfer function
2334    if (Tcl_Eval(interp, def_transfunc) != TCL_OK) {
2335        WARN("bad default transfer function:\n%s",
2336             Tcl_GetStringResult(interp));
2337    }
2338}
Note: See TracBrowser for help on using the repository browser.