source: nanovis/trunk/FlowCmd.cpp @ 4880

Last change on this file since 4880 was 4880, checked in by ldelgass, 5 years ago

Fix leak in sendDataToClient method (when USE_THREADS is enabled)

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