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

Last change on this file since 5329 was 5304, checked in by ldelgass, 9 years ago

merge r5302:5303 from trunk

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