source: nanovis/branches/1.2/Command.cpp @ 6619

Last change on this file since 6619 was 6619, checked in by ldelgass, 3 years ago

merge r6576:6577 from nanovis trunk (timeout option)

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