source: nanovis/branches/1.1/Command.cpp @ 4923

Last change on this file since 4923 was 4904, checked in by ldelgass, 5 years ago

Merge serveral changes from trunk. Does not include threading, world space
changes, etc.

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