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

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

Add methods to query map layers (optionally by type)

File size: 46.1 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 <osgEarth/Registry>
22#include <osgEarthFeatures/FeatureModelSource>
23#include <osgEarthSymbology/Color>
24#include <osgEarthSymbology/Style>
25#include <osgEarthSymbology/StyleSheet>
26#include <osgEarthSymbology/LineSymbol>
27#include <osgEarthSymbology/RenderSymbol>
28
29#include <osgEarthDrivers/gdal/GDALOptions>
30#include <osgEarthDrivers/tms/TMSOptions>
31#include <osgEarthDrivers/wms/WMSOptions>
32#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
33#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
34
35#include "Trace.h"
36#include "CmdProc.h"
37#include "ReadBuffer.h"
38#include "Types.h"
39#include "RendererCmd.h"
40#include "RenderServer.h"
41#include "Renderer.h"
42#include "PPMWriter.h"
43#include "TGAWriter.h"
44#include "ResponseQueue.h"
45#ifdef USE_READ_THREAD
46#include "CommandQueue.h"
47#endif
48
49using namespace GeoVis;
50
51static int lastCmdStatus;
52
53#ifndef USE_THREADS
54static ssize_t
55SocketWrite(const void *bytes, size_t len)
56{
57    size_t ofs = 0;
58    ssize_t bytesWritten;
59    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
60        ofs += bytesWritten;
61        if (ofs == len)
62            break;
63    }
64    if (bytesWritten < 0) {
65        ERROR("write: %s", strerror(errno));
66    }
67    return bytesWritten;
68}
69#endif
70
71static bool
72SocketRead(char *bytes, size_t len)
73{
74    ReadBuffer::BufferStatus status;
75    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
76    TRACE("followingData status: %d", status);
77    return (status == ReadBuffer::OK);
78}
79
80ssize_t
81GeoVis::queueResponse(const void *bytes, size_t len,
82                      Response::AllocationType allocType,
83                      Response::ResponseType type)
84{
85#ifdef USE_THREADS
86    Response *response = new Response(type);
87    response->setMessage((unsigned char *)bytes, len, allocType);
88    g_outQueue->enqueue(response);
89    return (ssize_t)len;
90#else
91    return SocketWrite(bytes, len);
92#endif
93}
94
95static int
96ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
97{
98    int result;
99#ifdef WANT_TRACE
100    char *str = Tcl_DStringValue(dsPtr);
101    std::string cmd(str);
102    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
103    TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str());
104#endif
105    lastCmdStatus = TCL_OK;
106    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
107                        Tcl_DStringLength(dsPtr),
108                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
109    Tcl_DStringSetLength(dsPtr, 0);
110    if (lastCmdStatus == TCL_BREAK) {
111        return TCL_BREAK;
112    }
113    lastCmdStatus = result;
114    if (result != TCL_OK) {
115        TRACE("Error: %d", result);
116    }
117    return result;
118}
119
120static int
121GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
122{
123    int value;
124
125    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
126        return TCL_ERROR;
127    }
128    *boolPtr = (bool)value;
129    return TCL_OK;
130}
131
132static int
133GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
134{
135    double value;
136
137    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
138        return TCL_ERROR;
139    }
140    *valuePtr = (float)value;
141    return TCL_OK;
142}
143
144static int
145CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
146               Tcl_Obj *const *objv)
147{
148    double quat[4];
149
150    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
151        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
152        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
153        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
154        return TCL_ERROR;
155    }
156
157    g_renderer->setCameraOrientation(quat);
158    return TCL_OK;
159}
160
161static int
162CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
163            Tcl_Obj *const *objv)
164{
165    double x, y;
166
167    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
168        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
169        return TCL_ERROR;
170    }
171
172    g_renderer->panCamera(x, y);
173    return TCL_OK;
174}
175
176static int
177CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
178              Tcl_Obj *const *objv)
179{
180    if (objc == 3) {
181        const char *string = Tcl_GetString(objv[2]);
182        char c = string[0];
183        if ((c != 'a') || (strcmp(string, "all") != 0)) {
184            Tcl_AppendResult(interp, "bad camera reset option \"", string,
185                         "\": should be all", (char*)NULL);
186            return TCL_ERROR;
187        }
188        g_renderer->resetCamera(true);
189    } else {
190        g_renderer->resetCamera(false);
191    }
192    return TCL_OK;
193}
194
195static int
196CameraRotateOp(ClientData clientData, Tcl_Interp *interp, int objc,
197               Tcl_Obj *const *objv)
198{
199    double x, y;
200
201    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
202        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
203        return TCL_ERROR;
204    }
205
206    g_renderer->rotateCamera(x, y);
207    return TCL_OK;
208}
209
210static int
211CameraThrowOp(ClientData clientData, Tcl_Interp *interp, int objc,
212              Tcl_Obj *const *objv)
213{
214    bool state;
215
216    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
217        return TCL_ERROR;
218    }
219
220    g_renderer->setThrowingEnabled(state);
221    return TCL_OK;
222}
223
224static int
225CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
226            Tcl_Obj *const *objv)
227{
228    double z;
229
230    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
231        return TCL_ERROR;
232    }
233
234    g_renderer->zoomCamera(z);
235    return TCL_OK;
236}
237
238static CmdSpec cameraOps[] = {
239    {"orient", 1, CameraOrientOp, 6, 6, "qw qx qy qz"},
240    {"pan",    1, CameraPanOp, 4, 4, "panX panY"},
241    {"reset",  2, CameraResetOp, 2, 3, "?all?"},
242    {"rotate", 2, CameraRotateOp, 4, 4, "azimuth elevation"},
243    {"throw",  1, CameraThrowOp, 3, 3, "bool"},
244    {"zoom",   1, CameraZoomOp, 3, 3, "zoomAmount"}
245};
246static int nCameraOps = NumCmdSpecs(cameraOps);
247
248static int
249CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
250          Tcl_Obj *const *objv)
251{
252    Tcl_ObjCmdProc *proc;
253
254    proc = GetOpFromObj(interp, nCameraOps, cameraOps,
255                        CMDSPEC_ARG1, objc, objv, 0);
256    if (proc == NULL) {
257        return TCL_ERROR;
258    }
259    return (*proc) (clientData, interp, objc, objv);
260}
261
262static int
263ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
264              Tcl_Obj *const *objv)
265{
266    Tcl_DString ds;
267    Tcl_Obj *objPtr, *listObjPtr, **items;
268    int numItems;
269    char buf[BUFSIZ];
270    const char *string;
271    int length;
272    int result;
273    static bool first = true;
274
275    /* Use the initial client key value pairs as the parts for a generating
276     * a unique file name. */
277    int fd = GeoVis::getStatsFile(interp, objv[1]);
278    if (fd < 0) {
279        Tcl_AppendResult(interp, "can't open stats file: ",
280                         Tcl_PosixError(interp), (char *)NULL);
281        return TCL_ERROR;
282    }
283    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
284    Tcl_IncrRefCount(listObjPtr);
285    if (first) {
286        first = false;
287        objPtr = Tcl_NewStringObj("render_start", 12);
288        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
289        /* server */
290        objPtr = Tcl_NewStringObj("server", 6);
291        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
292        objPtr = Tcl_NewStringObj("geovis", 6);
293        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
294        /* pid */
295        objPtr = Tcl_NewStringObj("pid", 3);
296        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
297        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
298        /* machine */
299        objPtr = Tcl_NewStringObj("machine", 7);
300        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
301        gethostname(buf, BUFSIZ-1);
302        buf[BUFSIZ-1] = '\0';
303        objPtr = Tcl_NewStringObj(buf, -1);
304        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
305    } else {
306        objPtr = Tcl_NewStringObj("render_info", 11);
307        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
308    }
309    /* date */
310    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
311    strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
312    buf[strlen(buf) - 1] = '\0';
313    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
314    /* date_secs */
315    Tcl_ListObjAppendElement(interp, listObjPtr,
316                             Tcl_NewStringObj("date_secs", 9));
317    Tcl_ListObjAppendElement(interp, listObjPtr,
318                             Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
319    /* Client arguments. */
320    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK) {
321        return TCL_ERROR;
322    }
323    for (int i = 0; i < numItems; i++) {
324        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
325    }
326    Tcl_DStringInit(&ds);
327    string = Tcl_GetStringFromObj(listObjPtr, &length);
328    Tcl_DStringAppend(&ds, string, length);
329    Tcl_DStringAppend(&ds, "\n", 1);
330    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds),
331                                      Tcl_DStringLength(&ds));
332    Tcl_DStringFree(&ds);
333    Tcl_DecrRefCount(listObjPtr);
334    return result;
335}
336
337static int
338ColorMapAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
339              Tcl_Obj *const *objv)
340{
341    const char *name = Tcl_GetString(objv[2]);
342    int cmapc, omapc;
343    Tcl_Obj **cmapv = NULL;
344    Tcl_Obj **omapv = NULL;
345
346    if (Tcl_ListObjGetElements(interp, objv[3], &cmapc, &cmapv) != TCL_OK) {
347        return TCL_ERROR;
348    }
349    if ((cmapc % 4) != 0) {
350        Tcl_AppendResult(interp, "wrong # elements in colormap: should be ",
351                         "{ value r g b ... }", (char*)NULL);
352        return TCL_ERROR;
353    }
354
355    osg::TransferFunction1D *colorMap = new osg::TransferFunction1D;
356    colorMap->allocate(256);
357
358    for (int i = 0; i < cmapc; i += 4) {
359        double val[4];
360        for (int j = 0; j < 4; j++) {
361            if (Tcl_GetDoubleFromObj(interp, cmapv[i+j], &val[j]) != TCL_OK) {
362                delete colorMap;
363                return TCL_ERROR;
364            }
365            if ((val[j] < 0.0) || (val[j] > 1.0)) {
366                Tcl_AppendResult(interp, "bad colormap value \"",
367                                 Tcl_GetString(cmapv[i+j]),
368                                 "\": should be in the range [0,1]", (char*)NULL);
369                delete colorMap;
370                return TCL_ERROR;
371            }
372        }
373        colorMap->setColor(val[0], osg::Vec4f(val[1], val[2], val[3], 1.0), false);
374    }
375
376    colorMap->updateImage();
377
378    if (Tcl_ListObjGetElements(interp, objv[4], &omapc, &omapv) != TCL_OK) {
379        delete colorMap;
380        return TCL_ERROR;
381    }
382    if ((omapc % 2) != 0) {
383        Tcl_AppendResult(interp, "wrong # elements in opacitymap: should be ",
384                         "{ value alpha ... }", (char*)NULL);
385        delete colorMap;
386        return TCL_ERROR;
387    }
388    for (int i = 0; i < omapc; i += 2) {
389        double val[2];
390        for (int j = 0; j < 2; j++) {
391            if (Tcl_GetDoubleFromObj(interp, omapv[i+j], &val[j]) != TCL_OK) {
392                delete colorMap;
393                return TCL_ERROR;
394            }
395            if ((val[j] < 0.0) || (val[j] > 1.0)) {
396                Tcl_AppendResult(interp, "bad opacitymap value \"",
397                                 Tcl_GetString(omapv[i+j]),
398                                 "\": should be in the range [0,1]", (char*)NULL);
399                delete colorMap;
400                return TCL_ERROR;
401            }
402        }
403#if 0
404        ColorMap::OpacityControlPoint ocp;
405        ocp.value = val[0];
406        ocp.alpha = val[1];
407        colorMap->addOpacityControlPoint(ocp);
408#endif
409    }
410
411    //colorMap->build();
412    g_renderer->addColorMap(name, colorMap);
413    return TCL_OK;
414}
415
416static int
417ColorMapDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
418                 Tcl_Obj *const *objv)
419{
420    if (objc == 3) {
421        const char *name = Tcl_GetString(objv[2]);
422        g_renderer->deleteColorMap(name);
423    } else {
424        g_renderer->deleteColorMap("all");
425    }
426
427    return TCL_OK;
428}
429
430static int
431ColorMapNumTableEntriesOp(ClientData clientData, Tcl_Interp *interp, int objc,
432                          Tcl_Obj *const *objv)
433{
434    int numEntries;
435    if (Tcl_GetIntFromObj(interp, objv[2], &numEntries) != TCL_OK) {
436        const char *str = Tcl_GetString(objv[2]);
437        if (str[0] == 'd' && strcmp(str, "default") == 0) {
438            numEntries = -1;
439        } else {
440            Tcl_AppendResult(interp, "bad colormap resolution value \"", str,
441                             "\": should be a positive integer or \"default\"", (char*)NULL);
442            return TCL_ERROR;
443        }
444    } else if (numEntries < 1) {
445        Tcl_AppendResult(interp, "bad colormap resolution value \"", Tcl_GetString(objv[2]),
446                         "\": should be a positive integer or \"default\"", (char*)NULL);
447        return TCL_ERROR;
448    }
449    if (objc == 4) {
450        const char *name = Tcl_GetString(objv[3]);
451
452        g_renderer->setColorMapNumberOfTableEntries(name, numEntries);
453    } else {
454        g_renderer->setColorMapNumberOfTableEntries("all", numEntries);
455    }
456    return TCL_OK;
457}
458
459static CmdSpec colorMapOps[] = {
460    {"add",    1, ColorMapAddOp,             5, 5, "colorMapName colormap alphamap"},
461    {"define", 3, ColorMapAddOp,             5, 5, "colorMapName colormap alphamap"},
462    {"delete", 3, ColorMapDeleteOp,          2, 3, "?colorMapName?"},
463    {"res",    1, ColorMapNumTableEntriesOp, 3, 4, "numTableEntries ?colorMapName?"}
464};
465static int nColorMapOps = NumCmdSpecs(colorMapOps);
466
467static int
468ColorMapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
469            Tcl_Obj *const *objv)
470{
471    Tcl_ObjCmdProc *proc;
472
473    proc = GetOpFromObj(interp, nColorMapOps, colorMapOps,
474                        CMDSPEC_ARG1, objc, objv, 0);
475    if (proc == NULL) {
476        return TCL_ERROR;
477    }
478    return (*proc) (clientData, interp, objc, objv);
479}
480
481static int
482ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
483              Tcl_Obj *const *objv)
484{
485    lastCmdStatus = TCL_BREAK;
486    return TCL_OK;
487}
488
489static int
490KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc,
491           Tcl_Obj *const *objv)
492{
493    int key;
494    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
495        return TCL_ERROR;
496    }
497
498    g_renderer->keyPress(key);
499    return TCL_OK;
500}
501
502static int
503KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
504               Tcl_Obj *const *objv)
505{
506    int key;
507    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
508        return TCL_ERROR;
509    }
510
511    g_renderer->keyRelease(key);
512    return TCL_OK;
513}
514
515static CmdSpec keyOps[] = {
516    {"press",    1, KeyPressOp,       3, 3, "key"},
517    {"release",  1, KeyReleaseOp,     3, 3, "key"},
518};
519static int nKeyOps = NumCmdSpecs(keyOps);
520
521static int
522KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
523         Tcl_Obj *const *objv)
524{
525    Tcl_ObjCmdProc *proc;
526
527    proc = GetOpFromObj(interp, nKeyOps, keyOps,
528                        CMDSPEC_ARG1, objc, objv, 0);
529    if (proc == NULL) {
530        return TCL_ERROR;
531    }
532    return (*proc) (clientData, interp, objc, objv);
533}
534
535static int
536MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
537              Tcl_Obj *const *objv)
538{
539    char *type = Tcl_GetString(objv[3]);
540    if (type[0] == 'i' && strcmp(type, "image") == 0) {
541        osgEarth::Drivers::GDALOptions opts;
542        char *url =  Tcl_GetString(objv[4]);
543        char *name = Tcl_GetString(objv[5]);
544
545        opts.url() = url;
546
547        g_renderer->addImageLayer(name, opts);
548    } else if (type[0] == 't' && strcmp(type, "tms") == 0) {
549        osgEarth::Drivers::TMSOptions opts;
550        char *url =  Tcl_GetString(objv[4]);
551        //char *tmsType = Tcl_GetString(objv[5]);
552        //char *format = Tcl_GetString(objv[6]);
553        char *name = Tcl_GetString(objv[5]);
554
555        opts.url() = url;
556        //opts.tmsType() = tmsType;
557        //opts.format() = format;
558
559        g_renderer->addImageLayer(name, opts);
560    } else if (type[0] == 'w' && strcmp(type, "wms") == 0) {
561        osgEarth::Drivers::WMSOptions opts;
562        char *url =  Tcl_GetString(objv[4]);
563        char *wmsLayers = Tcl_GetString(objv[5]);
564        char *format = Tcl_GetString(objv[6]);
565        bool transparent;
566        if (GetBooleanFromObj(interp, objv[7], &transparent) != TCL_OK) {
567            return TCL_ERROR;
568        }
569        char *name = Tcl_GetString(objv[8]);
570
571        opts.url() = url;
572        opts.layers() = wmsLayers;
573        opts.format() = format;
574        opts.transparent() = transparent;
575
576        g_renderer->addImageLayer(name, opts);
577    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
578        osgEarth::Drivers::GDALOptions opts;
579        char *url =  Tcl_GetString(objv[4]);
580        char *name = Tcl_GetString(objv[5]);
581
582        opts.url() = url;
583
584        g_renderer->addElevationLayer(name, opts);
585    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
586        osgEarth::Drivers::OGRFeatureOptions opts;
587        char *url =  Tcl_GetString(objv[4]);
588        char *name = Tcl_GetString(objv[5]);
589        opts.url() = url;
590
591        osgEarth::Symbology::Style style;
592        osgEarth::Symbology::PointSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
593        ls->fill()->color() = osgEarth::Symbology::Color::Black;
594        ls->size() = 2.0f;
595
596        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
597        rs->depthOffset()->enabled() = true;
598        rs->depthOffset()->minBias() = 1000;
599
600        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
601        geomOpts.featureOptions() = opts;
602        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
603        geomOpts.styles()->addStyle(style);
604        geomOpts.enableLighting() = false;
605        g_renderer->addModelLayer(name, geomOpts);
606    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
607        osgEarth::Drivers::OGRFeatureOptions opts;
608        char *url =  Tcl_GetString(objv[4]);
609        char *name = Tcl_GetString(objv[5]);
610        opts.url() = url;
611
612        osgEarth::Symbology::Style style;
613        osgEarth::Symbology::PolygonSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
614        ls->fill()->color() = osgEarth::Symbology::Color::White;
615
616        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
617        rs->depthOffset()->enabled() = true;
618        rs->depthOffset()->minBias() = 1000;
619
620        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
621        geomOpts.featureOptions() = opts;
622        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
623        geomOpts.styles()->addStyle(style);
624        geomOpts.enableLighting() = false;
625        g_renderer->addModelLayer(name, geomOpts);
626    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
627        osgEarth::Drivers::OGRFeatureOptions opts;
628        char *url =  Tcl_GetString(objv[4]);
629        char *name = Tcl_GetString(objv[5]);
630        opts.url() = url;
631
632        osgEarth::Symbology::Style style;
633        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
634        ls->stroke()->color() = osgEarth::Symbology::Color::Black;
635        ls->stroke()->width() = 2.0f;
636
637        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
638        rs->depthOffset()->enabled() = true;
639        rs->depthOffset()->minBias() = 1000;
640
641        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
642        geomOpts.featureOptions() = opts;
643        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
644        geomOpts.styles()->addStyle(style);
645        geomOpts.enableLighting() = false;
646        g_renderer->addModelLayer(name, geomOpts);
647   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
648        osgEarth::Drivers::OGRFeatureOptions opts;
649        char *url =  Tcl_GetString(objv[4]);
650        char *name = Tcl_GetString(objv[5]);
651        opts.url() = url;
652
653        osgEarth::Symbology::Style style;
654        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
655        ts->halo()->color() = osgEarth::Symbology::Color::White;
656        ts->halo()->width() = 2.0f;
657        ts->fill()->color() = osgEarth::Symbology::Color::Black;
658
659        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
660        rs->depthOffset()->enabled() = true;
661        rs->depthOffset()->minBias() = 1000;
662
663        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
664        geomOpts.featureOptions() = opts;
665        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
666        geomOpts.styles()->addStyle(style);
667        geomOpts.enableLighting() = false;
668        g_renderer->addModelLayer(name, geomOpts);
669    } else {
670        Tcl_AppendResult(interp, "unknown map layer type \"", type,
671                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
672        return TCL_ERROR;
673    }
674    return TCL_OK;
675}
676
677static int
678MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
679                 Tcl_Obj *const *objv)
680{
681    if (objc > 3) {
682        char *name = Tcl_GetString(objv[3]);
683        g_renderer->removeImageLayer(name);
684        g_renderer->removeElevationLayer(name);
685        g_renderer->removeModelLayer(name);
686    } else {
687        g_renderer->clearMap();
688    }
689
690    return TCL_OK;
691}
692
693static int
694MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
695               Tcl_Obj *const *objv)
696{
697    int pos;
698    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
699        return TCL_ERROR;
700    }
701    char *name = Tcl_GetString(objv[4]);
702    if (pos < 0) {
703        Tcl_AppendResult(interp, "bad layer pos ", pos,
704                         ": must be positive", (char*)NULL);
705        return TCL_ERROR;
706    }
707    g_renderer->moveImageLayer(name, (unsigned int)pos);
708    g_renderer->moveElevationLayer(name, (unsigned int)pos);
709    g_renderer->moveModelLayer(name, (unsigned int)pos);
710
711    return TCL_OK;
712}
713
714static int
715MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
716                  Tcl_Obj *const *objv)
717{
718    double opacity;
719    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
720        return TCL_ERROR;
721    }
722    char *name = Tcl_GetString(objv[4]);
723    if (opacity < 0.0 || opacity > 1.0) {
724        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
725                         ": must be [0,1]", (char*)NULL);
726        return TCL_ERROR;
727    }
728    g_renderer->setImageLayerOpacity(name, opacity);
729    g_renderer->setModelLayerOpacity(name, opacity);
730
731    return TCL_OK;
732}
733
734static int
735MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
736                Tcl_Obj *const *objv)
737{
738    std::vector<std::string> layers;
739    if (objc < 4) {
740        g_renderer->getImageLayerNames(layers);
741        g_renderer->getElevationLayerNames(layers);
742        g_renderer->getModelLayerNames(layers);
743    } else {
744        char *type = Tcl_GetString(objv[3]);
745        if (type[0] == 'i' && strcmp(type, "image") == 0) {
746            g_renderer->getImageLayerNames(layers);
747        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
748            g_renderer->getElevationLayerNames(layers);
749        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
750            g_renderer->getModelLayerNames(layers);
751        } else {
752            Tcl_AppendResult(interp, "uknown type \"", type,
753                         "\": must be image, elevation or model", (char*)NULL);
754            return TCL_ERROR;
755        }
756    }
757    std::ostringstream oss;
758    size_t len = 0;
759    oss << "nv>dataset names {";
760    len += 18;
761    for (size_t i = 0; i < layers.size(); i++) {
762        oss << "\"" << layers[i] << "\"";
763        len += 2 + layers[i].length();
764        if (i < layers.size() - 1) {
765            oss << " ";
766            len++;
767        }
768    }
769    oss << "}\n";
770    len += 2;
771#ifdef USE_THREADS
772    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
773#else
774    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
775
776    if (bytesWritten < 0) {
777        return TCL_ERROR;
778    }
779#endif /*USE_THREADS*/
780    return TCL_OK;
781}
782
783static int
784MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
785                  Tcl_Obj *const *objv)
786{
787    bool visible;
788    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
789        return TCL_ERROR;
790    }
791    char *name = Tcl_GetString(objv[4]);
792
793    g_renderer->setImageLayerVisibility(name, visible);
794    g_renderer->setElevationLayerVisibility(name, visible);
795    g_renderer->setModelLayerVisibility(name, visible);
796
797    return TCL_OK;
798}
799
800static CmdSpec mapLayerOps[] = {
801    {"add",     1, MapLayerAddOp,       6, 9, "type url ?args? name"},
802    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
803    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
804    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
805    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
806    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
807};
808static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
809
810static int
811MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
812           Tcl_Obj *const *objv)
813{
814    Tcl_ObjCmdProc *proc;
815
816    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
817                        CMDSPEC_ARG2, objc, objv, 0);
818    if (proc == NULL) {
819        return TCL_ERROR;
820    }
821    return (*proc) (clientData, interp, objc, objv);
822}
823
824static int
825MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
826          Tcl_Obj *const *objv)
827{
828    char *opt = Tcl_GetString(objv[2]);
829    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
830        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
831    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
832        std::ostringstream path;
833        path << "server:" << Tcl_GetString(objv[3]);
834        g_renderer->loadEarthFile(path.str().c_str());
835    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
836        opt = Tcl_GetString(objv[3]);
837        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
838            return TCL_ERROR;
839        }
840        int len;
841        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
842            return TCL_ERROR;
843        }
844        // Read Earth file from socket
845        char *buf = (char *)malloc((size_t)len);
846        SocketRead(buf, (size_t)len);
847        std::ostringstream path;
848        path << "/tmp/tmp" << getpid() << ".earth";
849        FILE *tmpFile = fopen(path.str().c_str(), "w");
850        fwrite(buf, len, 1, tmpFile);
851        fclose(tmpFile);
852        g_renderer->loadEarthFile(path.str().c_str());
853        unlink(path.str().c_str());
854        free(buf);
855    } else {
856        return TCL_ERROR;
857    }
858    return TCL_OK;
859}
860
861static int
862MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
863           Tcl_Obj *const *objv)
864{
865    char *typeStr = Tcl_GetString(objv[2]);
866    osgEarth::MapOptions::CoordinateSystemType type;
867    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
868        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
869    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
870        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
871    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
872        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
873    } else {
874        Tcl_AppendResult(interp, "bad map type \"", typeStr,
875                         "\": must be geocentric or projected", (char*)NULL);
876        return TCL_ERROR;
877    }
878
879    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
880        if (objc < 4) {
881            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
882            return TCL_ERROR;
883        }
884        char *profile = Tcl_GetString(objv[3]);
885        if (objc > 4) {
886            if (objc < 8) {
887                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
888                return TCL_ERROR;
889            }
890            double bounds[4];
891            for (int i = 0; i < 4; i++) {
892                if (Tcl_GetDoubleFromObj(interp, objv[4+i], &bounds[i]) != TCL_OK) {
893                    return TCL_ERROR;
894                }
895            }
896            // Check if min > max
897            if (bounds[0] > bounds[2] ||
898                bounds[1] > bounds[3]) {
899                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
900                return TCL_ERROR;
901            }
902            if (strcmp(profile, "geodetic") == 0 ||
903                strcmp(profile, "epsg:4326") == 0 ) {
904                if (bounds[0] < -180. || bounds[0] > 180. ||
905                    bounds[2] < -180. || bounds[2] > 180. ||
906                    bounds[1] < -90. || bounds[1] > 90. ||
907                    bounds[3] < -90. || bounds[3] > 90.) {
908                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
909                    return TCL_ERROR;
910                }
911            } else if (strcmp(profile, "spherical-mercator") == 0 ||
912                       strcmp(profile, "epsg:900913") == 0 ||
913                       strcmp(profile, "epsg:3857") == 0) {
914                for (int i = 0; i < 4; i++) {
915                    if (bounds[i] < -20037508.34 || bounds[i] > 20037508.34) {
916                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
917                        return TCL_ERROR;
918                    }
919                }
920            }
921
922            g_renderer->resetMap(type, profile, bounds);
923        } else {
924            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
925                Tcl_AppendResult(interp, "bad named profile \"", profile,
926                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
927                return TCL_ERROR;
928            }
929            g_renderer->resetMap(type, profile);
930        }
931    } else {
932        // No profile required for geocentric (3D) maps
933        g_renderer->resetMap(type);
934    }
935
936    return TCL_OK;
937}
938
939static int
940MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
941                  Tcl_Obj *const *objv)
942{
943    bool state;
944    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
945        return TCL_ERROR;
946    }
947    TRACE("Not implemented");
948    //g_renderer->setTerrainEdges(state);
949    return TCL_OK;
950}
951
952static int
953MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
954                     Tcl_Obj *const *objv)
955{
956    bool state;
957    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
958        return TCL_ERROR;
959    }
960
961    g_renderer->setTerrainLighting(state);
962    return TCL_OK;
963}
964
965static int
966MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
967                      Tcl_Obj *const *objv)
968{
969    float color[3];
970    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
971        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
972        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
973        return TCL_ERROR;
974    }
975    TRACE("Not implemented");
976    //g_renderer->setTerrainLineColor(color);
977    return TCL_OK;
978}
979
980static int
981MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
982                      Tcl_Obj *const *objv)
983{
984    double scale;
985    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
986        return TCL_ERROR;
987    }
988
989    g_renderer->setTerrainVerticalScale(scale);
990    return TCL_OK;
991}
992
993static int
994MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
995                      Tcl_Obj *const *objv)
996{
997    bool state;
998    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
999        return TCL_ERROR;
1000    }
1001
1002    g_renderer->setTerrainWireframe(state);
1003    return TCL_OK;
1004}
1005
1006static CmdSpec mapTerrainOps[] = {
1007    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
1008    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
1009    {"linecolor", 2, MapTerrainLineColorOp, 6, 6, "r g b"},
1010    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
1011    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
1012};
1013static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
1014
1015static int
1016MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
1017           Tcl_Obj *const *objv)
1018{
1019    Tcl_ObjCmdProc *proc;
1020
1021    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
1022                        CMDSPEC_ARG2, objc, objv, 0);
1023    if (proc == NULL) {
1024        return TCL_ERROR;
1025    }
1026    return (*proc) (clientData, interp, objc, objv);
1027}
1028
1029static CmdSpec mapOps[] = {
1030    {"layer",    2, MapLayerOp,       3, 0, "op ?params...?"},
1031    {"load",     2, MapLoadOp,        4, 5, "options"},
1032    {"reset",    1, MapResetOp,       3, 8, "type ?profile xmin ymin xmax ymax?"},
1033    {"terrain",  1, MapTerrainOp,     3, 0, "op ?params...?"},
1034};
1035static int nMapOps = NumCmdSpecs(mapOps);
1036
1037static int
1038MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1039       Tcl_Obj *const *objv)
1040{
1041    Tcl_ObjCmdProc *proc;
1042
1043    proc = GetOpFromObj(interp, nMapOps, mapOps,
1044                        CMDSPEC_ARG1, objc, objv, 0);
1045    if (proc == NULL) {
1046        return TCL_ERROR;
1047    }
1048    return (*proc) (clientData, interp, objc, objv);
1049}
1050
1051static int
1052MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1053             Tcl_Obj *const *objv)
1054{
1055    int button;
1056    double x, y;
1057
1058    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1059        return TCL_ERROR;
1060    }
1061    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1062        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1063        return TCL_ERROR;
1064    }
1065
1066    g_renderer->mouseClick(button, x, y);
1067    return TCL_OK;
1068}
1069
1070static int
1071MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1072                   Tcl_Obj *const *objv)
1073{
1074    int button;
1075    double x, y;
1076
1077    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1078        return TCL_ERROR;
1079    }
1080    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1081        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1082        return TCL_ERROR;
1083    }
1084
1085    g_renderer->mouseDoubleClick(button, x, y);
1086    return TCL_OK;
1087}
1088
1089static int
1090MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
1091            Tcl_Obj *const *objv)
1092{
1093    int button;
1094    double x, y;
1095
1096    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1097        return TCL_ERROR;
1098    }
1099    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1100        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1101        return TCL_ERROR;
1102    }
1103
1104    g_renderer->mouseDrag(button, x, y);
1105    return TCL_OK;
1106}
1107
1108static int
1109MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1110              Tcl_Obj *const *objv)
1111{
1112    double x, y;
1113
1114    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
1115        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
1116        return TCL_ERROR;
1117    }
1118
1119    g_renderer->mouseMotion(x, y);
1120    return TCL_OK;
1121}
1122
1123static int
1124MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1125               Tcl_Obj *const *objv)
1126{
1127    int button;
1128    double x, y;
1129
1130    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1131        return TCL_ERROR;
1132    }
1133    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1134        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1135        return TCL_ERROR;
1136    }
1137
1138    g_renderer->mouseRelease(button, x, y);
1139    return TCL_OK;
1140}
1141
1142static int
1143MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
1144              Tcl_Obj *const *objv)
1145{
1146    int direction;
1147
1148    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
1149        return TCL_ERROR;
1150    }
1151
1152    g_renderer->mouseScroll(direction);
1153    return TCL_OK;
1154}
1155
1156static CmdSpec mouseOps[] = {
1157    {"click",    1, MouseClickOp,       5, 5, "button x y"},
1158    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
1159    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
1160    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
1161    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
1162    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
1163};
1164static int nMouseOps = NumCmdSpecs(mouseOps);
1165
1166static int
1167MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1168         Tcl_Obj *const *objv)
1169{
1170    Tcl_ObjCmdProc *proc;
1171
1172    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
1173                        CMDSPEC_ARG1, objc, objv, 0);
1174    if (proc == NULL) {
1175        return TCL_ERROR;
1176    }
1177    return (*proc) (clientData, interp, objc, objv);
1178}
1179
1180static int
1181RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
1182                 Tcl_Obj *const *objv)
1183{
1184    g_renderer->eventuallyRender();
1185    return TCL_OK;
1186}
1187
1188static CmdSpec rendererOps[] = {
1189    {"render",     1, RendererRenderOp, 2, 2, ""},
1190};
1191static int nRendererOps = NumCmdSpecs(rendererOps);
1192
1193static int
1194RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1195            Tcl_Obj *const *objv)
1196{
1197    Tcl_ObjCmdProc *proc;
1198
1199    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
1200                        CMDSPEC_ARG1, objc, objv, 0);
1201    if (proc == NULL) {
1202        return TCL_ERROR;
1203    }
1204    return (*proc) (clientData, interp, objc, objv);
1205}
1206
1207static int
1208ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1209                Tcl_Obj *const *objv)
1210{
1211    float color[3];
1212
1213    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
1214        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
1215        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
1216        return TCL_ERROR;
1217    }
1218
1219    g_renderer->setBackgroundColor(color);
1220    return TCL_OK;
1221}
1222
1223static int
1224ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1225             Tcl_Obj *const *objv)
1226{
1227    int width, height;
1228
1229    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
1230        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
1231        return TCL_ERROR;
1232    }
1233
1234    g_renderer->setWindowSize(width, height);
1235    return TCL_OK;
1236}
1237
1238static CmdSpec screenOps[] = {
1239    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
1240    {"size", 1, ScreenSizeOp, 4, 4, "width height"}
1241};
1242static int nScreenOps = NumCmdSpecs(screenOps);
1243
1244static int
1245ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1246          Tcl_Obj *const *objv)
1247{
1248    Tcl_ObjCmdProc *proc;
1249
1250    proc = GetOpFromObj(interp, nScreenOps, screenOps,
1251                        CMDSPEC_ARG1, objc, objv, 0);
1252    if (proc == NULL) {
1253        return TCL_ERROR;
1254    }
1255    return (*proc) (clientData, interp, objc, objv);
1256}
1257
1258#ifdef USE_READ_THREAD
1259int
1260GeoVis::queueCommands(Tcl_Interp *interp,
1261                      ClientData clientData,
1262                      ReadBuffer *inBufPtr)
1263{
1264    Tcl_DString commandString;
1265    Tcl_DStringInit(&commandString);
1266    fd_set readFds;
1267
1268    FD_ZERO(&readFds);
1269    FD_SET(inBufPtr->file(), &readFds);
1270    while (inBufPtr->isLineAvailable() ||
1271           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
1272        size_t numBytes;
1273        unsigned char *buffer;
1274
1275        /* A short read is treated as an error here because we assume that we
1276         * will always get commands line by line. */
1277        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1278            /* Terminate the server if we can't communicate with the client
1279             * anymore. */
1280            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1281                TRACE("Exiting server on EOF from client");
1282                return -1;
1283            } else {
1284                ERROR("Exiting server, failed to read from client: %s",
1285                      strerror(errno));
1286                return -1;
1287            }
1288        }
1289        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
1290        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
1291            // Add to queue
1292            Command *command = new Command(Command::COMMAND);
1293            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
1294                                Tcl_DStringLength(&commandString), Command::VOLATILE);
1295            g_inQueue->enqueue(command);
1296            Tcl_DStringSetLength(&commandString, 0);
1297        }
1298        FD_SET(inBufPtr->file(), &readFds);
1299    }
1300
1301    return 1;
1302}
1303#endif
1304
1305/**
1306 * \brief Execute commands from client in Tcl interpreter
1307 *
1308 * In this threaded model, the select call is for event compression.  We
1309 * want to execute render server commands as long as they keep coming. 
1310 * This lets us execute a stream of many commands but render once.  This
1311 * benefits camera movements, screen resizing, and opacity changes
1312 * (using a slider on the client).  The down side is you don't render
1313 * until there's a lull in the command stream.  If the client needs an
1314 * image, it can issue an "imgflush" command.  That breaks us out of the
1315 * read loop.
1316 */
1317int
1318GeoVis::processCommands(Tcl_Interp *interp,
1319                        ClientData clientData,
1320                        ReadBuffer *inBufPtr,
1321                        int fdOut,
1322                        long timeout)
1323{
1324    int ret = 1;
1325    int status = TCL_OK;
1326
1327    Tcl_DString command;
1328    Tcl_DStringInit(&command);
1329    fd_set readFds;
1330    struct timeval tv, *tvPtr;
1331
1332    FD_ZERO(&readFds);
1333    FD_SET(inBufPtr->file(), &readFds);
1334    tvPtr = NULL;                       /* Wait for the first read. This is so
1335                                         * that we don't spin when no data is
1336                                         * available. */
1337    if (timeout >= 0L) {
1338        tv.tv_sec = 0L;
1339        tv.tv_usec = timeout;
1340        tvPtr = &tv;
1341    } else {
1342        TRACE("Blocking on select()");
1343    }
1344    while (inBufPtr->isLineAvailable() ||
1345           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
1346        size_t numBytes;
1347        unsigned char *buffer;
1348
1349        /* A short read is treated as an error here because we assume that we
1350         * will always get commands line by line. */
1351        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1352            /* Terminate the server if we can't communicate with the client
1353             * anymore. */
1354            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1355                TRACE("Exiting server on EOF from client");
1356                return -1;
1357            } else {
1358                ERROR("Exiting server, failed to read from client: %s",
1359                      strerror(errno));
1360                return -1;
1361            }
1362        }
1363        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
1364        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
1365            struct timeval start, finish;
1366            gettimeofday(&start, NULL);
1367            status = ExecuteCommand(interp, &command);
1368            gettimeofday(&finish, NULL);
1369            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
1370            g_stats.nCommands++;
1371            if (status == TCL_BREAK) {
1372                return 2;               /* This was caused by a "imgflush"
1373                                         * command. Break out of the read loop
1374                                         * and allow a new image to be
1375                                         * rendered. */
1376            } else { //if (status != TCL_OK) {
1377                ret = 0;
1378                if (handleError(interp, clientData, status, fdOut) < 0) {
1379                    return -1;
1380                }
1381            }
1382            if (status == TCL_OK) {
1383                ret = 3;
1384            }
1385        }
1386
1387        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
1388                                         * if no data is available. */
1389        FD_SET(inBufPtr->file(), &readFds);
1390        tvPtr = &tv;
1391    }
1392
1393    return ret;
1394}
1395
1396/**
1397 * \brief Send error message to client socket
1398 */
1399int
1400GeoVis::handleError(Tcl_Interp *interp,
1401                    ClientData clientData,
1402                    int status, int fdOut)
1403{
1404    const char *string;
1405    int nBytes;
1406
1407    if (status != TCL_OK) {
1408        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
1409        nBytes = strlen(string);
1410        if (nBytes > 0) {
1411            TRACE("status=%d errorInfo=(%s)", status, string);
1412
1413            std::ostringstream oss;
1414            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
1415            nBytes = oss.str().length();
1416
1417            if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
1418                return -1;
1419            }
1420        }
1421    }
1422
1423    string = getUserMessages();
1424    nBytes = strlen(string);
1425    if (nBytes > 0) {
1426        TRACE("userError=(%s)", string);
1427
1428        std::ostringstream oss;
1429        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
1430        nBytes = oss.str().length();
1431
1432        if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
1433            return -1;
1434        }
1435
1436        clearUserMessages();
1437    }
1438
1439    return 0;
1440}
1441
1442/**
1443 * \brief Create Tcl interpreter and add commands
1444 *
1445 * \return The initialized Tcl interpreter
1446 */
1447void
1448GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
1449{
1450    Tcl_MakeSafe(interp);
1451    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
1452    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
1453    Tcl_CreateObjCommand(interp, "colormap",       ColorMapCmd,       clientData, NULL);
1454    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
1455    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
1456    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
1457    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
1458    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
1459    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
1460}
1461
1462/**
1463 * \brief Delete Tcl commands and interpreter
1464 */
1465void GeoVis::exitTcl(Tcl_Interp *interp)
1466{
1467    Tcl_DeleteCommand(interp, "camera");
1468    Tcl_DeleteCommand(interp, "clientinfo");
1469    Tcl_DeleteCommand(interp, "colormap");
1470    Tcl_DeleteCommand(interp, "imgflush");
1471    Tcl_DeleteCommand(interp, "key");
1472    Tcl_DeleteCommand(interp, "map");
1473    Tcl_DeleteCommand(interp, "mouse");
1474    Tcl_DeleteCommand(interp, "renderer");
1475    Tcl_DeleteCommand(interp, "screen");
1476
1477    Tcl_DeleteInterp(interp);
1478}
Note: See TracBrowser for help on using the repository browser.