source: trunk/packages/vizservers/geovis/RendererCmd.cpp @ 4057

Last change on this file since 4057 was 4057, checked in by ldelgass, 11 years ago

Remove references to Rappture, as it isn't a dependency of geovis

File size: 30.5 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2013  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cstdlib>
9#include <cstdio>
10#include <cstring>
11#include <cfloat>
12#include <cerrno>
13#include <string>
14#include <sstream>
15#include <vector>
16#include <unistd.h>
17#include <sys/select.h>
18#include <sys/uio.h>
19#include <tcl.h>
20
21#include <osgEarthFeatures/FeatureModelSource>
22#include <osgEarthSymbology/Color>
23#include <osgEarthSymbology/Style>
24#include <osgEarthSymbology/StyleSheet>
25#include <osgEarthSymbology/LineSymbol>
26
27#include <osgEarthDrivers/gdal/GDALOptions>
28#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
29#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
30
31#include "Trace.h"
32#include "CmdProc.h"
33#include "ReadBuffer.h"
34#include "Types.h"
35#include "RendererCmd.h"
36#include "RenderServer.h"
37#include "Renderer.h"
38#include "PPMWriter.h"
39#include "TGAWriter.h"
40#include "ResponseQueue.h"
41#ifdef USE_READ_THREAD
42#include "CommandQueue.h"
43#endif
44
45using namespace GeoVis;
46
47static int lastCmdStatus;
48
49#ifndef USE_THREADS
50static ssize_t
51SocketWrite(const void *bytes, size_t len)
52{
53    size_t ofs = 0;
54    ssize_t bytesWritten;
55    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
56        ofs += bytesWritten;
57        if (ofs == len)
58            break;
59    }
60    if (bytesWritten < 0) {
61        ERROR("write: %s", strerror(errno));
62    }
63    return bytesWritten;
64}
65#endif
66
67static bool
68SocketRead(char *bytes, size_t len)
69{
70    ReadBuffer::BufferStatus status;
71    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
72    TRACE("followingData status: %d", status);
73    return (status == ReadBuffer::OK);
74}
75
76ssize_t
77GeoVis::queueResponse(const void *bytes, size_t len,
78                      Response::AllocationType allocType,
79                      Response::ResponseType type)
80{
81#ifdef USE_THREADS
82    Response *response = new Response(type);
83    response->setMessage((unsigned char *)bytes, len, allocType);
84    g_outQueue->enqueue(response);
85    return (ssize_t)len;
86#else
87    return SocketWrite(bytes, len);
88#endif
89}
90
91static int
92ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
93{
94    int result;
95#ifdef WANT_TRACE
96    char *str = Tcl_DStringValue(dsPtr);
97    std::string cmd(str);
98    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
99    TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str());
100#endif
101    lastCmdStatus = TCL_OK;
102    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
103                        Tcl_DStringLength(dsPtr),
104                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
105    Tcl_DStringSetLength(dsPtr, 0);
106    if (lastCmdStatus == TCL_BREAK) {
107        return TCL_BREAK;
108    }
109    lastCmdStatus = result;
110    if (result != TCL_OK) {
111        TRACE("Error: %d", result);
112    }
113    return result;
114}
115
116static int
117GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
118{
119    int value;
120
121    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
122        return TCL_ERROR;
123    }
124    *boolPtr = (bool)value;
125    return TCL_OK;
126}
127
128static int
129GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
130{
131    double value;
132
133    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
134        return TCL_ERROR;
135    }
136    *valuePtr = (float)value;
137    return TCL_OK;
138}
139
140static int
141CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
142               Tcl_Obj *const *objv)
143{
144    double quat[4];
145
146    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
147        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
148        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
149        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
150        return TCL_ERROR;
151    }
152
153    g_renderer->setCameraOrientation(quat);
154    return TCL_OK;
155}
156
157static int
158CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
159            Tcl_Obj *const *objv)
160{
161    double x, y;
162
163    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
164        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
165        return TCL_ERROR;
166    }
167
168    g_renderer->panCamera(x, y);
169    return TCL_OK;
170}
171
172static int
173CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
174              Tcl_Obj *const *objv)
175{
176    if (objc == 3) {
177        const char *string = Tcl_GetString(objv[2]);
178        char c = string[0];
179        if ((c != 'a') || (strcmp(string, "all") != 0)) {
180            Tcl_AppendResult(interp, "bad camera reset option \"", string,
181                         "\": should be all", (char*)NULL);
182            return TCL_ERROR;
183        }
184        g_renderer->resetCamera(true);
185    } else {
186        g_renderer->resetCamera(false);
187    }
188    return TCL_OK;
189}
190
191static int
192CameraRotateOp(ClientData clientData, Tcl_Interp *interp, int objc,
193               Tcl_Obj *const *objv)
194{
195    double x, y;
196
197    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
198        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
199        return TCL_ERROR;
200    }
201
202    g_renderer->rotateCamera(x, y);
203    return TCL_OK;
204}
205
206static int
207CameraThrowOp(ClientData clientData, Tcl_Interp *interp, int objc,
208              Tcl_Obj *const *objv)
209{
210    bool state;
211
212    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
213        return TCL_ERROR;
214    }
215
216    g_renderer->setThrowingEnabled(state);
217    return TCL_OK;
218}
219
220static int
221CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
222            Tcl_Obj *const *objv)
223{
224    double z;
225
226    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
227        return TCL_ERROR;
228    }
229
230    g_renderer->zoomCamera(z);
231    return TCL_OK;
232}
233
234static CmdSpec cameraOps[] = {
235    {"orient", 1, CameraOrientOp, 6, 6, "qw qx qy qz"},
236    {"pan",    1, CameraPanOp, 4, 4, "panX panY"},
237    {"reset",  2, CameraResetOp, 2, 3, "?all?"},
238    {"rotate", 2, CameraRotateOp, 4, 4, "azimuth elevation"},
239    {"throw",  1, CameraThrowOp, 3, 3, "bool"},
240    {"zoom",   1, CameraZoomOp, 3, 3, "zoomAmount"}
241};
242static int nCameraOps = NumCmdSpecs(cameraOps);
243
244static int
245CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
246          Tcl_Obj *const *objv)
247{
248    Tcl_ObjCmdProc *proc;
249
250    proc = GetOpFromObj(interp, nCameraOps, cameraOps,
251                        CMDSPEC_ARG1, objc, objv, 0);
252    if (proc == NULL) {
253        return TCL_ERROR;
254    }
255    return (*proc) (clientData, interp, objc, objv);
256}
257
258static int
259ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
260              Tcl_Obj *const *objv)
261{
262    Tcl_DString ds;
263    Tcl_Obj *objPtr, *listObjPtr, **items;
264    int numItems;
265    char buf[BUFSIZ];
266    const char *string;
267    int length;
268    int result;
269    static bool first = true;
270
271    /* Use the initial client key value pairs as the parts for a generating
272     * a unique file name. */
273    int fd = GeoVis::getStatsFile(interp, objv[1]);
274    if (fd < 0) {
275        Tcl_AppendResult(interp, "can't open stats file: ",
276                         Tcl_PosixError(interp), (char *)NULL);
277        return TCL_ERROR;
278    }
279    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
280    Tcl_IncrRefCount(listObjPtr);
281    if (first) {
282        first = false;
283        objPtr = Tcl_NewStringObj("render_start", 12);
284        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
285        /* server */
286        objPtr = Tcl_NewStringObj("server", 6);
287        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
288        objPtr = Tcl_NewStringObj("geovis", 6);
289        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
290        /* pid */
291        objPtr = Tcl_NewStringObj("pid", 3);
292        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
293        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
294        /* machine */
295        objPtr = Tcl_NewStringObj("machine", 7);
296        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
297        gethostname(buf, BUFSIZ-1);
298        buf[BUFSIZ-1] = '\0';
299        objPtr = Tcl_NewStringObj(buf, -1);
300        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
301    } else {
302        objPtr = Tcl_NewStringObj("render_info", 11);
303        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
304    }
305    /* date */
306    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
307    strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
308    buf[strlen(buf) - 1] = '\0';
309    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
310    /* date_secs */
311    Tcl_ListObjAppendElement(interp, listObjPtr,
312                             Tcl_NewStringObj("date_secs", 9));
313    Tcl_ListObjAppendElement(interp, listObjPtr,
314                             Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
315    /* Client arguments. */
316    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK) {
317        return TCL_ERROR;
318    }
319    for (int i = 0; i < numItems; i++) {
320        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
321    }
322    Tcl_DStringInit(&ds);
323    string = Tcl_GetStringFromObj(listObjPtr, &length);
324    Tcl_DStringAppend(&ds, string, length);
325    Tcl_DStringAppend(&ds, "\n", 1);
326    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds),
327                                      Tcl_DStringLength(&ds));
328    Tcl_DStringFree(&ds);
329    Tcl_DecrRefCount(listObjPtr);
330    return result;
331}
332
333static int
334ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
335              Tcl_Obj *const *objv)
336{
337    lastCmdStatus = TCL_BREAK;
338    return TCL_OK;
339}
340
341static int
342KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc,
343           Tcl_Obj *const *objv)
344{
345    int key;
346    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
347        return TCL_ERROR;
348    }
349
350    g_renderer->keyPress(key);
351    return TCL_OK;
352}
353
354static int
355KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
356               Tcl_Obj *const *objv)
357{
358    int key;
359    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
360        return TCL_ERROR;
361    }
362
363    g_renderer->keyRelease(key);
364    return TCL_OK;
365}
366
367static CmdSpec keyOps[] = {
368    {"press",    1, KeyPressOp,       3, 3, "key"},
369    {"release",  1, KeyReleaseOp,     3, 3, "key"},
370};
371static int nKeyOps = NumCmdSpecs(keyOps);
372
373static int
374KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
375         Tcl_Obj *const *objv)
376{
377    Tcl_ObjCmdProc *proc;
378
379    proc = GetOpFromObj(interp, nKeyOps, keyOps,
380                        CMDSPEC_ARG1, objc, objv, 0);
381    if (proc == NULL) {
382        return TCL_ERROR;
383    }
384    return (*proc) (clientData, interp, objc, objv);
385}
386
387static int
388MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
389              Tcl_Obj *const *objv)
390{
391    char *type = Tcl_GetString(objv[3]);
392    if (type[0] == 'i' && strcmp(type, "image") == 0) {
393        osgEarth::Drivers::GDALOptions opts;
394        char *url =  Tcl_GetString(objv[4]);
395        char *name = Tcl_GetString(objv[5]);
396
397        opts.url() = url;
398
399        g_renderer->addImageLayer(name, opts);
400    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
401        osgEarth::Drivers::GDALOptions opts;
402        char *url =  Tcl_GetString(objv[4]);
403        char *name = Tcl_GetString(objv[5]);
404
405        opts.url() = url;
406
407        g_renderer->addElevationLayer(name, opts);
408    } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
409        osgEarth::Drivers::OGRFeatureOptions opts;
410        char *url =  Tcl_GetString(objv[4]);
411        char *name = Tcl_GetString(objv[5]);
412        opts.url() = url;
413
414        osgEarth::Symbology::Style style;
415        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
416        ls->stroke()->color() = osgEarth::Symbology::Color::Black;
417        ls->stroke()->width() = 2.0f;
418
419        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
420        geomOpts.featureOptions() = opts;
421        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
422        geomOpts.styles()->addStyle(style);
423        geomOpts.enableLighting() = false;
424        g_renderer->addModelLayer(name, geomOpts);
425    } else {
426        Tcl_AppendResult(interp, "unknown map layer type \"", type,
427                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
428        return TCL_ERROR;
429    }
430    return TCL_OK;
431}
432
433static int
434MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
435                 Tcl_Obj *const *objv)
436{
437    if (objc > 3) {
438        char *name = Tcl_GetString(objv[3]);
439        g_renderer->removeImageLayer(name);
440        g_renderer->removeElevationLayer(name);
441        g_renderer->removeModelLayer(name);
442    } else {
443        g_renderer->clearMap();
444    }
445
446    return TCL_OK;
447}
448
449static int
450MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
451               Tcl_Obj *const *objv)
452{
453    int pos;
454    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
455        return TCL_ERROR;
456    }
457    char *name = Tcl_GetString(objv[4]);
458    if (pos < 0) {
459        Tcl_AppendResult(interp, "bad layer pos ", pos,
460                         ": must be positive", (char*)NULL);
461        return TCL_ERROR;
462    }
463    g_renderer->moveImageLayer(name, (unsigned int)pos);
464    g_renderer->moveElevationLayer(name, (unsigned int)pos);
465    g_renderer->moveModelLayer(name, (unsigned int)pos);
466
467    return TCL_OK;
468}
469
470static int
471MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
472                  Tcl_Obj *const *objv)
473{
474    double opacity;
475    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
476        return TCL_ERROR;
477    }
478    char *name = Tcl_GetString(objv[4]);
479    if (opacity < 0.0 || opacity > 1.0) {
480        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
481                         ": must be [0,1]", (char*)NULL);
482        return TCL_ERROR;
483    }
484    g_renderer->setImageLayerOpacity(name, opacity);
485    g_renderer->setModelLayerOpacity(name, opacity);
486
487    return TCL_OK;
488}
489
490static int
491MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
492                  Tcl_Obj *const *objv)
493{
494    bool visible;
495    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
496        return TCL_ERROR;
497    }
498    char *name = Tcl_GetString(objv[4]);
499
500    g_renderer->setImageLayerVisibility(name, visible);
501    g_renderer->setElevationLayerVisibility(name, visible);
502    g_renderer->setModelLayerVisibility(name, visible);
503
504    return TCL_OK;
505}
506
507static CmdSpec mapLayerOps[] = {
508    {"add",     1, MapLayerAddOp,       6, 6, "type url name"},
509    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
510    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
511    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
512    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
513};
514static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
515
516static int
517MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
518           Tcl_Obj *const *objv)
519{
520    Tcl_ObjCmdProc *proc;
521
522    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
523                        CMDSPEC_ARG2, objc, objv, 0);
524    if (proc == NULL) {
525        return TCL_ERROR;
526    }
527    return (*proc) (clientData, interp, objc, objv);
528}
529
530static int
531MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
532          Tcl_Obj *const *objv)
533{
534    char *opt = Tcl_GetString(objv[2]);
535    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
536        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
537    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
538        std::ostringstream path;
539        path << "server:" << Tcl_GetString(objv[3]);
540        g_renderer->loadEarthFile(path.str().c_str());
541    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
542        opt = Tcl_GetString(objv[3]);
543        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
544            return TCL_ERROR;
545        }
546        int len;
547        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
548            return TCL_ERROR;
549        }
550        // Read Earth file from socket
551        char *buf = (char *)malloc((size_t)len);
552        SocketRead(buf, (size_t)len);
553        std::ostringstream path;
554        path << "/tmp/tmp" << getpid() << ".earth";
555        FILE *tmpFile = fopen(path.str().c_str(), "w");
556        fwrite(buf, len, 1, tmpFile);
557        fclose(tmpFile);
558        g_renderer->loadEarthFile(path.str().c_str());
559        unlink(path.str().c_str());
560        free(buf);
561    } else {
562        return TCL_ERROR;
563    }
564    return TCL_OK;
565}
566
567static int
568MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
569           Tcl_Obj *const *objv)
570{
571    char *typeStr = Tcl_GetString(objv[2]);
572    osgEarth::MapOptions::CoordinateSystemType type;
573    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
574        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
575    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
576        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
577    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
578        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
579    } else {
580        Tcl_AppendResult(interp, "bad map type \"", typeStr,
581                         "\": must be geocentric, geocentric_cube or projected", (char*)NULL);
582        return TCL_ERROR;
583    }
584
585    char *profile = NULL;
586    if (objc > 3) {
587        profile = Tcl_GetString(objv[3]);
588    }
589
590    g_renderer->resetMap(type, profile);
591
592    return TCL_OK;
593}
594
595static CmdSpec mapOps[] = {
596    {"layer",    2, MapLayerOp,       3, 6, "op ?params...?"},
597    {"load",     2, MapLoadOp,        4, 5, "options"},
598    {"reset",    1, MapResetOp,       3, 4, "type ?profile?"},
599};
600static int nMapOps = NumCmdSpecs(mapOps);
601
602static int
603MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
604       Tcl_Obj *const *objv)
605{
606    Tcl_ObjCmdProc *proc;
607
608    proc = GetOpFromObj(interp, nMapOps, mapOps,
609                        CMDSPEC_ARG1, objc, objv, 0);
610    if (proc == NULL) {
611        return TCL_ERROR;
612    }
613    return (*proc) (clientData, interp, objc, objv);
614}
615
616static int
617MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
618             Tcl_Obj *const *objv)
619{
620    int button;
621    double x, y;
622
623    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
624        return TCL_ERROR;
625    }
626    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
627        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
628        return TCL_ERROR;
629    }
630
631    g_renderer->mouseClick(button, x, y);
632    return TCL_OK;
633}
634
635static int
636MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
637                   Tcl_Obj *const *objv)
638{
639    int button;
640    double x, y;
641
642    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
643        return TCL_ERROR;
644    }
645    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
646        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
647        return TCL_ERROR;
648    }
649
650    g_renderer->mouseDoubleClick(button, x, y);
651    return TCL_OK;
652}
653
654static int
655MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
656            Tcl_Obj *const *objv)
657{
658    int button;
659    double x, y;
660
661    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
662        return TCL_ERROR;
663    }
664    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
665        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
666        return TCL_ERROR;
667    }
668
669    g_renderer->mouseDrag(button, x, y);
670    return TCL_OK;
671}
672
673static int
674MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
675              Tcl_Obj *const *objv)
676{
677    double x, y;
678
679    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
680        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
681        return TCL_ERROR;
682    }
683
684    g_renderer->mouseMotion(x, y);
685    return TCL_OK;
686}
687
688static int
689MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
690               Tcl_Obj *const *objv)
691{
692    int button;
693    double x, y;
694
695    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
696        return TCL_ERROR;
697    }
698    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
699        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
700        return TCL_ERROR;
701    }
702
703    g_renderer->mouseRelease(button, x, y);
704    return TCL_OK;
705}
706
707static int
708MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
709              Tcl_Obj *const *objv)
710{
711    int direction;
712
713    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
714        return TCL_ERROR;
715    }
716
717    g_renderer->mouseScroll(direction);
718    return TCL_OK;
719}
720
721static CmdSpec mouseOps[] = {
722    {"click",    1, MouseClickOp,       5, 5, "button x y"},
723    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
724    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
725    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
726    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
727    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
728};
729static int nMouseOps = NumCmdSpecs(mouseOps);
730
731static int
732MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
733         Tcl_Obj *const *objv)
734{
735    Tcl_ObjCmdProc *proc;
736
737    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
738                        CMDSPEC_ARG1, objc, objv, 0);
739    if (proc == NULL) {
740        return TCL_ERROR;
741    }
742    return (*proc) (clientData, interp, objc, objv);
743}
744
745static int
746RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
747                 Tcl_Obj *const *objv)
748{
749    g_renderer->eventuallyRender();
750    return TCL_OK;
751}
752
753static CmdSpec rendererOps[] = {
754    {"render",     1, RendererRenderOp, 2, 2, ""},
755};
756static int nRendererOps = NumCmdSpecs(rendererOps);
757
758static int
759RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
760            Tcl_Obj *const *objv)
761{
762    Tcl_ObjCmdProc *proc;
763
764    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
765                        CMDSPEC_ARG1, objc, objv, 0);
766    if (proc == NULL) {
767        return TCL_ERROR;
768    }
769    return (*proc) (clientData, interp, objc, objv);
770}
771
772static int
773ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
774                Tcl_Obj *const *objv)
775{
776    float color[3];
777
778    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
779        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
780        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
781        return TCL_ERROR;
782    }
783
784    g_renderer->setBackgroundColor(color);
785    return TCL_OK;
786}
787
788static int
789ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
790             Tcl_Obj *const *objv)
791{
792    int width, height;
793
794    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
795        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
796        return TCL_ERROR;
797    }
798
799    g_renderer->setWindowSize(width, height);
800    return TCL_OK;
801}
802
803static CmdSpec screenOps[] = {
804    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
805    {"size", 1, ScreenSizeOp, 4, 4, "width height"}
806};
807static int nScreenOps = NumCmdSpecs(screenOps);
808
809static int
810ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
811          Tcl_Obj *const *objv)
812{
813    Tcl_ObjCmdProc *proc;
814
815    proc = GetOpFromObj(interp, nScreenOps, screenOps,
816                        CMDSPEC_ARG1, objc, objv, 0);
817    if (proc == NULL) {
818        return TCL_ERROR;
819    }
820    return (*proc) (clientData, interp, objc, objv);
821}
822
823#ifdef USE_READ_THREAD
824int
825GeoVis::queueCommands(Tcl_Interp *interp,
826                      ClientData clientData,
827                      ReadBuffer *inBufPtr)
828{
829    Tcl_DString commandString;
830    Tcl_DStringInit(&commandString);
831    fd_set readFds;
832
833    FD_ZERO(&readFds);
834    FD_SET(inBufPtr->file(), &readFds);
835    while (inBufPtr->isLineAvailable() ||
836           (select(1, &readFds, NULL, NULL, NULL) > 0)) {
837        size_t numBytes;
838        unsigned char *buffer;
839
840        /* A short read is treated as an error here because we assume that we
841         * will always get commands line by line. */
842        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
843            /* Terminate the server if we can't communicate with the client
844             * anymore. */
845            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
846                TRACE("Exiting server on EOF from client");
847                return -1;
848            } else {
849                ERROR("Exiting server, failed to read from client: %s",
850                      strerror(errno));
851                return -1;
852            }
853        }
854        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
855        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
856            // Add to queue
857            Command *command = new Command(Command::COMMAND);
858            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
859                                Tcl_DStringLength(&commandString), Command::VOLATILE);
860            g_inQueue->enqueue(command);
861            Tcl_DStringSetLength(&commandString, 0);
862        }
863        FD_SET(inBufPtr->file(), &readFds);
864    }
865
866    return 1;
867}
868#endif
869
870/**
871 * \brief Execute commands from client in Tcl interpreter
872 *
873 * In this threaded model, the select call is for event compression.  We
874 * want to execute render server commands as long as they keep coming. 
875 * This lets us execute a stream of many commands but render once.  This
876 * benefits camera movements, screen resizing, and opacity changes
877 * (using a slider on the client).  The down side is you don't render
878 * until there's a lull in the command stream.  If the client needs an
879 * image, it can issue an "imgflush" command.  That breaks us out of the
880 * read loop.
881 */
882int
883GeoVis::processCommands(Tcl_Interp *interp,
884                        ClientData clientData,
885                        ReadBuffer *inBufPtr,
886                        int fdOut,
887                        long timeout)
888{
889    int ret = 1;
890    int status = TCL_OK;
891
892    Tcl_DString command;
893    Tcl_DStringInit(&command);
894    fd_set readFds;
895    struct timeval tv, *tvPtr;
896
897    FD_ZERO(&readFds);
898    FD_SET(inBufPtr->file(), &readFds);
899    tvPtr = NULL;                       /* Wait for the first read. This is so
900                                         * that we don't spin when no data is
901                                         * available. */
902    if (timeout >= 0L) {
903        tv.tv_sec = 0L;
904        tv.tv_usec = timeout;
905        tvPtr = &tv;
906    } else {
907        TRACE("Blocking on select()");
908    }
909    while (inBufPtr->isLineAvailable() ||
910           (select(1, &readFds, NULL, NULL, tvPtr) > 0)) {
911        size_t numBytes;
912        unsigned char *buffer;
913
914        /* A short read is treated as an error here because we assume that we
915         * will always get commands line by line. */
916        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
917            /* Terminate the server if we can't communicate with the client
918             * anymore. */
919            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
920                TRACE("Exiting server on EOF from client");
921                return -1;
922            } else {
923                ERROR("Exiting server, failed to read from client: %s",
924                      strerror(errno));
925                return -1;
926            }
927        }
928        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
929        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
930            struct timeval start, finish;
931            gettimeofday(&start, NULL);
932            status = ExecuteCommand(interp, &command);
933            gettimeofday(&finish, NULL);
934            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
935            g_stats.nCommands++;
936            if (status == TCL_BREAK) {
937                return 2;               /* This was caused by a "imgflush"
938                                         * command. Break out of the read loop
939                                         * and allow a new image to be
940                                         * rendered. */
941            } else { //if (status != TCL_OK) {
942                ret = 0;
943                if (handleError(interp, clientData, status, fdOut) < 0) {
944                    return -1;
945                }
946            }
947            if (status == TCL_OK) {
948                ret = 3;
949            }
950        }
951
952        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
953                                         * if no data is available. */
954        FD_SET(inBufPtr->file(), &readFds);
955        tvPtr = &tv;
956    }
957
958    return ret;
959}
960
961/**
962 * \brief Send error message to client socket
963 */
964int
965GeoVis::handleError(Tcl_Interp *interp,
966                    ClientData clientData,
967                    int status, int fdOut)
968{
969    const char *string;
970    int nBytes;
971
972    if (status != TCL_OK) {
973        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
974        nBytes = strlen(string);
975        if (nBytes > 0) {
976            TRACE("status=%d errorInfo=(%s)", status, string);
977
978            std::ostringstream oss;
979            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
980            nBytes = oss.str().length();
981
982            if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
983                return -1;
984            }
985        }
986    }
987
988    string = getUserMessages();
989    nBytes = strlen(string);
990    if (nBytes > 0) {
991        TRACE("userError=(%s)", string);
992
993        std::ostringstream oss;
994        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
995        nBytes = oss.str().length();
996
997        if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
998            return -1;
999        }
1000
1001        clearUserMessages();
1002    }
1003
1004    return 0;
1005}
1006
1007/**
1008 * \brief Create Tcl interpreter and add commands
1009 *
1010 * \return The initialized Tcl interpreter
1011 */
1012void
1013GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
1014{
1015    Tcl_MakeSafe(interp);
1016    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
1017    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
1018    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
1019    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
1020    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
1021    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
1022    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
1023    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
1024}
1025
1026/**
1027 * \brief Delete Tcl commands and interpreter
1028 */
1029void GeoVis::exitTcl(Tcl_Interp *interp)
1030{
1031    Tcl_DeleteCommand(interp, "camera");
1032    Tcl_DeleteCommand(interp, "clientinfo");
1033    Tcl_DeleteCommand(interp, "imgflush");
1034    Tcl_DeleteCommand(interp, "key");
1035    Tcl_DeleteCommand(interp, "map");
1036    Tcl_DeleteCommand(interp, "mouse");
1037    Tcl_DeleteCommand(interp, "renderer");
1038    Tcl_DeleteCommand(interp, "screen");
1039
1040    Tcl_DeleteInterp(interp);
1041}
Note: See TracBrowser for help on using the repository browser.