source: branches/blt4/packages/vizservers/nanovis/FlowCmd.cpp @ 3892

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