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

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

Add "camera go" command for moving viewpoint based on screen coordinates. This
is intended to replace the server-side button 1 double click event.

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