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

Last change on this file since 4906 was 4904, checked in by ldelgass, 9 years ago

Merge serveral changes from trunk. Does not include threading, world space
changes, etc.

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