source: trunk/packages/vizservers/nanovis/FlowCmd.cpp @ 4063

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

Remove some more Rappture deps from nanovis

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