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

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

Preallocate full size Rappture buffer for data payloads

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