source: nanovis/branches/1.1/FlowCmd.cpp @ 4599

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

Revert video command to return zero length image response if ffmpeg fails so
the client can deal with the error. However, continue to log an error on the
server end if ffmpeg fails.

  • Property svn:eol-style set to native
File size: 59.0 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (c) 2004-2013  HUBzero Foundation, LLC
4 *
5 */
6#include <assert.h>
7#define _OPEN_SYS
8#include <fcntl.h>
9#define _XOPEN_SOURCE_EXTENDED 1
10#include <sys/uio.h>
11#include <sys/stat.h>
12#include <stdlib.h>
13#include <stddef.h>
14#include <limits.h>
15#include <stdint.h>
16#include <unistd.h>
17#include <poll.h>
18#include <tcl.h>
19
20#include <RpField1D.h>
21#include <RpFieldRect3D.h>
22#include <RpFieldPrism3D.h>
23#include <RpOutcome.h>
24
25#include <vrmath/Vector3f.h>
26
27#include "nvconf.h"
28
29#include "nanovis.h"
30#include "CmdProc.h"
31#include "FlowCmd.h"
32#include "FlowTypes.h"
33#include "FlowBox.h"
34#include "FlowParticles.h"
35#include "Switch.h"
36#include "TransferFunction.h"
37#include "NvLIC.h"
38#include "Trace.h"
39#include "Unirect.h"
40#include "VelocityArrowsSlice.h"
41#include "Volume.h"
42
43using namespace vrmath;
44
45static Rappture::SwitchParseProc AxisSwitchProc;
46static Rappture::SwitchCustom axisSwitch = {
47    AxisSwitchProc, NULL, 0,
48};
49
50static Rappture::SwitchParseProc ColorSwitchProc;
51static Rappture::SwitchCustom colorSwitch = {
52    ColorSwitchProc, NULL, 0,
53};
54
55static Rappture::SwitchParseProc PointSwitchProc;
56static Rappture::SwitchCustom pointSwitch = {
57    PointSwitchProc, NULL, 0,
58};
59
60static Rappture::SwitchParseProc PositionSwitchProc;
61static Rappture::SwitchCustom positionSwitch = {
62    PositionSwitchProc, NULL, 0,
63};
64
65static Rappture::SwitchParseProc TransferFunctionSwitchProc;
66static Rappture::SwitchCustom transferFunctionSwitch = {
67    TransferFunctionSwitchProc, NULL, 0,
68};
69
70Rappture::SwitchSpec FlowCmd::_switches[] = {
71    {Rappture::SWITCH_FLOAT, "-ambient", "value",
72     offsetof(FlowValues, ambient), 0},
73    {Rappture::SWITCH_BOOLEAN, "-arrows", "boolean",
74     offsetof(FlowValues, showArrows), 0},
75    {Rappture::SWITCH_CUSTOM, "-axis", "axis",
76     offsetof(FlowValues, slicePos.axis), 0, 0, &axisSwitch},
77    {Rappture::SWITCH_FLOAT, "-diffuse", "value",
78     offsetof(FlowValues, diffuse), 0},
79    {Rappture::SWITCH_BOOLEAN, "-hide", "boolean",
80     offsetof(FlowValues, isHidden), 0},
81    {Rappture::SWITCH_BOOLEAN, "-light2side", "boolean",
82     offsetof(FlowValues, twoSidedLighting), 0},
83    {Rappture::SWITCH_FLOAT, "-opacity", "value",
84     offsetof(FlowValues, opacity), 0},
85    {Rappture::SWITCH_BOOLEAN, "-outline", "boolean",
86     offsetof(FlowValues, showOutline), 0},
87    {Rappture::SWITCH_CUSTOM, "-position", "number",
88     offsetof(FlowValues, slicePos), 0, 0, &positionSwitch},
89    {Rappture::SWITCH_BOOLEAN, "-slice", "boolean",
90     offsetof(FlowValues, sliceVisible), 0},
91    {Rappture::SWITCH_FLOAT, "-specularExp", "value",
92     offsetof(FlowValues, specularExp), 0},
93    {Rappture::SWITCH_FLOAT, "-specularLevel", "value",
94     offsetof(FlowValues, specular), 0},
95    {Rappture::SWITCH_CUSTOM, "-transferfunction", "name",
96     offsetof(FlowValues, transferFunction), 0, 0, &transferFunctionSwitch},
97    {Rappture::SWITCH_BOOLEAN, "-volume", "boolean",
98     offsetof(FlowValues, showVolume), 0},
99    {Rappture::SWITCH_END}
100};
101
102Rappture::SwitchSpec FlowParticles::_switches[] = {
103    {Rappture::SWITCH_CUSTOM, "-axis", "string",
104     offsetof(FlowParticlesValues, position.axis), 0, 0, &axisSwitch},
105    {Rappture::SWITCH_CUSTOM, "-color", "{r g b a}",
106     offsetof(FlowParticlesValues, color), 0, 0,  &colorSwitch},
107    {Rappture::SWITCH_BOOLEAN, "-hide", "boolean",
108     offsetof(FlowParticlesValues, isHidden), 0},
109    {Rappture::SWITCH_CUSTOM, "-position", "number",
110     offsetof(FlowParticlesValues, position), 0, 0, &positionSwitch},
111    {Rappture::SWITCH_FLOAT, "-size", "float",
112     offsetof(FlowParticlesValues, particleSize), 0},
113    {Rappture::SWITCH_END}
114};
115
116Rappture::SwitchSpec FlowBox::_switches[] = {
117    {Rappture::SWITCH_CUSTOM, "-color", "{r g b a}",
118     offsetof(FlowBoxValues, color), 0, 0,  &colorSwitch},
119    {Rappture::SWITCH_CUSTOM, "-corner1", "{x y z}",
120     offsetof(FlowBoxValues, corner1), 0, 0, &pointSwitch},
121    {Rappture::SWITCH_CUSTOM, "-corner2", "{x y z}",
122     offsetof(FlowBoxValues, corner2), 0, 0, &pointSwitch},
123    {Rappture::SWITCH_BOOLEAN, "-hide", "boolean",
124     offsetof(FlowBoxValues, isHidden), 0},
125    {Rappture::SWITCH_FLOAT, "-linewidth", "number",
126     offsetof(FlowBoxValues, lineWidth), 0},
127    {Rappture::SWITCH_END}
128};
129
130static Tcl_ObjCmdProc FlowInstObjCmd;
131static Tcl_CmdDeleteProc FlowInstDeleteProc;
132
133FlowCmd::FlowCmd(Tcl_Interp *interp, const char *name) :
134    _interp(interp),
135    _name(name),
136    _data(NULL),
137    _volume(NULL),
138    _field(NULL)
139{
140    memset(&_sv, 0, sizeof(FlowValues));
141    _sv.sliceVisible = 1;
142    _sv.transferFunction = NanoVis::getTransferFunction("default");
143
144    _cmdToken = Tcl_CreateObjCommand(_interp, (char *)name,
145                                     (Tcl_ObjCmdProc *)FlowInstObjCmd,
146                                     this, FlowInstDeleteProc);
147}
148
149FlowCmd::~FlowCmd()
150{
151    TRACE("Enter");
152
153    Rappture::FreeSwitches(_switches, &_sv, 0);
154    if (_field != NULL) {
155        delete _field;
156    }
157    if (_data != NULL) {
158        delete _data;
159    }
160    if (_volume != NULL) {
161        NanoVis::removeVolume(_volume);
162        _volume = NULL;
163    }
164    for (BoxHashmap::iterator itr = _boxTable.begin();
165         itr != _boxTable.end(); ++itr) {
166        delete itr->second;
167    }
168    _boxTable.clear();
169    for (ParticlesHashmap::iterator itr = _particlesTable.begin();
170         itr != _particlesTable.end(); ++itr) {
171        delete itr->second;
172    }
173    _particlesTable.clear();
174}
175
176void
177FlowCmd::getBounds(Vector3f& min,
178                   Vector3f& max,
179                   bool onlyVisible)
180{
181    TRACE("Enter");
182
183    if (onlyVisible && !visible())
184        return;
185
186#if 0  // Using volume bounds instead of these
187    if (isDataLoaded()) {
188        Vector3f umin, umax;
189        Rappture::Unirect3d *unirect = data();
190        unirect->getWorldSpaceBounds(umin, umax);
191        if (min.x > umin.x) {
192            min.x = umin.x;
193        }
194        if (max.x < umax.x) {
195            max.x = umax.x;
196        }
197        if (min.y > umin.y) {
198            min.y = umin.y;
199        }
200        if (max.y < umax.y) {
201            max.y = umax.y;
202        }
203        if (min.z > umin.z) {
204            min.z = umin.z;
205        }
206        if (max.z < umax.z) {
207            max.z = umax.z;
208        }
209    }
210#endif
211    for (BoxHashmap::iterator itr = _boxTable.begin();
212         itr != _boxTable.end(); ++itr) {
213        FlowBox *box = itr->second;
214        if (!onlyVisible || box->visible()) {
215            Vector3f fbmin, fbmax;
216            box->getWorldSpaceBounds(fbmin, fbmax,
217                                     getVolume());
218            if (min.x > fbmin.x) {
219                min.x = fbmin.x;
220            }
221            if (max.x < fbmax.x) {
222                max.x = fbmax.x;
223            }
224            if (min.y > fbmin.y) {
225                min.y = fbmin.y;
226            }
227            if (max.y < fbmax.y) {
228                max.y = fbmax.y;
229            }
230            if (min.z > fbmin.z) {
231                min.z = fbmin.z;
232            }
233            if (max.z < fbmax.z) {
234                max.z = fbmax.z;
235            }
236        }
237    }
238}
239
240void
241FlowCmd::resetParticles()
242{
243    for (ParticlesHashmap::iterator itr = _particlesTable.begin();
244         itr != _particlesTable.end(); ++itr) {
245        itr->second->reset();
246    }
247}
248
249void
250FlowCmd::advect()
251{
252    NvVectorField *fieldPtr = getVectorField();
253    fieldPtr->active(true);
254    for (ParticlesHashmap::iterator itr = _particlesTable.begin();
255         itr != _particlesTable.end(); ++itr) {
256        if (itr->second->visible()) {
257            itr->second->advect();
258        }
259    }
260}
261
262void
263FlowCmd::render()
264{
265    _field->active(true);
266    _field->render();
267    for (ParticlesHashmap::iterator itr = _particlesTable.begin();
268         itr != _particlesTable.end(); ++itr) {
269        if (itr->second->visible()) {
270            itr->second->render();
271        }
272    }
273    renderBoxes();
274}
275
276FlowParticles *
277FlowCmd::createParticles(const char *particlesName)
278{
279    ParticlesHashmap::iterator itr = _particlesTable.find(particlesName);
280    if (itr != _particlesTable.end()) {
281        TRACE("Deleting existing particle injection plane '%s'", particlesName);
282        delete itr->second;
283        _particlesTable.erase(itr);
284    }
285    FlowParticles *particles = new FlowParticles(particlesName);
286    _particlesTable[particlesName] = particles;
287    return particles;
288}
289
290FlowParticles *
291FlowCmd::getParticles(const char *particlesName)
292{
293    ParticlesHashmap::iterator itr;
294    itr = _particlesTable.find(particlesName);
295    if (itr == _particlesTable.end()) {
296        TRACE("Can't find particle injection plane '%s' in '%s'", particlesName, name());
297        return NULL;
298    }
299    return itr->second;
300}
301
302void
303FlowCmd::deleteParticles(const char *particlesName)
304{
305    ParticlesHashmap::iterator itr = _particlesTable.find(particlesName);
306    if (itr == _particlesTable.end()) {
307        TRACE("Can't find particle injection plane '%s' in '%s'", particlesName, name());
308        return;
309    }
310    delete itr->second;
311    _particlesTable.erase(itr);
312}
313
314void
315FlowCmd::getParticlesNames(std::vector<std::string>& names)
316{
317    for (ParticlesHashmap::iterator itr = _particlesTable.begin();
318         itr != _particlesTable.end(); ++itr) {
319        names.push_back(std::string(itr->second->name()));
320    }
321}
322
323FlowBox *
324FlowCmd::createBox(const char *boxName)
325{
326    BoxHashmap::iterator itr = _boxTable.find(boxName);
327    if (itr != _boxTable.end()) {
328        TRACE("Deleting existing box '%s'", boxName);
329        delete itr->second;
330        _boxTable.erase(itr);
331    }
332    FlowBox *box = new FlowBox(boxName);
333    _boxTable[boxName] = box;
334    return box;
335}
336
337FlowBox *
338FlowCmd::getBox(const char *boxName)
339{
340    BoxHashmap::iterator itr = _boxTable.find(boxName);
341    if (itr == _boxTable.end()) {
342        TRACE("Can't find box '%s' in '%s'", boxName, name());
343        return NULL;
344    }
345    return itr->second;
346}
347
348void
349FlowCmd::deleteBox(const char *boxName)
350{
351    BoxHashmap::iterator itr = _boxTable.find(boxName);
352    if (itr == _boxTable.end()) {
353        TRACE("Can't find box '%s' in '%s'", boxName, name());
354        return;
355    }
356    delete itr->second;
357    _boxTable.erase(itr);
358}
359
360void FlowCmd::getBoxNames(std::vector<std::string>& names)
361{
362    for (BoxHashmap::iterator itr = _boxTable.begin();
363         itr != _boxTable.end(); ++itr) {
364        names.push_back(std::string(itr->second->name()));
365    }
366}
367
368void
369FlowCmd::initializeParticles()
370{
371    for (ParticlesHashmap::iterator itr = _particlesTable.begin();
372         itr != _particlesTable.end(); ++itr) {
373        itr->second->initialize();
374    }
375}
376
377bool
378FlowCmd::scaleVectorField()
379{
380    if (_volume != NULL) {
381        TRACE("Removing existing volume: %s", _volume->name());
382        NanoVis::removeVolume(_volume);
383        _volume = NULL;
384    }
385    float *vdata = getScaledVector();
386    if (vdata == NULL) {
387        return false;
388    }
389    Volume *volume = makeVolume(vdata);
390    delete [] vdata;
391    if (volume == NULL) {
392        return false;
393    }
394    _volume = volume;
395
396    // Remove the associated vector field.
397    if (_field != NULL) {
398        delete _field;
399    }
400    _field = new NvVectorField();
401    if (_field == NULL) {
402        return false;
403    }
404
405    Vector3f scale = volume->getPhysicalScaling();
406    Vector3f location = _volume->location();
407
408    _field->setVectorField(_volume,
409                           location,
410                           scale.x,
411                           scale.y,
412                           scale.z,
413                           NanoVis::magMax);
414
415    if (NanoVis::licRenderer != NULL) {
416        NanoVis::licRenderer->
417            setVectorField(_volume->textureID(),
418                           location,
419                           scale.x,
420                           scale.y,
421                           scale.z,
422                           _volume->wAxis.max());
423        setCurrentPosition();
424        setAxis();
425        setActive();
426    }
427
428    if (NanoVis::velocityArrowsSlice != NULL) {
429        NanoVis::velocityArrowsSlice->
430            setVectorField(_volume->textureID(),
431                           location,
432                           scale.x,
433                           scale.y,
434                           scale.z,
435                           _volume->wAxis.max());
436        NanoVis::velocityArrowsSlice->axis(_sv.slicePos.axis);
437        NanoVis::velocityArrowsSlice->slicePos(_sv.slicePos.value);
438        NanoVis::velocityArrowsSlice->enabled(_sv.showArrows);
439    }
440
441    for (ParticlesHashmap::iterator itr = _particlesTable.begin();
442         itr != _particlesTable.end(); ++itr) {
443        itr->second->setVectorField(_volume,
444                                    location,
445                                    scale.x,
446                                    scale.y,
447                                    scale.z,
448                                    _volume->wAxis.max());
449    }
450    return true;
451}
452
453void
454FlowCmd::renderBoxes()
455{
456    for (BoxHashmap::iterator itr = _boxTable.begin();
457         itr != _boxTable.end(); ++itr) {
458        if (itr->second->visible()) {
459            itr->second->render(_volume);
460        }
461    }
462}
463
464float *
465FlowCmd::getScaledVector()
466{
467    assert(_data->nComponents() == 3);
468    size_t n = _data->nValues() / _data->nComponents() * 4;
469    float *data = new float[n];
470    if (data == NULL) {
471        return NULL;
472    }
473    memset(data, 0, sizeof(float) * n);
474    float *destPtr = data;
475    const float *values = _data->values();
476    for (size_t iz = 0; iz < _data->zNum(); iz++) {
477        for (size_t iy = 0; iy < _data->yNum(); iy++) {
478            for (size_t ix = 0; ix < _data->xNum(); ix++) {
479                double vx, vy, vz, vm;
480                vx = values[0];
481                vy = values[1];
482                vz = values[2];
483                vm = sqrt(vx*vx + vy*vy + vz*vz);
484                destPtr[0] = vm / NanoVis::magMax;
485                destPtr[1] = vx /(2.0*NanoVis::magMax) + 0.5;
486                destPtr[2] = vy /(2.0*NanoVis::magMax) + 0.5;
487                destPtr[3] = vz /(2.0*NanoVis::magMax) + 0.5;
488                values += 3;
489                destPtr += 4;
490            }
491        }
492    }
493    return data;
494}
495
496Volume *
497FlowCmd::makeVolume(float *data)
498{
499    Volume *volume =
500        NanoVis::loadVolume(_name.c_str(),
501                            _data->xNum(),
502                            _data->yNum(),
503                            _data->zNum(),
504                            4, data,
505                            NanoVis::magMin, NanoVis::magMax, 0);
506    volume->xAxis.setRange(_data->xMin(), _data->xMax());
507    volume->yAxis.setRange(_data->yMin(), _data->yMax());
508    volume->zAxis.setRange(_data->zMin(), _data->zMax());
509
510    TRACE("min=%g %g %g max=%g %g %g mag=%g %g",
511          NanoVis::xMin, NanoVis::yMin, NanoVis::zMin,
512          NanoVis::xMax, NanoVis::yMax, NanoVis::zMax,
513          NanoVis::magMin, NanoVis::magMax);
514
515    volume->disableCutplane(0);
516    volume->disableCutplane(1);
517    volume->disableCutplane(2);
518
519    /* Initialize the volume with the previously configured values. */
520    volume->transferFunction(_sv.transferFunction);
521    volume->dataEnabled(_sv.showVolume);
522    volume->twoSidedLighting(_sv.twoSidedLighting);
523    volume->outline(_sv.showOutline);
524    volume->opacityScale(_sv.opacity);
525    volume->ambient(_sv.ambient);
526    volume->diffuse(_sv.diffuse);
527    volume->specularLevel(_sv.specular);
528    volume->specularExponent(_sv.specularExp);
529    volume->visible(_sv.showVolume);
530
531    Vector3f volScaling = volume->getPhysicalScaling();
532    Vector3f loc(volScaling);
533    loc *= -0.5;
534    volume->location(loc);
535
536    Volume::updatePending = true;
537    return volume;
538}
539
540static int
541FlowDataFileOp(ClientData clientData, Tcl_Interp *interp, int objc,
542               Tcl_Obj *const *objv)
543{
544    Rappture::Outcome result;
545
546    const char *fileName;
547    fileName = Tcl_GetString(objv[3]);
548    TRACE("File: %s", fileName);
549
550    int nComponents;
551    if (Tcl_GetIntFromObj(interp, objv[4], &nComponents) != TCL_OK) {
552        return TCL_ERROR;
553    }
554    if ((nComponents < 1) || (nComponents > 4)) {
555        Tcl_AppendResult(interp, "bad # of components \"",
556                         Tcl_GetString(objv[4]), "\"", (char *)NULL);
557        return TCL_ERROR;
558    }
559    Rappture::Buffer buf;
560    if (!buf.load(result, fileName)) {
561        Tcl_AppendResult(interp, "can't load data from \"", fileName, "\": ",
562                         result.remark(), (char *)NULL);
563        return TCL_ERROR;
564    }
565
566    Rappture::Unirect3d *dataPtr;
567    dataPtr = new Rappture::Unirect3d(nComponents);
568    FlowCmd *flowPtr = (FlowCmd *)clientData;
569    size_t length = buf.size();
570    char *bytes = (char *)buf.bytes();
571    if ((length > 4) && (strncmp(bytes, "<DX>", 4) == 0)) {
572        if (!dataPtr->importDx(result, nComponents, length-4, bytes+4)) {
573            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
574            delete dataPtr;
575            return TCL_ERROR;
576        }
577    } else if ((length > 10) && (strncmp(bytes, "unirect3d ", 10) == 0)) {
578        if (dataPtr->parseBuffer(interp, buf) != TCL_OK) {
579            delete dataPtr;
580            return TCL_ERROR;
581        }
582    } else if ((length > 10) && (strncmp(bytes, "unirect2d ", 10) == 0)) {
583        Rappture::Unirect2d *u2dPtr;
584        u2dPtr = new Rappture::Unirect2d(nComponents);
585        if (u2dPtr->parseBuffer(interp, buf) != TCL_OK) {
586            delete u2dPtr;
587            return TCL_ERROR;
588        }
589        dataPtr->convert(u2dPtr);
590        delete u2dPtr;
591    } else {
592        TRACE("header is %.14s", buf.bytes());
593        if (!dataPtr->importDx(result, nComponents, length, bytes)) {
594            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
595            delete dataPtr;
596            return TCL_ERROR;
597        }
598    }
599    if (dataPtr->nValues() == 0) {
600        delete dataPtr;
601        Tcl_AppendResult(interp, "no data found in \"", fileName, "\"",
602                         (char *)NULL);
603        return TCL_ERROR;
604    }
605    flowPtr->data(dataPtr);
606    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
607    return TCL_OK;
608}
609
610/**
611 * $flow data follows nbytes nComponents
612 */
613static int
614FlowDataFollowsOp(ClientData clientData, Tcl_Interp *interp, int objc,
615                  Tcl_Obj *const *objv)
616{
617    Rappture::Outcome result;
618
619    TRACE("Enter");
620
621    int nBytes;
622    if (Tcl_GetIntFromObj(interp, objv[3], &nBytes) != TCL_OK) {
623        ERROR("Bad nBytes \"%s\"", Tcl_GetString(objv[3]));
624        return TCL_ERROR;
625    }
626    if (nBytes <= 0) {
627        Tcl_AppendResult(interp, "bad # bytes request \"",
628                         Tcl_GetString(objv[3]), "\" for \"data follows\"", (char *)NULL);
629        ERROR("Bad nbytes %d", nBytes);
630        return TCL_ERROR;
631    }
632    int nComponents;
633    if (Tcl_GetIntFromObj(interp, objv[4], &nComponents) != TCL_OK) {
634        ERROR("Bad # of components \"%s\"", Tcl_GetString(objv[4]));
635        return TCL_ERROR;
636    }
637    if (nComponents <= 0) {
638        Tcl_AppendResult(interp, "bad # of components request \"",
639                         Tcl_GetString(objv[4]), "\" for \"data follows\"", (char *)NULL);
640        ERROR("Bad # of components %d", nComponents);
641        return TCL_ERROR;
642    }
643    Rappture::Buffer buf;
644    TRACE("Flow data loading bytes: %d components: %d", nBytes, nComponents);
645    if (GetDataStream(interp, buf, nBytes) != TCL_OK) {
646        return TCL_ERROR;
647    }
648    Rappture::Unirect3d *dataPtr;
649    dataPtr = new Rappture::Unirect3d(nComponents);
650
651    FlowCmd *flowPtr = (FlowCmd *)clientData;
652    size_t length = buf.size();
653    char *bytes = (char *)buf.bytes();
654    if ((length > 4) && (strncmp(bytes, "<DX>", 4) == 0)) {
655        if (!dataPtr->importDx(result, nComponents, length - 4, bytes + 4)) {
656            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
657            delete dataPtr;
658            return TCL_ERROR;
659        }
660    } else if ((length > 10) && (strncmp(bytes, "unirect3d ", 10) == 0)) {
661        if (dataPtr->parseBuffer(interp, buf) != TCL_OK) {
662            delete dataPtr;
663            return TCL_ERROR;
664        }
665    } else if ((length > 10) && (strncmp(bytes, "unirect2d ", 10) == 0)) {
666        Rappture::Unirect2d *u2dPtr;
667        u2dPtr = new Rappture::Unirect2d(nComponents);
668        if (u2dPtr->parseBuffer(interp, buf) != TCL_OK) {
669            delete u2dPtr;
670            return TCL_ERROR;
671        }
672        dataPtr->convert(u2dPtr);
673        delete u2dPtr;
674    } else {
675        TRACE("header is %.14s", buf.bytes());
676        if (!dataPtr->importDx(result, nComponents, length, bytes)) {
677            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
678            delete dataPtr;
679            return TCL_ERROR;
680        }
681    }
682    if (dataPtr->nValues() == 0) {
683        delete dataPtr;
684        Tcl_AppendResult(interp, "no data found in stream", (char *)NULL);
685        return TCL_ERROR;
686    }
687    TRACE("nx = %d ny = %d nz = %d",
688          dataPtr->xNum(), dataPtr->yNum(), dataPtr->zNum());
689    TRACE("x0 = %lg y0 = %lg z0 = %lg",
690          dataPtr->xMin(), dataPtr->yMin(), dataPtr->zMin());
691    TRACE("lx = %lg ly = %lg lz = %lg",
692          dataPtr->xMax() - dataPtr->xMin(),
693          dataPtr->yMax() - dataPtr->yMin(),
694          dataPtr->zMax() - dataPtr->zMin());
695    TRACE("dx = %lg dy = %lg dz = %lg",
696          dataPtr->xNum() > 1 ? (dataPtr->xMax() - dataPtr->xMin())/(dataPtr->xNum()-1) : 0,
697          dataPtr->yNum() > 1 ? (dataPtr->yMax() - dataPtr->yMin())/(dataPtr->yNum()-1) : 0,
698          dataPtr->zNum() > 1 ? (dataPtr->zMax() - dataPtr->zMin())/(dataPtr->zNum()-1) : 0);
699    TRACE("magMin = %lg magMax = %lg",
700          dataPtr->magMin(), dataPtr->magMax());
701    flowPtr->data(dataPtr);
702    {
703        char info[1024];
704        ssize_t nWritten;
705        size_t length;
706
707        length = sprintf(info, "nv>data tag %s min %g max %g\n",
708                         flowPtr->name(), dataPtr->magMin(), dataPtr->magMax());
709        nWritten  = write(1, info, length);
710        assert(nWritten == (ssize_t)strlen(info));
711    }
712    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
713    return TCL_OK;
714}
715
716static Rappture::CmdSpec flowDataOps[] = {
717    {"file",    2, FlowDataFileOp,    5, 5, "fileName nComponents",},
718    {"follows", 2, FlowDataFollowsOp, 5, 5, "size nComponents",},
719};
720static int nFlowDataOps = NumCmdSpecs(flowDataOps);
721
722static int
723FlowDataOp(ClientData clientData, Tcl_Interp *interp, int objc,
724           Tcl_Obj *const *objv)
725{
726    Tcl_ObjCmdProc *proc;
727
728    proc = Rappture::GetOpFromObj(interp, nFlowDataOps, flowDataOps,
729                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
730    if (proc == NULL) {
731        return TCL_ERROR;
732    }
733    return (*proc) (clientData, interp, objc, objv);
734}
735
736float
737FlowCmd::getRelativePosition(FlowPosition *posPtr)
738{
739    if (posPtr->flags == RELPOS) {
740        return posPtr->value;
741    }
742    switch (posPtr->axis) {
743    case AXIS_X: 
744        return (posPtr->value - NanoVis::xMin) /
745            (NanoVis::xMax - NanoVis::xMin);
746    case AXIS_Y: 
747        return (posPtr->value - NanoVis::yMin) /
748            (NanoVis::yMax - NanoVis::yMin);
749    case AXIS_Z: 
750        return (posPtr->value - NanoVis::zMin) /
751            (NanoVis::zMax - NanoVis::zMin);
752    }
753    return 0.0;
754}
755
756float
757FlowCmd::getRelativePosition()
758{
759    return FlowCmd::getRelativePosition(&_sv.slicePos);
760}
761
762/* Static NanoVis class commands. */
763
764FlowCmd *
765NanoVis::getFlow(const char *name)
766{
767    FlowHashmap::iterator itr = flowTable.find(name);
768    if (itr == flowTable.end()) {
769        TRACE("Can't find flow '%s'", name);
770        return NULL;
771    }
772    return itr->second;
773}
774
775FlowCmd *
776NanoVis::createFlow(Tcl_Interp *interp, const char *name)
777{
778    FlowHashmap::iterator itr = flowTable.find(name);
779    if (itr != flowTable.end()) {
780        ERROR("Flow '%s' already exists", name);
781        return NULL;
782    }
783    FlowCmd *flow = new FlowCmd(interp, name);
784    flowTable[name] = flow;
785    return flow;
786}
787
788/**
789 * \brief Delete flow object and hash table entry
790 *
791 * This is called by the flow command instance delete callback
792 */
793void
794NanoVis::deleteFlow(const char *name)
795{
796    FlowHashmap::iterator itr = flowTable.find(name);
797    if (itr != flowTable.end()) {
798        delete itr->second;
799        flowTable.erase(itr);
800    }
801}
802
803/**
804 * \brief Delete all flow object commands
805 *
806 * This will also delete the flow objects and hash table entries
807 */
808void
809NanoVis::deleteFlows(Tcl_Interp *interp)
810{
811    FlowHashmap::iterator itr;
812    for (itr = flowTable.begin();
813         itr != flowTable.end(); ++itr) {
814        Tcl_DeleteCommandFromToken(interp, itr->second->getCommandToken());
815    }
816    flowTable.clear();
817}
818
819bool
820NanoVis::mapFlows()
821{
822    TRACE("Enter");
823
824    flags &= ~MAP_FLOWS;
825
826    /*
827     * Step 1. Get the overall min and max magnitudes of all the
828     *         flow vectors.
829     */
830    magMin = DBL_MAX, magMax = -DBL_MAX;
831
832    for (FlowHashmap::iterator itr = flowTable.begin();
833         itr != flowTable.end(); ++itr) {
834        FlowCmd *flow = itr->second;
835        double min, max;
836        if (!flow->isDataLoaded()) {
837            continue;
838        }
839        Rappture::Unirect3d *data = flow->data();
840        min = data->magMin();
841        max = data->magMax();
842        if (min < magMin) {
843            magMin = min;
844        }
845        if (max > magMax) {
846            magMax = max;
847        }
848        if (data->xMin() < xMin) {
849            xMin = data->xMin();
850        }
851        if (data->yMin() < yMin) {
852            yMin = data->yMin();
853        }
854        if (data->zMin() < zMin) {
855            zMin = data->zMin();
856        }
857        if (data->xMax() > xMax) {
858            xMax = data->xMax();
859        }
860        if (data->yMax() > yMax) {
861            yMax = data->yMax();
862        }
863        if (data->zMax() > zMax) {
864            zMax = data->zMax();
865        }
866    }
867
868    TRACE("magMin=%g magMax=%g", NanoVis::magMin, NanoVis::magMax);
869
870    /*
871     * Step 2. Generate the vector field from each data set.
872     */
873    for (FlowHashmap::iterator itr = flowTable.begin();
874         itr != flowTable.end(); ++itr) {
875        FlowCmd *flow = itr->second;
876        if (!flow->isDataLoaded()) {
877            continue; // Flow exists, but no data has been loaded yet.
878        }
879        if (flow->visible()) {
880            flow->initializeParticles();
881        }
882        if (!flow->scaleVectorField()) {
883            return false;
884        }
885        // FIXME: This doesn't work when there is more than one flow.
886        licRenderer->setOffset(flow->getRelativePosition());
887        velocityArrowsSlice->slicePos(flow->getRelativePosition());
888    }
889    advectFlows();
890    return true;
891}
892
893void
894NanoVis::getFlowBounds(Vector3f& min,
895                       Vector3f& max,
896                       bool onlyVisible)
897{
898    TRACE("Enter");
899
900    min.set(FLT_MAX, FLT_MAX, FLT_MAX);
901    max.set(-FLT_MAX, -FLT_MAX, -FLT_MAX);
902
903    for (FlowHashmap::iterator itr = flowTable.begin();
904         itr != flowTable.end(); ++itr) {
905        itr->second->getBounds(min, max, onlyVisible);
906    }
907}
908
909void
910NanoVis::renderFlows()
911{
912    for (FlowHashmap::iterator itr = flowTable.begin();
913         itr != flowTable.end(); ++itr) {
914        FlowCmd *flow = itr->second;
915        if (flow->isDataLoaded() && flow->visible()) {
916            flow->render();
917        }
918    }
919    flags &= ~REDRAW_PENDING;
920}
921
922void
923NanoVis::resetFlows()
924{
925    if (licRenderer->active()) {
926        NanoVis::licRenderer->reset();
927    }
928    for (FlowHashmap::iterator itr = flowTable.begin();
929         itr != flowTable.end(); ++itr) {
930        FlowCmd *flow = itr->second;
931        if (flow->isDataLoaded() && flow->visible()) {
932            flow->resetParticles();
933        }
934    }
935}   
936
937void
938NanoVis::advectFlows()
939{
940    for (FlowHashmap::iterator itr = flowTable.begin();
941         itr != flowTable.end(); ++itr) {
942        FlowCmd *flow = itr->second;
943        if (flow->isDataLoaded() && flow->visible()) {
944            flow->advect();
945        }
946    }
947}
948
949/**
950 * \brief Convert a Tcl_Obj representing the label of a child node into its
951 * integer node id.
952 *
953 * \param clientData Flag indicating if the node is considered before or
954 * after the insertion position.
955 * \param interp Interpreter to send results back to
956 * \param switchName Not used
957 * \param objPtr String representation
958 * \param record Structure record
959 * \param offset Not used
960 * \param flags Not used
961 *
962 * \return The return value is a standard Tcl result.
963 */
964static int
965AxisSwitchProc(ClientData clientData, Tcl_Interp *interp,
966               const char *switchName, Tcl_Obj *objPtr,
967               char *record, int offset, int flags)
968{
969    const char *string = Tcl_GetString(objPtr);
970    if (string[1] == '\0') {
971        FlowCmd::SliceAxis *axisPtr = (FlowCmd::SliceAxis *)(record + offset);
972        char c;
973        c = tolower((unsigned char)string[0]);
974        if (c == 'x') {
975            *axisPtr = FlowCmd::AXIS_X;
976            return TCL_OK;
977        } else if (c == 'y') {
978            *axisPtr = FlowCmd::AXIS_Y;
979            return TCL_OK;
980        } else if (c == 'z') {
981            *axisPtr = FlowCmd::AXIS_Z;
982            return TCL_OK;
983        }
984        /*FALLTHRU*/
985    }
986    Tcl_AppendResult(interp, "bad axis \"", string,
987                     "\": should be x, y, or z", (char*)NULL);
988    return TCL_ERROR;
989}
990
991/**
992 * \brief Convert a Tcl_Obj representing the label of a list of four color
993 * components in to a RGBA color value.
994 *
995 * \param clientData Flag indicating if the node is considered before or
996 * after the insertion position.
997 * \param interp Interpreter to send results back to
998 * \param switchName Not used
999 * \param objPtr String representation
1000 * \param record Structure record
1001 * \param offset Not used
1002 * \param flags Not used
1003 *
1004 * \return The return value is a standard Tcl result.
1005 */
1006static int
1007ColorSwitchProc(ClientData clientData, Tcl_Interp *interp,
1008                const char *switchName, Tcl_Obj *objPtr,
1009                char *record, int offset, int flags)
1010{
1011    Tcl_Obj **objv;
1012    int objc;
1013    FlowColor *color = (FlowColor *)(record + offset);
1014
1015    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
1016        return TCL_ERROR;
1017    }
1018    if ((objc < 3) || (objc > 4)) {
1019        Tcl_AppendResult(interp, "wrong # of elements in color definition",
1020                         (char *)NULL);
1021        return TCL_ERROR;
1022    }
1023    float values[4];
1024    int i;
1025    values[3] = 1.0f;
1026    for (i = 0; i < objc; i++) {
1027        float value;
1028
1029        if (GetFloatFromObj(interp, objv[i], &value) != TCL_OK) {
1030            return TCL_ERROR;
1031        }
1032        if ((value < 0.0) || (value > 1.0)) {
1033            Tcl_AppendResult(interp, "bad component value in \"",
1034                             Tcl_GetString(objPtr), "\": color values must be [0..1]",
1035                             (char *)NULL);
1036            return TCL_ERROR;
1037        }
1038        values[i] = value;
1039    }
1040    color->r = values[0];
1041    color->g = values[1];
1042    color->b = values[2];
1043    color->a = values[3];
1044    return TCL_OK;
1045}
1046
1047/**
1048 * \brief Convert a Tcl_Obj representing the a 3-D coordinate into a point.
1049 *
1050 * \param clientData Flag indicating if the node is considered before or
1051 * after the insertion position.
1052 * \param interp Interpreter to send results back to
1053 * \param switchName Not used
1054 * \param objPtr String representation
1055 * \param record Structure record
1056 * \param offset Not used
1057 * \param flags Not used
1058 *
1059 * \return The return value is a standard Tcl result.
1060 */
1061static int
1062PointSwitchProc(ClientData clientData, Tcl_Interp *interp,
1063                const char *switchName, Tcl_Obj *objPtr,
1064                char *record, int offset, int flags)
1065{
1066    FlowPoint *point = (FlowPoint *)(record + offset);
1067    int objc;
1068    Tcl_Obj **objv;
1069
1070    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
1071        return TCL_ERROR;
1072    }
1073    if (objc != 3) {
1074        Tcl_AppendResult(interp, "wrong # of elements for box coordinates: "
1075                         " should be \"x y z\"", (char *)NULL);
1076        return TCL_ERROR;
1077    }
1078    float values[3];
1079    int i;
1080    for (i = 0; i < objc; i++) {
1081        float value;
1082
1083        if (GetFloatFromObj(interp, objv[i], &value) != TCL_OK) {
1084            return TCL_ERROR;
1085        }
1086        values[i] = value;
1087    }
1088    point->x = values[0];
1089    point->y = values[1];
1090    point->z = values[2];
1091    return TCL_OK;
1092}
1093
1094/**
1095 * \brief Convert a Tcl_Obj representing the a 3-D coordinate into a point.
1096 *
1097 * \param clientData Flag indicating if the node is considered before or
1098 * after the insertion position.
1099 * \param interp Interpreter to send results back to
1100 * \param switchName Not used
1101 * \param objPtr String representation
1102 * \param record Structure record
1103 * \param offset Not used
1104 * \param flags Not used
1105 *
1106 * \return The return value is a standard Tcl result.
1107 */
1108static int
1109PositionSwitchProc(ClientData clientData, Tcl_Interp *interp,
1110                   const char *switchName, Tcl_Obj *objPtr,
1111                   char *record, int offset, int flags)
1112{
1113    FlowPosition *posPtr = (FlowPosition *)(record + offset);
1114    const char *string;
1115    char *p;
1116
1117    string = Tcl_GetString(objPtr);
1118    p = strrchr((char *)string, '%');
1119    if (p == NULL) {
1120        float value;
1121
1122        if (GetFloatFromObj(interp, objPtr, &value) != TCL_OK) {
1123            return TCL_ERROR;
1124        }
1125        posPtr->value = value;
1126        posPtr->flags = ABSPOS;
1127    } else {
1128        double value;
1129
1130        *p = '\0';
1131        if (Tcl_GetDouble(interp, string, &value) != TCL_OK) {
1132            return TCL_ERROR;
1133        }
1134        posPtr->value = (float)value * 0.01;
1135        posPtr->flags = RELPOS;
1136    }
1137    return TCL_OK;
1138}
1139
1140/**
1141 * \brief Convert a Tcl_Obj representing the transfer function into a
1142 *  TransferFunction pointer. 
1143 *
1144 * The transfer function must have been previously defined.
1145 *
1146 * \param clientData Flag indicating if the node is considered before or
1147 * after the insertion position.
1148 * \param interp Interpreter to send results back to
1149 * \param switchName Not used
1150 * \param objPtr String representation
1151 * \param record Structure record
1152 * \param offset Not used
1153 * \param flags Not used
1154 *
1155 * \return The return value is a standard Tcl result.
1156 */
1157static int
1158TransferFunctionSwitchProc(ClientData clientData, Tcl_Interp *interp,
1159                           const char *switchName, Tcl_Obj *objPtr,
1160                           char *record, int offset, int flags)
1161{
1162    TransferFunction **funcPtrPtr = (TransferFunction **)(record + offset);
1163    TransferFunction *tf = NanoVis::getTransferFunction(Tcl_GetString(objPtr));
1164    if (tf == NULL) {
1165        Tcl_AppendResult(interp, "transfer function \"", Tcl_GetString(objPtr),
1166                         "\" is not defined", (char*)NULL);
1167        return TCL_ERROR;
1168    }
1169    *funcPtrPtr = tf;
1170    return TCL_OK;
1171}
1172
1173static int
1174FlowConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
1175                Tcl_Obj *const *objv)
1176{
1177    FlowCmd *flowPtr = (FlowCmd *)clientData;
1178
1179    if (flowPtr->parseSwitches(interp, objc - 2, objv + 2) != TCL_OK) {
1180        return TCL_ERROR;
1181    }
1182    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
1183    return TCL_OK;
1184}
1185
1186static int
1187FlowParticlesAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1188                   Tcl_Obj *const *objv)
1189{
1190    FlowCmd *flow = (FlowCmd *)clientData;
1191    const char *particlesName = Tcl_GetString(objv[3]);
1192    FlowParticles *particles = flow->createParticles(particlesName);
1193    if (particles == NULL) {
1194        Tcl_AppendResult(interp, "Flow particle injection plane \"",
1195                         particlesName,
1196                         "\" already exists or could not be created",
1197                         (char*)NULL);
1198        return TCL_ERROR;
1199    }
1200    if (particles->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
1201        flow->deleteParticles(particlesName);
1202        return TCL_ERROR;
1203    }
1204    particles->configure();
1205    NanoVis::eventuallyRedraw();
1206    Tcl_SetObjResult(interp, objv[3]);
1207    return TCL_OK;
1208}
1209
1210static int
1211FlowParticlesConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
1212                         Tcl_Obj *const *objv)
1213{
1214    FlowCmd *flow = (FlowCmd *)clientData;
1215    const char *particlesName = Tcl_GetString(objv[3]);
1216    FlowParticles *particles = flow->getParticles(particlesName);
1217    if (particles == NULL) {
1218        Tcl_AppendResult(interp, "Flow particle injection plane \"",
1219                         particlesName, "\" not found",
1220                         (char*)NULL);
1221        return TCL_ERROR;
1222    }
1223    if (particles->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
1224        return TCL_ERROR;
1225    }
1226    particles->configure();
1227    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
1228    return TCL_OK;
1229}
1230
1231static int
1232FlowParticlesDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1233                      Tcl_Obj *const *objv)
1234{
1235    FlowCmd *flow = (FlowCmd *)clientData;
1236    for (int i = 3; i < objc; i++) {
1237        flow->deleteParticles(Tcl_GetString(objv[i]));
1238    }
1239    NanoVis::eventuallyRedraw();
1240    return TCL_OK;
1241}
1242
1243static int
1244FlowParticlesNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1245                     Tcl_Obj *const *objv)
1246{
1247    FlowCmd *flow = (FlowCmd *)clientData;
1248    Tcl_Obj *listObjPtr;
1249    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
1250    std::vector<std::string> names;
1251    flow->getParticlesNames(names);
1252    for (std::vector<std::string>::iterator itr = names.begin();
1253         itr != names.end(); ++itr) {
1254        Tcl_Obj *objPtr = Tcl_NewStringObj(itr->c_str(), -1);
1255        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1256    }
1257    Tcl_SetObjResult(interp, listObjPtr);
1258    return TCL_OK;
1259}
1260
1261static Rappture::CmdSpec flowParticlesOps[] = {
1262    {"add",        1, FlowParticlesAddOp,        4, 0, "name ?switches?",},
1263    {"configure",  1, FlowParticlesConfigureOp,  4, 0, "name ?switches?",},
1264    {"delete",     1, FlowParticlesDeleteOp,     4, 0, "name ?name...?"},
1265    {"names",      1, FlowParticlesNamesOp,      3, 3, ""},
1266};
1267
1268static int nFlowParticlesOps = NumCmdSpecs(flowParticlesOps);
1269
1270/**
1271 * \brief This procedure is invoked to process commands on behalf of the flow
1272 * object.
1273 *
1274 * Side effects: See the user documentation.
1275 *
1276 * $flow particles oper $name
1277 *
1278 * \return A standard Tcl result.
1279 */
1280static int
1281FlowParticlesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1282                Tcl_Obj *const *objv)
1283{
1284    Tcl_ObjCmdProc *proc;
1285    proc = Rappture::GetOpFromObj(interp, nFlowParticlesOps, flowParticlesOps,
1286                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
1287    if (proc == NULL) {
1288        return TCL_ERROR;
1289    }
1290    FlowCmd *flowPtr = (FlowCmd *)clientData;
1291    Tcl_Preserve(flowPtr);
1292    int result;
1293    result = (*proc) (clientData, interp, objc, objv);
1294    Tcl_Release(flowPtr);
1295    return result;
1296}
1297
1298static int
1299FlowBoxAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1300             Tcl_Obj *const *objv)
1301{
1302    FlowCmd *flow = (FlowCmd *)clientData;
1303    const char *boxName = Tcl_GetString(objv[3]);
1304    FlowBox *box = flow->createBox(boxName);
1305    if (box == NULL) {
1306        Tcl_AppendResult(interp, "Flow box \"", boxName,
1307                         "\" already exists or could not be created",
1308                         (char*)NULL);
1309        return TCL_ERROR;
1310    }
1311    if (box->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
1312        flow->deleteBox(boxName);
1313        return TCL_ERROR;
1314    }
1315    NanoVis::eventuallyRedraw();
1316    Tcl_SetObjResult(interp, objv[3]);
1317    return TCL_OK;
1318}
1319
1320static int
1321FlowBoxConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
1322                   Tcl_Obj *const *objv)
1323{
1324    FlowCmd *flow = (FlowCmd *)clientData;
1325    const char *boxName = Tcl_GetString(objv[3]);
1326    FlowBox *box = flow->getBox(boxName);
1327    if (box == NULL) {
1328        Tcl_AppendResult(interp, "Flow box \"", boxName, "\" not found",
1329                         (char*)NULL);
1330        return TCL_ERROR;
1331    }
1332    if (box->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
1333        return TCL_ERROR;
1334    }
1335    NanoVis::eventuallyRedraw();
1336    return TCL_OK;
1337}
1338
1339static int
1340FlowBoxDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1341                Tcl_Obj *const *objv)
1342{
1343    FlowCmd *flow = (FlowCmd *)clientData;
1344    for (int i = 3; i < objc; i++) {
1345        flow->deleteBox(Tcl_GetString(objv[i]));
1346    }
1347    NanoVis::eventuallyRedraw();
1348    return TCL_OK;
1349}
1350
1351static int
1352FlowBoxNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1353               Tcl_Obj *const *objv)
1354{
1355    FlowCmd *flow = (FlowCmd *)clientData;
1356    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
1357    std::vector<std::string> names;
1358    flow->getBoxNames(names);
1359    for (std::vector<std::string>::iterator itr = names.begin();
1360         itr != names.end(); ++itr) {
1361        Tcl_Obj *objPtr = Tcl_NewStringObj(itr->c_str(), -1);
1362        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1363    }
1364    Tcl_SetObjResult(interp, listObjPtr);
1365    return TCL_OK;
1366}
1367
1368static Rappture::CmdSpec flowBoxOps[] = {
1369    {"add",        1, FlowBoxAddOp,        4, 0, "name ?switches?",},
1370    {"configure",  1, FlowBoxConfigureOp,  4, 0, "name ?switches?",},
1371    {"delete",     1, FlowBoxDeleteOp,     4, 0, "name ?name...?"},
1372    {"names",      1, FlowBoxNamesOp,      3, 3, ""},
1373};
1374
1375static int nFlowBoxOps = NumCmdSpecs(flowBoxOps);
1376
1377/**
1378 * \brief This procedure is invoked to process commands on behalf of the flow
1379 *         object.
1380 *
1381 * Side effects:  See the user documentation.
1382 *
1383 * \return A standard Tcl result.
1384 */
1385static int
1386FlowBoxOp(ClientData clientData, Tcl_Interp *interp, int objc,
1387          Tcl_Obj *const *objv)
1388{
1389    Tcl_ObjCmdProc *proc;
1390    proc = Rappture::GetOpFromObj(interp, nFlowBoxOps, flowBoxOps,
1391                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
1392    if (proc == NULL) {
1393        return TCL_ERROR;
1394    }
1395    FlowCmd *flowPtr = (FlowCmd *)clientData;
1396    Tcl_Preserve(flowPtr);
1397    int result;
1398    result = (*proc) (clientData, interp, objc, objv);
1399    Tcl_Release(flowPtr);
1400    return result;
1401}
1402
1403/*
1404 * CLIENT COMMAND:
1405 *   $flow legend <width> <height>
1406 *
1407 * Clients use this to generate a legend image for the specified
1408 * transfer function.  The legend image is a color gradient from 0
1409 * to one, drawn in the given transfer function.  The resulting image
1410 * is returned in the size <width> x <height>.
1411 */
1412static int
1413FlowLegendOp(ClientData clientData, Tcl_Interp *interp, int objc,
1414             Tcl_Obj *const *objv)
1415{
1416    FlowCmd *flowPtr = (FlowCmd *)clientData;
1417   
1418    const char *string = Tcl_GetString(objv[1]);
1419    TransferFunction *tf;
1420    tf = flowPtr->getTransferFunction();
1421    if (tf == NULL) {
1422        Tcl_AppendResult(interp, "unknown transfer function \"", string, "\"",
1423                         (char*)NULL);
1424        return TCL_ERROR;
1425    }
1426    const char *label;
1427    label = Tcl_GetString(objv[0]);
1428    int w, h;
1429    if ((Tcl_GetIntFromObj(interp, objv[2], &w) != TCL_OK) ||
1430        (Tcl_GetIntFromObj(interp, objv[3], &h) != TCL_OK)) {
1431        return TCL_ERROR;
1432    }
1433    if (NanoVis::flags & NanoVis::MAP_FLOWS) {
1434        NanoVis::mapFlows();
1435    }
1436    NanoVis::renderLegend(tf, NanoVis::magMin, NanoVis::magMax, w, h, label);
1437    return TCL_OK;
1438}
1439
1440static Rappture::CmdSpec flowInstOps[] = {
1441    {"box",         1, FlowBoxOp,        2, 0, "oper ?args?"},
1442    {"configure",   1, FlowConfigureOp,  2, 0, "?switches?"},
1443    {"data",        1, FlowDataOp,       2, 0, "oper ?args?"},
1444    {"legend",      1, FlowLegendOp,     4, 4, "w h"},
1445    {"particles",   1, FlowParticlesOp,  2, 0, "oper ?args?"}
1446};
1447static int nFlowInstOps = NumCmdSpecs(flowInstOps);
1448
1449/**
1450 * \brief This procedure is invoked to process commands on behalf of the flow
1451 * object.
1452 *
1453 * Side effects: See the user documentation.
1454 *
1455 * \return A standard Tcl result.
1456 */
1457static int
1458FlowInstObjCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1459               Tcl_Obj *const *objv)
1460{
1461    Tcl_ObjCmdProc *proc;
1462    proc = Rappture::GetOpFromObj(interp, nFlowInstOps, flowInstOps,
1463                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
1464    if (proc == NULL) {
1465        return TCL_ERROR;
1466    }
1467    assert(CheckGL(AT));
1468    FlowCmd *flow = (FlowCmd *)clientData;
1469    Tcl_Preserve(flow);
1470    int result = (*proc) (clientData, interp, objc, objv);
1471    Tcl_Release(flow);
1472    return result;
1473}
1474
1475/**
1476 * \brief Deletes the command associated with the flow
1477 *
1478 * This is called only when the command associated with the flow is destroyed.
1479 */
1480static void
1481FlowInstDeleteProc(ClientData clientData)
1482{
1483    FlowCmd *flow = (FlowCmd *)clientData;
1484    NanoVis::deleteFlow(flow->name());
1485}
1486
1487static int
1488FlowAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1489          Tcl_Obj *const *objv)
1490{
1491    const char *name = Tcl_GetString(objv[2]);
1492    Tcl_CmdInfo cmdInfo;
1493    if (Tcl_GetCommandInfo(interp, name, &cmdInfo)) {
1494        Tcl_AppendResult(interp, "an another command \"", name,
1495                         "\" already exists.", (char *)NULL);
1496        return TCL_ERROR;
1497    }
1498    FlowCmd *flow = NanoVis::createFlow(interp, name);
1499    if (flow == NULL) {
1500        Tcl_AppendResult(interp, "Flow \"", name, "\" already exists",
1501                         (char*)NULL);
1502        return TCL_ERROR;
1503    }
1504    if (flow->parseSwitches(interp, objc - 3, objv + 3) != TCL_OK) {
1505        Tcl_DeleteCommand(interp, flow->name());
1506        return TCL_ERROR;
1507    }
1508    Tcl_SetObjResult(interp, objv[2]);
1509    NanoVis::eventuallyRedraw();
1510    return TCL_OK;
1511}
1512
1513static int
1514FlowDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1515             Tcl_Obj *const *objv)
1516{
1517    for (int i = 2; i < objc; i++) {
1518        FlowCmd *flow = NanoVis::getFlow(Tcl_GetString(objv[i]));
1519        if (flow != NULL) {
1520            Tcl_DeleteCommandFromToken(interp, flow->getCommandToken());
1521        }
1522    }
1523    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
1524    return TCL_OK;
1525}
1526
1527static int
1528FlowExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1529             Tcl_Obj *const *objv)
1530{
1531    bool value = false;
1532    FlowCmd *flow = NanoVis::getFlow(Tcl_GetString(objv[2]));
1533    if (flow != NULL) {
1534        value = true;
1535    }
1536    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), (int)value);
1537    return TCL_OK;
1538}
1539
1540/**
1541 * \brief flow goto number
1542 */
1543static int
1544FlowGotoOp(ClientData clientData, Tcl_Interp *interp, int objc,
1545           Tcl_Obj *const *objv)
1546{
1547    int nSteps;
1548    if (Tcl_GetIntFromObj(interp, objv[2], &nSteps) != TCL_OK) {
1549        return TCL_ERROR;
1550    }
1551    if ((nSteps < 0) || (nSteps > SHRT_MAX)) {
1552        Tcl_AppendResult(interp, "flow goto: bad # of steps \"",
1553                         Tcl_GetString(objv[2]), "\"", (char *)NULL);
1554        return TCL_ERROR;
1555    }
1556    NanoVis::resetFlows();
1557    if (NanoVis::flags & NanoVis::MAP_FLOWS) {
1558        NanoVis::mapFlows();
1559    }
1560    NanoVis::advectFlows();
1561    for (int i = 0; i < nSteps; i++) {
1562        if (NanoVis::licRenderer->active()) {
1563            NanoVis::licRenderer->convolve();
1564        }
1565        NanoVis::advectFlows();
1566    }
1567    NanoVis::eventuallyRedraw();
1568    return TCL_OK;
1569}
1570
1571static int
1572FlowNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1573            Tcl_Obj *const *objv)
1574{
1575    Tcl_Obj *listObjPtr;
1576    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
1577    for (NanoVis::FlowHashmap::iterator itr = NanoVis::flowTable.begin();
1578         itr != NanoVis::flowTable.end(); ++itr) {
1579        FlowCmd *flow = itr->second;
1580        Tcl_Obj *objPtr = Tcl_NewStringObj(flow->name(), -1);
1581        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1582    }
1583    Tcl_SetObjResult(interp, listObjPtr);
1584    return TCL_OK;
1585}
1586
1587static int
1588FlowNextOp(ClientData clientData, Tcl_Interp *interp, int objc,
1589           Tcl_Obj *const *objv)
1590{
1591    assert(NanoVis::licRenderer != NULL);
1592    if (NanoVis::flags & NanoVis::MAP_FLOWS) {
1593        NanoVis::mapFlows();
1594    }
1595    NanoVis::eventuallyRedraw();
1596    NanoVis::licRenderer->convolve();
1597    NanoVis::advectFlows();
1598    return TCL_OK;
1599}
1600
1601static int
1602FlowResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
1603            Tcl_Obj *const *objv)
1604{
1605    NanoVis::resetFlows();
1606    return TCL_OK;
1607}
1608
1609/**
1610 * \brief Convert a Tcl_Obj representing the video format into its
1611 * integer id.
1612 *
1613 * \param clientData Not used
1614 * \param interp Interpreter to send results back to
1615 * \param switchName Not used
1616 * \param objPtr String representation
1617 * \param record Structure record
1618 * \param offset Not used
1619 * \param flags Not used
1620 *
1621 * \return The return value is a standard Tcl result.
1622 */
1623static int
1624VideoFormatSwitchProc(ClientData clientData, Tcl_Interp *interp,
1625                      const char *switchName, Tcl_Obj *objPtr,
1626                      char *record, int offset, int flags)
1627{
1628    Tcl_Obj **formatObjPtr = (Tcl_Obj **)(record + offset);
1629    Tcl_Obj *fmtObjPtr;
1630    const char *string;
1631    char c;
1632    int length;
1633
1634    string = Tcl_GetStringFromObj(objPtr, &length);
1635    c = string[0];
1636    if ((c == 'm') && (length > 1) &&
1637        (strncmp(string, "mpeg", length) == 0)) {
1638        fmtObjPtr =  Tcl_NewStringObj("mpeg1video", 10);
1639    } else if ((c == 't') && (strncmp(string, "theora", length) == 0)) {
1640        fmtObjPtr =  Tcl_NewStringObj("theora", 6);
1641    } else if ((c == 'm') && (length > 1) &&
1642               (strncmp(string, "mov", length) == 0)) {
1643        fmtObjPtr =  Tcl_NewStringObj("mov", 3);
1644    } else {
1645        Tcl_AppendResult(interp, "bad video format \"", string,
1646                         "\": should be mpeg, theora, or mov", (char*)NULL);
1647        return TCL_ERROR;
1648    }
1649    if (*formatObjPtr != NULL) {
1650        Tcl_DecrRefCount(*formatObjPtr);
1651    }
1652    Tcl_IncrRefCount(fmtObjPtr);
1653    *formatObjPtr = fmtObjPtr;
1654    return TCL_OK;
1655}
1656
1657struct FlowVideoSwitches {
1658    float frameRate;         /**< Frame rate */
1659    int bitRate;             /**< Video bitrate */
1660    int width, height;       /**< Dimensions of video frame. */
1661    int numFrames;
1662    Tcl_Obj *formatObjPtr;
1663};
1664
1665static Rappture::SwitchParseProc VideoFormatSwitchProc;
1666static Rappture::SwitchCustom videoFormatSwitch = {
1667    VideoFormatSwitchProc, NULL, 0,
1668};
1669
1670Rappture::SwitchSpec FlowCmd::videoSwitches[] = {
1671    {Rappture::SWITCH_INT, "-bitrate", "value",
1672     offsetof(FlowVideoSwitches, bitRate), 0},
1673    {Rappture::SWITCH_CUSTOM, "-format", "string",
1674     offsetof(FlowVideoSwitches, formatObjPtr), 0, 0, &videoFormatSwitch},
1675    {Rappture::SWITCH_FLOAT, "-framerate", "value",
1676     offsetof(FlowVideoSwitches, frameRate), 0},
1677    {Rappture::SWITCH_INT, "-height", "integer",
1678     offsetof(FlowVideoSwitches, height), 0},
1679    {Rappture::SWITCH_INT, "-numframes", "count",
1680     offsetof(FlowVideoSwitches, numFrames), 0},
1681    {Rappture::SWITCH_INT, "-width", "integer",
1682     offsetof(FlowVideoSwitches, width), 0},
1683    {Rappture::SWITCH_END}
1684};
1685
1686#ifdef HAVE_FFMPEG
1687
1688static int
1689ppmWriteToFile(Tcl_Interp *interp, const char *path, FlowVideoSwitches *switchesPtr)
1690{
1691    int f;
1692   
1693    /* Open the named file for writing. */
1694    f = creat(path, 0600);
1695    if (f < 0) {
1696        Tcl_AppendResult(interp, "can't open temporary image file \"", path,
1697                         "\": ", Tcl_PosixError(interp), (char *)NULL);
1698        return TCL_ERROR;
1699    }
1700    // Generate the PPM binary file header
1701    char header[200];
1702#define PPM_MAXVAL 255
1703    sprintf(header, "P6 %d %d %d\n", switchesPtr->width, switchesPtr->height,
1704        PPM_MAXVAL);
1705
1706    size_t header_length = strlen(header);
1707    size_t wordsPerRow = (switchesPtr->width * 24 + 31) / 32;
1708    size_t bytesPerRow = wordsPerRow * 4;
1709    size_t rowLength = switchesPtr->width * 3;
1710    size_t numRecords = switchesPtr->height + 1;
1711
1712    struct iovec *iov;
1713    iov = (struct iovec *)malloc(sizeof(struct iovec) * numRecords);
1714
1715    // Add the PPM image header.
1716    iov[0].iov_base = header;
1717    iov[0].iov_len = header_length;
1718
1719    // Now add the image data, reversing the order of the rows.
1720    int y;
1721    unsigned char *srcRowPtr = NanoVis::screenBuffer;
1722    /* Reversing the pointers for the image rows.  PPM is top-to-bottom. */
1723    for (y = switchesPtr->height; y >= 1; y--) {
1724        iov[y].iov_base = srcRowPtr;
1725        iov[y].iov_len = rowLength;
1726        srcRowPtr += bytesPerRow;
1727    }
1728    if (writev(f, iov, numRecords) < 0) {
1729        Tcl_AppendResult(interp, "writing image to \"", path, "\" failed: ",
1730                         Tcl_PosixError(interp), (char *)NULL);
1731        free(iov);
1732        close(f);
1733        return TCL_ERROR;
1734    }
1735    close(f);
1736    free(iov);
1737    return TCL_OK;
1738}
1739
1740static int
1741MakeImageFiles(Tcl_Interp *interp, char *tmpFileName,
1742               FlowVideoSwitches *switchesPtr, bool *cancelPtr)
1743{
1744    struct pollfd pollResults;
1745    pollResults.fd = fileno(NanoVis::stdin);
1746    pollResults.events = POLLIN;
1747#define PENDING_TIMEOUT          10  /* milliseconds. */
1748    int timeout = PENDING_TIMEOUT;
1749
1750    int oldWidth, oldHeight;
1751    oldWidth = NanoVis::winWidth;
1752    oldHeight = NanoVis::winHeight;
1753
1754    if ((switchesPtr->width != oldWidth) ||
1755        (switchesPtr->height != oldHeight)) {
1756        // Resize to the requested size.
1757        NanoVis::resizeOffscreenBuffer(switchesPtr->width, switchesPtr->height);
1758    }
1759    NanoVis::resetFlows();
1760    *cancelPtr = false;
1761    int result = TCL_OK;
1762    size_t length = strlen(tmpFileName);
1763    for (int i = 1; i <= switchesPtr->numFrames; i++) {
1764        if (((i & 0xF) == 0) && (poll(&pollResults, 1, timeout) > 0)) {
1765            /* If there's another command on stdin, that means the client is
1766             * trying to cancel this operation. */
1767            *cancelPtr = true;
1768            break;
1769        }
1770        if (NanoVis::licRenderer->active()) {
1771            NanoVis::licRenderer->convolve();
1772        }
1773        NanoVis::advectFlows();
1774
1775        int fboOrig;
1776        glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
1777
1778        NanoVis::bindOffscreenBuffer();
1779        NanoVis::render();
1780        NanoVis::readScreen();
1781
1782        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
1783
1784        sprintf(tmpFileName + length, "/image%d.ppm", i);
1785        result = ppmWriteToFile(interp, tmpFileName, switchesPtr);
1786        if (result != TCL_OK) {
1787            break;
1788        }
1789    }
1790    if ((switchesPtr->width != oldWidth) ||
1791        (switchesPtr->height != oldHeight)) {
1792        NanoVis::resizeOffscreenBuffer(oldWidth, oldHeight);
1793    }
1794    tmpFileName[length] = '\0';
1795    NanoVis::resetFlows();
1796    return result;
1797}
1798
1799static int
1800MakeMovie(Tcl_Interp *interp, char *tmpFileName, const char *token,
1801          FlowVideoSwitches *switchesPtr)
1802{
1803#ifndef FFMPEG
1804#  define FFMPEG "/usr/bin/ffmpeg"
1805#endif
1806    /* Generate the movie from the frame images by exec-ing ffmpeg */
1807    /* The ffmpeg command is
1808     *   ffmpeg -f image2 -i /var/tmp/xxxxx/image%d.ppm                 \
1809     *      -b bitrate -f framerate /var/tmp/xxxxx/movie.mpeg
1810     */
1811    char cmd[BUFSIZ];
1812    sprintf(cmd, "%s -f image2 -i %s/image%%d.ppm -f %s -b %d -r %f -",
1813            FFMPEG, tmpFileName, Tcl_GetString(switchesPtr->formatObjPtr),
1814            switchesPtr->bitRate, switchesPtr->frameRate);
1815    TRACE("Enter: %s", cmd);
1816    FILE *f;
1817    f = popen(cmd, "r");
1818    if (f == NULL) {
1819        Tcl_AppendResult(interp, "can't run ffmpeg: ",
1820                         Tcl_PosixError(interp), (char *)NULL);
1821        return TCL_ERROR;
1822    }
1823    Rappture::Buffer data;
1824    size_t total = 0;
1825    for (;;) {
1826        ssize_t numRead;
1827        char buffer[BUFSIZ];
1828       
1829        numRead = fread(buffer, sizeof(unsigned char), BUFSIZ, f);
1830        total += numRead;
1831        if (numRead == 0) {             // EOF
1832            break;
1833        }
1834        if (numRead < 0) {              // Error
1835            ERROR("Can't read movie data: %s",
1836                  Tcl_PosixError(interp));
1837            Tcl_AppendResult(interp, "can't read movie data: ",
1838                Tcl_PosixError(interp), (char *)NULL);
1839            return TCL_ERROR;
1840        }
1841        if (!data.append(buffer, numRead)) {
1842            ERROR("Can't append movie data to buffer %d bytes",
1843                  numRead);
1844            Tcl_AppendResult(interp, "can't append movie data to buffer",
1845                             (char *)NULL);
1846            return TCL_ERROR;
1847        }
1848    }
1849    if (data.size() == 0) {
1850        ERROR("ffmpeg returned 0 bytes");
1851    }
1852    // Send zero length to client so it can deal with error
1853    sprintf(cmd,"nv>image -type movie -token \"%s\" -bytes %lu\n",
1854            token, (unsigned long)data.size());
1855    NanoVis::sendDataToClient(cmd, data.bytes(), data.size());
1856    return TCL_OK;
1857}
1858
1859static int
1860FlowVideoOp(ClientData clientData, Tcl_Interp *interp, int objc,
1861            Tcl_Obj *const *objv)
1862{
1863    FlowVideoSwitches switches;
1864    const char *token;
1865
1866    token = Tcl_GetString(objv[2]);
1867    switches.frameRate = 25.0f;                // Default frame rate 25 fps
1868    switches.bitRate = 6000000;                // Default video bit rate.
1869    switches.width = NanoVis::winWidth;
1870    switches.height = NanoVis::winHeight;
1871    switches.numFrames = 100;
1872    switches.formatObjPtr = Tcl_NewStringObj("mpeg1video", 10);
1873    Tcl_IncrRefCount(switches.formatObjPtr);
1874    if (Rappture::ParseSwitches(interp, FlowCmd::videoSwitches,
1875                objc - 3, objv + 3, &switches, SWITCH_DEFAULTS) < 0) {
1876        return TCL_ERROR;
1877    }
1878    if ((switches.width < 0) || (switches.width > SHRT_MAX) ||
1879        (switches.height < 0) || (switches.height > SHRT_MAX)) {
1880        Tcl_AppendResult(interp, "bad dimensions for video", (char *)NULL);
1881        return TCL_ERROR;
1882    }
1883    if ((switches.frameRate < 0.0f) || (switches.frameRate > 30.0f)) {
1884        Tcl_AppendResult(interp, "bad frame rate.", (char *)NULL);
1885        return TCL_ERROR;
1886    }
1887    if (switches.bitRate < 0) {
1888        Tcl_AppendResult(interp, "bad bit rate.", (char *)NULL);
1889        return TCL_ERROR;
1890    }
1891    if (NanoVis::licRenderer == NULL) {
1892        Tcl_AppendResult(interp, "no lic renderer.", (char *)NULL);
1893        return TCL_ERROR;
1894    }
1895    TRACE("FLOW started");
1896
1897    char *tmpFileName;
1898    char nameTemplate[200];
1899    strcpy(nameTemplate,"/var/tmp/flowXXXXXX");
1900    tmpFileName = mkdtemp(nameTemplate);
1901    int result = TCL_OK;
1902    if (tmpFileName == NULL) {
1903        Tcl_AppendResult(interp, "can't create temporary directory \"",
1904                         nameTemplate, "\" for frame image files: ",
1905                         Tcl_PosixError(interp), (char *)NULL);
1906        return TCL_ERROR;
1907    }
1908    size_t length = strlen(tmpFileName);
1909    bool canceled = false;
1910    result = MakeImageFiles(interp, tmpFileName, &switches, &canceled);
1911    if ((result == TCL_OK) && (!canceled)) {
1912        result = MakeMovie(interp, tmpFileName, token, &switches);
1913    }
1914    for (int i = 1; i <= switches.numFrames; i++) {
1915        sprintf(tmpFileName + length, "/image%d.ppm", i);
1916        unlink(tmpFileName);
1917    }       
1918    tmpFileName[length] = '\0';
1919    rmdir(tmpFileName);
1920    Rappture::FreeSwitches(FlowCmd::videoSwitches, &switches, 0);
1921    return result;
1922}
1923#else
1924/**
1925 *  Not implemented
1926 */
1927static int
1928FlowVideoOp(ClientData clientData, Tcl_Interp *interp, int objc,
1929            Tcl_Obj *const *objv)
1930{
1931    return TCL_OK;
1932}
1933#endif /* HAVE_FFMPEG */
1934
1935static Rappture::CmdSpec flowCmdOps[] = {
1936    {"add",      1, FlowAddOp,     3, 0, "name ?option value...?",},
1937    {"delete",   1, FlowDeleteOp,  3, 0, "name ?name...?",},
1938    {"exists",   1, FlowExistsOp,  3, 3, "name",},
1939    {"goto",     1, FlowGotoOp,    3, 3, "nSteps",},
1940    {"names",    1, FlowNamesOp,   2, 2, "",},
1941    {"next",     2, FlowNextOp,    2, 2, "",},
1942    {"reset",    1, FlowResetOp,   2, 2, "",},
1943    {"video",    1, FlowVideoOp,   3, 0, "token ?switches...?",},
1944};
1945static int nFlowCmdOps = NumCmdSpecs(flowCmdOps);
1946
1947static int
1948FlowCmdProc(ClientData clientData, Tcl_Interp *interp, int objc,
1949            Tcl_Obj *const *objv)
1950{
1951    Tcl_ObjCmdProc *proc;
1952
1953    proc = Rappture::GetOpFromObj(interp, nFlowCmdOps, flowCmdOps,
1954                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
1955    if (proc == NULL) {
1956        return TCL_ERROR;
1957    }
1958    return (*proc) (clientData, interp, objc, objv);
1959}
1960
1961/**
1962 *\brief This procedure is invoked to initialize the "flow" command.
1963 *
1964 * Side effects:
1965 *    Creates the new command and adds a new entry into a global Tcl
1966 *    associative array.
1967 */
1968int
1969FlowCmdInitProc(Tcl_Interp *interp)
1970{
1971    Tcl_CreateObjCommand(interp, "flow", FlowCmdProc, NULL, NULL);
1972    return TCL_OK;
1973}
Note: See TracBrowser for help on using the repository browser.