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

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

Use nv namespace for classes in nanovis rather than prefixing class names with
Nv (still need to convert shader classes).

  • 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 "LIC.h"
40#include "Unirect.h"
41#include "VelocityArrowsSlice.h"
42#include "Volume.h"
43#include "Trace.h"
44
45using namespace nv;
46using namespace vrmath;
47
48static Rappture::SwitchParseProc AxisSwitchProc;
49static Rappture::SwitchCustom axisSwitch = {
50    AxisSwitchProc, NULL, 0,
51};
52
53static Rappture::SwitchParseProc ColorSwitchProc;
54static Rappture::SwitchCustom colorSwitch = {
55    ColorSwitchProc, NULL, 0,
56};
57
58static Rappture::SwitchParseProc PointSwitchProc;
59static Rappture::SwitchCustom pointSwitch = {
60    PointSwitchProc, NULL, 0,
61};
62
63static Rappture::SwitchParseProc PositionSwitchProc;
64static Rappture::SwitchCustom positionSwitch = {
65    PositionSwitchProc, NULL, 0,
66};
67
68static Rappture::SwitchParseProc TransferFunctionSwitchProc;
69static Rappture::SwitchCustom transferFunctionSwitch = {
70    TransferFunctionSwitchProc, NULL, 0,
71};
72
73Rappture::SwitchSpec Flow::_switches[] = {
74    {Rappture::SWITCH_FLOAT, "-ambient", "value",
75     offsetof(FlowValues, ambient), 0},
76    {Rappture::SWITCH_BOOLEAN, "-arrows", "boolean",
77     offsetof(FlowValues, showArrows), 0},
78    {Rappture::SWITCH_CUSTOM, "-axis", "axis",
79     offsetof(FlowValues, slicePos.axis), 0, 0, &axisSwitch},
80    {Rappture::SWITCH_FLOAT, "-diffuse", "value",
81     offsetof(FlowValues, diffuse), 0},
82    {Rappture::SWITCH_BOOLEAN, "-hide", "boolean",
83     offsetof(FlowValues, isHidden), 0},
84    {Rappture::SWITCH_BOOLEAN, "-light2side", "boolean",
85     offsetof(FlowValues, twoSidedLighting), 0},
86    {Rappture::SWITCH_FLOAT, "-opacity", "value",
87     offsetof(FlowValues, opacity), 0},
88    {Rappture::SWITCH_BOOLEAN, "-outline", "boolean",
89     offsetof(FlowValues, showOutline), 0},
90    {Rappture::SWITCH_CUSTOM, "-position", "number",
91     offsetof(FlowValues, slicePos), 0, 0, &positionSwitch},
92    {Rappture::SWITCH_BOOLEAN, "-slice", "boolean",
93     offsetof(FlowValues, sliceVisible), 0},
94    {Rappture::SWITCH_FLOAT, "-specularExp", "value",
95     offsetof(FlowValues, specularExp), 0},
96    {Rappture::SWITCH_FLOAT, "-specularLevel", "value",
97     offsetof(FlowValues, specular), 0},
98    {Rappture::SWITCH_CUSTOM, "-transferfunction", "name",
99     offsetof(FlowValues, transferFunction), 0, 0, &transferFunctionSwitch},
100    {Rappture::SWITCH_BOOLEAN, "-volume", "boolean",
101     offsetof(FlowValues, showVolume), 0},
102    {Rappture::SWITCH_END}
103};
104
105Rappture::SwitchSpec FlowParticles::_switches[] = {
106    {Rappture::SWITCH_CUSTOM, "-axis", "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
842nv::FlowInstObjCmd(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
865nv::FlowInstDeleteProc(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
1309nv::FlowCmdInitProc(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.