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

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

Add type parameter to layer add, first pass at feature layers.

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