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

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

Preallocate full size Rappture buffer for data payloads

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