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

Last change on this file since 4063 was 4063, checked in by ldelgass, 6 years ago

Remove some more Rappture deps from nanovis

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