source: nanovis/branches/1.2/FlowCmd.cpp @ 5516

Last change on this file since 5516 was 5489, checked in by ldelgass, 9 years ago

Move cumulative flow min/max magnitude to Flow.

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