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

Last change on this file since 3630 was 3630, checked in by ldelgass, 7 years ago

Nanovis refactoring to fix problems with scaling and multiple results.
Do rendering in world space to properly place and scale multiple data sets.
Also fix flows to reduce resets of animations. More work toward removing
Cg dependency. Fix panning to convert viewport coords to world coords.

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