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

Last change on this file since 5699 was 5698, checked in by ldelgass, 9 years ago

Merge camera changes from trunk

  • Property svn:eol-style set to native
File size: 76.9 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    } else {
908        objPtr = Tcl_NewStringObj("render_info", 11);
909        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
910    }
911    Tcl_DStringInit(&ds);
912    /* date */
913    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
914    strcpy(buf, ctime(&g_stats.start.tv_sec));
915    buf[strlen(buf) - 1] = '\0';
916    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
917    /* date_secs */
918    Tcl_ListObjAppendElement(interp, listObjPtr,
919                             Tcl_NewStringObj("date_secs", 9));
920    Tcl_ListObjAppendElement(interp, listObjPtr,
921                             Tcl_NewLongObj(g_stats.start.tv_sec));
922    /* Client arguments. */
923    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK) {
924        return TCL_ERROR;
925    }
926    for (i = 0; i < numItems; i++) {
927        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
928    }
929    Tcl_DStringInit(&ds);
930    string = Tcl_GetStringFromObj(listObjPtr, &length);
931    Tcl_DStringAppend(&ds, string, length);
932    Tcl_DStringAppend(&ds, "\n", 1);
933#ifdef KEEPSTATS
934    result = writeToStatsFile(f, Tcl_DStringValue(&ds),
935                              Tcl_DStringLength(&ds));
936#else
937    TRACE("clientinfo: %s", Tcl_DStringValue(&ds));
938#endif
939    Tcl_DStringFree(&ds);
940    Tcl_DecrRefCount(listObjPtr);
941    return result;
942}
943
944/*
945 * ----------------------------------------------------------------------
946 * CLIENT COMMAND:
947 *   legend <volumeIndex> <width> <height>
948 *
949 * Clients use this to generate a legend image for the specified
950 * transfer function.  The legend image is a color gradient from 0
951 * to one, drawn in the given transfer function.  The resulting image
952 * is returned in the size <width> x <height>.
953 * ----------------------------------------------------------------------
954 */
955static int
956LegendCmd(ClientData clientData, Tcl_Interp *interp, int objc,
957          Tcl_Obj *const *objv)
958{
959    if (objc != 4) {
960        Tcl_AppendResult(interp, "wrong # args: should be \"",
961            Tcl_GetString(objv[0]), " transfunc width height\"", (char*)NULL);
962        return TCL_ERROR;
963    }
964
965    const char *tfName = Tcl_GetString(objv[1]);
966    TransferFunction *tf = NanoVis::getTransferFunction(tfName);
967    if (tf == NULL) {
968        Tcl_AppendResult(interp, "unknown transfer function \"", tfName, "\"",
969                             (char*)NULL);
970        return TCL_ERROR;
971    }
972    int w, h;
973    if ((Tcl_GetIntFromObj(interp, objv[2], &w) != TCL_OK) ||
974        (Tcl_GetIntFromObj(interp, objv[3], &h) != TCL_OK)) {
975        return TCL_ERROR;
976    }
977    if (Volume::updatePending) {
978        NanoVis::setVolumeRanges();
979    }
980    NanoVis::renderLegend(tf, Volume::valueMin, Volume::valueMax, w, h, tfName);
981    return TCL_OK;
982}
983
984static int
985ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
986                Tcl_Obj *const *objv)
987{
988    float rgb[3];
989    if ((GetFloatFromObj(interp, objv[2], &rgb[0]) != TCL_OK) ||
990        (GetFloatFromObj(interp, objv[3], &rgb[1]) != TCL_OK) ||
991        (GetFloatFromObj(interp, objv[4], &rgb[2]) != TCL_OK)) {
992        return TCL_ERROR;
993    }
994    NanoVis::setBgColor(rgb);
995    return TCL_OK;
996}
997
998/*
999 * ----------------------------------------------------------------------
1000 * CLIENT COMMAND:
1001 *   screen size <width> <height>
1002 *
1003 * Clients send this command to set the size of the rendering area.
1004 * Future images are generated at the specified width/height.
1005 * ----------------------------------------------------------------------
1006 */
1007static int
1008ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1009             Tcl_Obj *const *objv)
1010{
1011    int w, h;
1012    if ((Tcl_GetIntFromObj(interp, objv[2], &w) != TCL_OK) ||
1013        (Tcl_GetIntFromObj(interp, objv[3], &h) != TCL_OK)) {
1014        return TCL_ERROR;
1015    }
1016    NanoVis::resizeOffscreenBuffer(w, h);
1017    return TCL_OK;
1018}
1019
1020static CmdSpec screenOps[] = {
1021    {"bgcolor",  1, ScreenBgColorOp,  5, 5, "r g b",},
1022    {"size",     1, ScreenSizeOp, 4, 4, "width height",},
1023};
1024static int nScreenOps = NumCmdSpecs(screenOps);
1025
1026static int
1027ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1028          Tcl_Obj *const *objv)
1029{
1030    Tcl_ObjCmdProc *proc;
1031
1032    proc = GetOpFromObj(interp, nScreenOps, screenOps,
1033                        CMDSPEC_ARG1, objc, objv, 0);
1034    if (proc == NULL) {
1035        return TCL_ERROR;
1036    }
1037    return (*proc) (clientData, interp, objc, objv);
1038}
1039
1040/*
1041 * ----------------------------------------------------------------------
1042 * CLIENT COMMAND:
1043 *   transfunc define <name> <colormap> <alphamap>
1044 *     where <colormap> = { <v> <r> <g> <b> ... }
1045 *           <alphamap> = { <v> <w> ... }
1046 *
1047 * Clients send these commands to manipulate the transfer functions.
1048 * ----------------------------------------------------------------------
1049 */
1050static int
1051TransfuncCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1052             Tcl_Obj *const *objv)
1053{
1054    if (objc < 2) {
1055        Tcl_AppendResult(interp, "wrong # args: should be \"",
1056                Tcl_GetString(objv[0]), " option arg arg...\"", (char*)NULL);
1057        return TCL_ERROR;
1058    }
1059
1060    const char *string = Tcl_GetString(objv[1]);
1061    char c = string[0];
1062    if ((c == 'd') && (strcmp(string, "define") == 0)) {
1063        if (objc != 5) {
1064            Tcl_AppendResult(interp, "wrong # args: should be \"",
1065                Tcl_GetString(objv[0]), " define name colorMap alphaMap\"",
1066                (char*)NULL);
1067            return TCL_ERROR;
1068        }
1069
1070        // decode the data and store in a series of fields
1071        int cmapc, amapc, i;
1072        Tcl_Obj **cmapv;
1073        Tcl_Obj **amapv;
1074
1075        amapv = cmapv = NULL;
1076        if (Tcl_ListObjGetElements(interp, objv[3], &cmapc, &cmapv) != TCL_OK) {
1077            return TCL_ERROR;
1078        }
1079        if ((cmapc % 4) != 0) {
1080            Tcl_AppendResult(interp, "wrong # elements is colormap: should be ",
1081                "{ v r g b ... }", (char*)NULL);
1082            return TCL_ERROR;
1083        }
1084        if (Tcl_ListObjGetElements(interp, objv[4], &amapc, &amapv) != TCL_OK) {
1085            return TCL_ERROR;
1086        }
1087        if ((amapc % 2) != 0) {
1088            Tcl_AppendResult(interp, "wrong # elements in alphamap: should be ",
1089                " { v w ... }", (char*)NULL);
1090            return TCL_ERROR;
1091        }
1092
1093        int numColors = cmapc/4;
1094        float *colorKeys = new float[numColors];
1095        Vector3f *colors = new Vector3f[numColors];
1096        for (i = 0; i < cmapc; i += 4) {
1097            int j;
1098            double q[4];
1099
1100            for (j=0; j < 4; j++) {
1101                if (Tcl_GetDoubleFromObj(interp, cmapv[i+j], &q[j]) != TCL_OK) {
1102                    return TCL_ERROR;
1103                }
1104                if ((q[j] < 0.0) || (q[j] > 1.0)) {
1105                    Tcl_AppendResult(interp, "bad colormap value \"",
1106                        Tcl_GetString(cmapv[i+j]),
1107                        "\": should be in the range 0-1", (char*)NULL);
1108                    return TCL_ERROR;
1109                }
1110            }
1111
1112            colorKeys[i/4] = (float)q[0];
1113            colors[i/4].set((float)q[1], (float)q[2], (float)q[3]);
1114        }
1115        int numAlphas = amapc/2;
1116        float *alphaKeys = new float[numAlphas];
1117        float *alphas = new float[numAlphas];
1118        for (i=0; i < amapc; i += 2) {
1119            double q[2];
1120            int j;
1121
1122            for (j=0; j < 2; j++) {
1123                if (Tcl_GetDoubleFromObj(interp, amapv[i+j], &q[j]) != TCL_OK) {
1124                    return TCL_ERROR;
1125                }
1126                if ((q[j] < 0.0) || (q[j] > 1.0)) {
1127                    Tcl_AppendResult(interp, "bad alphamap value \"",
1128                        Tcl_GetString(amapv[i+j]),
1129                        "\": should be in the range 0-1", (char*)NULL);
1130                    return TCL_ERROR;
1131                }
1132            }
1133
1134            alphaKeys[i/2] = (float)q[0];
1135            alphas[i/2] = (float)q[1];
1136        }
1137        // sample the given function into discrete slots
1138        const int nslots = 256;
1139        float data[4*nslots];
1140        for (i=0; i < nslots; i++) {
1141            float x = float(i)/(nslots-1);
1142            Vector3f color;
1143            float alpha;
1144            TransferFunction::sample(x, colorKeys, colors, numColors, &color);
1145            TransferFunction::sample(x, alphaKeys, alphas, numAlphas, &alpha);
1146
1147            data[4*i]   = color.r;
1148            data[4*i+1] = color.g;
1149            data[4*i+2] = color.b;
1150            data[4*i+3] = alpha;
1151        }
1152        delete [] colorKeys;
1153        delete [] colors;
1154        delete [] alphaKeys;
1155        delete [] alphas;
1156        // find or create this transfer function
1157        NanoVis::defineTransferFunction(Tcl_GetString(objv[2]), nslots, data);
1158    } else {
1159        Tcl_AppendResult(interp, "bad option \"", string,
1160                "\": should be define", (char*)NULL);
1161        return TCL_ERROR;
1162    }
1163    return TCL_OK;
1164}
1165
1166/*
1167 * ----------------------------------------------------------------------
1168 * CLIENT COMMAND:
1169 *   up axis
1170 *
1171 * Clients use this to set the "up" direction for all volumes.  Volumes
1172 * are oriented such that this direction points upward.
1173 * ----------------------------------------------------------------------
1174 */
1175static int
1176UpCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
1177{
1178    if (objc != 2) {
1179        Tcl_AppendResult(interp, "wrong # args: should be \"",
1180                         Tcl_GetString(objv[0]), " x|y|z|-x|-y|-z\"", (char*)NULL);
1181        return TCL_ERROR;
1182    }
1183
1184    int sign;
1185    int axis;
1186    if (GetAxisDirFromObj(interp, objv[1], &axis, &sign) != TCL_OK) {
1187        return TCL_ERROR;
1188    }
1189    NanoVis::setCameraUpdir(Camera::AxisDirection((axis+1)*sign));
1190    return TCL_OK;
1191}
1192
1193static int
1194VolumeAnimationCaptureOp(ClientData clientData, Tcl_Interp *interp, int objc,
1195                         Tcl_Obj *const *objv)
1196{
1197    int total;
1198    if (Tcl_GetIntFromObj(interp, objv[3], &total) != TCL_OK) {
1199        return TCL_ERROR;
1200    }
1201    VolumeInterpolator *interpolator =
1202        NanoVis::volRenderer->getVolumeInterpolator();
1203    interpolator->start();
1204    if (interpolator->isStarted()) {
1205        const char *dirName = (objc < 5) ? NULL : Tcl_GetString(objv[4]);
1206        for (int frameNum = 0; frameNum < total; ++frameNum) {
1207            float fraction;
1208
1209            fraction = ((float)frameNum) / (total - 1);
1210            TRACE("fraction : %f", fraction);
1211            interpolator->update(fraction);
1212
1213            int fboOrig;
1214            glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
1215
1216            NanoVis::bindOffscreenBuffer();  //enable offscreen render
1217
1218            NanoVis::render();
1219            NanoVis::readScreen();
1220
1221            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
1222
1223            /* FIXME: this function requires 4-byte aligned RGB rows,
1224             * but screen buffer is now 1 byte aligned for PPM images.
1225             */
1226            writeBMPFile(frameNum, dirName,
1227                         NanoVis::screenBuffer,
1228                         NanoVis::winWidth, NanoVis::winHeight);
1229        }
1230    }
1231    return TCL_OK;
1232}
1233
1234static int
1235VolumeAnimationClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
1236                       Tcl_Obj *const *objv)
1237{
1238    NanoVis::volRenderer->clearAnimatedVolumeInfo();
1239    return TCL_OK;
1240}
1241
1242static int
1243VolumeAnimationStartOp(ClientData clientData, Tcl_Interp *interp, int objc,
1244                       Tcl_Obj *const *objv)
1245{
1246    NanoVis::volRenderer->startVolumeAnimation();
1247    return TCL_OK;
1248}
1249
1250static int
1251VolumeAnimationStopOp(ClientData clientData, Tcl_Interp *interp, int objc,
1252                      Tcl_Obj *const *objv)
1253{
1254    NanoVis::volRenderer->stopVolumeAnimation();
1255    return TCL_OK;
1256}
1257
1258static int
1259VolumeAnimationVolumesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1260                         Tcl_Obj *const *objv)
1261{
1262    std::vector<Volume *> volumes;
1263    if (GetVolumes(interp, objc - 3, objv + 3, &volumes) != TCL_OK) {
1264        return TCL_ERROR;
1265    }
1266    TRACE("parsing volume data identifier");
1267    NanoVis::VolumeHashmap::iterator itr;
1268    for (itr = NanoVis::volumeTable.begin();
1269         itr != NanoVis::volumeTable.end(); ++itr) {
1270        NanoVis::volRenderer->addAnimatedVolume(itr->second);
1271    }
1272    return TCL_OK;
1273}
1274
1275static CmdSpec volumeAnimationOps[] = {
1276    {"capture",   2, VolumeAnimationCaptureOp,  4, 5, "numframes ?filename?",},
1277    {"clear",     2, VolumeAnimationClearOp,    3, 3, "",},
1278    {"start",     3, VolumeAnimationStartOp,    3, 3, "",},
1279    {"stop",      3, VolumeAnimationStopOp,     3, 3, "",},
1280    {"volumes",   1, VolumeAnimationVolumesOp,  3, 0, "?indices?",},
1281};
1282
1283static int nVolumeAnimationOps = NumCmdSpecs(volumeAnimationOps);
1284
1285static int
1286VolumeAnimationOp(ClientData clientData, Tcl_Interp *interp, int objc,
1287                  Tcl_Obj *const *objv)
1288{
1289    Tcl_ObjCmdProc *proc;
1290
1291    proc = GetOpFromObj(interp, nVolumeAnimationOps,
1292                        volumeAnimationOps, CMDSPEC_ARG2, objc, objv, 0);
1293    if (proc == NULL) {
1294        return TCL_ERROR;
1295    }
1296    return (*proc) (clientData, interp, objc, objv);
1297}
1298
1299static int
1300VolumeDataFollowsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1301                    Tcl_Obj *const *objv)
1302{
1303    TRACE("Data Loading");
1304
1305    int nbytes;
1306    if (Tcl_GetIntFromObj(interp, objv[3], &nbytes) != TCL_OK) {
1307        return TCL_ERROR;
1308    }
1309    const char *tag = Tcl_GetString(objv[4]);
1310
1311    if (nbytes <= 0) {
1312        Tcl_AppendResult(interp, "bad # bytes request \"",
1313                         Tcl_GetString(objv[3]), "\" for \"data follows\"", (char *)NULL);
1314        ERROR("Bad nbytes %d", nbytes);
1315        return TCL_ERROR;
1316    }
1317
1318    Rappture::Buffer buf(nbytes);
1319    if (GetDataStream(interp, buf, nbytes) != TCL_OK) {
1320        return TCL_ERROR;
1321    }
1322    const char *bytes = buf.bytes();
1323    size_t nBytes = buf.size();
1324
1325    TRACE("Checking header [%.20s]", bytes);
1326
1327    Volume *volume = NULL;
1328
1329    if ((nBytes > 5) && (strncmp(bytes, "<HDR>", 5) == 0)) {
1330        TRACE("ZincBlende Stream loading...");
1331        volume = ZincBlendeReconstructor::getInstance()->loadFromMemory(bytes);
1332        if (volume == NULL) {
1333            Tcl_AppendResult(interp, "can't get volume instance", (char *)NULL);
1334            return TCL_ERROR;
1335        }
1336        TRACE("finish loading");
1337
1338        Vector3f scale = volume->getPhysicalScaling();
1339        Vector3f pos(scale);
1340        pos *= -0.5;
1341        volume->setPosition(pos);
1342
1343        NanoVis::VolumeHashmap::iterator itr = NanoVis::volumeTable.find(tag);
1344        if (itr != NanoVis::volumeTable.end()) {
1345            Tcl_AppendResult(interp, "volume \"", tag, "\" already exists.",
1346                             (char *)NULL);
1347            return TCL_ERROR;
1348        }
1349        NanoVis::volumeTable[tag] = volume;
1350        volume->name(tag);
1351    } else if ((nBytes > 14) && (strncmp(bytes, "# vtk DataFile", 14) == 0)) {
1352        TRACE("VTK loading...");
1353#ifdef USE_VTK
1354        volume = load_vtk_volume_stream(tag, bytes, nBytes);
1355#else
1356        std::stringstream fdata;
1357        fdata.write(bytes, nBytes);
1358        volume = load_vtk_volume_stream(tag, fdata);
1359#endif
1360        if (volume == NULL) {
1361            Tcl_AppendResult(interp, "Failed to load VTK file", (char*)NULL);
1362            return TCL_ERROR;
1363        }
1364    } else {
1365#ifdef USE_DX_READER
1366        // **Deprecated** OpenDX format
1367        if ((nBytes > 5) && (strncmp(bytes, "<ODX>", 5) == 0)) {
1368            bytes += 5;
1369            nBytes -= 5;
1370        } else if ((nBytes > 4) && (strncmp(bytes, "<DX>", 4) == 0)) {
1371            bytes += 4;
1372            nBytes -= 4;
1373        }
1374        TRACE("DX loading...");
1375        std::stringstream fdata;
1376        fdata.write(bytes, nBytes);
1377        volume = load_dx_volume_stream(tag, fdata);
1378        if (volume == NULL) {
1379            Tcl_AppendResult(interp, "Failed to load DX file", (char*)NULL);
1380            return TCL_ERROR;
1381        }
1382#else
1383        Tcl_AppendResult(interp, "Loading DX files is not supported by this server", (char*)NULL);
1384        return TCL_ERROR;
1385#endif
1386    }
1387
1388    if (volume != NULL) {
1389        volume->disableCutplane(0);
1390        volume->disableCutplane(1);
1391        volume->disableCutplane(2);
1392        volume->transferFunction(NanoVis::getTransferFunction("default"));
1393        volume->visible(true);
1394
1395        if (Volume::updatePending) {
1396            NanoVis::setVolumeRanges();
1397        }
1398
1399        char info[1024];
1400        int cmdLength =
1401            sprintf(info, "nv>data tag %s min %g max %g vmin %g vmax %g\n", tag,
1402                    volume->wAxis.min(), volume->wAxis.max(),
1403                    Volume::valueMin, Volume::valueMax);
1404#ifdef USE_THREADS
1405        queueResponse(info, cmdLength, Response::VOLATILE);
1406#else
1407        if (SocketWrite(info, (size_t)cmdLength) != (ssize_t)cmdLength) {
1408            ERROR("Short write");
1409            return TCL_ERROR;
1410        }
1411#endif
1412    }
1413
1414    return TCL_OK;
1415}
1416
1417static int
1418VolumeDataStateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1419                  Tcl_Obj *const *objv)
1420{
1421    bool state;
1422    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1423        return TCL_ERROR;
1424    }
1425    std::vector<Volume *> ivol;
1426    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1427        return TCL_ERROR;
1428    }
1429    std::vector<Volume *>::iterator iter;
1430    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1431        (*iter)->dataEnabled(state);
1432    }
1433    return TCL_OK;
1434}
1435
1436static CmdSpec volumeDataOps[] = {
1437    {"follows",   1, VolumeDataFollowsOp, 5, 5, "nbytes tag",},
1438    {"state",     1, VolumeDataStateOp,   4, 0, "bool ?indices?",},
1439};
1440static int nVolumeDataOps = NumCmdSpecs(volumeDataOps);
1441
1442static int
1443VolumeDataOp(ClientData clientData, Tcl_Interp *interp, int objc,
1444             Tcl_Obj *const *objv)
1445{
1446    Tcl_ObjCmdProc *proc;
1447
1448    proc = GetOpFromObj(interp, nVolumeDataOps, volumeDataOps,
1449                        CMDSPEC_ARG2, objc, objv, 0);
1450    if (proc == NULL) {
1451        return TCL_ERROR;
1452    }
1453    return (*proc) (clientData, interp, objc, objv);
1454}
1455
1456static int
1457VolumeDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1458               Tcl_Obj *const *objv)
1459{
1460    for (int i = 2; i < objc; i++) {
1461        Volume *volume;
1462        if (GetVolumeFromObj(interp, objv[i], &volume) != TCL_OK) {
1463            return TCL_ERROR;
1464        }
1465        NanoVis::removeVolume(volume);
1466    }
1467    NanoVis::eventuallyRedraw();
1468    return TCL_OK;
1469}
1470
1471static int
1472VolumeExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1473               Tcl_Obj *const *objv)
1474{
1475    bool value;
1476    Volume *volume;
1477
1478    value = false;
1479    if (GetVolumeFromObj(NULL, objv[2], &volume) == TCL_OK) {
1480        value = true;
1481    }
1482    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), (int)value);
1483    return TCL_OK;
1484}
1485
1486static int
1487VolumeNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1488              Tcl_Obj *const *objv)
1489{
1490    Tcl_Obj *listObjPtr;
1491    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
1492    NanoVis::VolumeHashmap::iterator itr;
1493    for (itr = NanoVis::volumeTable.begin();
1494         itr != NanoVis::volumeTable.end(); ++itr) {
1495        Tcl_Obj *objPtr = Tcl_NewStringObj(itr->second->name(), -1);
1496        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1497    }
1498    Tcl_SetObjResult(interp, listObjPtr);
1499    return TCL_OK;
1500}
1501
1502static int
1503VolumeOutlineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1504                     Tcl_Obj *const *objv)
1505{
1506    float rgb[3];
1507    if (GetColor(interp, objc - 3, objv + 3, rgb) != TCL_OK) {
1508        return TCL_ERROR;
1509    }
1510    std::vector<Volume *> ivol;
1511    if (GetVolumes(interp, objc - 6, objv + 6, &ivol) != TCL_OK) {
1512        return TCL_ERROR;
1513    }
1514    std::vector<Volume *>::iterator iter;
1515    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1516        (*iter)->setOutlineColor(rgb);
1517    }
1518    return TCL_OK;
1519}
1520
1521static int
1522VolumeOutlineStateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1523                     Tcl_Obj *const *objv)
1524{
1525    bool state;
1526    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1527        return TCL_ERROR;
1528    }
1529    std::vector<Volume *> ivol;
1530    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1531        return TCL_ERROR;
1532    }
1533    std::vector<Volume *>::iterator iter;
1534    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1535        (*iter)->outline(state);
1536    }
1537    return TCL_OK;
1538}
1539
1540static CmdSpec volumeOutlineOps[] = {
1541    {"color",     1, VolumeOutlineColorOp,  6, 0, "r g b ?indices?",},
1542    {"state",     1, VolumeOutlineStateOp,  4, 0, "bool ?indices?",},
1543    {"visible",   1, VolumeOutlineStateOp,  4, 0, "bool ?indices?",},
1544};
1545static int nVolumeOutlineOps = NumCmdSpecs(volumeOutlineOps);
1546
1547static int
1548VolumeOutlineOp(ClientData clientData, Tcl_Interp *interp, int objc,
1549                Tcl_Obj *const *objv)
1550{
1551    Tcl_ObjCmdProc *proc;
1552
1553    proc = GetOpFromObj(interp, nVolumeOutlineOps, volumeOutlineOps,
1554                        CMDSPEC_ARG2, objc, objv, 0);
1555    if (proc == NULL) {
1556        return TCL_ERROR;
1557    }
1558    return (*proc) (clientData, interp, objc, objv);
1559}
1560
1561static int
1562VolumeShadingAmbientOp(ClientData clientData, Tcl_Interp *interp, int objc,
1563                       Tcl_Obj *const *objv)
1564{
1565    float ambient;
1566    if (GetFloatFromObj(interp, objv[3], &ambient) != TCL_OK) {
1567        return TCL_ERROR;
1568    }
1569    if (ambient < 0.0f || ambient > 1.0f) {
1570        WARN("Invalid ambient coefficient requested: %g, should be [0,1]", ambient);
1571    }
1572    std::vector<Volume *> ivol;
1573    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1574        return TCL_ERROR;
1575    }
1576    std::vector<Volume *>::iterator iter;
1577    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1578        (*iter)->ambient(ambient);
1579    }
1580    return TCL_OK;
1581}
1582
1583static int
1584VolumeShadingDiffuseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1585                       Tcl_Obj *const *objv)
1586{
1587    float diffuse;
1588    if (GetFloatFromObj(interp, objv[3], &diffuse) != TCL_OK) {
1589        return TCL_ERROR;
1590    }
1591    if (diffuse < 0.0f || diffuse > 1.0f) {
1592        WARN("Invalid diffuse coefficient requested: %g, should be [0,1]", diffuse);
1593    }
1594    std::vector<Volume *> ivol;
1595    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1596        return TCL_ERROR;
1597    }
1598    std::vector<Volume *>::iterator iter;
1599    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1600        (*iter)->diffuse(diffuse);
1601    }
1602    return TCL_OK;
1603}
1604
1605static int
1606VolumeShadingIsosurfaceOp(ClientData clientData, Tcl_Interp *interp, int objc,
1607                          Tcl_Obj *const *objv)
1608{
1609    bool iso_surface;
1610    if (GetBooleanFromObj(interp, objv[3], &iso_surface) != TCL_OK) {
1611        return TCL_ERROR;
1612    }
1613    std::vector<Volume *> ivol;
1614    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1615        return TCL_ERROR;
1616    }
1617    std::vector<Volume *>::iterator iter;
1618    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1619        (*iter)->isosurface(iso_surface);
1620    }
1621    return TCL_OK;
1622}
1623
1624static int
1625VolumeShadingLight2SideOp(ClientData clientData, Tcl_Interp *interp, int objc,
1626                          Tcl_Obj *const *objv)
1627{
1628    bool twoSidedLighting;
1629    if (GetBooleanFromObj(interp, objv[3], &twoSidedLighting) != TCL_OK) {
1630        return TCL_ERROR;
1631    }
1632    std::vector<Volume *> ivol;
1633    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1634        return TCL_ERROR;
1635    }
1636    std::vector<Volume *>::iterator iter;
1637    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1638        (*iter)->twoSidedLighting(twoSidedLighting);
1639    }
1640    return TCL_OK;
1641}
1642
1643static int
1644VolumeShadingOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
1645                       Tcl_Obj *const *objv)
1646{
1647
1648    float opacity;
1649    if (GetFloatFromObj(interp, objv[3], &opacity) != TCL_OK) {
1650        return TCL_ERROR;
1651    }
1652    TRACE("set opacity %f", opacity);
1653    std::vector<Volume *> ivol;
1654    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1655        return TCL_ERROR;
1656    }
1657    std::vector<Volume *>::iterator iter;
1658    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1659        (*iter)->opacityScale(opacity);
1660    }
1661    return TCL_OK;
1662}
1663
1664static int
1665VolumeShadingSpecularOp(ClientData clientData, Tcl_Interp *interp, int objc,
1666                        Tcl_Obj *const *objv)
1667{
1668    float specular;
1669    if (GetFloatFromObj(interp, objv[3], &specular) != TCL_OK) {
1670        return TCL_ERROR;
1671    }
1672    if (specular < 0.0f || specular > 1.0f) {
1673        WARN("Invalid specular coefficient requested: %g, should be [0,1]", specular);
1674    }
1675    std::vector<Volume *> ivol;
1676    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1677        return TCL_ERROR;
1678    }
1679    std::vector<Volume *>::iterator iter;
1680    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1681        (*iter)->specularLevel(specular);
1682    }
1683    return TCL_OK;
1684}
1685
1686static int
1687VolumeShadingSpecularExpOp(ClientData clientData, Tcl_Interp *interp, int objc,
1688                           Tcl_Obj *const *objv)
1689{
1690    float specularExp;
1691    if (GetFloatFromObj(interp, objv[3], &specularExp) != TCL_OK) {
1692        return TCL_ERROR;
1693    }
1694    if (specularExp < 0.0f || specularExp > 128.0f) {
1695        WARN("Invalid specular exponent requested: %g, should be [0,128]", specularExp);
1696    }
1697    std::vector<Volume *> ivol;
1698    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1699        return TCL_ERROR;
1700    }
1701    std::vector<Volume *>::iterator iter;
1702    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1703        (*iter)->specularExponent(specularExp);
1704    }
1705    return TCL_OK;
1706}
1707
1708static int
1709VolumeShadingTransFuncOp(ClientData clientData, Tcl_Interp *interp, int objc,
1710                         Tcl_Obj *const *objv)
1711{
1712    const char *name = Tcl_GetString(objv[3]);
1713    TransferFunction *tf = NanoVis::getTransferFunction(name);
1714    if (tf == NULL) {
1715        Tcl_AppendResult(interp, "transfer function \"", name,
1716                         "\" is not defined", (char*)NULL);
1717        return TCL_ERROR;
1718    }
1719    std::vector<Volume *> ivol;
1720    if (GetVolumes(interp, objc - 4, objv + 4, &ivol) != TCL_OK) {
1721        return TCL_ERROR;
1722    }
1723    std::vector<Volume *>::iterator iter;
1724    for (iter = ivol.begin(); iter != ivol.end(); ++iter) {
1725        TRACE("setting %s with transfer function %s", (*iter)->name(),
1726               tf->name());
1727        (*iter)->transferFunction(tf);
1728    }
1729    return TCL_OK;
1730}
1731
1732static CmdSpec volumeShadingOps[] = {
1733    {"ambient",       1, VolumeShadingAmbientOp,     4, 0, "value ?indices?",},
1734    {"diffuse",       1, VolumeShadingDiffuseOp,     4, 0, "value ?indices?",},
1735    {"isosurface",    1, VolumeShadingIsosurfaceOp,  4, 0, "bool ?indices?",},
1736    {"light2side",    1, VolumeShadingLight2SideOp,  4, 0, "bool ?indices?",},
1737    {"opacity",       1, VolumeShadingOpacityOp,     4, 0, "value ?indices?",},
1738    {"specularExp",   9, VolumeShadingSpecularExpOp, 4, 0, "value ?indices?",},
1739    {"specularLevel", 9, VolumeShadingSpecularOp,    4, 0, "value ?indices?",},
1740    {"transfunc",     1, VolumeShadingTransFuncOp,   4, 0, "funcName ?indices?",},
1741};
1742static int nVolumeShadingOps = NumCmdSpecs(volumeShadingOps);
1743
1744static int
1745VolumeShadingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1746                Tcl_Obj *const *objv)
1747{
1748    Tcl_ObjCmdProc *proc;
1749
1750    proc = GetOpFromObj(interp, nVolumeShadingOps, volumeShadingOps,
1751                        CMDSPEC_ARG2, objc, objv, 0);
1752    if (proc == NULL) {
1753        return TCL_ERROR;
1754    }
1755    return (*proc) (clientData, interp, objc, objv);
1756}
1757
1758static int
1759VolumeStateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1760              Tcl_Obj *const *objv)
1761{
1762    bool state;
1763    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1764        return TCL_ERROR;
1765    }
1766    std::vector<Volume *> ivol;
1767    if (GetVolumes(interp, objc - 3, objv + 3, &ivol) != TCL_OK) {
1768        return TCL_ERROR;
1769    }
1770    std::vector<Volume *>::iterator iter;
1771    for (iter = ivol.begin(); iter != ivol.end(); iter++) {
1772        (*iter)->visible(state);
1773    }
1774    return TCL_OK;
1775}
1776
1777static CmdSpec volumeOps[] = {
1778    {"animation", 2, VolumeAnimationOp,   3, 0, "oper ?args?",},
1779    {"data",      2, VolumeDataOp,        3, 0, "oper ?args?",},
1780    {"delete",    2, VolumeDeleteOp,      3, 0, "?name...?",},
1781    {"exists",    1, VolumeExistsOp,      3, 3, "name",},
1782    {"names",     1, VolumeNamesOp,       2, 2, "",},
1783    {"outline",   1, VolumeOutlineOp,     3, 0, "oper ?args?",},
1784    {"shading",   2, VolumeShadingOp,     3, 0, "oper ?args?",},
1785    {"state",     2, VolumeStateOp,       3, 0, "bool ?indices?",},
1786};
1787static int nVolumeOps = NumCmdSpecs(volumeOps);
1788
1789static int
1790VolumeCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1791          Tcl_Obj *const *objv)
1792{
1793    Tcl_ObjCmdProc *proc;
1794
1795    proc = GetOpFromObj(interp, nVolumeOps, volumeOps,
1796                        CMDSPEC_ARG1, objc, objv, 0);
1797    if (proc == NULL) {
1798        return TCL_ERROR;
1799    }
1800    return (*proc) (clientData, interp, objc, objv);
1801}
1802
1803static int
1804HeightMapDataFollowsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1805                       Tcl_Obj *const *objv)
1806{
1807    int nBytes;
1808    if (Tcl_GetIntFromObj(interp, objv[3], &nBytes) != TCL_OK) {
1809        return TCL_ERROR;
1810    }
1811    const char *tag = Tcl_GetString(objv[4]);
1812
1813    Rappture::Buffer buf(nBytes);
1814    if (GetDataStream(interp, buf, nBytes) != TCL_OK) {
1815        return TCL_ERROR;
1816    }
1817    Unirect2d data(1);
1818    if (data.parseBuffer(interp, buf.bytes(), buf.size()) != TCL_OK) {
1819        return TCL_ERROR;
1820    }
1821    if (data.nValues() == 0) {
1822        Tcl_AppendResult(interp, "no data found in stream", (char *)NULL);
1823        return TCL_ERROR;
1824    }
1825    if (!data.isInitialized()) {
1826        return TCL_ERROR;
1827    }
1828    HeightMap *heightMap;
1829    NanoVis::HeightMapHashmap::iterator itr = NanoVis::heightMapTable.find(tag);
1830    if (itr != NanoVis::heightMapTable.end()) {
1831        heightMap = itr->second;
1832    } else {
1833        heightMap = new HeightMap();
1834        NanoVis::heightMapTable[tag] = heightMap;
1835    }
1836    TRACE("Number of heightmaps=%d", NanoVis::heightMapTable.size());
1837    // Must set units before the heights.
1838    heightMap->xAxis.units(data.xUnits());
1839    heightMap->yAxis.units(data.yUnits());
1840    heightMap->zAxis.units(data.vUnits());
1841    heightMap->wAxis.units(data.yUnits());
1842    heightMap->setHeight(data.xMin(), data.yMin(), data.xMax(), data.yMax(),
1843                         data.xNum(), data.yNum(), data.transferValues());
1844    heightMap->transferFunction(NanoVis::getTransferFunction("default"));
1845    heightMap->setVisible(true);
1846    heightMap->setLineContourVisible(true);
1847    NanoVis::eventuallyRedraw();
1848    return TCL_OK;
1849}
1850
1851static int
1852HeightMapDataVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1853                       Tcl_Obj *const *objv)
1854{
1855    bool visible;
1856    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1857        return TCL_ERROR;
1858    }
1859    std::vector<HeightMap *> imap;
1860    if (GetHeightMaps(interp, objc - 4, objv + 4, &imap) != TCL_OK) {
1861        return TCL_ERROR;
1862    }
1863    std::vector<HeightMap *>::iterator iter;
1864    for (iter = imap.begin(); iter != imap.end(); iter++) {
1865        (*iter)->setVisible(visible);
1866    }
1867    NanoVis::eventuallyRedraw();
1868    return TCL_OK;
1869}
1870
1871static CmdSpec heightMapDataOps[] = {
1872    {"follows",  1, HeightMapDataFollowsOp, 5, 5, "size heightmapName",},
1873    {"visible",  1, HeightMapDataVisibleOp, 4, 0, "bool ?heightmapNames...?",},
1874};
1875static int nHeightMapDataOps = NumCmdSpecs(heightMapDataOps);
1876
1877static int
1878HeightMapDataOp(ClientData clientData, Tcl_Interp *interp, int objc,
1879                Tcl_Obj *const *objv)
1880{
1881    Tcl_ObjCmdProc *proc;
1882
1883    proc = GetOpFromObj(interp, nHeightMapDataOps, heightMapDataOps,
1884                        CMDSPEC_ARG2, objc, objv, 0);
1885    if (proc == NULL) {
1886        return TCL_ERROR;
1887    }
1888    return (*proc) (clientData, interp, objc, objv);
1889}
1890
1891static int
1892HeightMapLineContourColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1893                            Tcl_Obj *const *objv)
1894{
1895    float rgb[3];
1896    if (GetColor(interp, objc - 3, objv + 3, rgb) != TCL_OK) {
1897        return TCL_ERROR;
1898    }
1899    std::vector<HeightMap *> imap;
1900    if (GetHeightMaps(interp, objc - 6, objv + 6, &imap) != TCL_OK) {
1901        return TCL_ERROR;
1902    }
1903    std::vector<HeightMap *>::iterator iter;
1904    for (iter = imap.begin(); iter != imap.end(); iter++) {
1905        (*iter)->setLineContourColor(rgb);
1906    }
1907    NanoVis::eventuallyRedraw();
1908    return TCL_OK;
1909}
1910
1911static int
1912HeightMapLineContourVisibleOp(ClientData clientData, Tcl_Interp *interp,
1913                              int objc, Tcl_Obj *const *objv)
1914{
1915    bool visible;
1916    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1917        return TCL_ERROR;
1918    }
1919    std::vector<HeightMap *> imap;
1920    if (GetHeightMaps(interp, objc - 4, objv + 4, &imap) != TCL_OK) {
1921        return TCL_ERROR;
1922    }
1923    std::vector<HeightMap *>::iterator iter;
1924    for (iter = imap.begin(); iter != imap.end(); iter++) {
1925        (*iter)->setLineContourVisible(visible);
1926    }
1927    NanoVis::eventuallyRedraw();
1928    return TCL_OK;
1929}
1930
1931static CmdSpec heightMapLineContourOps[] = {
1932    {"color",   1, HeightMapLineContourColorOp,   6, 0, "r g b ?heightmapNames...?",},
1933    {"visible", 1, HeightMapLineContourVisibleOp, 4, 0, "bool ?heightmapNames...?",},
1934};
1935static int nHeightMapLineContourOps = NumCmdSpecs(heightMapLineContourOps);
1936
1937static int
1938HeightMapLineContourOp(ClientData clientData, Tcl_Interp *interp, int objc,
1939                       Tcl_Obj *const *objv)
1940{
1941    Tcl_ObjCmdProc *proc;
1942
1943    proc = GetOpFromObj(interp, nHeightMapLineContourOps,
1944                        heightMapLineContourOps, CMDSPEC_ARG2, objc, objv, 0);
1945    if (proc == NULL) {
1946        return TCL_ERROR;
1947    }
1948    return (*proc) (clientData, interp, objc, objv);
1949}
1950
1951static int
1952HeightMapCullOp(ClientData clientData, Tcl_Interp *interp, int objc,
1953                Tcl_Obj *const *objv)
1954{
1955    RenderContext::CullMode mode;
1956    if (GetCullMode(interp, objv[2], &mode) != TCL_OK) {
1957        return TCL_ERROR;
1958    }
1959    NanoVis::renderContext->setCullMode(mode);
1960    NanoVis::eventuallyRedraw();
1961    return TCL_OK;
1962}
1963
1964static int
1965HeightMapCreateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1966                  Tcl_Obj *const *objv)
1967{
1968    const char *tag = Tcl_GetString(objv[2]);
1969    NanoVis::HeightMapHashmap::iterator itr = NanoVis::heightMapTable.find(tag);
1970    if (itr != NanoVis::heightMapTable.end()) {
1971        Tcl_AppendResult(interp, "heightmap \"", tag, "\" already exists.",
1972                         (char *)NULL);
1973        return TCL_ERROR;
1974    }
1975    /* heightmap create xmin ymin xmax ymax xnum ynum values */
1976    HeightMap *heightMap = CreateHeightMap(clientData, interp, objc - 3, objv + 3);
1977    if (heightMap == NULL) {
1978        return TCL_ERROR;
1979    }
1980    NanoVis::heightMapTable[tag] = heightMap;
1981    NanoVis::eventuallyRedraw();
1982    TRACE("Number of heightmaps=%d", NanoVis::heightMapTable.size());
1983    return TCL_OK;
1984}
1985
1986static int
1987HeightMapLegendOp(ClientData clientData, Tcl_Interp *interp, int objc,
1988                  Tcl_Obj *const *objv)
1989{
1990    HeightMap *hmPtr;
1991    if (GetHeightMapFromObj(interp, objv[2], &hmPtr) != TCL_OK) {
1992        return TCL_ERROR;
1993    }
1994    const char *tag;
1995    tag = Tcl_GetString(objv[2]);
1996    TransferFunction *tfPtr;
1997    tfPtr = hmPtr->transferFunction();
1998    if (tfPtr == NULL) {
1999        Tcl_AppendResult(interp, "no transfer function defined for heightmap"
2000                         " \"", tag, "\"", (char*)NULL);
2001        return TCL_ERROR;
2002    }
2003    int w, h;
2004    if ((Tcl_GetIntFromObj(interp, objv[3], &w) != TCL_OK) ||
2005        (Tcl_GetIntFromObj(interp, objv[4], &h) != TCL_OK)) {
2006        return TCL_ERROR;
2007    }
2008    if (HeightMap::updatePending) {
2009        NanoVis::setHeightmapRanges();
2010    }
2011    NanoVis::renderLegend(tfPtr, HeightMap::valueMin, HeightMap::valueMax,
2012                          w, h, tag);
2013    return TCL_OK;
2014}
2015
2016static int
2017HeightMapPolygonOp(ClientData clientData, Tcl_Interp *interp, int objc,
2018                   Tcl_Obj *const *objv)
2019{
2020    RenderContext::PolygonMode mode;
2021    if (GetPolygonMode(interp, objv[2], &mode) != TCL_OK) {
2022        return TCL_ERROR;
2023    }
2024    NanoVis::renderContext->setPolygonMode(mode);
2025    NanoVis::eventuallyRedraw();
2026    return TCL_OK;
2027}
2028
2029static int
2030HeightMapShadingOp(ClientData clientData, Tcl_Interp *interp, int objc,
2031                 Tcl_Obj *const *objv)
2032{
2033    RenderContext::ShadingModel model;
2034    if (GetShadingModel(interp, objv[2], &model) != TCL_OK) {
2035        return TCL_ERROR;
2036    }
2037    NanoVis::renderContext->setShadingModel(model);
2038    NanoVis::eventuallyRedraw();
2039    return TCL_OK;
2040}
2041
2042static int
2043HeightMapTransFuncOp(ClientData clientData, Tcl_Interp *interp, int objc,
2044                     Tcl_Obj *const *objv)
2045{
2046    const char *name;
2047    name = Tcl_GetString(objv[2]);
2048    TransferFunction *tf = NanoVis::getTransferFunction(name);
2049    if (tf == NULL) {
2050        Tcl_AppendResult(interp, "transfer function \"", name,
2051                         "\" is not defined", (char*)NULL);
2052        return TCL_ERROR;
2053    }
2054    std::vector<HeightMap *> imap;
2055    if (GetHeightMaps(interp, objc - 3, objv + 3, &imap) != TCL_OK) {
2056        return TCL_ERROR;
2057    }
2058    std::vector<HeightMap *>::iterator iter;
2059    for (iter = imap.begin(); iter != imap.end(); iter++) {
2060        (*iter)->transferFunction(tf);
2061    }
2062    NanoVis::eventuallyRedraw();
2063    return TCL_OK;
2064}
2065
2066static int
2067HeightMapOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
2068                   Tcl_Obj *const *objv)
2069{
2070    float opacity;
2071    if (GetFloatFromObj(interp, objv[2], &opacity) != TCL_OK) {
2072        return TCL_ERROR;
2073    }
2074    std::vector<HeightMap *> heightmaps;
2075    if (GetHeightMaps(interp, objc - 3, objv + 3, &heightmaps) != TCL_OK) {
2076        return TCL_ERROR;
2077    }
2078    std::vector<HeightMap *>::iterator iter;
2079    for (iter = heightmaps.begin(); iter != heightmaps.end(); iter++) {
2080        (*iter)->opacity(opacity);
2081    }
2082    NanoVis::eventuallyRedraw();
2083    return TCL_OK;
2084}
2085
2086static CmdSpec heightMapOps[] = {
2087    {"create",       2, HeightMapCreateOp,      10, 10, "heightmapName xmin ymin xmax ymax xnum ynum values",},
2088    {"cull",         2, HeightMapCullOp,        3, 3, "mode",},
2089    {"data",         1, HeightMapDataOp,        3, 0, "oper ?args?",},
2090    {"legend",       2, HeightMapLegendOp,      5, 5, "heightmapName width height",},
2091    {"linecontour",  2, HeightMapLineContourOp, 2, 0, "oper ?args?",},
2092    {"opacity",      1, HeightMapOpacityOp,     3, 0, "value ?heightmapNames...? ",},
2093    {"polygon",      1, HeightMapPolygonOp,     3, 3, "mode",},
2094    {"shading",      1, HeightMapShadingOp,     3, 3, "model",},
2095    {"transfunc",    2, HeightMapTransFuncOp,   3, 0, "name ?heightmapNames...?",},
2096};
2097static int nHeightMapOps = NumCmdSpecs(heightMapOps);
2098
2099static int
2100HeightMapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2101             Tcl_Obj *const *objv)
2102{
2103    Tcl_ObjCmdProc *proc;
2104
2105    proc = GetOpFromObj(interp, nHeightMapOps, heightMapOps,
2106                        CMDSPEC_ARG1, objc, objv, 0);
2107    if (proc == NULL) {
2108        return TCL_ERROR;
2109    }
2110    return (*proc) (clientData, interp, objc, objv);
2111}
2112
2113static int
2114GridAxisColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2115                Tcl_Obj *const *objv)
2116{
2117    float r, g, b, a;
2118    if ((GetFloatFromObj(interp, objv[2], &r) != TCL_OK) ||
2119        (GetFloatFromObj(interp, objv[3], &g) != TCL_OK) ||
2120        (GetFloatFromObj(interp, objv[4], &b) != TCL_OK)) {
2121        return TCL_ERROR;
2122    }
2123    a = 1.0f;
2124    if ((objc == 6) && (GetFloatFromObj(interp, objv[5], &a) != TCL_OK)) {
2125        return TCL_ERROR;
2126    }
2127    if (NanoVis::grid) {
2128        NanoVis::grid->setAxisColor(r, g, b, a);
2129    }
2130    return TCL_OK;
2131}
2132
2133static int
2134GridAxisNameOp(ClientData clientData, Tcl_Interp *interp, int objc,
2135               Tcl_Obj *const *objv)
2136{
2137    int axis;
2138    if (GetAxisFromObj(interp, objv[2], &axis) != TCL_OK) {
2139        return TCL_ERROR;
2140    }
2141    if (NanoVis::grid != NULL) {
2142        Axis *axisPtr = NULL;
2143        switch (axis) {
2144        case 0: axisPtr = &NanoVis::grid->xAxis; break;
2145        case 1: axisPtr = &NanoVis::grid->yAxis; break;
2146        case 2: axisPtr = &NanoVis::grid->zAxis; break;
2147        }
2148        axisPtr->title(Tcl_GetString(objv[3]));
2149        axisPtr->units(Tcl_GetString(objv[4]));
2150    }
2151    return TCL_OK;
2152}
2153
2154static int
2155GridLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2156                Tcl_Obj *const *objv)
2157{
2158    float r, g, b, a;
2159    if ((GetFloatFromObj(interp, objv[2], &r) != TCL_OK) ||
2160        (GetFloatFromObj(interp, objv[3], &g) != TCL_OK) ||
2161        (GetFloatFromObj(interp, objv[4], &b) != TCL_OK)) {
2162        return TCL_ERROR;
2163    }
2164    a = 1.0f;
2165    if ((objc == 6) && (GetFloatFromObj(interp, objv[5], &a) != TCL_OK)) {
2166        return TCL_ERROR;
2167    }
2168    if (NanoVis::grid) {
2169        NanoVis::grid->setLineColor(r, g, b, a);
2170    }
2171    return TCL_OK;
2172}
2173
2174static int
2175GridVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
2176              Tcl_Obj *const *objv)
2177{
2178    bool visible;
2179    if (GetBooleanFromObj(interp, objv[2], &visible) != TCL_OK) {
2180        return TCL_ERROR;
2181    }
2182    NanoVis::grid->setVisible(visible);
2183    return TCL_OK;
2184}
2185
2186static CmdSpec gridOps[] = {
2187    {"axiscolor",  5, GridAxisColorOp,  5, 6, "r g b ?a?",},
2188    {"axisname",   5, GridAxisNameOp,   5, 5, "index title units",},
2189    {"linecolor",  7, GridLineColorOp,  5, 6, "r g b ?a?",},
2190    {"visible",    1, GridVisibleOp,    3, 3, "bool",},
2191};
2192static int nGridOps = NumCmdSpecs(gridOps);
2193
2194static int
2195GridCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2196        Tcl_Obj *const *objv)
2197{
2198    Tcl_ObjCmdProc *proc;
2199
2200    proc = GetOpFromObj(interp, nGridOps, gridOps,
2201                        CMDSPEC_ARG1, objc, objv, 0);
2202    if (proc == NULL) {
2203        return TCL_ERROR;
2204    }
2205    return (*proc) (clientData, interp, objc, objv);
2206}
2207
2208static int
2209AxisCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2210        Tcl_Obj *const *objv)
2211{
2212    if (objc < 2) {
2213        Tcl_AppendResult(interp, "wrong # args: should be \"",
2214                Tcl_GetString(objv[0]), " option arg arg...\"", (char*)NULL);
2215        return TCL_ERROR;
2216    }
2217    const char *string = Tcl_GetString(objv[1]);
2218    char c = string[0];
2219    if ((c == 'v') && (strcmp(string, "visible") == 0)) {
2220        bool visible;
2221
2222        if (GetBooleanFromObj(interp, objv[2], &visible) != TCL_OK) {
2223            return TCL_ERROR;
2224        }
2225        NanoVis::orientationIndicator->setVisible(visible);
2226    } else {
2227        Tcl_AppendResult(interp, "bad axis option \"", string,
2228                         "\": should be visible", (char*)NULL);
2229        return TCL_ERROR;
2230    }
2231    return TCL_OK;
2232}
2233
2234static int
2235ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2236              Tcl_Obj *const *objv)
2237{
2238    lastCmdStatus = TCL_BREAK;
2239    return TCL_OK;
2240}
2241
2242#if !defined(USE_NEW_EVENT_LOOP) && !defined(USE_THREADS)
2243int
2244nv::processCommands(Tcl_Interp *interp, FILE *inFile, int fdOut)
2245{
2246    int ret = 0;
2247    int status = TCL_OK;
2248
2249    Tcl_DString cmdbuffer;
2250    Tcl_DStringInit(&cmdbuffer);
2251
2252    int flags = fcntl(0, F_GETFL, 0);
2253    fcntl(0, F_SETFL, flags & ~O_NONBLOCK);
2254
2255    //  Read and execute as many commands as we can from stdin...
2256    bool isComplete = false;
2257    while ((!feof(inFile)) && (status == TCL_OK)) {
2258        //
2259        //  Read the next command from the buffer.  First time through we
2260        //  block here and wait if necessary until a command comes in.
2261        //
2262        //  BE CAREFUL: Read only one command, up to a newline.  The "volume
2263        //  data follows" command needs to be able to read the data
2264        //  immediately following the command, and we shouldn't consume it
2265        //  here.
2266        //
2267        while (!feof(inFile)) {
2268            int c = fgetc(inFile);
2269            char ch;
2270            if (c <= 0) {
2271                if (errno == EWOULDBLOCK) {
2272                    break;
2273                }
2274                return -1;
2275            }
2276            ch = (char)c;
2277            Tcl_DStringAppend(&cmdbuffer, &ch, 1);
2278            if (ch == '\n') {
2279                isComplete = Tcl_CommandComplete(Tcl_DStringValue(&cmdbuffer));
2280                if (isComplete) {
2281                    break;
2282                }
2283            }
2284        }
2285        // no command? then we're done for now
2286        if (Tcl_DStringLength(&cmdbuffer) == 0) {
2287            break;
2288        }
2289        if (isComplete) {
2290            // back to original flags during command evaluation...
2291            fcntl(0, F_SETFL, flags & ~O_NONBLOCK);
2292            struct timeval start, finish;
2293            gettimeofday(&start, NULL);
2294            status = ExecuteCommand(interp, &cmdbuffer);
2295            gettimeofday(&finish, NULL);
2296            // non-blocking for next read -- we might not get anything
2297            fcntl(0, F_SETFL, flags | O_NONBLOCK);
2298            isComplete = false;
2299            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
2300            g_stats.nCommands++;
2301        }
2302    }
2303    fcntl(0, F_SETFL, flags);
2304
2305    if (status != TCL_OK) {
2306        if (handleError(interp, status, fdOut) < 0) {
2307            return -1;
2308        }
2309        TRACE("Leaving on ERROR");
2310    }
2311
2312    return ret;
2313}
2314
2315#endif
2316
2317/**
2318 * \brief Execute commands from client in Tcl interpreter
2319 *
2320 * In this threaded model, the select call is for event compression.  We
2321 * want to execute render server commands as long as they keep coming. 
2322 * This lets us execute a stream of many commands but render once.  This
2323 * benefits camera movements, screen resizing, and opacity changes
2324 * (using a slider on the client).  The down side is you don't render
2325 * until there's a lull in the command stream.  If the client needs an
2326 * image, it can issue an "imgflush" command.  That breaks us out of the
2327 * read loop.
2328 */
2329int
2330nv::processCommands(Tcl_Interp *interp,
2331                    ReadBuffer *inBufPtr, int fdOut)
2332{
2333    int ret = 1;
2334    int status = TCL_OK;
2335
2336    Tcl_DString command;
2337    Tcl_DStringInit(&command);
2338    fd_set readFds;
2339    struct timeval tv, *tvPtr;
2340
2341    FD_ZERO(&readFds);
2342    FD_SET(inBufPtr->file(), &readFds);
2343    tvPtr = NULL;                       /* Wait for the first read. This is so
2344                                         * that we don't spin when no data is
2345                                         * available. */
2346    while (inBufPtr->isLineAvailable() ||
2347           (select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr) > 0)) {
2348        size_t numBytes;
2349        unsigned char *buffer;
2350
2351        /* A short read is treated as an error here because we assume that we
2352         * will always get commands line by line. */
2353        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2354            /* Terminate the server if we can't communicate with the client
2355             * anymore. */
2356            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2357                TRACE("Exiting server on EOF from client");
2358                return -1;
2359            } else {
2360                ERROR("Exiting server, failed to read from client: %s",
2361                      strerror(errno));
2362                return -1;
2363            }
2364        }
2365        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
2366        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
2367            struct timeval start, finish;
2368            gettimeofday(&start, NULL);
2369            status = ExecuteCommand(interp, &command);
2370            gettimeofday(&finish, NULL);
2371            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
2372            g_stats.nCommands++;
2373            if (status == TCL_BREAK) {
2374                return 1;               /* This was caused by a "imgflush"
2375                                         * command. Break out of the read loop
2376                                         * and allow a new image to be
2377                                         * rendered. */
2378            } else { //if (status != TCL_OK) {
2379                ret = 0;
2380                if (handleError(interp, status, fdOut) < 0) {
2381                    return -1;
2382                }
2383            }
2384        }
2385
2386        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
2387                                         * if no data is available. */
2388        FD_SET(inBufPtr->file(), &readFds);
2389        tvPtr = &tv;
2390    }
2391
2392    return ret;
2393}
2394
2395/**
2396 * \brief Send error message to client socket
2397 */
2398int
2399nv::handleError(Tcl_Interp *interp, int status, int fdOut)
2400{
2401    const char *string;
2402    int nBytes;
2403
2404    if (status != TCL_OK) {
2405        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
2406        nBytes = strlen(string);
2407        if (nBytes > 0) {
2408            TRACE("status=%d errorInfo=(%s)", status, string);
2409
2410            std::ostringstream oss;
2411            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2412            std::string ostr = oss.str();
2413            nBytes = ostr.length();
2414
2415#ifdef USE_THREADS
2416            queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR);
2417#else
2418            if (write(fdOut, oss.str().c_str(), nBytes) < 0) {
2419                ERROR("write failed: %s", strerror(errno));
2420                return -1;
2421            }
2422#endif
2423        }
2424    }
2425
2426    std::string msg = getUserMessages();
2427    nBytes = msg.length();
2428    if (nBytes > 0) {
2429        string = msg.c_str();
2430        TRACE("userError=(%s)", string);
2431
2432        std::ostringstream oss;
2433        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2434        std::string ostr = oss.str();
2435        nBytes = ostr.length();
2436
2437#ifdef USE_THREADS
2438        queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR);
2439#else
2440        if (write(fdOut, ostr.c_str(), nBytes) < 0) {
2441            ERROR("write failed: %s", strerror(errno));
2442            return -1;
2443        }
2444#endif
2445        clearUserMessages();
2446    }
2447
2448    return 0;
2449}
2450
2451void
2452nv::initTcl(Tcl_Interp *interp, ClientData clientData)
2453{
2454    Tcl_MakeSafe(interp);
2455
2456    Tcl_CreateObjCommand(interp, "axis",        AxisCmd,        clientData, NULL);
2457    Tcl_CreateObjCommand(interp, "camera",      CameraCmd,      clientData, NULL);
2458    Tcl_CreateObjCommand(interp, "clientinfo",  ClientInfoCmd,  clientData, NULL);
2459    Tcl_CreateObjCommand(interp, "cutplane",    CutplaneCmd,    clientData, NULL);
2460    FlowCmdInitProc(interp, clientData);
2461    Tcl_CreateObjCommand(interp, "grid",        GridCmd,        clientData, NULL);
2462    Tcl_CreateObjCommand(interp, "heightmap",   HeightMapCmd,   clientData, NULL);
2463    Tcl_CreateObjCommand(interp, "imgflush",    ImageFlushCmd,  clientData, NULL);
2464    Tcl_CreateObjCommand(interp, "legend",      LegendCmd,      clientData, NULL);
2465    Tcl_CreateObjCommand(interp, "screen",      ScreenCmd,      clientData, NULL);
2466    Tcl_CreateObjCommand(interp, "snapshot",    SnapshotCmd,    clientData, NULL);
2467    Tcl_CreateObjCommand(interp, "transfunc",   TransfuncCmd,   clientData, NULL);
2468    Tcl_CreateObjCommand(interp, "up",          UpCmd,          clientData, NULL);
2469    Tcl_CreateObjCommand(interp, "volume",      VolumeCmd,      clientData, NULL);
2470
2471    // create a default transfer function
2472    if (Tcl_Eval(interp, def_transfunc) != TCL_OK) {
2473        WARN("bad default transfer function:\n%s",
2474             Tcl_GetStringResult(interp));
2475    }
2476}
Note: See TracBrowser for help on using the repository browser.