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

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

Add param to 'camera go' for setting zoom ratio.

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