source: nanovis/trunk/Command.cpp

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

Add optional idle timeout to nanovis server. Allows server to close connection
and exit after a specified period without commands from the client.

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