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

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

merge r3597 from trunk

  • 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    }
[1429]233    Rappture::Buffer buf;
[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    }
[1478]238    Rappture::Unirect3d *dataPtr;
239    dataPtr = new Rappture::Unirect3d(nComponents);
240
[4612]241    Flow *flow = (Flow *)clientData;
[1446]242    size_t length = buf.size();
243    char *bytes = (char *)buf.bytes();
244    if ((length > 4) && (strncmp(bytes, "<DX>", 4) == 0)) {
[2922]245        if (!dataPtr->importDx(result, nComponents, length - 4, bytes + 4)) {
[2875]246            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
247            delete dataPtr;
248            return TCL_ERROR;
249        }
[1462]250    } else if ((length > 10) && (strncmp(bytes, "unirect3d ", 10) == 0)) {
[2922]251        if (dataPtr->parseBuffer(interp, buf) != TCL_OK) {
[2875]252            delete dataPtr;
253            return TCL_ERROR;
254        }
[1462]255    } else if ((length > 10) && (strncmp(bytes, "unirect2d ", 10) == 0)) {
[2875]256        Rappture::Unirect2d *u2dPtr;
257        u2dPtr = new Rappture::Unirect2d(nComponents);
[2922]258        if (u2dPtr->parseBuffer(interp, buf) != TCL_OK) {
[2875]259            delete u2dPtr;
260            return TCL_ERROR;
261        }
[2922]262        dataPtr->convert(u2dPtr);
[2875]263        delete u2dPtr;
[1429]264    } else {
[3564]265        TRACE("header is %.14s", buf.bytes());
[2922]266        if (!dataPtr->importDx(result, nComponents, length, bytes)) {
[2875]267            Tcl_AppendResult(interp, result.remark(), (char *)NULL);
268            delete dataPtr;
269            return TCL_ERROR;
270        }
[1429]271    }
[1510]272    if (dataPtr->nValues() == 0) {
[2875]273        delete dataPtr;
274        Tcl_AppendResult(interp, "no data found in stream", (char *)NULL);
275        return TCL_ERROR;
[1510]276    }
[3565]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",
[3564]282          dataPtr->xMax() - dataPtr->xMin(),
283          dataPtr->yMax() - dataPtr->yMin(),
284          dataPtr->zMax() - dataPtr->zMin());
[3565]285    TRACE("dx = %lg dy = %lg dz = %lg",
[3564]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);
[3565]289    TRACE("magMin = %lg magMax = %lg",
[3564]290          dataPtr->magMin(), dataPtr->magMax());
[4612]291    flow->data(dataPtr);
[1431]292    {
293        char info[1024];
[2875]294        ssize_t nWritten;
295        size_t length;
[1431]296
[3476]297        length = sprintf(info, "nv>data tag %s min %g max %g\n",
[4612]298                         flow->name(), dataPtr->magMin(), dataPtr->magMax());
[2372]299        nWritten  = write(1, info, length);
[2875]300        assert(nWritten == (ssize_t)strlen(info));
[1431]301    }
[2877]302    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
[1429]303    return TCL_OK;
304}
305
306static Rappture::CmdSpec flowDataOps[] = {
[1434]307    {"file",    2, FlowDataFileOp,    5, 5, "fileName nComponents",},
308    {"follows", 2, FlowDataFollowsOp, 5, 5, "size nComponents",},
[1429]309};
310static int nFlowDataOps = NumCmdSpecs(flowDataOps);
311
312static int
313FlowDataOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]314           Tcl_Obj *const *objv)
[1429]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
[3567]326/**
[2875]327 * \brief Convert a Tcl_Obj representing the label of a child node into its
328 * integer node id.
[1429]329 *
[2875]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
[1429]338 *
[2875]339 * \return The return value is a standard Tcl result.
[1429]340 */
341static int
[2875]342AxisSwitchProc(ClientData clientData, Tcl_Interp *interp,
343               const char *switchName, Tcl_Obj *objPtr,
344               char *record, int offset, int flags)
[1429]345{
346    const char *string = Tcl_GetString(objPtr);
347    if (string[1] == '\0') {
[4612]348        Flow::SliceAxis *axisPtr = (Flow::SliceAxis *)(record + offset);
[1429]349        char c;
350        c = tolower((unsigned char)string[0]);
351        if (c == 'x') {
[4612]352            *axisPtr = Flow::AXIS_X;
[1429]353            return TCL_OK;
354        } else if (c == 'y') {
[4612]355            *axisPtr = Flow::AXIS_Y;
[1429]356            return TCL_OK;
357        } else if (c == 'z') {
[4612]358            *axisPtr = Flow::AXIS_Z;
[1429]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
[2875]368/**
369 * \brief Convert a Tcl_Obj representing the label of a list of four color
370 * components in to a RGBA color value.
[1429]371 *
[2875]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.
[1429]382 */
383static int
[2875]384ColorSwitchProc(ClientData clientData, Tcl_Interp *interp,
385                const char *switchName, Tcl_Obj *objPtr,
386                char *record, int offset, int flags)
[1429]387{
388    Tcl_Obj **objv;
389    int objc;
[3567]390    FlowColor *color = (FlowColor *)(record + offset);
[1429]391
392    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
[2875]393        return TCL_ERROR;
[1429]394    }
395    if ((objc < 3) || (objc > 4)) {
[2875]396        Tcl_AppendResult(interp, "wrong # of elements in color definition",
397                         (char *)NULL);
398        return TCL_ERROR;
[1429]399    }
400    float values[4];
401    int i;
402    values[3] = 1.0f;
403    for (i = 0; i < objc; i++) {
[2875]404        float value;
[1429]405
[2875]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    }
[3567]417    color->r = values[0];
418    color->g = values[1];
419    color->b = values[2];
420    color->a = values[3];
[1429]421    return TCL_OK;
422}
423
[2875]424/**
425 * \brief Convert a Tcl_Obj representing the a 3-D coordinate into a point.
[1429]426 *
[2875]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
[1429]435 *
[2875]436 * \return The return value is a standard Tcl result.
[1429]437 */
438static int
[2875]439PointSwitchProc(ClientData clientData, Tcl_Interp *interp,
440                const char *switchName, Tcl_Obj *objPtr,
441                char *record, int offset, int flags)
[1429]442{
[3567]443    FlowPoint *point = (FlowPoint *)(record + offset);
[1429]444    int objc;
445    Tcl_Obj **objv;
446
447    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
[2875]448        return TCL_ERROR;
[1429]449    }
450    if (objc != 3) {
[2875]451        Tcl_AppendResult(interp, "wrong # of elements for box coordinates: "
452                         " should be \"x y z\"", (char *)NULL);
453        return TCL_ERROR;
[1429]454    }
455    float values[3];
456    int i;
457    for (i = 0; i < objc; i++) {
[2875]458        float value;
[1429]459
[2875]460        if (GetFloatFromObj(interp, objv[i], &value) != TCL_OK) {
461            return TCL_ERROR;
462        }
463        values[i] = value;
464    }
[3567]465    point->x = values[0];
466    point->y = values[1];
467    point->z = values[2];
[1429]468    return TCL_OK;
469}
470
[2875]471/**
472 * \brief Convert a Tcl_Obj representing the a 3-D coordinate into a point.
[1429]473 *
[2875]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
[1429]482 *
[2875]483 * \return The return value is a standard Tcl result.
[1429]484 */
485static int
[2875]486PositionSwitchProc(ClientData clientData, Tcl_Interp *interp,
487                   const char *switchName, Tcl_Obj *objPtr,
488                   char *record, int offset, int flags)
[1429]489{
490    FlowPosition *posPtr = (FlowPosition *)(record + offset);
491    const char *string;
492    char *p;
493
494    string = Tcl_GetString(objPtr);
[1571]495    p = strrchr((char *)string, '%');
[1429]496    if (p == NULL) {
[2875]497        float value;
[1429]498
[2875]499        if (GetFloatFromObj(interp, objPtr, &value) != TCL_OK) {
500            return TCL_ERROR;
501        }
502        posPtr->value = value;
503        posPtr->flags = ABSPOS;
[1429]504    } else {
[2875]505        double value;
[1429]506
[2875]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;
[1429]513    }
514    return TCL_OK;
515}
516
[2875]517/**
518 * \brief Convert a Tcl_Obj representing the transfer function into a
519 *  TransferFunction pointer. 
[1429]520 *
[2875]521 * The transfer function must have been previously defined.
[1429]522 *
[2875]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
[1429]531 *
[2875]532 * \return The return value is a standard Tcl result.
[1429]533 */
534static int
[2875]535TransferFunctionSwitchProc(ClientData clientData, Tcl_Interp *interp,
536                           const char *switchName, Tcl_Obj *objPtr,
537                           char *record, int offset, int flags)
[1429]538{
539    TransferFunction **funcPtrPtr = (TransferFunction **)(record + offset);
[3567]540    TransferFunction *tf = NanoVis::getTransferFunction(Tcl_GetString(objPtr));
541    if (tf == NULL) {
[1429]542        Tcl_AppendResult(interp, "transfer function \"", Tcl_GetString(objPtr),
543                         "\" is not defined", (char*)NULL);
544        return TCL_ERROR;
545    }
[3567]546    *funcPtrPtr = tf;
[1429]547    return TCL_OK;
548}
549
550static int
551FlowConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]552                Tcl_Obj *const *objv)
[1429]553{
[4612]554    Flow *flow = (Flow *)clientData;
[1429]555
[4612]556    if (flow->parseSwitches(interp, objc - 2, objv + 2) != TCL_OK) {
[2875]557        return TCL_ERROR;
[1429]558    }
[2877]559    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
[1429]560    return TCL_OK;
561}
562
563static int
564FlowParticlesAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]565                   Tcl_Obj *const *objv)
[1429]566{
[4612]567    Flow *flow = (Flow *)clientData;
[3567]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);
[2875]575        return TCL_ERROR;
[1429]576    }
[3567]577    if (particles->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
578        flow->deleteParticles(particlesName);
[2875]579        return TCL_ERROR;
[1429]580    }
[3567]581    particles->configure();
[2877]582    NanoVis::eventuallyRedraw();
[1429]583    Tcl_SetObjResult(interp, objv[3]);
584    return TCL_OK;
585}
586
587static int
588FlowParticlesConfigureOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]589                         Tcl_Obj *const *objv)
[1429]590{
[4612]591    Flow *flow = (Flow *)clientData;
[3567]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);
[2875]598        return TCL_ERROR;
[1429]599    }
[3567]600    if (particles->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
[2875]601        return TCL_ERROR;
[1429]602    }
[3567]603    particles->configure();
[2877]604    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
[1429]605    return TCL_OK;
606}
607
608static int
609FlowParticlesDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]610                      Tcl_Obj *const *objv)
[1429]611{
[4612]612    Flow *flow = (Flow *)clientData;
[3567]613    for (int i = 3; i < objc; i++) {
614        flow->deleteParticles(Tcl_GetString(objv[i]));
[1429]615    }
[2877]616    NanoVis::eventuallyRedraw();
[1429]617    return TCL_OK;
618}
619
620static int
621FlowParticlesNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]622                     Tcl_Obj *const *objv)
[1429]623{
[4612]624    Flow *flow = (Flow *)clientData;
[1429]625    Tcl_Obj *listObjPtr;
626    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
[3567]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);
[2875]632        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
[1429]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?",},
[3567]641    {"delete",     1, FlowParticlesDeleteOp,     4, 0, "name ?name...?"},
642    {"names",      1, FlowParticlesNamesOp,      3, 3, ""},
[1429]643};
644
645static int nFlowParticlesOps = NumCmdSpecs(flowParticlesOps);
646
[2875]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 */
[1429]657static int
658FlowParticlesOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]659                Tcl_Obj *const *objv)
[1429]660{
661    Tcl_ObjCmdProc *proc;
662    proc = Rappture::GetOpFromObj(interp, nFlowParticlesOps, flowParticlesOps,
[2875]663                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
[1429]664    if (proc == NULL) {
[2875]665        return TCL_ERROR;
[1429]666    }
[4612]667    Flow *flow = (Flow *)clientData;
668    Tcl_Preserve(flow);
[1429]669    int result;
670    result = (*proc) (clientData, interp, objc, objv);
[4612]671    Tcl_Release(flow);
[1429]672    return result;
673}
674
675static int
676FlowBoxAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]677             Tcl_Obj *const *objv)
[1429]678{
[4612]679    Flow *flow = (Flow *)clientData;
[3567]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);
[2875]686        return TCL_ERROR;
[1429]687    }
[3567]688    if (box->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
689        flow->deleteBox(boxName);
[2875]690        return TCL_ERROR;
[1429]691    }
[3567]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{
[4612]701    Flow *flow = (Flow *)clientData;
[3567]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);
[2875]707        return TCL_ERROR;
[1429]708    }
[3567]709    if (box->parseSwitches(interp, objc - 4, objv + 4) != TCL_OK) {
710        return TCL_ERROR;
711    }
[2877]712    NanoVis::eventuallyRedraw();
[1429]713    return TCL_OK;
714}
715
716static int
717FlowBoxDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]718                Tcl_Obj *const *objv)
[1429]719{
[4612]720    Flow *flow = (Flow *)clientData;
[3567]721    for (int i = 3; i < objc; i++) {
722        flow->deleteBox(Tcl_GetString(objv[i]));
[1429]723    }
[2877]724    NanoVis::eventuallyRedraw();
[1429]725    return TCL_OK;
726}
727
728static int
729FlowBoxNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]730               Tcl_Obj *const *objv)
[1429]731{
[4612]732    Flow *flow = (Flow *)clientData;
[3567]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);
[2875]739        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
[1429]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?",},
[3567]748    {"delete",     1, FlowBoxDeleteOp,     4, 0, "name ?name...?"},
749    {"names",      1, FlowBoxNamesOp,      3, 3, ""},
[1429]750};
751
752static int nFlowBoxOps = NumCmdSpecs(flowBoxOps);
753
[2875]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 */
[1429]762static int
763FlowBoxOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]764          Tcl_Obj *const *objv)
[1429]765{
766    Tcl_ObjCmdProc *proc;
767    proc = Rappture::GetOpFromObj(interp, nFlowBoxOps, flowBoxOps,
[2875]768                                  Rappture::CMDSPEC_ARG2, objc, objv, 0);
[1429]769    if (proc == NULL) {
[2875]770        return TCL_ERROR;
[1429]771    }
[4612]772    Flow *flow = (Flow *)clientData;
773    Tcl_Preserve(flow);
[1429]774    int result;
775    result = (*proc) (clientData, interp, objc, objv);
[4612]776    Tcl_Release(flow);
[1429]777    return result;
778}
779
780/*
[1474]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,
[2875]791             Tcl_Obj *const *objv)
[1474]792{
[4612]793    Flow *flow = (Flow *)clientData;
[1474]794   
795    const char *string = Tcl_GetString(objv[1]);
796    TransferFunction *tf;
[4612]797    tf = flow->getTransferFunction();
[1474]798    if (tf == NULL) {
799        Tcl_AppendResult(interp, "unknown transfer function \"", string, "\"",
[2875]800                         (char*)NULL);
[1474]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    }
[1510]810    if (NanoVis::flags & NanoVis::MAP_FLOWS) {
[3566]811        NanoVis::mapFlows();
[1474]812    }
[2877]813    NanoVis::renderLegend(tf, NanoVis::magMin, NanoVis::magMax, w, h, label);
[1474]814    return TCL_OK;
815}
816
[1429]817static Rappture::CmdSpec flowInstOps[] = {
818    {"box",         1, FlowBoxOp,        2, 0, "oper ?args?"},
819    {"configure",   1, FlowConfigureOp,  2, 0, "?switches?"},
[2875]820    {"data",        1, FlowDataOp,       2, 0, "oper ?args?"},
[1474]821    {"legend",      1, FlowLegendOp,     4, 4, "w h"},
[1429]822    {"particles",   1, FlowParticlesOp,  2, 0, "oper ?args?"}
823};
824static int nFlowInstOps = NumCmdSpecs(flowInstOps);
825
[2875]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 */
[4612]834int
[1429]835FlowInstObjCmd(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]836               Tcl_Obj *const *objv)
[1429]837{
838    Tcl_ObjCmdProc *proc;
839    proc = Rappture::GetOpFromObj(interp, nFlowInstOps, flowInstOps,
[2875]840                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
[1429]841    if (proc == NULL) {
[2875]842        return TCL_ERROR;
[1429]843    }
844    assert(CheckGL(AT));
[4612]845    Flow *flow = (Flow *)clientData;
[3567]846    Tcl_Preserve(flow);
847    int result = (*proc) (clientData, interp, objc, objv);
848    Tcl_Release(flow);
[1429]849    return result;
850}
851
[2875]852/**
[3567]853 * \brief Deletes the command associated with the flow
[1429]854 *
[3567]855 * This is called only when the command associated with the flow is destroyed.
[1429]856 */
[4612]857void
[1429]858FlowInstDeleteProc(ClientData clientData)
859{
[4612]860    Flow *flow = (Flow *)clientData;
[3567]861    NanoVis::deleteFlow(flow->name());
[1429]862}
863
864static int
865FlowAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]866          Tcl_Obj *const *objv)
[1429]867{
[3567]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);
[3576]873        return TCL_ERROR;
[1429]874    }
[4612]875    Flow *flow = NanoVis::createFlow(interp, name);
[3567]876    if (flow == NULL) {
877        Tcl_AppendResult(interp, "Flow \"", name, "\" already exists",
878                         (char*)NULL);
[2875]879        return TCL_ERROR;
[1429]880    }
[3567]881    if (flow->parseSwitches(interp, objc - 3, objv + 3) != TCL_OK) {
882        Tcl_DeleteCommand(interp, flow->name());
[2875]883        return TCL_ERROR;
[1429]884    }
885    Tcl_SetObjResult(interp, objv[2]);
[2877]886    NanoVis::eventuallyRedraw();
[1429]887    return TCL_OK;
888}
889
890static int
891FlowDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]892             Tcl_Obj *const *objv)
[1429]893{
[3567]894    for (int i = 2; i < objc; i++) {
[4612]895        Flow *flow = NanoVis::getFlow(Tcl_GetString(objv[i]));
[3567]896        if (flow != NULL) {
897            Tcl_DeleteCommandFromToken(interp, flow->getCommandToken());
[2875]898        }
[1429]899    }
[2877]900    NanoVis::eventuallyRedraw(NanoVis::MAP_FLOWS);
[1429]901    return TCL_OK;
902}
903
[1431]904static int
905FlowExistsOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]906             Tcl_Obj *const *objv)
[1431]907{
[3567]908    bool value = false;
[4612]909    Flow *flow = NanoVis::getFlow(Tcl_GetString(objv[2]));
[3567]910    if (flow != NULL) {
[2875]911        value = true;
[1431]912    }
913    Tcl_SetBooleanObj(Tcl_GetObjResult(interp), (int)value);
914    return TCL_OK;
915}
916
[2875]917/**
918 * \brief flow goto number
[1467]919 */
920static int
921FlowGotoOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]922           Tcl_Obj *const *objv)
[1467]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)) {
[2875]929        Tcl_AppendResult(interp, "flow goto: bad # of steps \"",
930                         Tcl_GetString(objv[2]), "\"", (char *)NULL);
931        return TCL_ERROR;
[1467]932    }
[3566]933    NanoVis::resetFlows();
[1467]934    if (NanoVis::flags & NanoVis::MAP_FLOWS) {
[3566]935        NanoVis::mapFlows();
[1467]936    }
[3566]937    NanoVis::advectFlows();
[3567]938    for (int i = 0; i < nSteps; i++) {
[2875]939        if (NanoVis::licRenderer->active()) {
940            NanoVis::licRenderer->convolve();
941        }
[3566]942        NanoVis::advectFlows();
[1467]943    }
[2877]944    NanoVis::eventuallyRedraw();
[1467]945    return TCL_OK;
946}
947
[1429]948static int
949FlowNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]950            Tcl_Obj *const *objv)
[1429]951{
952    Tcl_Obj *listObjPtr;
953    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
[3567]954    for (NanoVis::FlowHashmap::iterator itr = NanoVis::flowTable.begin();
955         itr != NanoVis::flowTable.end(); ++itr) {
[4612]956        Flow *flow = itr->second;
[3567]957        Tcl_Obj *objPtr = Tcl_NewStringObj(flow->name(), -1);
[2875]958        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
[1429]959    }
960    Tcl_SetObjResult(interp, listObjPtr);
961    return TCL_OK;
962}
963
964static int
965FlowNextOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]966           Tcl_Obj *const *objv)
[1429]967{
968    assert(NanoVis::licRenderer != NULL);
[1431]969    if (NanoVis::flags & NanoVis::MAP_FLOWS) {
[3566]970        NanoVis::mapFlows();
[1429]971    }
[2877]972    NanoVis::eventuallyRedraw();
[1429]973    NanoVis::licRenderer->convolve();
[3566]974    NanoVis::advectFlows();
[1429]975    return TCL_OK;
976}
977
978static int
979FlowResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]980            Tcl_Obj *const *objv)
[1429]981{
[3566]982    NanoVis::resetFlows();
[1429]983    return TCL_OK;
984}
985
[2875]986/**
987 * \brief Convert a Tcl_Obj representing the video format into its
988 * integer id.
[2070]989 *
[2875]990 * \param clientData Not used
991 * \param interp Interpreter to send results back to
992 * \param switchName Not used
993 * \param objPtr String representation
994 * \param record Structure record
995 * \param offset Not used
996 * \param flags Not used
[2070]997 *
[2875]998 * \return The return value is a standard Tcl result.
[2070]999 */
1000static int
[2875]1001VideoFormatSwitchProc(ClientData clientData, Tcl_Interp *interp,
1002                      const char *switchName, Tcl_Obj *objPtr,
1003                      char *record, int offset, int flags)
[2070]1004{
[3559]1005    Tcl_Obj **formatObjPtr = (Tcl_Obj **)(record + offset);
1006    Tcl_Obj *fmtObjPtr;
[2070]1007    const char *string;
1008    char c;
[3559]1009    int length;
[2070]1010
[3559]1011    string = Tcl_GetStringFromObj(objPtr, &length);
[2070]1012    c = string[0];
[3559]1013    if ((c == 'm') && (length > 1) &&
1014        (strncmp(string, "mpeg", length) == 0)) {
1015        fmtObjPtr =  Tcl_NewStringObj("mpeg1video", 10);
1016    } else if ((c == 't') && (strncmp(string, "theora", length) == 0)) {
1017        fmtObjPtr =  Tcl_NewStringObj("theora", 6);
1018    } else if ((c == 'm') && (length > 1) &&
1019               (strncmp(string, "mov", length) == 0)) {
1020        fmtObjPtr =  Tcl_NewStringObj("mov", 3);
[2070]1021    } else {
[2875]1022        Tcl_AppendResult(interp, "bad video format \"", string,
1023                         "\": should be mpeg, theora, or mov", (char*)NULL);
[3559]1024        return TCL_ERROR;
[2070]1025    }
[3559]1026    if (*formatObjPtr != NULL) {
1027        Tcl_DecrRefCount(*formatObjPtr);
1028    }
1029    Tcl_IncrRefCount(fmtObjPtr);
1030    *formatObjPtr = fmtObjPtr;
1031    return TCL_OK;
[2070]1032}
1033
[3559]1034struct FlowVideoSwitches {
[2875]1035    float frameRate;         /**< Frame rate */
1036    int bitRate;             /**< Video bitrate */
1037    int width, height;       /**< Dimensions of video frame. */
[3559]1038    int numFrames;
1039    Tcl_Obj *formatObjPtr;
[1515]1040};
1041
1042static Rappture::SwitchParseProc VideoFormatSwitchProc;
1043static Rappture::SwitchCustom videoFormatSwitch = {
1044    VideoFormatSwitchProc, NULL, 0,
1045};
1046
[4612]1047static Rappture::SwitchSpec flowVideoSwitches[] = {
[2810]1048    {Rappture::SWITCH_INT, "-bitrate", "value",
[3559]1049     offsetof(FlowVideoSwitches, bitRate), 0},
[1515]1050    {Rappture::SWITCH_CUSTOM, "-format", "string",
[3559]1051     offsetof(FlowVideoSwitches, formatObjPtr), 0, 0, &videoFormatSwitch},
[1515]1052    {Rappture::SWITCH_FLOAT, "-framerate", "value",
[3559]1053     offsetof(FlowVideoSwitches, frameRate), 0},
[1515]1054    {Rappture::SWITCH_INT, "-height", "integer",
[3559]1055     offsetof(FlowVideoSwitches, height), 0},
[1515]1056    {Rappture::SWITCH_INT, "-numframes", "count",
[3559]1057     offsetof(FlowVideoSwitches, numFrames), 0},
[1515]1058    {Rappture::SWITCH_INT, "-width", "integer",
[3559]1059     offsetof(FlowVideoSwitches, width), 0},
[1515]1060    {Rappture::SWITCH_END}
1061};
1062
[3559]1063#ifdef HAVE_FFMPEG
1064
[1429]1065static int
[3559]1066ppmWriteToFile(Tcl_Interp *interp, const char *path, FlowVideoSwitches *switchesPtr)
[1429]1067{
[3559]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);
[1429]1082
[3559]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;
[1515]1088
[3559]1089    struct iovec *iov;
1090    iov = (struct iovec *)malloc(sizeof(struct iovec) * numRecords);
[1515]1091
[3559]1092    // Add the PPM image header.
1093    iov[0].iov_base = header;
1094    iov[0].iov_len = header_length;
[1515]1095
[3559]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;
[1429]1104    }
[3559]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);
[2875]1110        return TCL_ERROR;
[1429]1111    }
[3559]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
[1508]1127    int oldWidth, oldHeight;
[2877]1128    oldWidth = NanoVis::winWidth;
1129    oldHeight = NanoVis::winHeight;
[1429]1130
[3559]1131    if ((switchesPtr->width != oldWidth) ||
1132        (switchesPtr->height != oldHeight)) {
[2875]1133        // Resize to the requested size.
[3559]1134        NanoVis::resizeOffscreenBuffer(switchesPtr->width, switchesPtr->height);
[1505]1135    }
[3577]1136    NanoVis::resetFlows();
[3559]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)) {
[2875]1142            /* If there's another command on stdin, that means the client is
1143             * trying to cancel this operation. */
[3559]1144            *cancelPtr = true;
[2875]1145            break;
1146        }
1147        if (NanoVis::licRenderer->active()) {
1148            NanoVis::licRenderer->convolve();
1149        }
[3577]1150        NanoVis::advectFlows();
[2930]1151
1152        int fboOrig;
1153        glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
1154
1155        NanoVis::bindOffscreenBuffer();
[3497]1156        NanoVis::render();
[2877]1157        NanoVis::readScreen();
[2930]1158
1159        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
1160
[3559]1161        sprintf(tmpFileName + length, "/image%d.ppm", i);
1162        result = ppmWriteToFile(interp, tmpFileName, switchesPtr);
1163        if (result != TCL_OK) {
1164            break;
1165        }
[1429]1166    }
[3559]1167    if ((switchesPtr->width != oldWidth) ||
1168        (switchesPtr->height != oldHeight)) {
1169        NanoVis::resizeOffscreenBuffer(oldWidth, oldHeight);
1170    }
1171    tmpFileName[length] = '\0';
[3577]1172    NanoVis::resetFlows();
[3559]1173    return result;
1174}
[1522]1175
[3559]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];
[3579]1189    sprintf(cmd, "%s -f image2 -i %s/image%%d.ppm -f %s -b %d -r %f -",
[3559]1190            FFMPEG, tmpFileName, Tcl_GetString(switchesPtr->formatObjPtr),
1191            switchesPtr->bitRate, switchesPtr->frameRate);
[3578]1192    TRACE("Enter: %s", cmd);
[3559]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
[3578]1212            ERROR("Can't read movie data: %s",
[3559]1213                  Tcl_PosixError(interp));
1214            Tcl_AppendResult(interp, "can't read movie data: ",
1215                Tcl_PosixError(interp), (char *)NULL);
[2875]1216            return TCL_ERROR;
1217        }
[3559]1218        if (!data.append(buffer, numRead)) {
[3578]1219            ERROR("Can't append movie data to buffer %d bytes",
[3559]1220                  numRead);
1221            Tcl_AppendResult(interp, "can't append movie data to buffer",
1222                             (char *)NULL);
1223            return TCL_ERROR;
1224        }
1225    }
[3578]1226    if (data.size() == 0) {
1227        ERROR("ffmpeg returned 0 bytes");
1228    }
[3584]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;
[3559]1234}
[1429]1235
[3559]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);
[4612]1251    if (Rappture::ParseSwitches(interp, flowVideoSwitches,
[3559]1252                objc - 3, objv + 3, &switches, SWITCH_DEFAULTS) < 0) {
1253        return TCL_ERROR;
[1429]1254    }
[3559]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;
[1501]1259    }
[3559]1260    if ((switches.frameRate < 0.0f) || (switches.frameRate > 30.0f)) {
1261        Tcl_AppendResult(interp, "bad frame rate.", (char *)NULL);
[2875]1262        return TCL_ERROR;
[1429]1263    }
[3559]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);
[4612]1297    Rappture::FreeSwitches(flowVideoSwitches, &switches, 0);
[3559]1298    return result;
[1429]1299}
[2070]1300#else
[2875]1301/**
1302 *  Not implemented
1303 */
[2070]1304static int
1305FlowVideoOp(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]1306            Tcl_Obj *const *objv)
[2070]1307{
[2875]1308    return TCL_OK;
[2070]1309}
[2875]1310#endif /* HAVE_FFMPEG */
[1429]1311
1312static Rappture::CmdSpec flowCmdOps[] = {
1313    {"add",      1, FlowAddOp,     3, 0, "name ?option value...?",},
[3567]1314    {"delete",   1, FlowDeleteOp,  3, 0, "name ?name...?",},
[1431]1315    {"exists",   1, FlowExistsOp,  3, 3, "name",},
[1467]1316    {"goto",     1, FlowGotoOp,    3, 3, "nSteps",},
[3567]1317    {"names",    1, FlowNamesOp,   2, 2, "",},
[1429]1318    {"next",     2, FlowNextOp,    2, 2, "",},
1319    {"reset",    1, FlowResetOp,   2, 2, "",},
[1517]1320    {"video",    1, FlowVideoOp,   3, 0, "token ?switches...?",},
[1429]1321};
1322static int nFlowCmdOps = NumCmdSpecs(flowCmdOps);
1323
1324static int
1325FlowCmdProc(ClientData clientData, Tcl_Interp *interp, int objc,
[2875]1326            Tcl_Obj *const *objv)
[1429]1327{
1328    Tcl_ObjCmdProc *proc;
1329
1330    proc = Rappture::GetOpFromObj(interp, nFlowCmdOps, flowCmdOps,
[2875]1331                                  Rappture::CMDSPEC_ARG1, objc, objv, 0);
[1429]1332    if (proc == NULL) {
[2875]1333        return TCL_ERROR;
[1429]1334    }
1335    return (*proc) (clientData, interp, objc, objv);
1336}
1337
[2875]1338/**
[3567]1339 *\brief This procedure is invoked to initialize the "flow" command.
[1429]1340 *
1341 * Side effects:
[2875]1342 *    Creates the new command and adds a new entry into a global Tcl
1343 *    associative array.
[1429]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.