source: trunk/packages/vizservers/nanovis/FlowCmd.cpp @ 3943

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

Fix vector magnitude computation in nanovis VTK reader

  • Property svn:eol-style set to native
File size: 42.3 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
19#include <tcl.h>
20
21#include <RpOutcome.h>
22
23#include <vrmath/Vector3f.h>
24
25#include "nvconf.h"
26
27#include "nanovisServer.h"
28#include "nanovis.h"
29#include "CmdProc.h"
30#include "Command.h"
31#include "PPMWriter.h"
32#ifdef USE_VTK
33#include "VtkDataSetReader.h"
34#endif
35#include "VtkReader.h"
36#include "FlowCmd.h"
37#include "FlowTypes.h"
38#include "Flow.h"
39#include "FlowBox.h"
40#include "FlowParticles.h"
41#include "Switch.h"
42#include "TransferFunction.h"
43#include "LIC.h"
44#include "Unirect.h"
45#include "VelocityArrowsSlice.h"
46#include "Volume.h"
47#include "Trace.h"
48
49using namespace nv;
50using namespace vrmath;
51
52static Rappture::SwitchParseProc AxisSwitchProc;
53static Rappture::SwitchCustom axisSwitch = {
54    AxisSwitchProc, NULL, 0,
55};
56
57static Rappture::SwitchParseProc ColorSwitchProc;
58static Rappture::SwitchCustom colorSwitch = {
59    ColorSwitchProc, NULL, 0,
60};
61
62static Rappture::SwitchParseProc PointSwitchProc;
63static Rappture::SwitchCustom pointSwitch = {
64    PointSwitchProc, NULL, 0,
65};
66
67static Rappture::SwitchParseProc PositionSwitchProc;
68static Rappture::SwitchCustom positionSwitch = {
69    PositionSwitchProc, NULL, 0,
70};
71
72static Rappture::SwitchParseProc TransferFunctionSwitchProc;
73static Rappture::SwitchCustom transferFunctionSwitch = {
74    TransferFunctionSwitchProc, NULL, 0,
75};
76
77Rappture::SwitchSpec Flow::_switches[] = {
78    {Rappture::SWITCH_FLOAT, "-ambient", "value",
79     offsetof(FlowValues, ambient), 0},
80    {Rappture::SWITCH_BOOLEAN, "-arrows", "boolean",
81     offsetof(FlowValues, showArrows), 0},
82    {Rappture::SWITCH_CUSTOM, "-axis", "axis",
83     offsetof(FlowValues, slicePos.axis), 0, 0, &axisSwitch},
84    {Rappture::SWITCH_FLOAT, "-diffuse", "value",
85     offsetof(FlowValues, diffuse), 0},
86    {Rappture::SWITCH_BOOLEAN, "-hide", "boolean",
87     offsetof(FlowValues, isHidden), 0},
88    {Rappture::SWITCH_BOOLEAN, "-light2side", "boolean",
89     offsetof(FlowValues, twoSidedLighting), 0},
90    {Rappture::SWITCH_FLOAT, "-opacity", "value",
91     offsetof(FlowValues, opacity), 0},
92    {Rappture::SWITCH_BOOLEAN, "-outline", "boolean",
93     offsetof(FlowValues, showOutline), 0},
94    {Rappture::SWITCH_CUSTOM, "-position", "number",
95     offsetof(FlowValues, slicePos), 0, 0, &positionSwitch},
96    {Rappture::SWITCH_BOOLEAN, "-slice", "boolean",
97     offsetof(FlowValues, sliceVisible), 0},
98    {Rappture::SWITCH_FLOAT, "-specularExp", "value",
99     offsetof(FlowValues, specularExp), 0},
100    {Rappture::SWITCH_FLOAT, "-specularLevel", "value",
101     offsetof(FlowValues, specular), 0},
102    {Rappture::SWITCH_CUSTOM, "-transferfunction", "name",
103     offsetof(FlowValues, transferFunction), 0, 0, &transferFunctionSwitch},
104    {Rappture::SWITCH_BOOLEAN, "-volume", "boolean",
105     offsetof(FlowValues, showVolume), 0},
106    {Rappture::SWITCH_END}
107};
108
109Rappture::SwitchSpec FlowParticles::_switches[] = {
110    {Rappture::SWITCH_CUSTOM, "-axis", "axis",
111     offsetof(FlowParticlesValues, position.axis), 0, 0, &axisSwitch},
112    {Rappture::SWITCH_CUSTOM, "-color", "{r g b a}",
113     offsetof(FlowParticlesValues, color), 0, 0,  &colorSwitch},
114    {Rappture::SWITCH_BOOLEAN, "-hide", "boolean",
115     offsetof(FlowParticlesValues, isHidden), 0},
116    {Rappture::SWITCH_CUSTOM, "-position", "number",
117     offsetof(FlowParticlesValues, position), 0, 0, &positionSwitch},
118    {Rappture::SWITCH_FLOAT, "-size", "float",
119     offsetof(FlowParticlesValues, particleSize), 0},
120    {Rappture::SWITCH_END}
121};
122
123Rappture::SwitchSpec FlowBox::_switches[] = {
124    {Rappture::SWITCH_CUSTOM, "-color", "{r g b a}",
125     offsetof(FlowBoxValues, color), 0, 0,  &colorSwitch},
126    {Rappture::SWITCH_CUSTOM, "-corner1", "{x y z}",
127     offsetof(FlowBoxValues, corner1), 0, 0, &pointSwitch},
128    {Rappture::SWITCH_CUSTOM, "-corner2", "{x y z}",
129     offsetof(FlowBoxValues, corner2), 0, 0, &pointSwitch},
130    {Rappture::SWITCH_BOOLEAN, "-hide", "boolean",
131     offsetof(FlowBoxValues, isHidden), 0},
132    {Rappture::SWITCH_FLOAT, "-linewidth", "number",
133     offsetof(FlowBoxValues, lineWidth), 0},
134    {Rappture::SWITCH_END}
135};
136
137static int
138FlowDataFileOp(ClientData clientData, Tcl_Interp *interp, int objc,
139               Tcl_Obj *const *objv)
140{
141    Rappture::Outcome result;
142
143    const char *fileName;
144    fileName = Tcl_GetString(objv[3]);
145    TRACE("File: %s", fileName);
146
147    int nComponents;
148    if (Tcl_GetIntFromObj(interp, objv[4], &nComponents) != TCL_OK) {
149        return TCL_ERROR;
150    }
151    if ((nComponents < 1) || (nComponents > 4)) {
152        Tcl_AppendResult(interp, "bad # of components \"", 
153                         Tcl_GetString(objv[4]), "\"", (char *)NULL);
154        return TCL_ERROR;
155    }
156    Rappture::Buffer buf;
157    if (!buf.load(result, fileName)) {
158        Tcl_AppendResult(interp, "can't load data from \"", fileName, "\": ",
159                         result.remark(), (char *)NULL);
160        return TCL_ERROR;
161    }
162
163    Rappture::Unirect3d *dataPtr;
164    dataPtr = new Rappture::Unirect3d(nComponents);
165    Flow *flow = (Flow *)clientData;
166    size_t length = buf.size();
167    char *bytes = (char *)buf.bytes();
168    if ((length > 4) && (strncmp(bytes, "<DX>", 4) == 0)) {
169        if (!dataPtr->importDx(result, nComponents, length-4, bytes+4)) {
170            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
171            delete dataPtr;
172            return TCL_ERROR;
173        }
174    } else if ((length > 10) && (strncmp(bytes, "unirect3d ", 10) == 0)) {
175        if (dataPtr->parseBuffer(interp, buf) != TCL_OK) {
176            delete dataPtr;
177            return TCL_ERROR;
178        }
179    } else if ((length > 10) && (strncmp(bytes, "unirect2d ", 10) == 0)) {
180        Rappture::Unirect2d *u2dPtr;
181        u2dPtr = new Rappture::Unirect2d(nComponents);
182        if (u2dPtr->parseBuffer(interp, buf) != TCL_OK) {
183            delete u2dPtr;
184            return TCL_ERROR;
185        }
186        dataPtr->convert(u2dPtr);
187        delete u2dPtr;
188    } else {
189        TRACE("header is %.14s", buf.bytes());
190        if (!dataPtr->importDx(result, nComponents, length, bytes)) {
191            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
192            delete dataPtr;
193            return TCL_ERROR;
194        }
195    }
196    if (dataPtr->nValues() == 0) {
197        delete dataPtr;
198        Tcl_AppendResult(interp, "no data found in \"", fileName, "\"",
199                         (char *)NULL);
200        return TCL_ERROR;
201    }
202    flow->data(dataPtr);
203    Flow::updatePending = true;
204    NanoVis::eventuallyRedraw();
205    return TCL_OK;
206}
207
208/**
209 * $flow data follows nbytes nComponents
210 */
211static int
212FlowDataFollowsOp(ClientData clientData, Tcl_Interp *interp, int objc,
213                  Tcl_Obj *const *objv)
214{
215    Rappture::Outcome result;
216
217    TRACE("Enter");
218
219    int nBytes;
220    if (Tcl_GetIntFromObj(interp, objv[3], &nBytes) != TCL_OK) {
221        ERROR("Bad nBytes \"%s\"", Tcl_GetString(objv[3]));
222        return TCL_ERROR;
223    }
224    if (nBytes <= 0) {
225        Tcl_AppendResult(interp, "bad # bytes request \"", 
226                         Tcl_GetString(objv[3]), "\" for \"data follows\"", (char *)NULL);
227        ERROR("Bad nbytes %d", nBytes);
228        return TCL_ERROR;
229    }
230    int nComponents;
231    if (Tcl_GetIntFromObj(interp, objv[4], &nComponents) != TCL_OK) {
232        ERROR("Bad # of components \"%s\"", Tcl_GetString(objv[4]));
233        return TCL_ERROR;
234    }
235    if (nComponents <= 0) {
236        Tcl_AppendResult(interp, "bad # of components request \"", 
237                         Tcl_GetString(objv[4]), "\" for \"data follows\"", (char *)NULL);
238        ERROR("Bad # of components %d", nComponents);
239        return TCL_ERROR;
240    }
241    Rappture::Buffer buf(nBytes);
242    TRACE("Flow data loading bytes: %d components: %d", nBytes, nComponents);
243    if (GetDataStream(interp, buf, nBytes) != TCL_OK) {
244        return TCL_ERROR;
245    }
246    char *bytes = (char *)buf.bytes();
247    size_t length = buf.size();
248
249    Rappture::Unirect3d *unirect = NULL;
250    Volume *volume = NULL;
251
252    Flow *flow = (Flow *)clientData;
253    if ((length > 4) && (strncmp(bytes, "<DX>", 4) == 0)) {
254        unirect = new Rappture::Unirect3d(nComponents);
255        if (!unirect->importDx(result, nComponents, length - 4, bytes + 4)) {
256            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
257            delete unirect;
258            return TCL_ERROR;
259        }
260    } else if ((length > 10) && (strncmp(bytes, "unirect3d ", 10) == 0)) {
261        unirect = new Rappture::Unirect3d(nComponents);
262        if (unirect->parseBuffer(interp, buf) != TCL_OK) {
263            delete unirect;
264            return TCL_ERROR;
265        }
266    } else if ((length > 10) && (strncmp(bytes, "unirect2d ", 10) == 0)) {
267        unirect = new Rappture::Unirect3d(nComponents);
268        Rappture::Unirect2d *u2dPtr;
269        u2dPtr = new Rappture::Unirect2d(nComponents);
270        if (u2dPtr->parseBuffer(interp, buf) != TCL_OK) {
271            delete unirect;
272            delete u2dPtr;
273            return TCL_ERROR;
274        }
275        unirect->convert(u2dPtr);
276        delete u2dPtr;
277    } else if ((length > 14) && (strncmp(bytes, "# vtk DataFile", 14) == 0)) {
278        TRACE("VTK loading...");
279        std::stringstream fdata;
280        fdata.write(bytes, length);
281        if (length <= 0) {
282            ERROR("data buffer is empty");
283            abort();
284        }
285        Rappture::Outcome context;
286#ifdef USE_VTK
287        volume = load_vtk_volume_stream(context, flow->name(), bytes, length);
288#else
289        std::stringstream fdata;
290        fdata.write(bytes, nBytes);
291        volume = load_vtk_volume_stream(context, flow->name(), fdata);
292#endif
293        if (volume == NULL) {
294            Tcl_AppendResult(interp, context.remark(), (char*)NULL);
295            return TCL_ERROR;
296        }
297    } else {
298        TRACE("header is %.14s", buf.bytes());
299        unirect = new Rappture::Unirect3d(nComponents);
300        if (!unirect->importDx(result, nComponents, length, bytes)) {
301            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
302            delete unirect;
303            return TCL_ERROR;
304        }
305    }
306    if (unirect != NULL && unirect->nValues() == 0) {
307        delete unirect;
308        Tcl_AppendResult(interp, "no data found in stream", (char *)NULL);
309        return TCL_ERROR;
310    }
311    if (unirect != NULL) {
312        TRACE("nx = %d ny = %d nz = %d",
313              unirect->xNum(), unirect->yNum(), unirect->zNum());
314        TRACE("x0 = %lg y0 = %lg z0 = %lg",
315              unirect->xMin(), unirect->yMin(), unirect->zMin());
316        TRACE("lx = %lg ly = %lg lz = %lg",
317              unirect->xMax() - unirect->xMin(),
318              unirect->yMax() - unirect->yMin(),
319              unirect->zMax() - unirect->zMin());
320        TRACE("dx = %lg dy = %lg dz = %lg",
321              unirect->xNum() > 1 ? (unirect->xMax() - unirect->xMin())/(unirect->xNum()-1) : 0,
322              unirect->yNum() > 1 ? (unirect->yMax() - unirect->yMin())/(unirect->yNum()-1) : 0,
323              unirect->zNum() > 1 ? (unirect->zMax() - unirect->zMin())/(unirect->zNum()-1) : 0);
324        TRACE("magMin = %lg magMax = %lg",
325              unirect->magMin(), unirect->magMax());
326
327        flow->data(unirect);
328    } else {
329        flow->data(volume);
330    }
331    double range[2];
332    flow->getVectorRange(range);
333    {
334        char info[1024];
335        int length = 
336            sprintf(info, "nv>data tag %s min %g max %g\n",
337                    flow->name(), range[0], range[1]);
338#ifdef USE_THREADS
339        queueResponse(info, (size_t)length, Response::VOLATILE);
340#else
341        if (SocketWrite(info, (size_t)length) < 0) {
342            return TCL_ERROR;
343        }
344#endif
345    }
346    Flow::updatePending = true;
347    NanoVis::eventuallyRedraw();
348    return TCL_OK;
349}
350
351static Rappture::CmdSpec flowDataOps[] = {
352    {"file",    2, FlowDataFileOp,    5, 5, "fileName nComponents",},
353    {"follows", 2, FlowDataFollowsOp, 5, 5, "size nComponents",},
354};
355static int nFlowDataOps = NumCmdSpecs(flowDataOps);
356
357static int
358FlowDataOp(ClientData clientData, Tcl_Interp *interp, int objc,
359           Tcl_Obj *const *objv)
360{
361    Tcl_ObjCmdProc *proc;
362
363    proc = Rappture::GetOpFromObj(interp, nFlowDataOps, flowDataOps,
364                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
365    if (proc == NULL) {
366        return TCL_ERROR;
367    }
368    return (*proc) (clientData, interp, objc, objv);
369}
370
371/**
372 * \brief Convert a Tcl_Obj representing the label of a child node into its
373 * integer node id.
374 *
375 * \param clientData Flag indicating if the node is considered before or
376 * after the insertion position.
377 * \param interp Interpreter to send results back to
378 * \param switchName Not used
379 * \param objPtr String representation
380 * \param record Structure record
381 * \param offset Not used
382 * \param flags Not used
383 *
384 * \return The return value is a standard Tcl result.
385 */
386static int
387AxisSwitchProc(ClientData clientData, Tcl_Interp *interp,
388               const char *switchName, Tcl_Obj *objPtr,
389               char *record, int offset, int flags)
390{
391    const char *string = Tcl_GetString(objPtr);
392    if (string[1] == '\0') {
393        FlowSliceAxis *axisPtr = (FlowSliceAxis *)(record + offset);
394        char c;
395        c = tolower((unsigned char)string[0]);
396        if (c == 'x') {
397            *axisPtr = AXIS_X;
398            return TCL_OK;
399        } else if (c == 'y') {
400            *axisPtr = AXIS_Y;
401            return TCL_OK;
402        } else if (c == 'z') {
403            *axisPtr = AXIS_Z;
404            return TCL_OK;
405        }
406        /*FALLTHRU*/
407    }
408    Tcl_AppendResult(interp, "bad axis \"", string,
409                     "\": should be x, y, or z", (char*)NULL);
410    return TCL_ERROR;
411}
412
413/**
414 * \brief Convert a Tcl_Obj representing the label of a list of four color
415 * components in to a RGBA color value.
416 *
417 * \param clientData Flag indicating if the node is considered before or
418 * after the insertion position.
419 * \param interp Interpreter to send results back to
420 * \param switchName Not used
421 * \param objPtr String representation
422 * \param record Structure record
423 * \param offset Not used
424 * \param flags Not used
425 *
426 * \return The return value is a standard Tcl result.
427 */
428static int
429ColorSwitchProc(ClientData clientData, Tcl_Interp *interp,
430                const char *switchName, Tcl_Obj *objPtr,
431                char *record, int offset, int flags)
432{
433    Tcl_Obj **objv;
434    int objc;
435    FlowColor *color = (FlowColor *)(record + offset);
436
437    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
438        return TCL_ERROR;
439    }
440    if ((objc < 3) || (objc > 4)) {
441        Tcl_AppendResult(interp, "wrong # of elements in color definition",
442                         (char *)NULL);
443        return TCL_ERROR;
444    }
445    float values[4];
446    int i;
447    values[3] = 1.0f;
448    for (i = 0; i < objc; i++) {
449        float value;
450
451        if (GetFloatFromObj(interp, objv[i], &value) != TCL_OK) {
452            return TCL_ERROR;
453        }
454        if ((value < 0.0) || (value > 1.0)) {
455            Tcl_AppendResult(interp, "bad component value in \"",
456                             Tcl_GetString(objPtr), "\": color values must be [0..1]",
457                             (char *)NULL);
458            return TCL_ERROR;
459        }
460        values[i] = value;
461    }
462    color->r = values[0];
463    color->g = values[1];
464    color->b = values[2];
465    color->a = values[3];
466    return TCL_OK;
467}
468
469/**
470 * \brief Convert a Tcl_Obj representing the a 3-D coordinate into a point.
471 *
472 * \param clientData Flag indicating if the node is considered before or
473 * after the insertion position.
474 * \param interp Interpreter to send results back to
475 * \param switchName Not used
476 * \param objPtr String representation
477 * \param record Structure record
478 * \param offset Not used
479 * \param flags Not used
480 *
481 * \return The return value is a standard Tcl result.
482 */
483static int
484PointSwitchProc(ClientData clientData, Tcl_Interp *interp,
485                const char *switchName, Tcl_Obj *objPtr,
486                char *record, int offset, int flags)
487{
488    FlowPoint *point = (FlowPoint *)(record + offset);
489    int objc;
490    Tcl_Obj **objv;
491
492    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
493        return TCL_ERROR;
494    }
495    if (objc != 3) {
496        Tcl_AppendResult(interp, "wrong # of elements for box coordinates: "
497                         " should be \"x y z\"", (char *)NULL);
498        return TCL_ERROR;
499    }
500    float values[3];
501    int i;
502    for (i = 0; i < objc; i++) {
503        float value;
504
505        if (GetFloatFromObj(interp, objv[i], &value) != TCL_OK) {
506            return TCL_ERROR;
507        }
508        values[i] = value;
509    }
510    point->x = values[0];
511    point->y = values[1];
512    point->z = values[2];
513    return TCL_OK;
514}
515
516/**
517 * \brief Convert a Tcl_Obj representing the a 3-D coordinate into a point.
518 *
519 * \param clientData Flag indicating if the node is considered before or
520 * after the insertion position.
521 * \param interp Interpreter to send results back to
522 * \param switchName Not used
523 * \param objPtr String representation
524 * \param record Structure record
525 * \param offset Not used
526 * \param flags Not used
527 *
528 * \return The return value is a standard Tcl result.
529 */
530static int
531PositionSwitchProc(ClientData clientData, Tcl_Interp *interp,
532                   const char *switchName, Tcl_Obj *objPtr,
533                   char *record, int offset, int flags)
534{
535    FlowPosition *posPtr = (FlowPosition *)(record + offset);
536    const char *string;
537    char *p;
538
539    string = Tcl_GetString(objPtr);
540    p = strrchr((char *)string, '%');
541    if (p == NULL) {
542        float value;
543
544        if (GetFloatFromObj(interp, objPtr, &value) != TCL_OK) {
545            return TCL_ERROR;
546        }
547        posPtr->value = value;
548        posPtr->flags = ABSPOS;
549    } else {
550        double value;
551
552        *p = '\0';
553        if (Tcl_GetDouble(interp, string, &value) != TCL_OK) {
554            return TCL_ERROR;
555        }
556        posPtr->value = (float)value * 0.01;
557        posPtr->flags = RELPOS;
558    }
559    return TCL_OK;
560}
561
562/**
563 * \brief Convert a Tcl_Obj representing the transfer function into a
564 *  TransferFunction pointer. 
565 *
566 * The transfer function must have been previously defined.
567 *
568 * \param clientData Flag indicating if the node is considered before or
569 * after the insertion position.
570 * \param interp Interpreter to send results back to
571 * \param switchName Not used
572 * \param objPtr String representation
573 * \param record Structure record
574 * \param offset Not used
575 * \param flags Not used
576 *
577 * \return The return value is a standard Tcl result.
578 */
579static int
580TransferFunctionSwitchProc(ClientData clientData, Tcl_Interp *interp,
581                           const char *switchName, Tcl_Obj *objPtr,
582                           char *record, int offset, int flags)
583{
584    TransferFunction **funcPtrPtr = (TransferFunction **)(record + offset);
585    TransferFunction *tf = NanoVis::getTransferFunction(Tcl_GetString(objPtr));
586    if (tf == NULL) {
587        Tcl_AppendResult(interp, "transfer function \"", Tcl_GetString(objPtr),
588                         "\" is not defined", (char*)NULL);
589        return TCL_ERROR;
590    }
591    *funcPtrPtr = tf;
592    return TCL_OK;
593}
594
595static int
596FlowConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
597                Tcl_Obj *const *objv)
598{
599    Flow *flow = (Flow *)clientData;
600
601    if (flow->parseSwitches(interp, objc - 2, objv + 2) != TCL_OK) {
602        return TCL_ERROR;
603    }
604    if (flow->configure()) {
605        Flow::updatePending = true;
606    }
607    NanoVis::eventuallyRedraw();
608    return TCL_OK;
609}
610
611static int
612FlowParticlesAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
613                   Tcl_Obj *const *objv)
614{
615    Flow *flow = (Flow *)clientData;
616    const char *particlesName = Tcl_GetString(objv[3]);
617    FlowParticles *particles = flow->createParticles(particlesName);
618    if (particles == NULL) {
619        Tcl_AppendResult(interp, "Flow particle injection plane \"",
620                         particlesName,
621                         "\" already exists or could not be created",
622                         (char*)NULL);
623        return TCL_ERROR;
624    }
625    if (particles->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
626        flow->deleteParticles(particlesName);
627        return TCL_ERROR;
628    }
629    particles->configure();
630    Flow::updatePending = true;
631    NanoVis::eventuallyRedraw();
632    Tcl_SetObjResult(interp, objv[3]);
633    return TCL_OK;
634}
635
636static int
637FlowParticlesConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
638                         Tcl_Obj *const *objv)
639{
640    Flow *flow = (Flow *)clientData;
641    const char *particlesName = Tcl_GetString(objv[3]);
642    FlowParticles *particles = flow->getParticles(particlesName);
643    if (particles == NULL) {
644        Tcl_AppendResult(interp, "Flow particle injection plane \"",
645                         particlesName, "\" not found",
646                         (char*)NULL);
647        return TCL_ERROR;
648    }
649    if (particles->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
650        return TCL_ERROR;
651    }
652    if (particles->configure()) {
653        Flow::updatePending = true;
654    }
655    NanoVis::eventuallyRedraw();
656    return TCL_OK;
657}
658
659static int
660FlowParticlesDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
661                      Tcl_Obj *const *objv)
662{
663    Flow *flow = (Flow *)clientData;
664    for (int i = 3; i < objc; i++) {
665        flow->deleteParticles(Tcl_GetString(objv[i]));
666    }
667    NanoVis::eventuallyRedraw();
668    return TCL_OK;
669}
670
671static int
672FlowParticlesNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
673                     Tcl_Obj *const *objv)
674{
675    Flow *flow = (Flow *)clientData;
676    Tcl_Obj *listObjPtr;
677    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
678    std::vector<std::string> names;
679    flow->getParticlesNames(names);
680    for (std::vector<std::string>::iterator itr = names.begin();
681         itr != names.end(); ++itr) {
682        Tcl_Obj *objPtr = Tcl_NewStringObj(itr->c_str(), -1);
683        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
684    }
685    Tcl_SetObjResult(interp, listObjPtr);
686    return TCL_OK;
687}
688
689static Rappture::CmdSpec flowParticlesOps[] = {
690    {"add",        1, FlowParticlesAddOp,        4, 0, "name ?switches?",},
691    {"configure",  1, FlowParticlesConfigureOp,  4, 0, "name ?switches?",},
692    {"delete",     1, FlowParticlesDeleteOp,     4, 0, "name ?name...?"},
693    {"names",      1, FlowParticlesNamesOp,      3, 3, ""},
694};
695
696static int nFlowParticlesOps = NumCmdSpecs(flowParticlesOps);
697
698/**
699 * \brief This procedure is invoked to process commands on behalf of the flow
700 * object.
701 *
702 * Side effects: See the user documentation.
703 *
704 * $flow particles oper $name
705 *
706 * \return A standard Tcl result.
707 */
708static int
709FlowParticlesOp(ClientData clientData, Tcl_Interp *interp, int objc, 
710                Tcl_Obj *const *objv)
711{
712    Tcl_ObjCmdProc *proc;
713    proc = Rappture::GetOpFromObj(interp, nFlowParticlesOps, flowParticlesOps, 
714                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
715    if (proc == NULL) {
716        return TCL_ERROR;
717    }
718    Flow *flow = (Flow *)clientData;
719    Tcl_Preserve(flow);
720    int result;
721    result = (*proc) (clientData, interp, objc, objv);
722    Tcl_Release(flow);
723    return result;
724}
725
726static int
727FlowBoxAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
728             Tcl_Obj *const *objv)
729{
730    Flow *flow = (Flow *)clientData;
731    const char *boxName = Tcl_GetString(objv[3]);
732    FlowBox *box = flow->createBox(boxName);
733    if (box == NULL) {
734        Tcl_AppendResult(interp, "Flow box \"", boxName, 
735                         "\" already exists or could not be created",
736                         (char*)NULL);
737        return TCL_ERROR;
738    }
739    if (box->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
740        flow->deleteBox(boxName);
741        return TCL_ERROR;
742    }
743    NanoVis::eventuallyRedraw();
744    Tcl_SetObjResult(interp, objv[3]);
745    return TCL_OK;
746}
747
748static int
749FlowBoxConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
750                   Tcl_Obj *const *objv)
751{
752    Flow *flow = (Flow *)clientData;
753    const char *boxName = Tcl_GetString(objv[3]);
754    FlowBox *box = flow->getBox(boxName);
755    if (box == NULL) {
756        Tcl_AppendResult(interp, "Flow box \"", boxName, "\" not found",
757                         (char*)NULL);
758        return TCL_ERROR;
759    }
760    if (box->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
761        return TCL_ERROR;
762    }
763    NanoVis::eventuallyRedraw();
764    return TCL_OK;
765}
766
767static int
768FlowBoxDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
769                Tcl_Obj *const *objv)
770{
771    Flow *flow = (Flow *)clientData;
772    for (int i = 3; i < objc; i++) {
773        flow->deleteBox(Tcl_GetString(objv[i]));
774    }
775    NanoVis::eventuallyRedraw();
776    return TCL_OK;
777}
778
779static int
780FlowBoxNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
781               Tcl_Obj *const *objv)
782{
783    Flow *flow = (Flow *)clientData;
784    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
785    std::vector<std::string> names;
786    flow->getBoxNames(names);
787    for (std::vector<std::string>::iterator itr = names.begin();
788         itr != names.end(); ++itr) {
789        Tcl_Obj *objPtr = Tcl_NewStringObj(itr->c_str(), -1);
790        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
791    }
792    Tcl_SetObjResult(interp, listObjPtr);
793    return TCL_OK;
794}
795
796static Rappture::CmdSpec flowBoxOps[] = {
797    {"add",        1, FlowBoxAddOp,        4, 0, "name ?switches?",},
798    {"configure",  1, FlowBoxConfigureOp,  4, 0, "name ?switches?",},
799    {"delete",     1, FlowBoxDeleteOp,     4, 0, "name ?name...?"},
800    {"names",      1, FlowBoxNamesOp,      3, 3, ""},
801};
802
803static int nFlowBoxOps = NumCmdSpecs(flowBoxOps);
804
805/**
806 * \brief This procedure is invoked to process commands on behalf of the flow
807 *         object.
808 *
809 * Side effects:  See the user documentation.
810 *
811 * \return A standard Tcl result.
812 */
813static int
814FlowBoxOp(ClientData clientData, Tcl_Interp *interp, int objc, 
815          Tcl_Obj *const *objv)
816{
817    Tcl_ObjCmdProc *proc;
818    proc = Rappture::GetOpFromObj(interp, nFlowBoxOps, flowBoxOps, 
819                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
820    if (proc == NULL) {
821        return TCL_ERROR;
822    }
823    Flow *flow = (Flow *)clientData;
824    Tcl_Preserve(flow);
825    int result;
826    result = (*proc) (clientData, interp, objc, objv);
827    Tcl_Release(flow);
828    return result;
829}
830
831/*
832 * CLIENT COMMAND:
833 *   $flow legend <width> <height>
834 *
835 * Clients use this to generate a legend image for the specified
836 * transfer function.  The legend image is a color gradient from 0
837 * to one, drawn in the given transfer function.  The resulting image
838 * is returned in the size <width> x <height>.
839 */
840static int
841FlowLegendOp(ClientData clientData, Tcl_Interp *interp, int objc, 
842             Tcl_Obj *const *objv)
843{
844    Flow *flow = (Flow *)clientData;
845   
846    const char *string = Tcl_GetString(objv[1]);
847    TransferFunction *tf;
848    tf = flow->getTransferFunction();
849    if (tf == NULL) {
850        Tcl_AppendResult(interp, "unknown transfer function \"", string, "\"",
851                         (char*)NULL);
852        return TCL_ERROR;
853    }
854    const char *label;
855    label = Tcl_GetString(objv[0]);
856    int w, h;
857    if ((Tcl_GetIntFromObj(interp, objv[2], &w) != TCL_OK) ||
858        (Tcl_GetIntFromObj(interp, objv[3], &h) != TCL_OK)) {
859        return TCL_ERROR;
860    }
861    if (Flow::updatePending) {
862        NanoVis::setFlowRanges();
863    }
864    NanoVis::renderLegend(tf, Flow::magMin, Flow::magMax, w, h, label);
865    return TCL_OK;
866}
867
868static Rappture::CmdSpec flowInstOps[] = {
869    {"box",         1, FlowBoxOp,        2, 0, "oper ?args?"},
870    {"configure",   1, FlowConfigureOp,  2, 0, "?switches?"},
871    {"data",        1, FlowDataOp,       2, 0, "oper ?args?"},
872    {"legend",      1, FlowLegendOp,     4, 4, "w h"},
873    {"particles",   1, FlowParticlesOp,  2, 0, "oper ?args?"}
874};
875static int nFlowInstOps = NumCmdSpecs(flowInstOps);
876
877/**
878 * \brief This procedure is invoked to process commands on behalf of the flow
879 * object.
880 *
881 * Side effects: See the user documentation.
882 *
883 * \return A standard Tcl result.
884 */
885int
886nv::FlowInstObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
887                   Tcl_Obj *const *objv)
888{
889    Tcl_ObjCmdProc *proc;
890    proc = Rappture::GetOpFromObj(interp, nFlowInstOps, flowInstOps, 
891                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
892    if (proc == NULL) {
893        return TCL_ERROR;
894    }
895    assert(CheckGL(AT));
896    Flow *flow = (Flow *)clientData;
897    Tcl_Preserve(flow);
898    int result = (*proc) (clientData, interp, objc, objv);
899    Tcl_Release(flow);
900    return result;
901}
902
903/**
904 * \brief Deletes the command associated with the flow
905 *
906 * This is called only when the command associated with the flow is destroyed.
907 */
908void
909nv::FlowInstDeleteProc(ClientData clientData)
910{
911    Flow *flow = (Flow *)clientData;
912    NanoVis::deleteFlow(flow->name());
913}
914
915static int
916FlowAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
917          Tcl_Obj *const *objv)
918{
919    const char *name = Tcl_GetString(objv[2]);
920    Tcl_CmdInfo cmdInfo;
921    if (Tcl_GetCommandInfo(interp, name, &cmdInfo)) {
922        Tcl_AppendResult(interp, "an another command \"", name, 
923                         "\" already exists.", (char *)NULL);
924        return TCL_ERROR;
925    }
926    Flow *flow = NanoVis::createFlow(interp, name);
927    if (flow == NULL) {
928        Tcl_AppendResult(interp, "Flow \"", name, "\" already exists",
929                         (char*)NULL);
930        return TCL_ERROR;
931    }
932    if (flow->parseSwitches(interp, objc - 3, objv + 3) != TCL_OK) {
933        Tcl_DeleteCommand(interp, flow->name());
934        return TCL_ERROR;
935    }
936    Tcl_SetObjResult(interp, objv[2]);
937    NanoVis::eventuallyRedraw();
938    return TCL_OK;
939}
940
941static int
942FlowDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
943             Tcl_Obj *const *objv)
944{
945    for (int i = 2; i < objc; i++) {
946        Flow *flow = NanoVis::getFlow(Tcl_GetString(objv[i]));
947        if (flow != NULL) {
948            Tcl_DeleteCommandFromToken(interp, flow->getCommandToken());
949        }
950    }
951    Flow::updatePending = true;
952    NanoVis::eventuallyRedraw();
953    return TCL_OK;
954}
955
956static int
957FlowExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
958             Tcl_Obj *const *objv)
959{
960    bool value = false;
961    Flow *flow = NanoVis::getFlow(Tcl_GetString(objv[2]));
962    if (flow != NULL) {
963        value = true;
964    }
965    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), (int)value);
966    return TCL_OK;
967}
968
969/**
970 * \brief flow goto number
971 */
972static int
973FlowGotoOp(ClientData clientData, Tcl_Interp *interp, int objc,
974           Tcl_Obj *const *objv)
975{
976    int nSteps;
977    if (Tcl_GetIntFromObj(interp, objv[2], &nSteps) != TCL_OK) {
978        return TCL_ERROR;
979    }
980    if ((nSteps < 0) || (nSteps > SHRT_MAX)) {
981        Tcl_AppendResult(interp, "flow goto: bad # of steps \"",
982                         Tcl_GetString(objv[2]), "\"", (char *)NULL);
983        return TCL_ERROR;
984    }
985    NanoVis::resetFlows();
986    if (Flow::updatePending) {
987        NanoVis::setFlowRanges();
988    }
989    for (int i = 0; i < nSteps; i++) {
990        NanoVis::licRenderer->convolve();
991        NanoVis::advectFlows();
992    }
993    NanoVis::eventuallyRedraw();
994    return TCL_OK;
995}
996
997static int
998FlowNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
999            Tcl_Obj *const *objv)
1000{
1001    Tcl_Obj *listObjPtr;
1002    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
1003    for (NanoVis::FlowHashmap::iterator itr = NanoVis::flowTable.begin();
1004         itr != NanoVis::flowTable.end(); ++itr) {
1005        Flow *flow = itr->second;
1006        Tcl_Obj *objPtr = Tcl_NewStringObj(flow->name(), -1);
1007        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1008    }
1009    Tcl_SetObjResult(interp, listObjPtr);
1010    return TCL_OK;
1011}
1012
1013static int
1014FlowNextOp(ClientData clientData, Tcl_Interp *interp, int objc,
1015           Tcl_Obj *const *objv)
1016{
1017    assert(NanoVis::licRenderer != NULL);
1018    if (Flow::updatePending) {
1019        NanoVis::setFlowRanges();
1020    }
1021    NanoVis::licRenderer->convolve();
1022    NanoVis::advectFlows();
1023    NanoVis::eventuallyRedraw();
1024    return TCL_OK;
1025}
1026
1027static int
1028FlowResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
1029            Tcl_Obj *const *objv)
1030{
1031    NanoVis::resetFlows();
1032    return TCL_OK;
1033}
1034
1035#ifdef HAVE_FFMPEG
1036
1037/**
1038 * \brief Convert a Tcl_Obj representing the video format into its
1039 * integer id.
1040 *
1041 * \param clientData Not used
1042 * \param interp Interpreter to send results back to
1043 * \param switchName Not used
1044 * \param objPtr String representation
1045 * \param record Structure record
1046 * \param offset Not used
1047 * \param flags Not used
1048 *
1049 * \return The return value is a standard Tcl result.
1050 */
1051static int
1052VideoFormatSwitchProc(ClientData clientData, Tcl_Interp *interp,
1053                      const char *switchName, Tcl_Obj *objPtr,
1054                      char *record, int offset, int flags)
1055{
1056    Tcl_Obj **formatObjPtr = (Tcl_Obj **)(record + offset);
1057    Tcl_Obj *fmtObjPtr;
1058    const char *string;
1059    char c; 
1060    int length;
1061
1062    string = Tcl_GetStringFromObj(objPtr, &length);
1063    c = string[0];
1064    if ((c == 'm') && (length > 1) && 
1065        (strncmp(string, "mpeg", length) == 0)) {
1066        fmtObjPtr =  Tcl_NewStringObj("mpeg1video", 10);
1067    } else if ((c == 't') && (strncmp(string, "theora", length) == 0)) {
1068        fmtObjPtr =  Tcl_NewStringObj("theora", 6);
1069    } else if ((c == 'm') && (length > 1) &&
1070               (strncmp(string, "mov", length) == 0)) {
1071        fmtObjPtr =  Tcl_NewStringObj("mov", 3);
1072    } else {
1073        Tcl_AppendResult(interp, "bad video format \"", string,
1074                         "\": should be mpeg, theora, or mov", (char*)NULL);
1075        return TCL_ERROR;
1076    }
1077    if (*formatObjPtr != NULL) {
1078        Tcl_DecrRefCount(*formatObjPtr);
1079    }
1080    Tcl_IncrRefCount(fmtObjPtr);
1081    *formatObjPtr = fmtObjPtr;
1082    return TCL_OK;
1083}
1084
1085struct FlowVideoSwitches {
1086    float frameRate;         /**< Frame rate */
1087    int bitRate;             /**< Video bitrate */
1088    int width, height;       /**< Dimensions of video frame. */
1089    int numFrames;
1090    Tcl_Obj *formatObjPtr;
1091};
1092
1093static Rappture::SwitchParseProc VideoFormatSwitchProc;
1094static Rappture::SwitchCustom videoFormatSwitch = {
1095    VideoFormatSwitchProc, NULL, 0,
1096};
1097
1098static Rappture::SwitchSpec flowVideoSwitches[] = {
1099    {Rappture::SWITCH_INT, "-bitrate", "value",
1100     offsetof(FlowVideoSwitches, bitRate), 0},
1101    {Rappture::SWITCH_CUSTOM, "-format", "string",
1102     offsetof(FlowVideoSwitches, formatObjPtr), 0, 0, &videoFormatSwitch},
1103    {Rappture::SWITCH_FLOAT, "-framerate", "value",
1104     offsetof(FlowVideoSwitches, frameRate), 0},
1105    {Rappture::SWITCH_INT, "-height", "integer",
1106     offsetof(FlowVideoSwitches, height), 0},
1107    {Rappture::SWITCH_INT, "-numframes", "count",
1108     offsetof(FlowVideoSwitches, numFrames), 0},
1109    {Rappture::SWITCH_INT, "-width", "integer",
1110     offsetof(FlowVideoSwitches, width), 0},
1111    {Rappture::SWITCH_END}
1112};
1113
1114static bool
1115MakeImageFiles(char *tmpFileName,
1116               int width, int height, int numFrames,
1117               bool *cancelPtr)
1118{
1119    struct pollfd pollResults;
1120    pollResults.fd = fileno(nv::g_fIn);
1121    pollResults.events = POLLIN;
1122#define PENDING_TIMEOUT          10  /* milliseconds. */
1123    int timeout = PENDING_TIMEOUT;
1124
1125    int oldWidth, oldHeight;
1126    oldWidth = NanoVis::winWidth;
1127    oldHeight = NanoVis::winHeight;
1128
1129    if (width != oldWidth ||
1130        height != oldHeight) {
1131        // Resize to the requested size.
1132        NanoVis::resizeOffscreenBuffer(width, height);
1133    }
1134    NanoVis::resetFlows();
1135    *cancelPtr = false;
1136    bool result = true;
1137    size_t length = strlen(tmpFileName);
1138    for (int i = 1; i <= numFrames; i++) {
1139        if (((i & 0xF) == 0) && (poll(&pollResults, 1, timeout) > 0)) {
1140            /* If there's another command on stdin, that means the client is
1141             * trying to cancel this operation. */
1142            *cancelPtr = true;
1143            break;
1144        }
1145
1146        NanoVis::licRenderer->convolve();
1147        NanoVis::advectFlows();
1148
1149        int fboOrig;
1150        glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
1151
1152        NanoVis::bindOffscreenBuffer();
1153        NanoVis::render();
1154        NanoVis::readScreen();
1155
1156        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
1157
1158        sprintf(tmpFileName + length, "/image%d.ppm", i);
1159        result = nv::writePPMFile(tmpFileName, NanoVis::screenBuffer,
1160                                  width, height);
1161        if (!result) {
1162            break;
1163        }
1164    }
1165    if (width != oldWidth || 
1166        height != oldHeight) {
1167        NanoVis::resizeOffscreenBuffer(oldWidth, oldHeight);
1168    }
1169    tmpFileName[length] = '\0';
1170    NanoVis::resetFlows();
1171    return result;
1172}
1173
1174static int
1175MakeMovie(Tcl_Interp *interp, char *tmpFileName, const char *token,
1176          FlowVideoSwitches *switchesPtr)
1177{
1178#ifndef FFMPEG
1179#  define FFMPEG "/usr/bin/ffmpeg"
1180#endif
1181    /* Generate the movie from the frame images by exec-ing ffmpeg */
1182    /* The ffmpeg command is
1183     *   ffmpeg -f image2 -i /var/tmp/xxxxx/image%d.ppm                 \
1184     *      -b bitrate -f framerate /var/tmp/xxxxx/movie.mpeg
1185     */
1186    char cmd[BUFSIZ];
1187    sprintf(cmd, "%s -f image2 -i %s/image%%d.ppm -f %s -b %d -r %f -",
1188            FFMPEG, tmpFileName, Tcl_GetString(switchesPtr->formatObjPtr), 
1189            switchesPtr->bitRate, switchesPtr->frameRate);
1190    TRACE("Enter: %s", cmd);
1191    FILE *f;
1192    f = popen(cmd, "r");
1193    if (f == NULL) {
1194        Tcl_AppendResult(interp, "can't run ffmpeg: ", 
1195                         Tcl_PosixError(interp), (char *)NULL);
1196        return TCL_ERROR;
1197    }
1198    Rappture::Buffer data;
1199    size_t total = 0;
1200    for (;;) {
1201        ssize_t numRead;
1202        char buffer[BUFSIZ]; 
1203       
1204        numRead = fread(buffer, sizeof(unsigned char), BUFSIZ, f);
1205        total += numRead;
1206        if (numRead == 0) {             // EOF
1207            break;
1208        }
1209        if (numRead < 0) {              // Error
1210            ERROR("Can't read movie data: %s",
1211                  Tcl_PosixError(interp));
1212            Tcl_AppendResult(interp, "can't read movie data: ", 
1213                Tcl_PosixError(interp), (char *)NULL);
1214            return TCL_ERROR;
1215        }
1216        if (!data.append(buffer, numRead)) {
1217            ERROR("Can't append movie data to buffer %d bytes",
1218                  numRead);
1219            Tcl_AppendResult(interp, "can't append movie data to buffer",
1220                             (char *)NULL);
1221            return TCL_ERROR;
1222        }
1223    }
1224    if (data.size() == 0) {
1225        ERROR("ffmpeg returned 0 bytes");
1226    }
1227    // Send zero length to client so it can deal with error
1228    sprintf(cmd,"nv>image -type movie -token \"%s\" -bytes %lu\n", 
1229            token, (unsigned long)data.size());
1230    nv::sendDataToClient(cmd, data.bytes(), data.size());
1231    return TCL_OK;
1232}
1233
1234static int
1235FlowVideoOp(ClientData clientData, Tcl_Interp *interp, int objc, 
1236            Tcl_Obj *const *objv)
1237{
1238    FlowVideoSwitches switches;
1239    const char *token;
1240
1241    token = Tcl_GetString(objv[2]);
1242    switches.frameRate = 25.0f;                // Default frame rate 25 fps
1243    switches.bitRate = 6000000;                // Default video bit rate.
1244    switches.width = NanoVis::winWidth;
1245    switches.height = NanoVis::winHeight;
1246    switches.numFrames = 100;
1247    switches.formatObjPtr = Tcl_NewStringObj("mpeg1video", 10);
1248    Tcl_IncrRefCount(switches.formatObjPtr);
1249    if (Rappture::ParseSwitches(interp, flowVideoSwitches, 
1250                objc - 3, objv + 3, &switches, SWITCH_DEFAULTS) < 0) {
1251        return TCL_ERROR;
1252    }
1253    if ((switches.width < 0) || (switches.width > SHRT_MAX) || 
1254        (switches.height < 0) || (switches.height > SHRT_MAX)) {
1255        Tcl_AppendResult(interp, "bad dimensions for video", (char *)NULL);
1256        return TCL_ERROR;
1257    }
1258    if ((switches.frameRate < 0.0f) || (switches.frameRate > 30.0f)) {
1259        Tcl_AppendResult(interp, "bad frame rate.", (char *)NULL);
1260        return TCL_ERROR;
1261    }
1262    if (switches.bitRate < 0) {
1263        Tcl_AppendResult(interp, "bad bit rate.", (char *)NULL);
1264        return TCL_ERROR;
1265    }
1266    if (NanoVis::licRenderer == NULL) {
1267        Tcl_AppendResult(interp, "no lic renderer.", (char *)NULL);
1268        return TCL_ERROR;
1269    }
1270    TRACE("FLOW started");
1271
1272    char *tmpFileName;
1273    char nameTemplate[200];
1274    strcpy(nameTemplate,"/var/tmp/flowXXXXXX");
1275    tmpFileName = mkdtemp(nameTemplate);
1276    int result = TCL_OK;
1277    if (tmpFileName == NULL) {
1278        Tcl_AppendResult(interp, "can't create temporary directory \"",
1279                         nameTemplate, "\" for frame image files: ", 
1280                         Tcl_PosixError(interp), (char *)NULL);
1281        return TCL_ERROR;
1282    }
1283    size_t length = strlen(tmpFileName);
1284    bool canceled = false;
1285    if (MakeImageFiles(tmpFileName,
1286                       switches.width, switches.height, switches.numFrames,
1287                       &canceled)) {
1288        result = TCL_OK;
1289    } else {
1290        result = TCL_ERROR;
1291    }
1292    if ((result == TCL_OK) && (!canceled)) {
1293        result = MakeMovie(interp, tmpFileName, token, &switches);
1294    }
1295    for (int i = 1; i <= switches.numFrames; i++) {
1296        sprintf(tmpFileName + length, "/image%d.ppm", i);
1297        unlink(tmpFileName);
1298    }
1299    tmpFileName[length] = '\0';
1300    rmdir(tmpFileName);
1301    Rappture::FreeSwitches(flowVideoSwitches, &switches, 0);
1302    return result;
1303}
1304#else
1305/**
1306 *  Not implemented
1307 */
1308static int
1309FlowVideoOp(ClientData clientData, Tcl_Interp *interp, int objc, 
1310            Tcl_Obj *const *objv)
1311{
1312    return TCL_OK;
1313}
1314#endif /* HAVE_FFMPEG */
1315
1316static Rappture::CmdSpec flowCmdOps[] = {
1317    {"add",      1, FlowAddOp,     3, 0, "name ?option value...?",},
1318    {"delete",   1, FlowDeleteOp,  3, 0, "name ?name...?",},
1319    {"exists",   1, FlowExistsOp,  3, 3, "name",},
1320    {"goto",     1, FlowGotoOp,    3, 3, "nSteps",},
1321    {"names",    1, FlowNamesOp,   2, 2, "",},
1322    {"next",     2, FlowNextOp,    2, 2, "",},
1323    {"reset",    1, FlowResetOp,   2, 2, "",},
1324    {"video",    1, FlowVideoOp,   3, 0, "token ?switches...?",},
1325};
1326static int nFlowCmdOps = NumCmdSpecs(flowCmdOps);
1327
1328static int
1329FlowCmdProc(ClientData clientData, Tcl_Interp *interp, int objc,
1330            Tcl_Obj *const *objv)
1331{
1332    Tcl_ObjCmdProc *proc;
1333
1334    proc = Rappture::GetOpFromObj(interp, nFlowCmdOps, flowCmdOps, 
1335                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
1336    if (proc == NULL) {
1337        return TCL_ERROR;
1338    }
1339    return (*proc) (clientData, interp, objc, objv);
1340}
1341
1342/**
1343 *\brief This procedure is invoked to initialize the "flow" command.
1344 *
1345 * Side effects:
1346 *    Creates the new command and adds a new entry into a global Tcl
1347 *    associative array.
1348 */
1349void
1350nv::FlowCmdInitProc(Tcl_Interp *interp, ClientData clientData)
1351{
1352    Tcl_CreateObjCommand(interp, "flow", FlowCmdProc, clientData, NULL);
1353}
Note: See TracBrowser for help on using the repository browser.