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

Last change on this file since 3497 was 3497, checked in by ldelgass, 6 years ago

Rename display() -> render() in nanovis

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