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

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

Fix for flow video capture

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