source: trunk/packages/vizservers/nanovis/Command.cpp @ 3492

Last change on this file since 3492 was 3492, checked in by ldelgass, 11 years ago

Fix camera reset for nanovis. Includes refactoring of vector/matrix classes
in nanovis to consolidate into vrmath library. Also add preliminary canonical
view control to clients for testing.

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