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

Last change on this file since 5516 was 5498, checked in by ldelgass, 9 years ago

Officially merge r3605 -- picks up OrientationIndicator?, but is disabled for
now.

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