source: nanovis/tags/1.1.2/FlowCmd.cpp @ 4937

Last change on this file since 4937 was 4613, checked in by ldelgass, 10 years ago

merge r3598 from trunk

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