source: trunk/packages/vizservers/vtkvis/RpVtkRenderer.cpp @ 2314

Last change on this file since 2314 was 2290, checked in by ldelgass, 13 years ago
  • Add contour3d (isosurface geometry) command to vtkvis
  • Use depth peeling algorithm (order independent transparency) by default, add 'renderer depthpeel' command to toggle.
  • Use tri strips in some geometry generators for better performance
  • Heightmap: 2D slice resampling of non-image 3D datasets (e.g. points, ugrids) Set a default scaling based on data range. Still some problems where images from resampling have zero values in out-of-bounds pixels (probe filter creates a mask array that could be used). Still need a method/heuristic to select image resolution when resampling.
  • Property svn:eol-style set to native
File size: 115.2 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2011, Purdue Research Foundation
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cfloat>
9#include <cstring>
10#include <cassert>
11
12#include <GL/gl.h>
13
14#ifdef WANT_TRACE
15#include <sys/time.h>
16#endif
17
18#include <vtkMath.h>
19#include <vtkCamera.h>
20#include <vtkCoordinate.h>
21#include <vtkTransform.h>
22#include <vtkCharArray.h>
23#include <vtkAxisActor2D.h>
24#ifdef USE_CUSTOM_AXES
25#include <vtkRpCubeAxesActor2D.h>
26#else
27#include <vtkCubeAxesActor.h>
28#include <vtkCubeAxesActor2D.h>
29#endif
30#include <vtkDataSetReader.h>
31#include <vtkDataSetMapper.h>
32#include <vtkContourFilter.h>
33#include <vtkPolyDataMapper.h>
34#include <vtkProperty.h>
35#include <vtkProperty2D.h>
36#include <vtkPointData.h>
37#include <vtkLookupTable.h>
38#include <vtkTextProperty.h>
39#include <vtkOpenGLRenderWindow.h>
40
41#include "RpVtkRenderer.h"
42#include "ColorMap.h"
43#include "Trace.h"
44
45#define ELAPSED_TIME(t1, t2) \
46    ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3f) : \
47     (((t2).tv_sec - (t1).tv_sec))*1.0e+3f + (float)((t2).tv_usec - (t1).tv_usec)/1.0e+3f)
48
49using namespace Rappture::VtkVis;
50
51Renderer::Renderer() :
52    _needsRedraw(true),
53    _windowWidth(320),
54    _windowHeight(320),
55    _cameraZoomRatio(1),
56    _useCumulativeRange(true),
57    _cumulativeRangeOnlyVisible(false),
58    _cameraMode(PERSPECTIVE)
59{
60    _bgColor[0] = 0;
61    _bgColor[1] = 0;
62    _bgColor[2] = 0;
63    _cameraPan[0] = 0;
64    _cameraPan[1] = 0;
65    _cumulativeDataRange[0] = 0.0;
66    _cumulativeDataRange[1] = 1.0;
67    // clipping planes to prevent overdrawing axes
68    _activeClipPlanes = vtkSmartPointer<vtkPlaneCollection>::New();
69    // bottom
70    _clipPlanes[0] = vtkSmartPointer<vtkPlane>::New();
71    _clipPlanes[0]->SetNormal(0, 1, 0);
72    _clipPlanes[0]->SetOrigin(0, 0, 0);
73    if (_cameraMode == IMAGE)
74        _activeClipPlanes->AddItem(_clipPlanes[0]);
75    // left
76    _clipPlanes[1] = vtkSmartPointer<vtkPlane>::New();
77    _clipPlanes[1]->SetNormal(1, 0, 0);
78    _clipPlanes[1]->SetOrigin(0, 0, 0);
79    if (_cameraMode == IMAGE)
80        _activeClipPlanes->AddItem(_clipPlanes[1]);
81    // top
82    _clipPlanes[2] = vtkSmartPointer<vtkPlane>::New();
83    _clipPlanes[2]->SetNormal(0, -1, 0);
84    _clipPlanes[2]->SetOrigin(0, 1, 0);
85    if (_cameraMode == IMAGE)
86        _activeClipPlanes->AddItem(_clipPlanes[2]);
87    // right
88    _clipPlanes[3] = vtkSmartPointer<vtkPlane>::New();
89    _clipPlanes[3]->SetNormal(-1, 0, 0);
90    _clipPlanes[3]->SetOrigin(1, 0, 0);
91    if (_cameraMode == IMAGE)
92        _activeClipPlanes->AddItem(_clipPlanes[3]);
93    _renderer = vtkSmartPointer<vtkRenderer>::New();
94    _renderer->LightFollowCameraOn();
95    initAxes();
96    initCamera();
97    storeCameraOrientation();
98    _renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
99#ifdef USE_OFFSCREEN_RENDERING
100    _renderWindow->DoubleBufferOff();
101    _renderWindow->OffScreenRenderingOn();
102#else
103    _renderWindow->SwapBuffersOff();
104#endif
105    _renderWindow->SetSize(_windowWidth, _windowHeight);
106    // Next 2 options needed to support depth peeling
107    _renderWindow->SetAlphaBitPlanes(1);
108    _renderWindow->SetMultiSamples(0);
109    _renderer->SetMaximumNumberOfPeels(100);
110    _renderer->SetUseDepthPeeling(1);
111    _renderWindow->AddRenderer(_renderer);
112    addColorMap("default", ColorMap::getDefault());
113    addColorMap("volumeDefault", ColorMap::getVolumeDefault());
114}
115
116Renderer::~Renderer()
117{
118    TRACE("Enter");
119    TRACE("Deleting Contour2Ds");
120    for (Contour2DHashmap::iterator itr = _contour2Ds.begin();
121             itr != _contour2Ds.end(); ++itr) {
122        delete itr->second;
123    }
124    _contour2Ds.clear();
125    TRACE("Deleting Contour3Ds");
126    for (Contour3DHashmap::iterator itr = _contour3Ds.begin();
127             itr != _contour3Ds.end(); ++itr) {
128        delete itr->second;
129    }
130    _contour3Ds.clear();
131    TRACE("Deleting Glyphs");
132    for (GlyphsHashmap::iterator itr = _glyphs.begin();
133             itr != _glyphs.end(); ++itr) {
134        delete itr->second;
135    }
136    _glyphs.clear();
137    TRACE("Deleting HeightMaps");
138    for (HeightMapHashmap::iterator itr = _heightMaps.begin();
139             itr != _heightMaps.end(); ++itr) {
140        delete itr->second;
141    }
142    _heightMaps.clear();
143    TRACE("Deleting PolyDatas");
144    for (PolyDataHashmap::iterator itr = _polyDatas.begin();
145             itr != _polyDatas.end(); ++itr) {
146        delete itr->second;
147    }
148    _polyDatas.clear();
149    TRACE("Deleting PseudoColors");
150    for (PseudoColorHashmap::iterator itr = _pseudoColors.begin();
151             itr != _pseudoColors.end(); ++itr) {
152        delete itr->second;
153    }
154    _pseudoColors.clear();
155    TRACE("Deleting Volumes");
156    for (VolumeHashmap::iterator itr = _volumes.begin();
157             itr != _volumes.end(); ++itr) {
158        delete itr->second;
159    }
160    _volumes.clear();
161    TRACE("Deleting ColorMaps");
162    // Delete color maps and data sets last in case references still
163    // exist
164    for (ColorMapHashmap::iterator itr = _colorMaps.begin();
165             itr != _colorMaps.end(); ++itr) {
166        delete itr->second;
167    }
168    _colorMaps.clear();
169    TRACE("Deleting DataSets");
170    for (DataSetHashmap::iterator itr = _dataSets.begin();
171             itr != _dataSets.end(); ++itr) {
172        delete itr->second;
173    }
174    _dataSets.clear();
175    TRACE("Leave");
176}
177
178/**
179 * \brief Add a DataSet to this Renderer
180 *
181 * This just adds the DataSet to the Renderer's list of data sets.
182 * In order to render the data, a graphics object using the data
183 * set must be added to the Renderer.
184 */
185void Renderer::addDataSet(const DataSetId& id)
186{
187    if (getDataSet(id) != NULL) {
188        WARN("Replacing existing dataset %s", id.c_str());
189        deleteDataSet(id);
190    }
191    _dataSets[id] = new DataSet(id);
192}
193
194/**
195 * \brief Remove the Contour2D isolines for the specified DataSet
196 *
197 * The underlying Contour2D is deleted, freeing its memory
198 */
199void Renderer::deleteContour2D(const DataSetId& id)
200{
201    Contour2DHashmap::iterator itr;
202
203    bool doAll = false;
204
205    if (id.compare("all") == 0) {
206        itr = _contour2Ds.begin();
207        doAll = true;
208    } else {
209        itr = _contour2Ds.find(id);
210    }
211    if (itr == _contour2Ds.end()) {
212        ERROR("Contour2D not found: %s", id.c_str());
213        return;
214    }
215
216    TRACE("Deleting Contour2Ds for %s", id.c_str());
217
218    do {
219        Contour2D *contour = itr->second;
220        if (contour->getProp())
221            _renderer->RemoveViewProp(contour->getProp());
222        delete contour;
223
224        itr = _contour2Ds.erase(itr);
225    } while (doAll && itr != _contour2Ds.end());
226
227    _needsRedraw = true;
228}
229
230/**
231 * \brief Remove the Contour3D isosurfaces for the specified DataSet
232 *
233 * The underlying Contour3D is deleted, freeing its memory
234 */
235void Renderer::deleteContour3D(const DataSetId& id)
236{
237    Contour3DHashmap::iterator itr;
238
239    bool doAll = false;
240
241    if (id.compare("all") == 0) {
242        itr = _contour3Ds.begin();
243        doAll = true;
244    } else {
245        itr = _contour3Ds.find(id);
246    }
247    if (itr == _contour3Ds.end()) {
248        ERROR("Contour3D not found: %s", id.c_str());
249        return;
250    }
251
252    TRACE("Deleting Contour3Ds for %s", id.c_str());
253
254    do {
255        Contour3D *contour = itr->second;
256        if (contour->getProp())
257            _renderer->RemoveViewProp(contour->getProp());
258        delete contour;
259
260        itr = _contour3Ds.erase(itr);
261    } while (doAll && itr != _contour3Ds.end());
262
263    _needsRedraw = true;
264}
265
266/**
267 * \brief Remove the Glyphs for the specified DataSet
268 *
269 * The underlying Glyphs is deleted, freeing its memory
270 */
271void Renderer::deleteGlyphs(const DataSetId& id)
272{
273    GlyphsHashmap::iterator itr;
274
275    bool doAll = false;
276
277    if (id.compare("all") == 0) {
278        itr = _glyphs.begin();
279        doAll = true;
280    } else {
281        itr = _glyphs.find(id);
282    }
283    if (itr == _glyphs.end()) {
284        ERROR("Glyphs not found: %s", id.c_str());
285        return;
286    }
287
288    TRACE("Deleting Glyphs for %s", id.c_str());
289
290    do {
291        Glyphs *glyphs = itr->second;
292        if (glyphs->getProp())
293            _renderer->RemoveViewProp(glyphs->getProp());
294        delete glyphs;
295
296        itr = _glyphs.erase(itr);
297    } while (doAll && itr != _glyphs.end());
298
299    _needsRedraw = true;
300}
301
302/**
303 * \brief Remove the HeightMap for the specified DataSet
304 *
305 * The underlying HeightMap is deleted, freeing its memory
306 */
307void Renderer::deleteHeightMap(const DataSetId& id)
308{
309    HeightMapHashmap::iterator itr;
310
311    bool doAll = false;
312
313    if (id.compare("all") == 0) {
314        itr = _heightMaps.begin();
315        doAll = true;
316    } else {
317        itr = _heightMaps.find(id);
318    }
319    if (itr == _heightMaps.end()) {
320        ERROR("HeightMap not found: %s", id.c_str());
321        return;
322    }
323
324    TRACE("Deleting HeightMaps for %s", id.c_str());
325
326    do {
327        HeightMap *hmap = itr->second;
328        if (hmap->getProp())
329            _renderer->RemoveViewProp(hmap->getProp());
330        delete hmap;
331
332        itr = _heightMaps.erase(itr);
333    } while (doAll && itr != _heightMaps.end());
334
335    _needsRedraw = true;
336}
337
338/**
339 * \brief Remove the PolyData mesh for the specified DataSet
340 *
341 * The underlying PolyData is deleted, freeing its memory
342 */
343void Renderer::deletePolyData(const DataSetId& id)
344{
345    PolyDataHashmap::iterator itr;
346
347    bool doAll = false;
348
349    if (id.compare("all") == 0) {
350        itr = _polyDatas.begin();
351        doAll = true;
352    } else {
353        itr = _polyDatas.find(id);
354    }
355    if (itr == _polyDatas.end()) {
356        ERROR("PolyData not found: %s", id.c_str());
357        return;
358    }
359
360    TRACE("Deleting PolyDatas for %s", id.c_str());
361
362    do {
363        PolyData *polyData = itr->second;
364        if (polyData->getProp())
365            _renderer->RemoveViewProp(polyData->getProp());
366        delete polyData;
367
368        itr = _polyDatas.erase(itr);
369    } while (doAll && itr != _polyDatas.end());
370
371    _needsRedraw = true;
372}
373
374/**
375 * \brief Remove the PseudoColor mapping for the specified DataSet
376 *
377 * The underlying PseudoColor object is deleted, freeing its memory
378 */
379void Renderer::deletePseudoColor(const DataSetId& id)
380{
381    PseudoColorHashmap::iterator itr;
382
383    bool doAll = false;
384
385    if (id.compare("all") == 0) {
386        itr = _pseudoColors.begin();
387        doAll = true;
388    } else {
389        itr = _pseudoColors.find(id);
390    }
391    if (itr == _pseudoColors.end()) {
392        ERROR("PseudoColor not found: %s", id.c_str());
393        return;
394    }
395
396    TRACE("Deleting PseudoColors for %s", id.c_str());
397
398    do  {
399        PseudoColor *ps = itr->second;
400        if (ps->getProp())
401            _renderer->RemoveViewProp(ps->getProp());
402        delete ps;
403
404        itr = _pseudoColors.erase(itr);
405    } while (doAll && itr != _pseudoColors.end());
406
407    _needsRedraw = true;
408}
409
410/**
411 * \brief Remove the Volume for the specified DataSet
412 *
413 * The underlying Volume is deleted, freeing its memory
414 */
415void Renderer::deleteVolume(const DataSetId& id)
416{
417    VolumeHashmap::iterator itr;
418
419    bool doAll = false;
420
421    if (id.compare("all") == 0) {
422        itr = _volumes.begin();
423        doAll = true;
424    } else {
425        itr = _volumes.find(id);
426    }
427    if (itr == _volumes.end()) {
428        ERROR("Volume not found: %s", id.c_str());
429        return;
430    }
431
432    TRACE("Deleting Volumes for %s", id.c_str());
433
434    do {
435        Volume *volume = itr->second;
436        if (volume->getProp())
437            _renderer->RemoveViewProp(volume->getProp());
438        delete volume;
439
440        itr = _volumes.erase(itr);
441    } while (doAll && itr != _volumes.end());
442
443    _needsRedraw = true;
444}
445
446/**
447 * \brief Remove the specified DataSet and associated rendering objects
448 *
449 * The underlying DataSet and any associated graphics
450 * objects are deleted, freeing the memory used.
451 */
452void Renderer::deleteDataSet(const DataSetId& id)
453{
454    DataSetHashmap::iterator itr;
455
456    bool doAll = false;
457
458    if (id.compare("all") == 0) {
459        itr = _dataSets.begin();
460        doAll = true;
461    } else {
462        itr = _dataSets.find(id);
463    }
464    if (itr == _dataSets.end()) {
465        ERROR("Unknown dataset %s", id.c_str());
466        return;
467    }
468
469    do {
470        TRACE("Deleting dataset %s", itr->second->getName().c_str());
471
472        deleteContour2D(itr->second->getName());
473        deleteContour3D(itr->second->getName());
474        deleteGlyphs(itr->second->getName());
475        deleteHeightMap(itr->second->getName());
476        deletePolyData(itr->second->getName());
477        deletePseudoColor(itr->second->getName());
478        deleteVolume(itr->second->getName());
479   
480        TRACE("After deleting graphics objects");
481
482        delete itr->second;
483        itr = _dataSets.erase(itr);
484    } while (doAll && itr != _dataSets.end());
485
486    // Update cumulative data range
487    collectDataRanges(_cumulativeDataRange, _cumulativeRangeOnlyVisible);
488    updateRanges(_useCumulativeRange);
489
490    _needsRedraw = true;
491}
492
493/**
494 * \brief Find the DataSet for the given DataSetId key
495 *
496 * \return A pointer to the DataSet, or NULL if not found
497 */
498DataSet *Renderer::getDataSet(const DataSetId& id)
499{
500    DataSetHashmap::iterator itr = _dataSets.find(id);
501    if (itr == _dataSets.end()) {
502        TRACE("DataSet not found: %s", id.c_str());
503        return NULL;
504    } else
505        return itr->second;
506}
507
508/**
509 * \brief (Re-)load the data for the specified DataSet key from a file
510 */
511bool Renderer::setDataFile(const DataSetId& id, const char *filename)
512{
513    DataSet *ds = getDataSet(id);
514    if (ds) {
515        bool ret = ds->setDataFile(filename);
516        collectDataRanges(_cumulativeDataRange, _cumulativeRangeOnlyVisible);
517        updateRanges(_useCumulativeRange);
518        _needsRedraw = true;
519        return ret;
520    } else
521        return false;
522}
523
524/**
525 * \brief (Re-)load the data for the specified DataSet key from a memory buffer
526 */
527bool Renderer::setData(const DataSetId& id, char *data, int nbytes)
528{
529    DataSet *ds = getDataSet(id);
530    if (ds) {
531        bool ret = ds->setData(data, nbytes);
532        collectDataRanges(_cumulativeDataRange, _cumulativeRangeOnlyVisible);
533        updateRanges(_useCumulativeRange);
534        _needsRedraw = true;
535        return ret;
536    } else
537        return false;
538}
539
540/**
541 * \brief Control whether the cumulative data range of datasets is used for
542 * colormapping and contours
543 */
544void Renderer::setUseCumulativeDataRange(bool state, bool onlyVisible)
545{
546    if (state != _useCumulativeRange) {
547        _useCumulativeRange = state;
548        _cumulativeRangeOnlyVisible = onlyVisible;
549        collectDataRanges(_cumulativeDataRange, _cumulativeRangeOnlyVisible);
550        updateRanges(_useCumulativeRange);
551        _needsRedraw = true;
552    }
553}
554
555void Renderer::resetAxes()
556{
557    TRACE("Resetting axes");
558    if (_cubeAxesActor == NULL ||
559        _cubeAxesActor2D == NULL) {
560        initAxes();
561    }
562    if (_cameraMode == IMAGE) {
563        if (_renderer->HasViewProp(_cubeAxesActor)) {
564            TRACE("Removing 3D axes");
565            _renderer->RemoveViewProp(_cubeAxesActor);
566        }
567        if (!_renderer->HasViewProp(_cubeAxesActor2D)) {
568            TRACE("Adding 2D axes");
569            _renderer->AddViewProp(_cubeAxesActor2D);
570        }
571    } else {
572        if (_renderer->HasViewProp(_cubeAxesActor2D)) {
573            TRACE("Removing 2D axes");
574            _renderer->RemoveViewProp(_cubeAxesActor2D);
575        }
576        if (!_renderer->HasViewProp(_cubeAxesActor)) {
577            TRACE("Adding 3D axes");
578            _renderer->AddViewProp(_cubeAxesActor);
579        }
580        double bounds[6];
581        collectBounds(bounds, false);
582        _cubeAxesActor->SetBounds(bounds);
583    }
584}
585
586/**
587 * \brief Set inital properties on the 2D Axes
588 */
589void Renderer::initAxes()
590{
591    TRACE("Initializing axes");
592    if (_cubeAxesActor == NULL)
593        _cubeAxesActor = vtkSmartPointer<vtkCubeAxesActor>::New();
594    _cubeAxesActor->SetCamera(_renderer->GetActiveCamera());
595    // Don't offset labels at origin
596    _cubeAxesActor->SetCornerOffset(0);
597    _cubeAxesActor->SetFlyModeToClosestTriad();
598
599#ifdef USE_CUSTOM_AXES
600    if (_cubeAxesActor2D == NULL)
601        _cubeAxesActor2D = vtkSmartPointer<vtkRpCubeAxesActor2D>::New();
602#else
603    if (_cubeAxesActor2D == NULL)
604        _cubeAxesActor2D = vtkSmartPointer<vtkCubeAxesActor2D>::New();
605#endif
606
607    _cubeAxesActor2D->SetCamera(_renderer->GetActiveCamera());
608    _cubeAxesActor2D->ZAxisVisibilityOff();
609    _cubeAxesActor2D->SetCornerOffset(0);
610    _cubeAxesActor2D->SetFlyModeToClosestTriad();
611
612    _cubeAxesActor2D->ScalingOff();
613    //_cubeAxesActor2D->SetShowActualBounds(0);
614    _cubeAxesActor2D->SetFontFactor(1.25);
615    // Use "nice" range and number of ticks/labels
616    _cubeAxesActor2D->GetXAxisActor2D()->AdjustLabelsOn();
617    _cubeAxesActor2D->GetYAxisActor2D()->AdjustLabelsOn();
618
619#ifdef USE_CUSTOM_AXES
620    _cubeAxesActor2D->SetAxisTitleTextProperty(NULL);
621    _cubeAxesActor2D->SetAxisLabelTextProperty(NULL);
622    //_cubeAxesActor2D->GetXAxisActor2D()->SizeFontRelativeToAxisOn();
623    _cubeAxesActor2D->GetXAxisActor2D()->GetTitleTextProperty()->BoldOn();
624    _cubeAxesActor2D->GetXAxisActor2D()->GetTitleTextProperty()->ItalicOff();
625    _cubeAxesActor2D->GetXAxisActor2D()->GetTitleTextProperty()->ShadowOn();
626    _cubeAxesActor2D->GetXAxisActor2D()->GetLabelTextProperty()->BoldOff();
627    _cubeAxesActor2D->GetXAxisActor2D()->GetLabelTextProperty()->ItalicOff();
628    _cubeAxesActor2D->GetXAxisActor2D()->GetLabelTextProperty()->ShadowOff();
629
630    //_cubeAxesActor2D->GetYAxisActor2D()->SizeFontRelativeToAxisOn();
631    _cubeAxesActor2D->GetYAxisActor2D()->GetTitleTextProperty()->BoldOn();
632    _cubeAxesActor2D->GetYAxisActor2D()->GetTitleTextProperty()->ItalicOff();
633    _cubeAxesActor2D->GetYAxisActor2D()->GetTitleTextProperty()->ShadowOn();
634    _cubeAxesActor2D->GetYAxisActor2D()->GetLabelTextProperty()->BoldOff();
635    _cubeAxesActor2D->GetYAxisActor2D()->GetLabelTextProperty()->ItalicOff();
636    _cubeAxesActor2D->GetYAxisActor2D()->GetLabelTextProperty()->ShadowOff();
637#else
638    _cubeAxesActor2D->GetAxisTitleTextProperty()->BoldOn();
639    _cubeAxesActor2D->GetAxisTitleTextProperty()->ItalicOff();
640    _cubeAxesActor2D->GetAxisTitleTextProperty()->ShadowOn();
641    _cubeAxesActor2D->GetAxisLabelTextProperty()->BoldOff();
642    _cubeAxesActor2D->GetAxisLabelTextProperty()->ItalicOff();
643    _cubeAxesActor2D->GetAxisLabelTextProperty()->ShadowOff();
644#endif
645
646    if (_cameraMode == IMAGE) {
647        if (!_renderer->HasViewProp(_cubeAxesActor2D))
648            _renderer->AddViewProp(_cubeAxesActor2D);
649    } else {
650        if (!_renderer->HasViewProp(_cubeAxesActor))
651            _renderer->AddViewProp(_cubeAxesActor);
652    }
653}
654
655/**
656 * \brief Set Fly mode of axes
657 */
658void Renderer::setAxesFlyMode(AxesFlyMode mode)
659{
660    if (_cubeAxesActor == NULL)
661        initAxes();
662    switch (mode) {
663    case FLY_STATIC_EDGES:
664        _cubeAxesActor->SetFlyModeToStaticEdges();
665        break;
666    case FLY_STATIC_TRIAD:
667        _cubeAxesActor->SetFlyModeToStaticTriad();
668        break;
669    case FLY_OUTER_EDGES:
670        _cubeAxesActor->SetFlyModeToOuterEdges();
671        break;
672    case FLY_FURTHEST_TRIAD:
673        _cubeAxesActor->SetFlyModeToFurthestTriad();
674        break;
675    case FLY_CLOSEST_TRIAD:
676    default:
677        _cubeAxesActor->SetFlyModeToClosestTriad();
678        break;
679    }
680    _needsRedraw = true;
681}
682
683/**
684 * \brief Set color of axes, ticks, labels, titles
685 */
686void Renderer::setAxesColor(double color[3])
687{
688    if (_cubeAxesActor != NULL) {
689        _cubeAxesActor->GetProperty()->SetColor(color);
690        _needsRedraw = true;
691    }
692    if (_cubeAxesActor2D != NULL) {
693        _cubeAxesActor2D->GetProperty()->SetColor(color);
694#ifdef USE_CUSTOM_AXES
695        _cubeAxesActor2D->GetXAxisActor2D()->GetTitleTextProperty()->SetColor(color);
696        _cubeAxesActor2D->GetXAxisActor2D()->GetLabelTextProperty()->SetColor(color);
697        _cubeAxesActor2D->GetYAxisActor2D()->GetTitleTextProperty()->SetColor(color);
698        _cubeAxesActor2D->GetYAxisActor2D()->GetLabelTextProperty()->SetColor(color);
699#else
700        _cubeAxesActor2D->GetAxisTitleTextProperty()->SetColor(color);
701        _cubeAxesActor2D->GetAxisLabelTextProperty()->SetColor(color);
702#endif
703        _needsRedraw = true;
704    }
705}
706
707/**
708 * \brief Turn on/off rendering of all axes gridlines
709 */
710void Renderer::setAxesGridVisibility(bool state)
711{
712    if (_cubeAxesActor != NULL) {
713        _cubeAxesActor->SetDrawXGridlines((state ? 1 : 0));
714        _cubeAxesActor->SetDrawYGridlines((state ? 1 : 0));
715        _cubeAxesActor->SetDrawZGridlines((state ? 1 : 0));
716        _needsRedraw = true;
717    }
718}
719
720/**
721 * \brief Turn on/off rendering of single axis gridlines
722 */
723void Renderer::setAxisGridVisibility(Axis axis, bool state)
724{
725    if (_cubeAxesActor != NULL) {
726        if (axis == X_AXIS) {
727            _cubeAxesActor->SetDrawXGridlines((state ? 1 : 0));
728        } else if (axis == Y_AXIS) {
729            _cubeAxesActor->SetDrawYGridlines((state ? 1 : 0));
730        } else if (axis == Z_AXIS) {
731            _cubeAxesActor->SetDrawZGridlines((state ? 1 : 0));
732        }
733        _needsRedraw = true;
734    }
735}
736
737/**
738 * \brief Turn on/off rendering of all axes
739 */
740void Renderer::setAxesVisibility(bool state)
741{
742    if (_cubeAxesActor != NULL) {
743        _cubeAxesActor->SetVisibility((state ? 1 : 0));
744        _needsRedraw = true;
745    }
746    if (_cubeAxesActor2D != NULL) {
747        _cubeAxesActor2D->SetVisibility((state ? 1 : 0));
748        _needsRedraw = true;
749    }
750    setAxisVisibility(X_AXIS, state);
751    setAxisVisibility(Y_AXIS, state);
752    setAxisVisibility(Z_AXIS, state);
753}
754
755/**
756 * \brief Turn on/off rendering of the specified axis
757 */
758void Renderer::setAxisVisibility(Axis axis, bool state)
759{
760    if (_cubeAxesActor != NULL) {
761        if (axis == X_AXIS) {
762            _cubeAxesActor->SetXAxisVisibility((state ? 1 : 0));
763        } else if (axis == Y_AXIS) {
764            _cubeAxesActor->SetYAxisVisibility((state ? 1 : 0));
765        } else if (axis == Z_AXIS) {
766            _cubeAxesActor->SetZAxisVisibility((state ? 1 : 0));
767        }
768        _needsRedraw = true;
769    }
770    if (_cubeAxesActor2D != NULL) {
771        if (axis == X_AXIS) {
772            _cubeAxesActor2D->SetXAxisVisibility((state ? 1 : 0));
773        } else if (axis == Y_AXIS) {
774            _cubeAxesActor2D->SetYAxisVisibility((state ? 1 : 0));
775        }
776        _needsRedraw = true;
777    }
778}
779
780/**
781 * \brief Set title of the specified axis
782 */
783void Renderer::setAxisTitle(Axis axis, const char *title)
784{
785    if (_cubeAxesActor != NULL) {
786        if (axis == X_AXIS) {
787            _cubeAxesActor->SetXTitle(title);
788        } else if (axis == Y_AXIS) {
789            _cubeAxesActor->SetYTitle(title);
790        } else if (axis == Z_AXIS) {
791            _cubeAxesActor->SetZTitle(title);
792        }
793        _needsRedraw = true;
794    }
795    if (_cubeAxesActor2D != NULL) {
796        if (axis == X_AXIS) {
797            _cubeAxesActor2D->SetXLabel(title);
798        } else if (axis == Y_AXIS) {
799            _cubeAxesActor2D->SetYLabel(title);
800        }
801        _needsRedraw = true;
802    }
803}
804
805/**
806 * \brief Set units of the specified axis
807 */
808void Renderer::setAxisUnits(Axis axis, const char *units)
809{
810    if (_cubeAxesActor != NULL) {
811        if (axis == X_AXIS) {
812            _cubeAxesActor->SetXUnits(units);
813        } else if (axis == Y_AXIS) {
814            _cubeAxesActor->SetYUnits(units);
815        } else if (axis == Z_AXIS) {
816            _cubeAxesActor->SetZUnits(units);
817        }
818        _needsRedraw = true;
819    }
820#ifdef notdef
821    if (_cubeAxesActor2D != NULL) {
822        if (axis == X_AXIS) {
823            _cubeAxesActor2D->SetXUnits(units);
824        } else if (axis == Y_AXIS) {
825            _cubeAxesActor2D->SetYUnits(units);
826        }
827        _needsRedraw = true;
828    }
829#endif
830}
831
832/**
833 * \brief Add a color map for use in the Renderer
834 */
835void Renderer::addColorMap(const ColorMapId& id, ColorMap *colorMap)
836{
837    if (colorMap != NULL) {
838        colorMap->build();
839        if (getColorMap(id) != NULL) {
840            WARN("Replacing existing colormap %s", id.c_str());
841            deleteColorMap(id);
842        }
843        _colorMaps[id] = colorMap;
844    } else {
845        ERROR("NULL ColorMap");
846    }
847}
848
849/**
850 * \brief Return the ColorMap associated with the colormap key given
851 */
852ColorMap *Renderer::getColorMap(const ColorMapId& id)
853{
854    ColorMapHashmap::iterator itr = _colorMaps.find(id);
855
856    if (itr == _colorMaps.end())
857        return NULL;
858    else
859        return itr->second;
860}
861
862/**
863 * \brief Remove the colormap associated with the key given
864 *
865 * The underlying vtkLookupTable will be deleted if it is not referenced
866 * by any other objects
867 */
868void Renderer::deleteColorMap(const ColorMapId& id)
869{
870    ColorMapHashmap::iterator itr;
871
872    bool doAll = false;
873
874    if (id.compare("all") == 0) {
875        itr = _colorMaps.begin();
876        doAll = true;
877    } else {
878        itr = _colorMaps.find(id);
879    }
880
881    if (itr == _colorMaps.end()) {
882        ERROR("Unknown ColorMap %s", id.c_str());
883        return;
884    }
885
886    do {
887        TRACE("Deleting ColorMap %s", itr->second->getName().c_str());
888
889        // TODO: Check if color map is used in PseudoColors?
890        delete itr->second;
891        itr = _colorMaps.erase(itr);
892    } while (doAll && itr != _colorMaps.end());
893}
894
895/**
896 * \brief Render a labelled legend image for the given colormap
897 *
898 * \return The image is rendered into the supplied array, false is
899 * returned if the color map is not found
900 */
901bool Renderer::renderColorMap(const ColorMapId& id,
902                              const DataSetId& dataSetID,
903                              const char *title,
904                              int width, int height,
905                              vtkUnsignedCharArray *imgData)
906{
907    ColorMap *colorMap = getColorMap(id);
908    if (colorMap == NULL)
909        return false;
910
911    if (_legendRenderWindow == NULL) {
912        _legendRenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
913#ifdef USE_OFFSCREEN_RENDERING
914        _legendRenderWindow->DoubleBufferOff();
915        _legendRenderWindow->OffScreenRenderingOn();
916#endif
917    }
918
919    _legendRenderWindow->SetSize(width, height);
920
921    if (_legendRenderer == NULL) {
922        _legendRenderer = vtkSmartPointer<vtkRenderer>::New();
923        _legendRenderWindow->AddRenderer(_legendRenderer);
924    }
925    _legendRenderer->SetBackground(_bgColor[0], _bgColor[1], _bgColor[2]);
926
927    if (_scalarBarActor == NULL) {
928        _scalarBarActor = vtkSmartPointer<vtkScalarBarActor>::New();
929        _scalarBarActor->UseOpacityOn();
930        _legendRenderer->AddViewProp(_scalarBarActor);
931    }
932
933    vtkSmartPointer<vtkLookupTable> lut = colorMap->getLookupTable();
934    if (dataSetID.compare("all") == 0) {
935        lut->SetRange(_cumulativeDataRange);
936    } else {
937        DataSet *dataSet = getDataSet(dataSetID);
938        if (dataSet == NULL) {
939            lut->SetRange(_cumulativeDataRange);
940        } else {
941            double range[2];
942            dataSet->getDataRange(range);
943            lut->SetRange(range);
944        }
945    }
946    _scalarBarActor->SetLookupTable(lut);
947
948    // Set viewport-relative width/height/pos
949    if (width > height) {
950        _scalarBarActor->SetOrientationToHorizontal();
951        _scalarBarActor->SetHeight(0.8);
952        _scalarBarActor->SetWidth(0.85);
953        _scalarBarActor->SetPosition(.075, .1);
954    } else {
955        _scalarBarActor->SetOrientationToVertical();
956        _scalarBarActor->SetHeight(0.9);
957        _scalarBarActor->SetWidth(0.8);
958        _scalarBarActor->SetPosition(.1, .05);
959    }
960    _scalarBarActor->SetTitle(title);
961    _scalarBarActor->GetTitleTextProperty()->ItalicOff();
962    _scalarBarActor->GetLabelTextProperty()->BoldOff();
963    _scalarBarActor->GetLabelTextProperty()->ItalicOff();
964    _scalarBarActor->GetLabelTextProperty()->ShadowOff();
965
966    _legendRenderWindow->Render();
967
968#ifdef RENDER_TARGA
969    _legendRenderWindow->MakeCurrent();
970    // Must clear previous errors first.
971    while (glGetError() != GL_NO_ERROR){
972        ;
973    }
974    int bytesPerPixel = TARGA_BYTES_PER_PIXEL;
975    int size = bytesPerPixel * width * height;
976
977    if (imgData->GetMaxId() + 1 != size)
978    {
979        imgData->SetNumberOfComponents(bytesPerPixel);
980        imgData->SetNumberOfValues(size);
981    }
982    glDisable(GL_TEXTURE_2D);
983    glReadBuffer(static_cast<GLenum>(vtkOpenGLRenderWindow::SafeDownCast(_renderWindow)->GetFrontLeftBuffer()));
984    glPixelStorei(GL_PACK_ALIGNMENT, 1);
985    if (bytesPerPixel == 4) {
986        glReadPixels(0, 0, width, height, GL_BGRA,
987                     GL_UNSIGNED_BYTE, imgData->GetPointer(0));
988    } else {
989        glReadPixels(0, 0, width, height, GL_BGR,
990                     GL_UNSIGNED_BYTE, imgData->GetPointer(0));
991    }
992    if (glGetError() != GL_NO_ERROR) {
993        ERROR("glReadPixels");
994    }
995#else
996    _legendRenderWindow->GetPixelData(0, 0, width-1, height-1,
997                                      !_legendRenderWindow->GetDoubleBuffer(),
998                                      imgData);
999#endif
1000    return true;
1001}
1002
1003/**
1004 * \brief Create a new Contour2D and associate it with the named DataSet
1005 */
1006void Renderer::addContour2D(const DataSetId& id)
1007{
1008    DataSetHashmap::iterator itr;
1009
1010    bool doAll = false;
1011
1012    if (id.compare("all") == 0) {
1013        itr = _dataSets.begin();
1014    } else {
1015        itr = _dataSets.find(id);
1016    }
1017    if (itr == _dataSets.end()) {
1018        ERROR("Unknown dataset %s", id.c_str());
1019        return;
1020    }
1021
1022    do {
1023        DataSet *ds = itr->second;
1024        const DataSetId& dsID = ds->getName();
1025
1026        if (getContour2D(dsID)) {
1027            WARN("Replacing existing contour2d %s", dsID.c_str());
1028            deleteContour2D(dsID);
1029        }
1030
1031        Contour2D *contour = new Contour2D();
1032        _contour2Ds[dsID] = contour;
1033
1034        contour->setDataSet(ds);
1035
1036        _renderer->AddViewProp(contour->getProp());
1037    } while (doAll && ++itr != _dataSets.end());
1038
1039    initCamera();
1040    _needsRedraw = true;
1041}
1042
1043/**
1044 * \brief Get the Contour2D associated with a named DataSet
1045 */
1046Contour2D *Renderer::getContour2D(const DataSetId& id)
1047{
1048    Contour2DHashmap::iterator itr = _contour2Ds.find(id);
1049
1050    if (itr == _contour2Ds.end()) {
1051        TRACE("Contour2D not found: %s", id.c_str());
1052        return NULL;
1053    } else
1054        return itr->second;
1055}
1056
1057/**
1058 * \brief Set the number of equally spaced contour isolines for the given DataSet
1059 */
1060void Renderer::setContour2DContours(const DataSetId& id, int numContours)
1061{
1062    Contour2DHashmap::iterator itr;
1063
1064    bool doAll = false;
1065
1066    if (id.compare("all") == 0) {
1067        itr = _contour2Ds.begin();
1068        doAll = true;
1069    } else {
1070        itr = _contour2Ds.find(id);
1071    }
1072    if (itr == _contour2Ds.end()) {
1073        ERROR("Contour2D not found: %s", id.c_str());
1074        return;
1075    }
1076
1077    do {
1078        if (_useCumulativeRange) {
1079            itr->second->setContours(numContours, _cumulativeDataRange);
1080        } else {
1081            itr->second->setContours(numContours);
1082        }
1083    } while (doAll && ++itr != _contour2Ds.end());
1084
1085    _needsRedraw = true;
1086}
1087
1088/**
1089 * \brief Set a list of isovalues for the given DataSet
1090 */
1091void Renderer::setContour2DContourList(const DataSetId& id, const std::vector<double>& contours)
1092{
1093    Contour2DHashmap::iterator itr;
1094
1095    bool doAll = false;
1096
1097    if (id.compare("all") == 0) {
1098        itr = _contour2Ds.begin();
1099        doAll = true;
1100    } else {
1101        itr = _contour2Ds.find(id);
1102    }
1103    if (itr == _contour2Ds.end()) {
1104        ERROR("Contour2D not found: %s", id.c_str());
1105        return;
1106    }
1107
1108    do {
1109        itr->second->setContourList(contours);
1110    } while (doAll && ++itr != _contour2Ds.end());
1111
1112     _needsRedraw = true;
1113}
1114
1115/**
1116 * \brief Set opacity of contour lines for the given DataSet
1117 */
1118void Renderer::setContour2DOpacity(const DataSetId& id, double opacity)
1119{
1120    Contour2DHashmap::iterator itr;
1121
1122    bool doAll = false;
1123
1124    if (id.compare("all") == 0) {
1125        itr = _contour2Ds.begin();
1126        doAll = true;
1127    } else {
1128        itr = _contour2Ds.find(id);
1129    }
1130    if (itr == _contour2Ds.end()) {
1131        ERROR("Contour2D not found: %s", id.c_str());
1132        return;
1133    }
1134
1135    do {
1136        itr->second->setOpacity(opacity);
1137    } while (doAll && ++itr != _contour2Ds.end());
1138
1139    _needsRedraw = true;
1140}
1141
1142/**
1143 * \brief Turn on/off rendering contour lines for the given DataSet
1144 */
1145void Renderer::setContour2DVisibility(const DataSetId& id, bool state)
1146{
1147    Contour2DHashmap::iterator itr;
1148
1149    bool doAll = false;
1150
1151    if (id.compare("all") == 0) {
1152        itr = _contour2Ds.begin();
1153        doAll = true;
1154    } else {
1155        itr = _contour2Ds.find(id);
1156    }
1157    if (itr == _contour2Ds.end()) {
1158        ERROR("Contour2D not found: %s", id.c_str());
1159        return;
1160    }
1161
1162    do {
1163        itr->second->setVisibility(state);
1164    } while (doAll && ++itr != _contour2Ds.end());
1165
1166    _needsRedraw = true;
1167}
1168
1169/**
1170 * \brief Set the RGB isoline color for the specified DataSet
1171 */
1172void Renderer::setContour2DEdgeColor(const DataSetId& id, float color[3])
1173{
1174    Contour2DHashmap::iterator itr;
1175
1176    bool doAll = false;
1177
1178    if (id.compare("all") == 0) {
1179        itr = _contour2Ds.begin();
1180        doAll = true;
1181    } else {
1182        itr = _contour2Ds.find(id);
1183    }
1184    if (itr == _contour2Ds.end()) {
1185        ERROR("Contour2D not found: %s", id.c_str());
1186        return;
1187    }
1188
1189    do {
1190        itr->second->setEdgeColor(color);
1191    } while (doAll && ++itr != _contour2Ds.end());
1192
1193    _needsRedraw = true;
1194}
1195
1196/**
1197 * \brief Set the isoline width for the specified DataSet (may be a no-op)
1198 *
1199 * If the OpenGL implementation/hardware does not support wide lines,
1200 * this function may not have an effect.
1201 */
1202void Renderer::setContour2DEdgeWidth(const DataSetId& id, float edgeWidth)
1203{
1204    Contour2DHashmap::iterator itr;
1205
1206    bool doAll = false;
1207
1208    if (id.compare("all") == 0) {
1209        itr = _contour2Ds.begin();
1210        doAll = true;
1211    } else {
1212        itr = _contour2Ds.find(id);
1213    }
1214    if (itr == _contour2Ds.end()) {
1215        ERROR("Contour2D not found: %s", id.c_str());
1216        return;
1217    }
1218
1219    do {
1220        itr->second->setEdgeWidth(edgeWidth);
1221    } while (doAll && ++itr != _contour2Ds.end());
1222
1223    _needsRedraw = true;
1224}
1225
1226/**
1227 * \brief Turn contour lighting on/off for the specified DataSet
1228 */
1229void Renderer::setContour2DLighting(const DataSetId& id, bool state)
1230{
1231    Contour2DHashmap::iterator itr;
1232
1233    bool doAll = false;
1234
1235    if (id.compare("all") == 0) {
1236        itr = _contour2Ds.begin();
1237        doAll = true;
1238    } else {
1239        itr = _contour2Ds.find(id);
1240    }
1241    if (itr == _contour2Ds.end()) {
1242        ERROR("Contour2D not found: %s", id.c_str());
1243        return;
1244    }
1245
1246    do {
1247        itr->second->setLighting(state);
1248    } while (doAll && ++itr != _contour2Ds.end());
1249    _needsRedraw = true;
1250}
1251
1252/**
1253 * \brief Create a new Contour3D and associate it with the named DataSet
1254 */
1255void Renderer::addContour3D(const DataSetId& id)
1256{
1257    DataSetHashmap::iterator itr;
1258
1259    bool doAll = false;
1260
1261    if (id.compare("all") == 0) {
1262        itr = _dataSets.begin();
1263    } else {
1264        itr = _dataSets.find(id);
1265    }
1266    if (itr == _dataSets.end()) {
1267        ERROR("Unknown dataset %s", id.c_str());
1268        return;
1269    }
1270
1271    do {
1272        DataSet *ds = itr->second;
1273        const DataSetId& dsID = ds->getName();
1274
1275        if (getContour3D(dsID)) {
1276            WARN("Replacing existing contour3d %s", dsID.c_str());
1277            deleteContour3D(dsID);
1278        }
1279
1280        Contour3D *contour = new Contour3D();
1281        _contour3Ds[dsID] = contour;
1282
1283        contour->setDataSet(ds);
1284
1285        // Use the default color map
1286        vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
1287        ColorMap *cmap = getColorMap("default");
1288        lut->DeepCopy(cmap->getLookupTable());
1289        if (_useCumulativeRange) {
1290            lut->SetRange(_cumulativeDataRange);
1291        } else {
1292            double range[2];
1293            ds->getDataRange(range);
1294            lut->SetRange(range);
1295        }
1296
1297        contour->setLookupTable(lut);
1298
1299        _renderer->AddViewProp(contour->getProp());
1300    } while (doAll && ++itr != _dataSets.end());
1301
1302    if (_cameraMode == IMAGE)
1303        setCameraMode(PERSPECTIVE);
1304    initCamera();
1305    _needsRedraw = true;
1306}
1307
1308/**
1309 * \brief Get the Contour3D associated with a named DataSet
1310 */
1311Contour3D *Renderer::getContour3D(const DataSetId& id)
1312{
1313    Contour3DHashmap::iterator itr = _contour3Ds.find(id);
1314
1315    if (itr == _contour3Ds.end()) {
1316        TRACE("Contour3D not found: %s", id.c_str());
1317        return NULL;
1318    } else
1319        return itr->second;
1320}
1321
1322/**
1323 * \brief Set the number of equally spaced isosurfaces for the given DataSet
1324 */
1325void Renderer::setContour3DContours(const DataSetId& id, int numContours)
1326{
1327    Contour3DHashmap::iterator itr;
1328
1329    bool doAll = false;
1330
1331    if (id.compare("all") == 0) {
1332        itr = _contour3Ds.begin();
1333        doAll = true;
1334    } else {
1335        itr = _contour3Ds.find(id);
1336    }
1337    if (itr == _contour3Ds.end()) {
1338        ERROR("Contour3D not found: %s", id.c_str());
1339        return;
1340    }
1341
1342    do {
1343        if (_useCumulativeRange) {
1344            itr->second->setContours(numContours, _cumulativeDataRange);
1345        } else {
1346            itr->second->setContours(numContours);
1347        }
1348    } while (doAll && ++itr != _contour3Ds.end());
1349
1350    initCamera();
1351    _needsRedraw = true;
1352}
1353
1354/**
1355 * \brief Set a list of isovalues for the given DataSet
1356 */
1357void Renderer::setContour3DContourList(const DataSetId& id, const std::vector<double>& contours)
1358{
1359    Contour3DHashmap::iterator itr;
1360
1361    bool doAll = false;
1362
1363    if (id.compare("all") == 0) {
1364        itr = _contour3Ds.begin();
1365        doAll = true;
1366    } else {
1367        itr = _contour3Ds.find(id);
1368    }
1369    if (itr == _contour3Ds.end()) {
1370        ERROR("Contour3D not found: %s", id.c_str());
1371        return;
1372    }
1373
1374    do {
1375        itr->second->setContourList(contours);
1376    } while (doAll && ++itr != _contour3Ds.end());
1377
1378    initCamera();
1379     _needsRedraw = true;
1380}
1381
1382/**
1383 * \brief Set opacity of isosurfaces for the given DataSet
1384 */
1385void Renderer::setContour3DOpacity(const DataSetId& id, double opacity)
1386{
1387    Contour3DHashmap::iterator itr;
1388
1389    bool doAll = false;
1390
1391    if (id.compare("all") == 0) {
1392        itr = _contour3Ds.begin();
1393        doAll = true;
1394    } else {
1395        itr = _contour3Ds.find(id);
1396    }
1397    if (itr == _contour3Ds.end()) {
1398        ERROR("Contour3D not found: %s", id.c_str());
1399        return;
1400    }
1401
1402    do {
1403        itr->second->setOpacity(opacity);
1404    } while (doAll && ++itr != _contour3Ds.end());
1405
1406    _needsRedraw = true;
1407}
1408
1409/**
1410 * \brief Turn on/off rendering isosurfaces for the given DataSet
1411 */
1412void Renderer::setContour3DVisibility(const DataSetId& id, bool state)
1413{
1414    Contour3DHashmap::iterator itr;
1415
1416    bool doAll = false;
1417
1418    if (id.compare("all") == 0) {
1419        itr = _contour3Ds.begin();
1420        doAll = true;
1421    } else {
1422        itr = _contour3Ds.find(id);
1423    }
1424    if (itr == _contour3Ds.end()) {
1425        ERROR("Contour3D not found: %s", id.c_str());
1426        return;
1427    }
1428
1429    do {
1430        itr->second->setVisibility(state);
1431    } while (doAll && ++itr != _contour3Ds.end());
1432
1433    _needsRedraw = true;
1434}
1435
1436/**
1437 * \brief Associate an existing named color map with a Contour3D for the given
1438 * DataSet
1439 */
1440void Renderer::setContour3DColorMap(const DataSetId& id, const ColorMapId& colorMapId)
1441{
1442    Contour3DHashmap::iterator itr;
1443
1444    bool doAll = false;
1445
1446    if (id.compare("all") == 0) {
1447        itr = _contour3Ds.begin();
1448        doAll = true;
1449    } else {
1450        itr = _contour3Ds.find(id);
1451    }
1452
1453    if (itr == _contour3Ds.end()) {
1454        ERROR("Contour3D not found: %s", id.c_str());
1455        return;
1456    }
1457
1458    ColorMap *cmap = getColorMap(colorMapId);
1459    if (cmap == NULL) {
1460        ERROR("Unknown colormap: %s", colorMapId.c_str());
1461        return;
1462    }
1463
1464    do {
1465        TRACE("Set Contour3D color map: %s for dataset %s", colorMapId.c_str(),
1466              itr->second->getDataSet()->getName().c_str());
1467
1468        // Make a copy of the generic colormap lookup table, so
1469        // data range can be set in the copy table to match the
1470        // dataset being plotted
1471        vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
1472        lut->DeepCopy(cmap->getLookupTable());
1473
1474        if (_useCumulativeRange) {
1475            lut->SetRange(_cumulativeDataRange);
1476        } else {
1477            if (itr->second->getDataSet() != NULL) {
1478                double range[2];
1479                itr->second->getDataSet()->getDataRange(range);
1480                lut->SetRange(range);
1481            }
1482        }
1483
1484        itr->second->setLookupTable(lut);
1485    } while (doAll && ++itr != _contour3Ds.end());
1486
1487    _needsRedraw = true;
1488}
1489
1490/**
1491 * \brief Set the RGB isosurface color for the specified DataSet
1492 */
1493void Renderer::setContour3DColor(const DataSetId& id, float color[3])
1494{
1495    Contour3DHashmap::iterator itr;
1496
1497    bool doAll = false;
1498
1499    if (id.compare("all") == 0) {
1500        itr = _contour3Ds.begin();
1501        doAll = true;
1502    } else {
1503        itr = _contour3Ds.find(id);
1504    }
1505    if (itr == _contour3Ds.end()) {
1506        ERROR("Contour3D not found: %s", id.c_str());
1507        return;
1508    }
1509
1510    do {
1511        itr->second->setColor(color);
1512    } while (doAll && ++itr != _contour3Ds.end());
1513    _needsRedraw = true;
1514}
1515
1516/**
1517 * \brief Turn on/off rendering isosurface edges for the given DataSet
1518 */
1519void Renderer::setContour3DEdgeVisibility(const DataSetId& id, bool state)
1520{
1521    Contour3DHashmap::iterator itr;
1522
1523    bool doAll = false;
1524
1525    if (id.compare("all") == 0) {
1526        itr = _contour3Ds.begin();
1527        doAll = true;
1528    } else {
1529        itr = _contour3Ds.find(id);
1530    }
1531    if (itr == _contour3Ds.end()) {
1532        ERROR("Contour3D not found: %s", id.c_str());
1533        return;
1534    }
1535
1536    do {
1537        itr->second->setEdgeVisibility(state);
1538    } while (doAll && ++itr != _contour3Ds.end());
1539
1540    _needsRedraw = true;
1541}
1542
1543/**
1544 * \brief Set the RGB isosurface edge color for the specified DataSet
1545 */
1546void Renderer::setContour3DEdgeColor(const DataSetId& id, float color[3])
1547{
1548    Contour3DHashmap::iterator itr;
1549
1550    bool doAll = false;
1551
1552    if (id.compare("all") == 0) {
1553        itr = _contour3Ds.begin();
1554        doAll = true;
1555    } else {
1556        itr = _contour3Ds.find(id);
1557    }
1558    if (itr == _contour3Ds.end()) {
1559        ERROR("Contour3D not found: %s", id.c_str());
1560        return;
1561    }
1562
1563    do {
1564        itr->second->setEdgeColor(color);
1565    } while (doAll && ++itr != _contour3Ds.end());
1566
1567    _needsRedraw = true;
1568}
1569
1570/**
1571 * \brief Set the isosurface edge width for the specified DataSet (may be a no-op)
1572 *
1573 * If the OpenGL implementation/hardware does not support wide lines,
1574 * this function may not have an effect.
1575 */
1576void Renderer::setContour3DEdgeWidth(const DataSetId& id, float edgeWidth)
1577{
1578    Contour3DHashmap::iterator itr;
1579
1580    bool doAll = false;
1581
1582    if (id.compare("all") == 0) {
1583        itr = _contour3Ds.begin();
1584        doAll = true;
1585    } else {
1586        itr = _contour3Ds.find(id);
1587    }
1588    if (itr == _contour3Ds.end()) {
1589        ERROR("Contour3D not found: %s", id.c_str());
1590        return;
1591    }
1592
1593    do {
1594        itr->second->setEdgeWidth(edgeWidth);
1595    } while (doAll && ++itr != _contour3Ds.end());
1596
1597    _needsRedraw = true;
1598}
1599
1600/**
1601 * \brief Set wireframe rendering for the specified DataSet
1602 */
1603void Renderer::setContour3DWireframe(const DataSetId& id, bool state)
1604{
1605    Contour3DHashmap::iterator itr;
1606
1607    bool doAll = false;
1608
1609    if (id.compare("all") == 0) {
1610        itr = _contour3Ds.begin();
1611        doAll = true;
1612    } else {
1613        itr = _contour3Ds.find(id);
1614    }
1615    if (itr == _contour3Ds.end()) {
1616        ERROR("Contour3D not found: %s", id.c_str());
1617        return;
1618    }
1619
1620    do {
1621        itr->second->setWireframe(state);
1622    } while (doAll && ++itr != _contour3Ds.end());
1623
1624    _needsRedraw = true;
1625}
1626
1627/**
1628 * \brief Turn contour lighting on/off for the specified DataSet
1629 */
1630void Renderer::setContour3DLighting(const DataSetId& id, bool state)
1631{
1632    Contour3DHashmap::iterator itr;
1633
1634    bool doAll = false;
1635
1636    if (id.compare("all") == 0) {
1637        itr = _contour3Ds.begin();
1638        doAll = true;
1639    } else {
1640        itr = _contour3Ds.find(id);
1641    }
1642    if (itr == _contour3Ds.end()) {
1643        ERROR("Contour3D not found: %s", id.c_str());
1644        return;
1645    }
1646
1647    do {
1648        itr->second->setLighting(state);
1649    } while (doAll && ++itr != _contour3Ds.end());
1650    _needsRedraw = true;
1651}
1652
1653/**
1654 * \brief Create a new Glyphs and associate it with the named DataSet
1655 */
1656void Renderer::addGlyphs(const DataSetId& id)
1657{
1658    DataSetHashmap::iterator itr;
1659
1660    bool doAll = false;
1661
1662    if (id.compare("all") == 0) {
1663        itr = _dataSets.begin();
1664    } else {
1665        itr = _dataSets.find(id);
1666    }
1667    if (itr == _dataSets.end()) {
1668        ERROR("Unknown dataset %s", id.c_str());
1669        return;
1670    }
1671
1672    do {
1673        DataSet *ds = itr->second;
1674        const DataSetId& dsID = ds->getName();
1675
1676        if (getGlyphs(dsID)) {
1677            WARN("Replacing existing Glyphs %s", dsID.c_str());
1678            deleteGlyphs(dsID);
1679        }
1680
1681        Glyphs *glyphs = new Glyphs();
1682        _glyphs[dsID] = glyphs;
1683
1684        glyphs->setDataSet(ds);
1685
1686        // Use the default color map
1687        vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
1688        ColorMap *cmap = getColorMap("default");
1689        lut->DeepCopy(cmap->getLookupTable());
1690        if (_useCumulativeRange) {
1691            lut->SetRange(_cumulativeDataRange);
1692        } else {
1693            double range[2];
1694            ds->getDataRange(range);
1695            lut->SetRange(range);
1696        }
1697
1698        glyphs->setLookupTable(lut);
1699
1700        _renderer->AddViewProp(glyphs->getProp());
1701    } while (doAll && ++itr != _dataSets.end());
1702
1703    if (_cameraMode == IMAGE)
1704        setCameraMode(PERSPECTIVE);
1705    initCamera();
1706
1707    _needsRedraw = true;
1708}
1709
1710/**
1711 * \brief Get the Glyphs associated with a named DataSet
1712 */
1713Glyphs *Renderer::getGlyphs(const DataSetId& id)
1714{
1715    GlyphsHashmap::iterator itr = _glyphs.find(id);
1716
1717    if (itr == _glyphs.end()) {
1718        TRACE("Glyphs not found: %s", id.c_str());
1719        return NULL;
1720    } else
1721        return itr->second;
1722}
1723
1724/**
1725 * \brief Associate an existing named color map with a Glyphs for the given DataSet
1726 */
1727void Renderer::setGlyphsColorMap(const DataSetId& id, const ColorMapId& colorMapId)
1728{
1729    GlyphsHashmap::iterator itr;
1730
1731    bool doAll = false;
1732
1733    if (id.compare("all") == 0) {
1734        itr = _glyphs.begin();
1735        doAll = true;
1736    } else {
1737        itr = _glyphs.find(id);
1738    }
1739
1740    if (itr == _glyphs.end()) {
1741        ERROR("Glyphs not found: %s", id.c_str());
1742        return;
1743    }
1744
1745    ColorMap *cmap = getColorMap(colorMapId);
1746    if (cmap == NULL) {
1747        ERROR("Unknown colormap: %s", colorMapId.c_str());
1748        return;
1749    }
1750
1751    do {
1752        TRACE("Set Glyphs color map: %s for dataset %s", colorMapId.c_str(),
1753              itr->second->getDataSet()->getName().c_str());
1754
1755        // Make a copy of the generic colormap lookup table, so
1756        // data range can be set in the copy table to match the
1757        // dataset being plotted
1758        vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
1759        lut->DeepCopy(cmap->getLookupTable());
1760
1761        if (_useCumulativeRange) {
1762            lut->SetRange(_cumulativeDataRange);
1763        } else {
1764            if (itr->second->getDataSet() != NULL) {
1765                double range[2];
1766                itr->second->getDataSet()->getDataRange(range);
1767                lut->SetRange(range);
1768            }
1769        }
1770
1771        itr->second->setLookupTable(lut);
1772    } while (doAll && ++itr != _glyphs.end());
1773
1774    _needsRedraw = true;
1775}
1776
1777/**
1778 * \brief Set the shape of Glyphs for the given DataSet
1779 */
1780void Renderer::setGlyphsShape(const DataSetId& id, Glyphs::GlyphShape shape)
1781{
1782    GlyphsHashmap::iterator itr;
1783
1784    bool doAll = false;
1785
1786    if (id.compare("all") == 0) {
1787        itr = _glyphs.begin();
1788        doAll = true;
1789    } else {
1790        itr = _glyphs.find(id);
1791    }
1792    if (itr == _glyphs.end()) {
1793        ERROR("Glyphs not found: %s", id.c_str());
1794        return;
1795    }
1796
1797    do {
1798        itr->second->setGlyphShape(shape);
1799    } while (doAll && ++itr != _glyphs.end());
1800
1801    _renderer->ResetCameraClippingRange();
1802    _needsRedraw = true;
1803}
1804
1805/**
1806 * \brief Set the glyph scaling factor for the given DataSet
1807 */
1808void Renderer::setGlyphsScaleFactor(const DataSetId& id, double scale)
1809{
1810    GlyphsHashmap::iterator itr;
1811
1812    bool doAll = false;
1813
1814    if (id.compare("all") == 0) {
1815        itr = _glyphs.begin();
1816        doAll = true;
1817    } else {
1818        itr = _glyphs.find(id);
1819    }
1820    if (itr == _glyphs.end()) {
1821        ERROR("Glyphs not found: %s", id.c_str());
1822        return;
1823    }
1824
1825    do {
1826        itr->second->setScaleFactor(scale);
1827    } while (doAll && ++itr != _glyphs.end());
1828
1829    _renderer->ResetCameraClippingRange();
1830    _needsRedraw = true;
1831}
1832
1833/**
1834 * \brief Set opacity of Glyphs for the given DataSet
1835 */
1836void Renderer::setGlyphsOpacity(const DataSetId& id, double opacity)
1837{
1838    GlyphsHashmap::iterator itr;
1839
1840    bool doAll = false;
1841
1842    if (id.compare("all") == 0) {
1843        itr = _glyphs.begin();
1844        doAll = true;
1845    } else {
1846        itr = _glyphs.find(id);
1847    }
1848    if (itr == _glyphs.end()) {
1849        ERROR("Glyphs not found: %s", id.c_str());
1850        return;
1851    }
1852
1853    do {
1854        itr->second->setOpacity(opacity);
1855    } while (doAll && ++itr != _glyphs.end());
1856
1857    _needsRedraw = true;
1858}
1859
1860/**
1861 * \brief Turn on/off rendering Glyphs for the given DataSet
1862 */
1863void Renderer::setGlyphsVisibility(const DataSetId& id, bool state)
1864{
1865    GlyphsHashmap::iterator itr;
1866
1867    bool doAll = false;
1868
1869    if (id.compare("all") == 0) {
1870        itr = _glyphs.begin();
1871        doAll = true;
1872    } else {
1873        itr = _glyphs.find(id);
1874    }
1875    if (itr == _glyphs.end()) {
1876        ERROR("Glyphs not found: %s", id.c_str());
1877        return;
1878    }
1879
1880    do {
1881        itr->second->setVisibility(state);
1882    } while (doAll && ++itr != _glyphs.end());
1883
1884    _needsRedraw = true;
1885}
1886
1887/**
1888 * \brief Turn Glyphs lighting on/off for the specified DataSet
1889 */
1890void Renderer::setGlyphsLighting(const DataSetId& id, bool state)
1891{
1892    GlyphsHashmap::iterator itr;
1893
1894    bool doAll = false;
1895
1896    if (id.compare("all") == 0) {
1897        itr = _glyphs.begin();
1898        doAll = true;
1899    } else {
1900        itr = _glyphs.find(id);
1901    }
1902    if (itr == _glyphs.end()) {
1903        ERROR("Glyphs not found: %s", id.c_str());
1904        return;
1905    }
1906
1907    do {
1908        itr->second->setLighting(state);
1909    } while (doAll && ++itr != _glyphs.end());
1910    _needsRedraw = true;
1911}
1912
1913/**
1914 * \brief Create a new HeightMap and associate it with the named DataSet
1915 */
1916void Renderer::addHeightMap(const DataSetId& id)
1917{
1918    DataSetHashmap::iterator itr;
1919
1920    bool doAll = false;
1921
1922    if (id.compare("all") == 0) {
1923        itr = _dataSets.begin();
1924    } else {
1925        itr = _dataSets.find(id);
1926    }
1927    if (itr == _dataSets.end()) {
1928        ERROR("Unknown dataset %s", id.c_str());
1929        return;
1930    }
1931
1932    do {
1933        DataSet *ds = itr->second;
1934        const DataSetId& dsID = ds->getName();
1935
1936        if (getHeightMap(dsID)) {
1937            WARN("Replacing existing HeightMap %s", dsID.c_str());
1938            deleteHeightMap(dsID);
1939        }
1940
1941        HeightMap *hmap = new HeightMap();
1942        _heightMaps[dsID] = hmap;
1943
1944        hmap->setDataSet(ds);
1945
1946        // Use the default color map
1947        vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
1948        ColorMap *cmap = getColorMap("default");
1949        lut->DeepCopy(cmap->getLookupTable());
1950        if (_useCumulativeRange) {
1951            lut->SetRange(_cumulativeDataRange);
1952        } else {
1953            double range[2];
1954            ds->getDataRange(range);
1955            lut->SetRange(range);
1956        }
1957
1958        hmap->setLookupTable(lut);
1959
1960        _renderer->AddViewProp(hmap->getProp());
1961    } while (doAll && ++itr != _dataSets.end());
1962
1963    if (_cameraMode == IMAGE)
1964        setCameraMode(PERSPECTIVE);
1965    initCamera();
1966
1967    _needsRedraw = true;
1968}
1969
1970/**
1971 * \brief Get the HeightMap associated with a named DataSet
1972 */
1973HeightMap *Renderer::getHeightMap(const DataSetId& id)
1974{
1975    HeightMapHashmap::iterator itr = _heightMaps.find(id);
1976
1977    if (itr == _heightMaps.end()) {
1978        TRACE("HeightMap not found: %s", id.c_str());
1979        return NULL;
1980    } else
1981        return itr->second;
1982}
1983
1984/**
1985 * \brief Set the volume slice used for mapping volumetric data
1986 */
1987void Renderer::setHeightMapVolumeSlice(const DataSetId& id, HeightMap::Axis axis, double ratio)
1988{
1989    HeightMapHashmap::iterator itr;
1990
1991    bool doAll = false;
1992
1993    if (id.compare("all") == 0) {
1994        itr = _heightMaps.begin();
1995        doAll = true;
1996    } else {
1997        itr = _heightMaps.find(id);
1998    }
1999
2000    if (itr == _heightMaps.end()) {
2001        ERROR("HeightMap not found: %s", id.c_str());
2002        return;
2003    }
2004
2005    do {
2006        itr->second->selectVolumeSlice(axis, ratio);
2007     } while (doAll && ++itr != _heightMaps.end());
2008
2009    initCamera();
2010    _needsRedraw = true;
2011}
2012
2013/**
2014 * \brief Set amount to scale scalar values when creating elevations
2015 * in the height map
2016 */
2017void Renderer::setHeightMapHeightScale(const DataSetId& id, double scale)
2018{
2019    HeightMapHashmap::iterator itr;
2020
2021    bool doAll = false;
2022
2023    if (id.compare("all") == 0) {
2024        itr = _heightMaps.begin();
2025        doAll = true;
2026    } else {
2027        itr = _heightMaps.find(id);
2028    }
2029
2030    if (itr == _heightMaps.end()) {
2031        ERROR("HeightMap not found: %s", id.c_str());
2032        return;
2033    }
2034
2035    do {
2036        itr->second->setHeightScale(scale);
2037     } while (doAll && ++itr != _heightMaps.end());
2038
2039    initCamera();
2040    _needsRedraw = true;
2041}
2042
2043/**
2044 * \brief Associate an existing named color map with a HeightMap for the given DataSet
2045 */
2046void Renderer::setHeightMapColorMap(const DataSetId& id, const ColorMapId& colorMapId)
2047{
2048    HeightMapHashmap::iterator itr;
2049
2050    bool doAll = false;
2051
2052    if (id.compare("all") == 0) {
2053        itr = _heightMaps.begin();
2054        doAll = true;
2055    } else {
2056        itr = _heightMaps.find(id);
2057    }
2058
2059    if (itr == _heightMaps.end()) {
2060        ERROR("HeightMap not found: %s", id.c_str());
2061        return;
2062    }
2063
2064    ColorMap *cmap = getColorMap(colorMapId);
2065    if (cmap == NULL) {
2066        ERROR("Unknown colormap: %s", colorMapId.c_str());
2067        return;
2068    }
2069
2070    do {
2071        TRACE("Set HeightMap color map: %s for dataset %s", colorMapId.c_str(),
2072              itr->second->getDataSet()->getName().c_str());
2073
2074        // Make a copy of the generic colormap lookup table, so
2075        // data range can be set in the copy table to match the
2076        // dataset being plotted
2077        vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
2078        lut->DeepCopy(cmap->getLookupTable());
2079
2080        if (_useCumulativeRange) {
2081            lut->SetRange(_cumulativeDataRange);
2082        } else {
2083            if (itr->second->getDataSet() != NULL) {
2084                double range[2];
2085                itr->second->getDataSet()->getDataRange(range);
2086                lut->SetRange(range);
2087            }
2088        }
2089
2090        itr->second->setLookupTable(lut);
2091    } while (doAll && ++itr != _heightMaps.end());
2092
2093    _needsRedraw = true;
2094}
2095
2096/**
2097 * \brief Set the number of equally spaced contour isolines for the given DataSet
2098 */
2099void Renderer::setHeightMapContours(const DataSetId& id, int numContours)
2100{
2101    HeightMapHashmap::iterator itr;
2102
2103    bool doAll = false;
2104
2105    if (id.compare("all") == 0) {
2106        itr = _heightMaps.begin();
2107        doAll = true;
2108    } else {
2109        itr = _heightMaps.find(id);
2110    }
2111    if (itr == _heightMaps.end()) {
2112        ERROR("HeightMap not found: %s", id.c_str());
2113        return;
2114    }
2115
2116    do {
2117        if (_useCumulativeRange) {
2118            itr->second->setContours(numContours, _cumulativeDataRange);
2119        } else {
2120            itr->second->setContours(numContours);
2121        }
2122    } while (doAll && ++itr != _heightMaps.end());
2123
2124    _needsRedraw = true;
2125}
2126
2127/**
2128 * \brief Set a list of height map contour isovalues for the given DataSet
2129 */
2130void Renderer::setHeightMapContourList(const DataSetId& id, const std::vector<double>& contours)
2131{
2132    HeightMapHashmap::iterator itr;
2133
2134    bool doAll = false;
2135
2136    if (id.compare("all") == 0) {
2137        itr = _heightMaps.begin();
2138        doAll = true;
2139    } else {
2140        itr = _heightMaps.find(id);
2141    }
2142    if (itr == _heightMaps.end()) {
2143        ERROR("HeightMap not found: %s", id.c_str());
2144        return;
2145    }
2146
2147    do {
2148        itr->second->setContourList(contours);
2149    } while (doAll && ++itr != _heightMaps.end());
2150
2151     _needsRedraw = true;
2152}
2153
2154/**
2155 * \brief Set opacity of height map for the given DataSet
2156 */
2157void Renderer::setHeightMapOpacity(const DataSetId& id, double opacity)
2158{
2159    HeightMapHashmap::iterator itr;
2160
2161    bool doAll = false;
2162
2163    if (id.compare("all") == 0) {
2164        itr = _heightMaps.begin();
2165        doAll = true;
2166    } else {
2167        itr = _heightMaps.find(id);
2168    }
2169    if (itr == _heightMaps.end()) {
2170        ERROR("HeightMap not found: %s", id.c_str());
2171        return;
2172    }
2173
2174    do {
2175        itr->second->setOpacity(opacity);
2176    } while (doAll && ++itr != _heightMaps.end());
2177
2178    _needsRedraw = true;
2179}
2180
2181/**
2182 * \brief Turn on/off rendering height map for the given DataSet
2183 */
2184void Renderer::setHeightMapVisibility(const DataSetId& id, bool state)
2185{
2186    HeightMapHashmap::iterator itr;
2187
2188    bool doAll = false;
2189
2190    if (id.compare("all") == 0) {
2191        itr = _heightMaps.begin();
2192        doAll = true;
2193    } else {
2194        itr = _heightMaps.find(id);
2195    }
2196    if (itr == _heightMaps.end()) {
2197        ERROR("HeightMap not found: %s", id.c_str());
2198        return;
2199    }
2200
2201    do {
2202        itr->second->setVisibility(state);
2203    } while (doAll && ++itr != _heightMaps.end());
2204
2205    _needsRedraw = true;
2206}
2207
2208/**
2209 * \brief Turn on/off rendering height map mesh edges for the given DataSet
2210 */
2211void Renderer::setHeightMapEdgeVisibility(const DataSetId& id, bool state)
2212{
2213    HeightMapHashmap::iterator itr;
2214
2215    bool doAll = false;
2216
2217    if (id.compare("all") == 0) {
2218        itr = _heightMaps.begin();
2219        doAll = true;
2220    } else {
2221        itr = _heightMaps.find(id);
2222    }
2223    if (itr == _heightMaps.end()) {
2224        ERROR("HeightMap not found: %s", id.c_str());
2225        return;
2226    }
2227
2228    do {
2229        itr->second->setEdgeVisibility(state);
2230    } while (doAll && ++itr != _heightMaps.end());
2231
2232    _needsRedraw = true;
2233}
2234
2235/**
2236 * \brief Set the RGB height map mesh edge color for the specified DataSet
2237 */
2238void Renderer::setHeightMapEdgeColor(const DataSetId& id, float color[3])
2239{
2240    HeightMapHashmap::iterator itr;
2241
2242    bool doAll = false;
2243
2244    if (id.compare("all") == 0) {
2245        itr = _heightMaps.begin();
2246        doAll = true;
2247    } else {
2248        itr = _heightMaps.find(id);
2249    }
2250    if (itr == _heightMaps.end()) {
2251        ERROR("HeightMap not found: %s", id.c_str());
2252        return;
2253    }
2254
2255    do {
2256        itr->second->setEdgeColor(color);
2257    } while (doAll && ++itr != _heightMaps.end());
2258
2259    _needsRedraw = true;
2260}
2261
2262/**
2263 * \brief Set the height map mesh edge width for the specified DataSet (may be a no-op)
2264 *
2265 * If the OpenGL implementation/hardware does not support wide lines,
2266 * this function may not have an effect.
2267 */
2268void Renderer::setHeightMapEdgeWidth(const DataSetId& id, float edgeWidth)
2269{
2270    HeightMapHashmap::iterator itr;
2271
2272    bool doAll = false;
2273
2274    if (id.compare("all") == 0) {
2275        itr = _heightMaps.begin();
2276        doAll = true;
2277    } else {
2278        itr = _heightMaps.find(id);
2279    }
2280    if (itr == _heightMaps.end()) {
2281        ERROR("HeightMap not found: %s", id.c_str());
2282        return;
2283    }
2284
2285    do {
2286        itr->second->setEdgeWidth(edgeWidth);
2287    } while (doAll && ++itr != _heightMaps.end());
2288
2289    _needsRedraw = true;
2290}
2291
2292/**
2293 * \brief Turn on/off rendering height map contour lines for the given DataSet
2294 */
2295void Renderer::setHeightMapContourVisibility(const DataSetId& id, bool state)
2296{
2297    HeightMapHashmap::iterator itr;
2298
2299    bool doAll = false;
2300
2301    if (id.compare("all") == 0) {
2302        itr = _heightMaps.begin();
2303        doAll = true;
2304    } else {
2305        itr = _heightMaps.find(id);
2306    }
2307    if (itr == _heightMaps.end()) {
2308        ERROR("HeightMap not found: %s", id.c_str());
2309        return;
2310    }
2311
2312    do {
2313        itr->second->setContourVisibility(state);
2314    } while (doAll && ++itr != _heightMaps.end());
2315
2316    _needsRedraw = true;
2317}
2318
2319/**
2320 * \brief Set the RGB height map isoline color for the specified DataSet
2321 */
2322void Renderer::setHeightMapContourEdgeColor(const DataSetId& id, float color[3])
2323{
2324    HeightMapHashmap::iterator itr;
2325
2326    bool doAll = false;
2327
2328    if (id.compare("all") == 0) {
2329        itr = _heightMaps.begin();
2330        doAll = true;
2331    } else {
2332        itr = _heightMaps.find(id);
2333    }
2334    if (itr == _heightMaps.end()) {
2335        ERROR("HeightMap not found: %s", id.c_str());
2336        return;
2337    }
2338
2339    do {
2340        itr->second->setContourEdgeColor(color);
2341    } while (doAll && ++itr != _heightMaps.end());
2342
2343    _needsRedraw = true;
2344}
2345
2346/**
2347 * \brief Set the height map isoline width for the specified DataSet (may be a no-op)
2348 *
2349 * If the OpenGL implementation/hardware does not support wide lines,
2350 * this function may not have an effect.
2351 */
2352void Renderer::setHeightMapContourEdgeWidth(const DataSetId& id, float edgeWidth)
2353{
2354    HeightMapHashmap::iterator itr;
2355
2356    bool doAll = false;
2357
2358    if (id.compare("all") == 0) {
2359        itr = _heightMaps.begin();
2360        doAll = true;
2361    } else {
2362        itr = _heightMaps.find(id);
2363    }
2364    if (itr == _heightMaps.end()) {
2365        ERROR("HeightMap not found: %s", id.c_str());
2366        return;
2367    }
2368
2369    do {
2370        itr->second->setContourEdgeWidth(edgeWidth);
2371    } while (doAll && ++itr != _heightMaps.end());
2372
2373    _needsRedraw = true;
2374}
2375
2376/**
2377 * \brief Turn height map lighting on/off for the specified DataSet
2378 */
2379void Renderer::setHeightMapLighting(const DataSetId& id, bool state)
2380{
2381    HeightMapHashmap::iterator itr;
2382
2383    bool doAll = false;
2384
2385    if (id.compare("all") == 0) {
2386        itr = _heightMaps.begin();
2387        doAll = true;
2388    } else {
2389        itr = _heightMaps.find(id);
2390    }
2391    if (itr == _heightMaps.end()) {
2392        ERROR("HeightMap not found: %s", id.c_str());
2393        return;
2394    }
2395
2396    do {
2397        itr->second->setLighting(state);
2398    } while (doAll && ++itr != _heightMaps.end());
2399    _needsRedraw = true;
2400}
2401
2402/**
2403 * \brief Create a new PolyData and associate it with the named DataSet
2404 */
2405void Renderer::addPolyData(const DataSetId& id)
2406{
2407    DataSetHashmap::iterator itr;
2408
2409    bool doAll = false;
2410
2411    if (id.compare("all") == 0) {
2412        itr = _dataSets.begin();
2413    } else {
2414        itr = _dataSets.find(id);
2415    }
2416    if (itr == _dataSets.end()) {
2417        ERROR("Unknown dataset %s", id.c_str());
2418        return;
2419    }
2420
2421    do {
2422        DataSet *ds = itr->second;
2423        const DataSetId& dsID = ds->getName();
2424
2425        if (getPolyData(dsID)) {
2426            WARN("Replacing existing polydata %s", dsID.c_str());
2427            deletePolyData(dsID);
2428        }
2429
2430        PolyData *polyData = new PolyData();
2431        _polyDatas[dsID] = polyData;
2432
2433        polyData->setDataSet(ds);
2434
2435        _renderer->AddViewProp(polyData->getProp());
2436    } while (doAll && ++itr != _dataSets.end());
2437
2438    if (_cameraMode == IMAGE)
2439        setCameraMode(PERSPECTIVE);
2440    initCamera();
2441    _needsRedraw = true;
2442}
2443
2444/**
2445 * \brief Get the PolyData associated with a named DataSet
2446 */
2447PolyData *Renderer::getPolyData(const DataSetId& id)
2448{
2449    PolyDataHashmap::iterator itr = _polyDatas.find(id);
2450
2451    if (itr == _polyDatas.end()) {
2452        TRACE("PolyData not found: %s", id.c_str());
2453        return NULL;
2454    } else
2455        return itr->second;
2456}
2457
2458/**
2459 * \brief Set opacity of the PolyData for the given DataSet
2460 */
2461void Renderer::setPolyDataOpacity(const DataSetId& id, double opacity)
2462{
2463    PolyDataHashmap::iterator itr;
2464
2465    bool doAll = false;
2466
2467    if (id.compare("all") == 0) {
2468        itr = _polyDatas.begin();
2469        doAll = true;
2470    } else {
2471        itr = _polyDatas.find(id);
2472    }
2473    if (itr == _polyDatas.end()) {
2474        ERROR("PolyData not found: %s", id.c_str());
2475        return;
2476    }
2477
2478    do {
2479        itr->second->setOpacity(opacity);
2480    } while (doAll && ++itr != _polyDatas.end());
2481
2482    _needsRedraw = true;
2483}
2484
2485/**
2486 * \brief Turn on/off rendering of the PolyData mapper for the given DataSet
2487 */
2488void Renderer::setPolyDataVisibility(const DataSetId& id, bool state)
2489{
2490    PolyDataHashmap::iterator itr;
2491
2492    bool doAll = false;
2493
2494    if (id.compare("all") == 0) {
2495        itr = _polyDatas.begin();
2496        doAll = true;
2497    } else {
2498        itr = _polyDatas.find(id);
2499    }
2500    if (itr == _polyDatas.end()) {
2501        ERROR("PolyData not found: %s", id.c_str());
2502        return;
2503    }
2504
2505    do {
2506        itr->second->setVisibility(state);
2507    } while (doAll && ++itr != _polyDatas.end());
2508
2509    _needsRedraw = true;
2510}
2511
2512/**
2513 * \brief Set the RGB polygon face color for the specified DataSet
2514 */
2515void Renderer::setPolyDataColor(const DataSetId& id, float color[3])
2516{
2517    PolyDataHashmap::iterator itr;
2518
2519    bool doAll = false;
2520
2521    if (id.compare("all") == 0) {
2522        itr = _polyDatas.begin();
2523        doAll = true;
2524    } else {
2525        itr = _polyDatas.find(id);
2526    }
2527    if (itr == _polyDatas.end()) {
2528        ERROR("PolyData not found: %s", id.c_str());
2529        return;
2530    }
2531
2532    do {
2533        itr->second->setColor(color);
2534    } while (doAll && ++itr != _polyDatas.end());
2535    _needsRedraw = true;
2536}
2537
2538/**
2539 * \brief Set the visibility of polygon edges for the specified DataSet
2540 */
2541void Renderer::setPolyDataEdgeVisibility(const DataSetId& id, bool state)
2542{
2543    PolyDataHashmap::iterator itr;
2544
2545    bool doAll = false;
2546
2547    if (id.compare("all") == 0) {
2548        itr = _polyDatas.begin();
2549        doAll = true;
2550    } else {
2551        itr = _polyDatas.find(id);
2552    }
2553    if (itr == _polyDatas.end()) {
2554        ERROR("PolyData not found: %s", id.c_str());
2555        return;
2556    }
2557
2558    do {
2559        itr->second->setEdgeVisibility(state);
2560    } while (doAll && ++itr != _polyDatas.end());
2561
2562    _needsRedraw = true;
2563}
2564
2565/**
2566 * \brief Set the RGB polygon edge color for the specified DataSet
2567 */
2568void Renderer::setPolyDataEdgeColor(const DataSetId& id, float color[3])
2569{
2570    PolyDataHashmap::iterator itr;
2571
2572    bool doAll = false;
2573
2574    if (id.compare("all") == 0) {
2575        itr = _polyDatas.begin();
2576        doAll = true;
2577    } else {
2578        itr = _polyDatas.find(id);
2579    }
2580    if (itr == _polyDatas.end()) {
2581        ERROR("PolyData not found: %s", id.c_str());
2582        return;
2583    }
2584
2585    do {
2586        itr->second->setEdgeColor(color);
2587    } while (doAll && ++itr != _polyDatas.end());
2588
2589    _needsRedraw = true;
2590}
2591
2592/**
2593 * \brief Set the polygon edge width for the specified DataSet (may be a no-op)
2594 *
2595 * If the OpenGL implementation/hardware does not support wide lines,
2596 * this function may not have an effect.
2597 */
2598void Renderer::setPolyDataEdgeWidth(const DataSetId& id, float edgeWidth)
2599{
2600    PolyDataHashmap::iterator itr;
2601
2602    bool doAll = false;
2603
2604    if (id.compare("all") == 0) {
2605        itr = _polyDatas.begin();
2606        doAll = true;
2607    } else {
2608        itr = _polyDatas.find(id);
2609    }
2610    if (itr == _polyDatas.end()) {
2611        ERROR("PolyData not found: %s", id.c_str());
2612        return;
2613    }
2614
2615    do {
2616        itr->second->setEdgeWidth(edgeWidth);
2617    } while (doAll && ++itr != _polyDatas.end());
2618
2619    _needsRedraw = true;
2620}
2621
2622/**
2623 * \brief Set wireframe rendering for the specified DataSet
2624 */
2625void Renderer::setPolyDataWireframe(const DataSetId& id, bool state)
2626{
2627    PolyDataHashmap::iterator itr;
2628
2629    bool doAll = false;
2630
2631    if (id.compare("all") == 0) {
2632        itr = _polyDatas.begin();
2633        doAll = true;
2634    } else {
2635        itr = _polyDatas.find(id);
2636    }
2637    if (itr == _polyDatas.end()) {
2638        ERROR("PolyData not found: %s", id.c_str());
2639        return;
2640    }
2641
2642    do {
2643        itr->second->setWireframe(state);
2644    } while (doAll && ++itr != _polyDatas.end());
2645
2646    _needsRedraw = true;
2647}
2648
2649/**
2650 * \brief Turn mesh lighting on/off for the specified DataSet
2651 */
2652void Renderer::setPolyDataLighting(const DataSetId& id, bool state)
2653{
2654    PolyDataHashmap::iterator itr;
2655
2656    bool doAll = false;
2657
2658    if (id.compare("all") == 0) {
2659        itr = _polyDatas.begin();
2660        doAll = true;
2661    } else {
2662        itr = _polyDatas.find(id);
2663    }
2664    if (itr == _polyDatas.end()) {
2665        ERROR("PolyData not found: %s", id.c_str());
2666        return;
2667    }
2668
2669    do {
2670        itr->second->setLighting(state);
2671    } while (doAll && ++itr != _polyDatas.end());
2672
2673    _needsRedraw = true;
2674}
2675
2676/**
2677 * \brief Create a new PseudoColor rendering for the specified DataSet
2678 */
2679void Renderer::addPseudoColor(const DataSetId& id)
2680{
2681    DataSetHashmap::iterator itr;
2682
2683    bool doAll = false;
2684
2685    if (id.compare("all") == 0) {
2686        itr = _dataSets.begin();
2687    } else {
2688        itr = _dataSets.find(id);
2689    }
2690    if (itr == _dataSets.end()) {
2691        ERROR("Unknown dataset %s", id.c_str());
2692        return;
2693    }
2694
2695    do {
2696        DataSet *ds = itr->second;
2697        const DataSetId& dsID = ds->getName();
2698
2699        if (getPseudoColor(dsID)) {
2700            WARN("Replacing existing pseudocolor %s", dsID.c_str());
2701            deletePseudoColor(dsID);
2702        }
2703        PseudoColor *pc = new PseudoColor();
2704        _pseudoColors[dsID] = pc;
2705
2706        pc->setDataSet(ds);
2707
2708        // Use the default color map
2709        vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
2710        ColorMap *cmap = getColorMap("default");
2711        lut->DeepCopy(cmap->getLookupTable());
2712        if (_useCumulativeRange) {
2713            lut->SetRange(_cumulativeDataRange);
2714        } else {
2715            double range[2];
2716            ds->getDataRange(range);
2717            lut->SetRange(range);
2718        }
2719
2720        pc->setLookupTable(lut);
2721
2722        _renderer->AddViewProp(pc->getProp());
2723    } while (doAll && ++itr != _dataSets.end());
2724
2725    initCamera();
2726    _needsRedraw = true;
2727}
2728
2729/**
2730 * \brief Get the PseudoColor associated with the specified DataSet
2731 */
2732PseudoColor *Renderer::getPseudoColor(const DataSetId& id)
2733{
2734    PseudoColorHashmap::iterator itr = _pseudoColors.find(id);
2735
2736    if (itr == _pseudoColors.end()) {
2737        TRACE("PseudoColor not found: %s", id.c_str());
2738        return NULL;
2739    } else
2740        return itr->second;
2741}
2742
2743/**
2744 * \brief Associate an existing named color map with a PseudoColor for the given DataSet
2745 */
2746void Renderer::setPseudoColorColorMap(const DataSetId& id, const ColorMapId& colorMapId)
2747{
2748    PseudoColorHashmap::iterator itr;
2749
2750    bool doAll = false;
2751
2752    if (id.compare("all") == 0) {
2753        itr = _pseudoColors.begin();
2754        doAll = true;
2755    } else {
2756        itr = _pseudoColors.find(id);
2757    }
2758
2759    if (itr == _pseudoColors.end()) {
2760        ERROR("PseudoColor not found: %s", id.c_str());
2761        return;
2762    }
2763
2764    ColorMap *cmap = getColorMap(colorMapId);
2765    if (cmap == NULL) {
2766        ERROR("Unknown colormap: %s", colorMapId.c_str());
2767        return;
2768    }
2769
2770    do {
2771        TRACE("Set PseudoColor color map: %s for dataset %s", colorMapId.c_str(),
2772              itr->second->getDataSet()->getName().c_str());
2773
2774        // Make a copy of the generic colormap lookup table, so
2775        // data range can be set in the copy table to match the
2776        // dataset being plotted
2777        vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
2778        lut->DeepCopy(cmap->getLookupTable());
2779
2780        if (_useCumulativeRange) {
2781            lut->SetRange(_cumulativeDataRange);
2782        } else {
2783            if (itr->second->getDataSet() != NULL) {
2784                double range[2];
2785                itr->second->getDataSet()->getDataRange(range);
2786                lut->SetRange(range);
2787            }
2788        }
2789
2790        itr->second->setLookupTable(lut);
2791    } while (doAll && ++itr != _pseudoColors.end());
2792
2793    _needsRedraw = true;
2794}
2795
2796/**
2797 * \brief Set opacity of the PseudoColor for the given DataSet
2798 */
2799void Renderer::setPseudoColorOpacity(const DataSetId& id, double opacity)
2800{
2801    PseudoColorHashmap::iterator itr;
2802
2803    bool doAll = false;
2804
2805    if (id.compare("all") == 0) {
2806        itr = _pseudoColors.begin();
2807        doAll = true;
2808    } else {
2809        itr = _pseudoColors.find(id);
2810    }
2811
2812    if (itr == _pseudoColors.end()) {
2813        ERROR("PseudoColor not found: %s", id.c_str());
2814        return;
2815    }
2816
2817    do {
2818        itr->second->setOpacity(opacity);
2819    } while (doAll && ++itr != _pseudoColors.end());
2820
2821    _needsRedraw = true;
2822}
2823
2824/**
2825 * \brief Turn on/off rendering of the PseudoColor mapper for the given DataSet
2826 */
2827void Renderer::setPseudoColorVisibility(const DataSetId& id, bool state)
2828{
2829    PseudoColorHashmap::iterator itr;
2830
2831    bool doAll = false;
2832
2833    if (id.compare("all") == 0) {
2834        itr = _pseudoColors.begin();
2835        doAll = true;
2836    } else {
2837        itr = _pseudoColors.find(id);
2838    }
2839
2840    if (itr == _pseudoColors.end()) {
2841        ERROR("PseudoColor not found: %s", id.c_str());
2842        return;
2843    }
2844
2845    do {
2846        itr->second->setVisibility(state);
2847    } while (doAll && ++itr != _pseudoColors.end());
2848
2849    _needsRedraw = true;
2850}
2851
2852/**
2853 * \brief Set the visibility of polygon edges for the specified DataSet
2854 */
2855void Renderer::setPseudoColorEdgeVisibility(const DataSetId& id, bool state)
2856{
2857    PseudoColorHashmap::iterator itr;
2858
2859    bool doAll = false;
2860
2861    if (id.compare("all") == 0) {
2862        itr = _pseudoColors.begin();
2863        doAll = true;
2864    } else {
2865        itr = _pseudoColors.find(id);
2866    }
2867
2868    if (itr == _pseudoColors.end()) {
2869        ERROR("PseudoColor not found: %s", id.c_str());
2870        return;
2871    }
2872
2873    do {
2874        itr->second->setEdgeVisibility(state);
2875    } while (doAll && ++itr != _pseudoColors.end());
2876
2877    _needsRedraw = true;
2878}
2879
2880/**
2881 * \brief Set the RGB polygon edge color for the specified DataSet
2882 */
2883void Renderer::setPseudoColorEdgeColor(const DataSetId& id, float color[3])
2884{
2885    PseudoColorHashmap::iterator itr;
2886
2887    bool doAll = false;
2888
2889    if (id.compare("all") == 0) {
2890        itr = _pseudoColors.begin();
2891        doAll = true;
2892    } else {
2893        itr = _pseudoColors.find(id);
2894    }
2895
2896    if (itr == _pseudoColors.end()) {
2897        ERROR("PseudoColor not found: %s", id.c_str());
2898        return;
2899    }
2900
2901    do {
2902        itr->second->setEdgeColor(color);
2903    } while (doAll && ++itr != _pseudoColors.end());
2904
2905    _needsRedraw = true;
2906}
2907
2908/**
2909 * \brief Set the polygon edge width for the specified DataSet (may be a no-op)
2910 *
2911 * If the OpenGL implementation/hardware does not support wide lines,
2912 * this function may not have an effect.
2913 */
2914void Renderer::setPseudoColorEdgeWidth(const DataSetId& id, float edgeWidth)
2915{
2916    PseudoColorHashmap::iterator itr;
2917
2918    bool doAll = false;
2919
2920    if (id.compare("all") == 0) {
2921        itr = _pseudoColors.begin();
2922        doAll = true;
2923    } else {
2924        itr = _pseudoColors.find(id);
2925    }
2926
2927    if (itr == _pseudoColors.end()) {
2928        ERROR("PseudoColor not found: %s", id.c_str());
2929        return;
2930    }
2931
2932    do {
2933        itr->second->setEdgeWidth(edgeWidth);
2934    } while (doAll && ++itr != _pseudoColors.end());
2935
2936    _needsRedraw = true;
2937}
2938
2939/**
2940 * \brief Turn mesh lighting on/off for the specified DataSet
2941 */
2942void Renderer::setPseudoColorLighting(const DataSetId& id, bool state)
2943{
2944    PseudoColorHashmap::iterator itr;
2945
2946    bool doAll = false;
2947
2948    if (id.compare("all") == 0) {
2949        itr = _pseudoColors.begin();
2950        doAll = true;
2951    } else {
2952        itr = _pseudoColors.find(id);
2953    }
2954
2955    if (itr == _pseudoColors.end()) {
2956        ERROR("PseudoColor not found: %s", id.c_str());
2957        return;
2958    }
2959
2960    do {
2961        itr->second->setLighting(state);
2962    } while (doAll && ++itr != _pseudoColors.end());
2963
2964    _needsRedraw = true;
2965}
2966
2967/**
2968 * \brief Create a new Volume and associate it with the named DataSet
2969 */
2970void Renderer::addVolume(const DataSetId& id)
2971{
2972    DataSetHashmap::iterator itr;
2973
2974    bool doAll = false;
2975
2976    if (id.compare("all") == 0) {
2977        itr = _dataSets.begin();
2978    } else {
2979        itr = _dataSets.find(id);
2980    }
2981    if (itr == _dataSets.end()) {
2982        ERROR("Unknown dataset %s", id.c_str());
2983        return;
2984    }
2985
2986    do {
2987        DataSet *ds = itr->second;
2988        const DataSetId& dsID = ds->getName();
2989
2990        if (getVolume(dsID)) {
2991            WARN("Replacing existing volume %s", dsID.c_str());
2992            deleteVolume(dsID);
2993        }
2994
2995        Volume *volume = new Volume();
2996        _volumes[dsID] = volume;
2997
2998        volume->setDataSet(ds);
2999
3000        if (_useCumulativeRange) {
3001            ColorMap *cmap = volume->getColorMap();
3002            volume->setColorMap(cmap, _cumulativeDataRange);
3003        }
3004
3005        _renderer->AddViewProp(volume->getProp());
3006    } while (doAll && ++itr != _dataSets.end());
3007
3008    if (_cameraMode == IMAGE)
3009        setCameraMode(PERSPECTIVE);
3010    initCamera();
3011    _needsRedraw = true;
3012}
3013
3014/**
3015 * \brief Get the Volume associated with a named DataSet
3016 */
3017Volume *Renderer::getVolume(const DataSetId& id)
3018{
3019    VolumeHashmap::iterator itr = _volumes.find(id);
3020
3021    if (itr == _volumes.end()) {
3022        TRACE("Volume not found: %s", id.c_str());
3023        return NULL;
3024    } else
3025        return itr->second;
3026}
3027
3028/**
3029 * \brief Associate an existing named color map with a Volume for the given DataSet
3030 */
3031void Renderer::setVolumeColorMap(const DataSetId& id, const ColorMapId& colorMapId)
3032{
3033    VolumeHashmap::iterator itr;
3034
3035    bool doAll = false;
3036
3037    if (id.compare("all") == 0) {
3038        itr = _volumes.begin();
3039        doAll = true;
3040    } else {
3041        itr = _volumes.find(id);
3042    }
3043
3044    if (itr == _volumes.end()) {
3045        ERROR("Volume not found: %s", id.c_str());
3046        return;
3047    }
3048
3049    ColorMap *cmap = getColorMap(colorMapId);
3050    if (cmap == NULL) {
3051        ERROR("Unknown colormap: %s", colorMapId.c_str());
3052        return;
3053    }
3054
3055    do {
3056        TRACE("Set Volume color map: %s for dataset %s", colorMapId.c_str(),
3057              itr->second->getDataSet()->getName().c_str());
3058
3059        if (_useCumulativeRange) {
3060            itr->second->setColorMap(cmap, _cumulativeDataRange);
3061        } else {
3062            itr->second->setColorMap(cmap);
3063        }
3064    } while (doAll && ++itr != _volumes.end());
3065
3066    _needsRedraw = true;
3067}
3068
3069/**
3070 * \brief Set Volume opacity scaling for the specified DataSet
3071 */
3072void Renderer::setVolumeOpacity(const DataSetId& id, double opacity)
3073{
3074    VolumeHashmap::iterator itr;
3075
3076    bool doAll = false;
3077
3078    if (id.compare("all") == 0) {
3079        itr = _volumes.begin();
3080        doAll = true;
3081    } else {
3082        itr = _volumes.find(id);
3083    }
3084    if (itr == _volumes.end()) {
3085        ERROR("Volume not found: %s", id.c_str());
3086        return;
3087    }
3088
3089    do {
3090        itr->second->setOpacity(opacity);
3091    } while (doAll && ++itr != _volumes.end());
3092
3093    _needsRedraw = true;
3094}
3095
3096/**
3097 * \brief Turn on/off rendering of the Volume mapper for the given DataSet
3098 */
3099void Renderer::setVolumeVisibility(const DataSetId& id, bool state)
3100{
3101    VolumeHashmap::iterator itr;
3102
3103    bool doAll = false;
3104
3105    if (id.compare("all") == 0) {
3106        itr = _volumes.begin();
3107        doAll = true;
3108    } else {
3109        itr = _volumes.find(id);
3110    }
3111    if (itr == _volumes.end()) {
3112        ERROR("Volume not found: %s", id.c_str());
3113        return;
3114    }
3115
3116    do {
3117        itr->second->setVisibility(state);
3118    } while (doAll && ++itr != _volumes.end());
3119
3120    _needsRedraw = true;
3121}
3122
3123/**
3124 * \brief Set volume ambient lighting/shading coefficient for the specified DataSet
3125 */
3126void Renderer::setVolumeAmbient(const DataSetId& id, double coeff)
3127{
3128    VolumeHashmap::iterator itr;
3129
3130    bool doAll = false;
3131
3132    if (id.compare("all") == 0) {
3133        itr = _volumes.begin();
3134        doAll = true;
3135    } else {
3136        itr = _volumes.find(id);
3137    }
3138    if (itr == _volumes.end()) {
3139        ERROR("Volume not found: %s", id.c_str());
3140        return;
3141    }
3142
3143    do {
3144        itr->second->setAmbient(coeff);
3145    } while (doAll && ++itr != _volumes.end());
3146
3147    _needsRedraw = true;
3148}
3149
3150/**
3151 * \brief Set volume diffuse lighting/shading coefficient for the specified DataSet
3152 */
3153void Renderer::setVolumeDiffuse(const DataSetId& id, double coeff)
3154{
3155    VolumeHashmap::iterator itr;
3156
3157    bool doAll = false;
3158
3159    if (id.compare("all") == 0) {
3160        itr = _volumes.begin();
3161        doAll = true;
3162    } else {
3163        itr = _volumes.find(id);
3164    }
3165    if (itr == _volumes.end()) {
3166        ERROR("Volume not found: %s", id.c_str());
3167        return;
3168    }
3169
3170    do {
3171        itr->second->setDiffuse(coeff);
3172    } while (doAll && ++itr != _volumes.end());
3173
3174    _needsRedraw = true;
3175}
3176
3177/**
3178 * \brief Set volume specular lighting/shading coefficient and power for the specified DataSet
3179 */
3180void Renderer::setVolumeSpecular(const DataSetId& id, double coeff, double power)
3181{
3182    VolumeHashmap::iterator itr;
3183
3184    bool doAll = false;
3185
3186    if (id.compare("all") == 0) {
3187        itr = _volumes.begin();
3188        doAll = true;
3189    } else {
3190        itr = _volumes.find(id);
3191    }
3192    if (itr == _volumes.end()) {
3193        ERROR("Volume not found: %s", id.c_str());
3194        return;
3195    }
3196
3197    do {
3198        itr->second->setSpecular(coeff, power);
3199    } while (doAll && ++itr != _volumes.end());
3200
3201    _needsRedraw = true;
3202}
3203
3204/**
3205 * \brief Turn volume lighting/shading on/off for the specified DataSet
3206 */
3207void Renderer::setVolumeLighting(const DataSetId& id, bool state)
3208{
3209    VolumeHashmap::iterator itr;
3210
3211    bool doAll = false;
3212
3213    if (id.compare("all") == 0) {
3214        itr = _volumes.begin();
3215        doAll = true;
3216    } else {
3217        itr = _volumes.find(id);
3218    }
3219    if (itr == _volumes.end()) {
3220        ERROR("Volume not found: %s", id.c_str());
3221        return;
3222    }
3223
3224    do {
3225        itr->second->setLighting(state);
3226    } while (doAll && ++itr != _volumes.end());
3227
3228    _needsRedraw = true;
3229}
3230
3231/**
3232 * \brief Resize the render window (image size for renderings)
3233 */
3234void Renderer::setWindowSize(int width, int height)
3235{
3236    // FIXME: Fix up panning on aspect change
3237#ifdef notdef
3238    if (_cameraPan[0] != 0.0) {
3239        _cameraPan[0] *= ((double)_windowWidth / width);
3240    }
3241    if (_cameraPan[1] != 0.0) {
3242        _cameraPan[1] *= ((double)_windowHeight / height);
3243    }
3244#endif
3245    _windowWidth = width;
3246    _windowHeight = height;
3247    _renderWindow->SetSize(_windowWidth, _windowHeight);
3248    if (_cameraMode == IMAGE) {
3249        setCameraZoomRegion(_imgWorldOrigin[0], _imgWorldOrigin[1],
3250                            _imgWorldDims[0], _imgWorldDims[1]);
3251    }
3252    _needsRedraw = true;
3253}
3254
3255/**
3256 * \brief Change the camera type: perspective, orthographic or image view
3257 *
3258 * Perspective mode is a normal 3D camera.
3259 *
3260 * Orthogrphic mode is parallel projection.
3261 *
3262 * Image mode is an orthographic camera with fixed axes and a clipping region
3263 * around the plot area, use setCameraZoomRegion to control the displayed area
3264 *
3265 * \param[in] mode Enum specifying camera type
3266 */
3267void Renderer::setCameraMode(CameraMode mode)
3268{
3269    if (_cameraMode == mode) return;
3270
3271    CameraMode origMode = _cameraMode;
3272    _cameraMode = mode;
3273    vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3274    switch (mode) {
3275    case ORTHO: {
3276        TRACE("Set camera to Ortho mode");
3277        camera->ParallelProjectionOn();
3278        if (origMode == IMAGE) {
3279            resetCamera(true);
3280        }
3281        break;
3282    }
3283    case PERSPECTIVE: {
3284        TRACE("Set camera to Perspective mode");
3285        camera->ParallelProjectionOff();
3286        if (origMode == IMAGE) {
3287            resetCamera(true);
3288        }
3289        break;
3290    }
3291    case IMAGE: {
3292        camera->ParallelProjectionOn();
3293        setCameraZoomRegion(_imgWorldOrigin[0], _imgWorldOrigin[1],
3294                            _imgWorldDims[0],_imgWorldDims[1]);
3295        TRACE("Set camera to Image mode");
3296        break;
3297    }
3298    default:
3299        ERROR("Unkown camera mode: %d", mode);
3300    }
3301    resetAxes();
3302
3303    _needsRedraw = true;
3304}
3305
3306/**
3307 * \brief Get the current camera mode
3308 */
3309Renderer::CameraMode Renderer::getCameraMode() const
3310{
3311    return _cameraMode;
3312}
3313
3314/**
3315 * \brief Set the orientation of the camera from a quaternion
3316 *
3317 * \param[in] quat A quaternion with scalar part first: w,x,y,z
3318 */
3319void Renderer::setCameraOrientation(double quat[4])
3320{
3321    if (_cameraMode == IMAGE)
3322        return;
3323    vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3324    vtkSmartPointer<vtkTransform> trans = vtkSmartPointer<vtkTransform>::New();
3325    double mat3[3][3];
3326    vtkMath::QuaternionToMatrix3x3(quat, mat3);
3327    vtkSmartPointer<vtkMatrix4x4> mat4 = vtkSmartPointer<vtkMatrix4x4>::New();
3328    for (int r = 0; r < 3; r++) {
3329        memcpy((*mat4)[r], mat3[r], sizeof(double)*3);
3330    }
3331    TRACE("Arcball camera matrix: %g %g %g %g %g %g %g %g %g",
3332          (*mat4)[0][0], (*mat4)[0][1], (*mat4)[0][2],
3333          (*mat4)[1][0], (*mat4)[1][1], (*mat4)[1][2],
3334          (*mat4)[2][0], (*mat4)[2][1], (*mat4)[2][2]);
3335    camera->SetPosition(0, 0, 1);
3336    camera->SetFocalPoint(0, 0, 0);
3337    camera->SetViewUp(0, 1, 0);
3338    camera->SetViewAngle(30);
3339    double bounds[6];
3340    collectBounds(bounds, false);
3341    _renderer->ResetCamera(bounds);
3342    camera->GetFocalPoint(_cameraFocalPoint);
3343    trans->Translate(+_cameraFocalPoint[0], +_cameraFocalPoint[1], +_cameraFocalPoint[2]);
3344    trans->Concatenate(mat4);
3345    trans->Translate(-_cameraFocalPoint[0], -_cameraFocalPoint[1], -_cameraFocalPoint[2]);
3346    camera->ApplyTransform(trans);
3347    storeCameraOrientation();
3348    if (_cameraZoomRatio != 1.0) {
3349        double z = _cameraZoomRatio;
3350        _cameraZoomRatio = 1.0;
3351        zoomCamera(z, true);
3352    }
3353    if (_cameraPan[0] != 0.0 || _cameraPan[1] != 0.0) {
3354        double panx = _cameraPan[0];
3355        double pany = -_cameraPan[1];
3356        _cameraPan[0] = 0;
3357        _cameraPan[1] = 0;
3358        panCamera(panx, pany, true);
3359    }
3360    _renderer->ResetCameraClippingRange();
3361    printCameraInfo(camera);
3362    _needsRedraw = true;
3363}
3364
3365/**
3366 * \brief Set the position and orientation of the camera
3367 *
3368 * \param[in] position x,y,z position of camera in world coordinates
3369 * \param[in] focalPoint x,y,z look-at point in world coordinates
3370 * \param[in] viewUp x,y,z up vector of camera
3371 */
3372void Renderer::setCameraOrientationAndPosition(double position[3],
3373                                               double focalPoint[3],
3374                                               double viewUp[3])
3375{
3376    memcpy(_cameraPos, position, sizeof(double)*3);
3377    memcpy(_cameraFocalPoint, focalPoint, sizeof(double)*3);
3378    memcpy(_cameraUp, viewUp, sizeof(double)*3);
3379    // Apply the new parameters to the VTK camera
3380    restoreCameraOrientation();
3381    _needsRedraw = true;
3382}
3383
3384/**
3385 * \brief Get the position and orientation of the camera
3386 *
3387 * \param[out] position x,y,z position of camera in world coordinates
3388 * \param[out] focalPoint x,y,z look-at point in world coordinates
3389 * \param[out] viewUp x,y,z up vector of camera
3390 */
3391void Renderer::getCameraOrientationAndPosition(double position[3],
3392                                               double focalPoint[3],
3393                                               double viewUp[3])
3394{
3395    memcpy(position, _cameraPos, sizeof(double)*3);
3396    memcpy(focalPoint, _cameraFocalPoint, sizeof(double)*3);
3397    memcpy(viewUp, _cameraUp, sizeof(double)*3);
3398}
3399
3400/**
3401 * \brief Saves the current camera orientation and position in order to
3402 * be able to later restore the saved orientation and position
3403 */
3404void Renderer::storeCameraOrientation()
3405{
3406    vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3407    camera->GetPosition(_cameraPos);
3408    camera->GetFocalPoint(_cameraFocalPoint);
3409    camera->GetViewUp(_cameraUp);
3410}
3411
3412/**
3413 * \brief Use the stored orientation and position to set the camera's
3414 * current state
3415 */
3416void Renderer::restoreCameraOrientation()
3417{
3418    vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3419    camera->SetPosition(_cameraPos);
3420    camera->SetFocalPoint(_cameraFocalPoint);
3421    camera->SetViewUp(_cameraUp);
3422}
3423
3424/**
3425 * \brief Reset pan, zoom, clipping planes and optionally rotation
3426 *
3427 * \param[in] resetOrientation Reset the camera rotation/orientation also
3428 */
3429void Renderer::resetCamera(bool resetOrientation)
3430{
3431    vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3432    if (_cameraMode == IMAGE) {
3433        initCamera();
3434    } else {
3435        if (resetOrientation) {
3436            camera->SetPosition(0, 0, 1);
3437            camera->SetFocalPoint(0, 0, 0);
3438            camera->SetViewUp(0, 1, 0);
3439            storeCameraOrientation();
3440        } else {
3441            restoreCameraOrientation();
3442        }
3443        camera->SetViewAngle(30);
3444        double bounds[6];
3445        collectBounds(bounds, false);
3446        _renderer->ResetCamera(bounds);
3447        _renderer->ResetCameraClippingRange();
3448        computeScreenWorldCoords();
3449    }
3450
3451    printCameraInfo(camera);
3452
3453    _cameraZoomRatio = 1;
3454    _cameraPan[0] = 0;
3455    _cameraPan[1] = 0;
3456
3457    _needsRedraw = true;
3458}
3459
3460/**
3461 * \brief Perform a relative rotation to current camera orientation
3462 *
3463 * Angles are in degrees, rotation is about focal point
3464 */
3465void Renderer::rotateCamera(double yaw, double pitch, double roll)
3466{
3467    if (_cameraMode == IMAGE)
3468        return;
3469    vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3470    camera->Azimuth(yaw); // Rotate about object
3471    //camera->SetYaw(yaw); // Rotate about camera
3472    camera->Elevation(pitch); // Rotate about object
3473    //camera->SetPitch(pitch); // Rotate about camera
3474    camera->Roll(roll); // Roll about camera view axis
3475    _renderer->ResetCameraClippingRange();
3476    storeCameraOrientation();
3477    computeScreenWorldCoords();
3478    _needsRedraw = true;
3479}
3480
3481/**
3482 * \brief Perform a 2D translation of the camera
3483 *
3484 * x,y pan amount are specified as signed absolute pan amount in viewport
3485 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
3486 * etc.
3487 *
3488 * \param[in] x Viewport coordinate horizontal panning
3489 * \param[in] y Viewport coordinate vertical panning (with origin at top)
3490 * \param[in] absolute Control if pan amount is relative to current or absolute
3491 */
3492void Renderer::panCamera(double x, double y, bool absolute)
3493{
3494    TRACE("Enter panCamera: %g %g, current abs: %g %g",
3495          x, y, _cameraPan[0], _cameraPan[1]);
3496
3497    if (_cameraMode == IMAGE) {
3498        // Reverse x rather than y, since we are panning the camera, and client
3499        // expects to be panning/moving the object
3500        x = -x * _screenWorldCoords[2];
3501        y = y * _screenWorldCoords[3];
3502
3503        if (absolute) {
3504            double panAbs[2];
3505            panAbs[0] = x;
3506            panAbs[1] = y;
3507            x -= _cameraPan[0];
3508            y -= _cameraPan[1];
3509            _cameraPan[0] = panAbs[0];
3510            _cameraPan[1] = panAbs[1];
3511        } else {
3512            _cameraPan[0] += x;
3513            _cameraPan[1] += y;
3514        }
3515
3516        _imgWorldOrigin[0] += x;
3517        _imgWorldOrigin[1] += y;
3518        setCameraZoomRegion(_imgWorldOrigin[0], _imgWorldOrigin[1],
3519                            _imgWorldDims[0], _imgWorldDims[1]);
3520    } else {
3521        y = -y;
3522        if (absolute) {
3523            double panAbs[2];
3524            panAbs[0] = x;
3525            panAbs[1] = y;
3526            x -= _cameraPan[0];
3527            y -= _cameraPan[1];
3528            _cameraPan[0] = panAbs[0];
3529            _cameraPan[1] = panAbs[1];
3530        } else {
3531            _cameraPan[0] += x;
3532            _cameraPan[1] += y;
3533        }
3534
3535        vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3536        double viewFocus[4], focalDepth, viewPoint[3];
3537        double newPickPoint[4], oldPickPoint[4], motionVector[3];
3538
3539        camera->GetFocalPoint(viewFocus);
3540        computeWorldToDisplay(viewFocus[0], viewFocus[1], viewFocus[2],
3541                              viewFocus);
3542        focalDepth = viewFocus[2];
3543
3544        computeDisplayToWorld(( x * 2. + 1.) * _windowWidth / 2.0,
3545                              ( y * 2. + 1.) * _windowHeight / 2.0,
3546                              focalDepth,
3547                              newPickPoint);
3548
3549        computeDisplayToWorld(_windowWidth / 2.0,
3550                              _windowHeight / 2.0,
3551                              focalDepth,
3552                              oldPickPoint);
3553
3554        // Camera motion is reversed
3555        motionVector[0] = oldPickPoint[0] - newPickPoint[0];
3556        motionVector[1] = oldPickPoint[1] - newPickPoint[1];
3557        motionVector[2] = oldPickPoint[2] - newPickPoint[2];
3558
3559        camera->GetFocalPoint(viewFocus);
3560        camera->GetPosition(viewPoint);
3561        camera->SetFocalPoint(motionVector[0] + viewFocus[0],
3562                              motionVector[1] + viewFocus[1],
3563                              motionVector[2] + viewFocus[2]);
3564
3565        camera->SetPosition(motionVector[0] + viewPoint[0],
3566                            motionVector[1] + viewPoint[1],
3567                            motionVector[2] + viewPoint[2]);
3568
3569        _renderer->ResetCameraClippingRange();
3570        storeCameraOrientation();
3571        //computeScreenWorldCoords();
3572    }
3573
3574    TRACE("Leave panCamera: %g %g, current abs: %g %g",
3575          x, y, _cameraPan[0], _cameraPan[1]);
3576
3577    _needsRedraw = true;
3578}
3579
3580/**
3581 * \brief Change the FOV of the camera
3582 *
3583 * \param[in] z Ratio to change zoom (greater than 1 is zoom in, less than 1 is zoom out)
3584 * \param[in] absolute Control if zoom factor is relative to current setting or absolute
3585 */
3586void Renderer::zoomCamera(double z, bool absolute)
3587{
3588    vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3589    TRACE("Enter Zoom: current abs: %g, z: %g, view angle %g",
3590          _cameraZoomRatio, z, camera->GetViewAngle());
3591
3592    if (absolute) {
3593        assert(_cameraZoomRatio > 0.0);
3594        double zAbs = z;
3595        z *= 1.0/_cameraZoomRatio;
3596        _cameraZoomRatio = zAbs;
3597    } else {
3598        _cameraZoomRatio *= z;
3599    }
3600
3601    if (_cameraMode == IMAGE) {
3602        double dx = _imgWorldDims[0];
3603        double dy = _imgWorldDims[1];
3604        _imgWorldDims[0] /= z;
3605        _imgWorldDims[1] /= z;
3606        dx -= _imgWorldDims[0];
3607        dy -= _imgWorldDims[1];
3608        _imgWorldOrigin[0] += dx/2.0;
3609        _imgWorldOrigin[1] += dy/2.0;
3610        setCameraZoomRegion(_imgWorldOrigin[0], _imgWorldOrigin[1],
3611                            _imgWorldDims[0], _imgWorldDims[1]);
3612    } else {
3613        camera->Zoom(z); // Change perspective FOV angle or ortho parallel scale
3614        //camera->Dolly(z); // Move camera forward/back
3615        _renderer->ResetCameraClippingRange();
3616        storeCameraOrientation();
3617        //computeScreenWorldCoords();
3618    }
3619
3620    TRACE("Leave Zoom: rel %g, new abs: %g, view angle %g",
3621          z, _cameraZoomRatio, camera->GetViewAngle());
3622
3623    _needsRedraw = true;
3624}
3625
3626/**
3627 * \brief Set the pan/zoom using a corner and dimensions in world coordinates
3628 *
3629 * \param[in] x left world coordinate
3630 * \param[in] y bottom  world coordinate
3631 * \param[in] width Width of zoom region in world coordinates
3632 * \param[in] height Height of zoom region in world coordinates
3633 */
3634void Renderer::setCameraZoomRegion(double x, double y, double width, double height)
3635{
3636    double camPos[2];
3637
3638    int pxOffsetX = 85;
3639    int pxOffsetY = 75;
3640    int outerGutter = 15;
3641
3642    int imgHeightPx = _windowHeight - pxOffsetY - outerGutter;
3643    int imgWidthPx = _windowWidth - pxOffsetX - outerGutter;
3644
3645    double imgAspect = width / height;
3646    double winAspect = (double)_windowWidth / _windowHeight;
3647
3648    double pxToWorld;
3649
3650    if (imgAspect >= winAspect) {
3651        pxToWorld = width / imgWidthPx;
3652    } else {
3653        pxToWorld = height / imgHeightPx;
3654    }
3655
3656    double offsetX = pxOffsetX * pxToWorld;
3657    double offsetY = pxOffsetY * pxToWorld;
3658
3659    TRACE("Window: %d %d", _windowWidth, _windowHeight);
3660    TRACE("ZoomRegion: %g %g %g %g", x, y, width, height);
3661    TRACE("pxToWorld: %g", pxToWorld);
3662    TRACE("offset: %g %g", offsetX, offsetY);
3663
3664    setCameraMode(IMAGE);
3665
3666    _imgWorldOrigin[0] = x;
3667    _imgWorldOrigin[1] = y;
3668    _imgWorldDims[0] = width;
3669    _imgWorldDims[1] = height;
3670
3671    camPos[0] = _imgWorldOrigin[0] - offsetX + (_windowWidth * pxToWorld)/2.0;
3672    camPos[1] = _imgWorldOrigin[1] - offsetY + (_windowHeight * pxToWorld)/2.0;
3673
3674    vtkSmartPointer<vtkCamera> camera = _renderer->GetActiveCamera();
3675    camera->ParallelProjectionOn();
3676    camera->SetPosition(camPos[0], camPos[1], 1);
3677    camera->SetFocalPoint(camPos[0], camPos[1], 0);
3678    camera->SetViewUp(0, 1, 0);
3679    camera->SetClippingRange(1, 2);
3680    // Half of world coordinate height of viewport (Documentation is wrong)
3681    camera->SetParallelScale(_windowHeight * pxToWorld / 2.0);
3682
3683    // bottom
3684    _clipPlanes[0]->SetOrigin(0, _imgWorldOrigin[1], 0);
3685    // left
3686    _clipPlanes[1]->SetOrigin(_imgWorldOrigin[0], 0, 0);
3687    // top
3688    _clipPlanes[2]->SetOrigin(0, _imgWorldOrigin[1] + _imgWorldDims[1], 0);
3689    // right
3690    _clipPlanes[3]->SetOrigin(_imgWorldOrigin[0] + _imgWorldDims[0], 0, 0);
3691
3692    _cubeAxesActor2D->SetBounds(_imgWorldOrigin[0], _imgWorldOrigin[0] + _imgWorldDims[0],
3693                                _imgWorldOrigin[1], _imgWorldOrigin[1] + _imgWorldDims[1], 0, 0);
3694
3695    // Compute screen world coordinates
3696    computeScreenWorldCoords();
3697
3698#ifdef DEBUG
3699    printCameraInfo(camera);
3700#endif
3701
3702    _needsRedraw = true;
3703}
3704
3705/**
3706 * \brief Convert pixel/display coordinates to world coordinates based on current camera
3707 */
3708void Renderer::computeDisplayToWorld(double x, double y, double z, double worldPt[4])
3709{
3710    _renderer->SetDisplayPoint(x, y, z);
3711    _renderer->DisplayToWorld();
3712    _renderer->GetWorldPoint(worldPt);
3713    if (worldPt[3]) {
3714        worldPt[0] /= worldPt[3];
3715        worldPt[1] /= worldPt[3];
3716        worldPt[2] /= worldPt[3];
3717        worldPt[3] = 1.0;
3718    }
3719}
3720
3721/**
3722 * \brief Convert world coordinates to pixel/display coordinates based on current camera
3723 */
3724void Renderer::computeWorldToDisplay(double x, double y, double z, double displayPt[3])
3725{
3726    _renderer->SetWorldPoint(x, y, z, 1.0);
3727    _renderer->WorldToDisplay();
3728    _renderer->GetDisplayPoint(displayPt);
3729}
3730
3731/**
3732 * \brief Compute the world coordinate bounds of the display rectangle
3733 */
3734void Renderer::computeScreenWorldCoords()
3735{
3736    // Start with viewport coords [-1,1]
3737    double x0 = -1;
3738    double y0 = -1;
3739    double z0 = -1;
3740    double x1 = 1;
3741    double y1 = 1;
3742    double z1 = -1;
3743
3744    vtkMatrix4x4 *mat = vtkMatrix4x4::New();
3745    double result[4];
3746
3747    // get the perspective transformation from the active camera
3748    mat->DeepCopy(_renderer->GetActiveCamera()->
3749                  GetCompositeProjectionTransformMatrix(_renderer->GetTiledAspectRatio(),0,1));
3750
3751    // use the inverse matrix
3752    mat->Invert();
3753
3754    // Transform point to world coordinates
3755    result[0] = x0;
3756    result[1] = y0;
3757    result[2] = z0;
3758    result[3] = 1.0;
3759
3760    mat->MultiplyPoint(result, result);
3761
3762    // Get the transformed vector & set WorldPoint
3763    // while we are at it try to keep w at one
3764    if (result[3]) {
3765        x0 = result[0] / result[3];
3766        y0 = result[1] / result[3];
3767        z0 = result[2] / result[3];
3768    }
3769
3770    result[0] = x1;
3771    result[1] = y1;
3772    result[2] = z1;
3773    result[3] = 1.0;
3774
3775    mat->MultiplyPoint(result, result);
3776
3777    if (result[3]) {
3778        x1 = result[0] / result[3];
3779        y1 = result[1] / result[3];
3780        z1 = result[2] / result[3];
3781    }
3782
3783    mat->Delete();
3784
3785    _screenWorldCoords[0] = x0;
3786    _screenWorldCoords[1] = y0;
3787    _screenWorldCoords[2] = x1 - x0;
3788    _screenWorldCoords[3] = y1 - y0;
3789}
3790
3791/**
3792 * \brief Get the world coordinates of the image camera plot area
3793 *
3794 * \param[out] xywh Array to hold x,y,width,height world coordinates
3795 */
3796void Renderer::getCameraZoomRegion(double xywh[4]) const
3797{
3798    xywh[0] = _imgWorldOrigin[0];
3799    xywh[1] = _imgWorldOrigin[1];
3800    xywh[2] = _imgWorldDims[0];
3801    xywh[3] = _imgWorldDims[1];
3802}
3803
3804/**
3805 * \brief Get the world origin and dimensions of the screen
3806 *
3807 * \param[out] xywh Array to hold x,y,width,height world coordinates
3808 */
3809void Renderer::getScreenWorldCoords(double xywh[4]) const
3810{
3811    memcpy(xywh, _screenWorldCoords, sizeof(double)*4);
3812}
3813
3814/**
3815 * \brief Compute bounding box containing the two input bounding boxes
3816 *
3817 * \param[out] boundsDest Union of the two input bounding boxes
3818 * \param[in] bounds1 Input bounding box
3819 * \param[in] bounds2 Input bounding box
3820 */
3821void Renderer::mergeBounds(double *boundsDest,
3822                           const double *bounds1, const double *bounds2)
3823{
3824    assert(boundsDest != NULL);
3825    assert(bounds1 != NULL);
3826    if (bounds2 == NULL) {
3827        WARN("NULL bounds2 array");
3828        return;
3829    }
3830    for (int i = 0; i < 6; i++) {
3831        if (i % 2 == 0)
3832            boundsDest[i] = min2(bounds1[i], bounds2[i]);
3833        else
3834            boundsDest[i] = max2(bounds1[i], bounds2[i]);
3835    }
3836}
3837
3838/**
3839 * \brief Collect bounds of all graphics objects
3840 *
3841 * \param[out] bounds Bounds of all scene objects
3842 * \param[in] onlyVisible Only collect bounds of visible objects
3843 */
3844void Renderer::collectBounds(double *bounds, bool onlyVisible)
3845{
3846    bounds[0] = DBL_MAX;
3847    bounds[1] = -DBL_MAX;
3848    bounds[2] = DBL_MAX;
3849    bounds[3] = -DBL_MAX;
3850    bounds[4] = DBL_MAX;
3851    bounds[5] = -DBL_MAX;
3852
3853    for (Contour2DHashmap::iterator itr = _contour2Ds.begin();
3854             itr != _contour2Ds.end(); ++itr) {
3855        if (!onlyVisible || itr->second->getVisibility())
3856            mergeBounds(bounds, bounds, itr->second->getProp()->GetBounds());
3857    }
3858    for (Contour3DHashmap::iterator itr = _contour3Ds.begin();
3859             itr != _contour3Ds.end(); ++itr) {
3860        if (!onlyVisible || itr->second->getVisibility())
3861            mergeBounds(bounds, bounds, itr->second->getProp()->GetBounds());
3862    }
3863    for (GlyphsHashmap::iterator itr = _glyphs.begin();
3864             itr != _glyphs.end(); ++itr) {
3865        if (!onlyVisible || itr->second->getVisibility())
3866            mergeBounds(bounds, bounds, itr->second->getProp()->GetBounds());
3867    }
3868    for (HeightMapHashmap::iterator itr = _heightMaps.begin();
3869             itr != _heightMaps.end(); ++itr) {
3870        if (!onlyVisible || itr->second->getVisibility())
3871            mergeBounds(bounds, bounds, itr->second->getProp()->GetBounds());
3872    }
3873    for (PolyDataHashmap::iterator itr = _polyDatas.begin();
3874             itr != _polyDatas.end(); ++itr) {
3875        if (!onlyVisible || itr->second->getVisibility())
3876            mergeBounds(bounds, bounds, itr->second->getProp()->GetBounds());
3877    }
3878    for (PseudoColorHashmap::iterator itr = _pseudoColors.begin();
3879             itr != _pseudoColors.end(); ++itr) {
3880        if (!onlyVisible || itr->second->getVisibility())
3881            mergeBounds(bounds, bounds, itr->second->getProp()->GetBounds());
3882    }
3883    for (VolumeHashmap::iterator itr = _volumes.begin();
3884             itr != _volumes.end(); ++itr) {
3885        if (!onlyVisible || itr->second->getVisibility())
3886            mergeBounds(bounds, bounds, itr->second->getProp()->GetBounds());
3887    }
3888    for (int i = 0; i < 6; i++) {
3889        if (i % 2 == 0) {
3890            if (bounds[i] == DBL_MAX)
3891                bounds[i] = 0;
3892        } else {
3893            if (bounds[i] == -DBL_MAX)
3894                bounds[i] = 1;
3895        }
3896    }
3897    TRACE("Bounds: %g %g %g %g %g %g",
3898          bounds[0],
3899          bounds[1],
3900          bounds[2],
3901          bounds[3],
3902          bounds[4],
3903          bounds[5]);
3904}
3905
3906/**
3907 * \brief Update data ranges for color-mapping and contours
3908 *
3909 * \param[in] useCumulative Use cumulative range of all DataSets
3910 */
3911void Renderer::updateRanges(bool useCumulative)
3912{
3913    for (Contour2DHashmap::iterator itr = _contour2Ds.begin();
3914         itr != _contour2Ds.end(); ++itr) {
3915        // Only need to update range if using evenly spaced contours
3916        if (itr->second->getContourList().empty()) {
3917            if (useCumulative) {
3918                itr->second->setContours(itr->second->getNumContours(), _cumulativeDataRange);
3919            } else {
3920                itr->second->setContours(itr->second->getNumContours());
3921            }
3922        }
3923    }
3924    for (Contour3DHashmap::iterator itr = _contour3Ds.begin();
3925         itr != _contour3Ds.end(); ++itr) {
3926        // Only need to update range if using evenly spaced contours
3927        if (itr->second->getContourList().empty()) {
3928            if (useCumulative) {
3929                itr->second->setContours(itr->second->getNumContours(), _cumulativeDataRange);
3930            } else {
3931                itr->second->setContours(itr->second->getNumContours());
3932            }
3933        }
3934    }
3935    for (GlyphsHashmap::iterator itr = _glyphs.begin();
3936         itr != _glyphs.end(); ++itr) {
3937        vtkLookupTable *lut = itr->second->getLookupTable();
3938        if (lut) {
3939            if (useCumulative) {
3940                lut->SetRange(_cumulativeDataRange);
3941            } else {
3942                double range[2];
3943                if (itr->second->getDataSet()) {
3944                    itr->second->getDataSet()->getDataRange(range);
3945                    lut->SetRange(range);
3946                }
3947            }
3948        }
3949    }
3950    for (HeightMapHashmap::iterator itr = _heightMaps.begin();
3951         itr != _heightMaps.end(); ++itr) {
3952        vtkLookupTable *lut = itr->second->getLookupTable();
3953        if (lut) {
3954            if (useCumulative) {
3955                lut->SetRange(_cumulativeDataRange);
3956            } else {
3957                double range[2];
3958                if (itr->second->getDataSet()) {
3959                    itr->second->getDataSet()->getDataRange(range);
3960                    lut->SetRange(range);
3961                }
3962            }
3963        }
3964        // Only need to update contour range if using evenly spaced contours
3965        if (itr->second->getContourList().empty()) {
3966            if (useCumulative) {
3967                itr->second->setContours(itr->second->getNumContours(), _cumulativeDataRange);
3968            } else {
3969                itr->second->setContours(itr->second->getNumContours());
3970            }
3971        }
3972    }
3973    for (PseudoColorHashmap::iterator itr = _pseudoColors.begin();
3974         itr != _pseudoColors.end(); ++itr) {
3975        vtkLookupTable *lut = itr->second->getLookupTable();
3976        if (lut) {
3977            if (useCumulative) {
3978                lut->SetRange(_cumulativeDataRange);
3979            } else {
3980                double range[2];
3981                if (itr->second->getDataSet()) {
3982                    itr->second->getDataSet()->getDataRange(range);
3983                    lut->SetRange(range);
3984                }
3985            }
3986        }
3987    }
3988    for (VolumeHashmap::iterator itr = _volumes.begin();
3989         itr != _volumes.end(); ++itr) {
3990        ColorMap *cmap = itr->second->getColorMap();
3991        if (cmap) {
3992            if (useCumulative) {
3993                itr->second->setColorMap(cmap, _cumulativeDataRange);
3994            } else {
3995                itr->second->setColorMap(cmap);
3996            }
3997        }
3998    }
3999}
4000
4001/**
4002 * \brief Collect cumulative data range of all DataSets
4003 *
4004 * \param[inout] range Data range of all DataSets
4005 * \param[in] onlyVisible Only collect range of visible DataSets
4006 */
4007void Renderer::collectDataRanges(double *range, bool onlyVisible)
4008{
4009    range[0] = DBL_MAX;
4010    range[1] = -DBL_MAX;
4011
4012    for (DataSetHashmap::iterator itr = _dataSets.begin();
4013         itr != _dataSets.end(); ++itr) {
4014        if (!onlyVisible || itr->second->getVisibility()) {
4015            double r[2];
4016            itr->second->getDataRange(r);
4017            range[0] = min2(range[0], r[0]);
4018            range[1] = max2(range[1], r[1]);
4019        }
4020    }
4021    if (range[0] == DBL_MAX)
4022        range[0] = 0;
4023    if (range[1] == -DBL_MAX)
4024        range[1] = 1;
4025}
4026
4027/**
4028 * \brief Initialize the camera zoom region to include the bounding volume given
4029 */
4030void Renderer::initCamera()
4031{
4032#ifdef WANT_TRACE
4033    switch (_cameraMode) {
4034    case IMAGE:
4035        TRACE("Image camera");
4036        break;
4037    case ORTHO:
4038        TRACE("Ortho camera");
4039        break;
4040    case PERSPECTIVE:
4041        TRACE("Perspective camera");
4042        break;
4043    default:
4044        TRACE("Unknown camera mode");
4045    }
4046#endif
4047    double bounds[6];
4048    collectBounds(bounds, false);
4049    _imgWorldOrigin[0] = bounds[0];
4050    _imgWorldOrigin[1] = bounds[2];
4051    _imgWorldDims[0] = bounds[1] - bounds[0];
4052    _imgWorldDims[1] = bounds[3] - bounds[2];
4053    _cameraPan[0] = 0;
4054    _cameraPan[1] = 0;
4055    _cameraZoomRatio = 1;
4056
4057    switch (_cameraMode) {
4058    case IMAGE:
4059        _renderer->ResetCamera(bounds);
4060        setCameraZoomRegion(_imgWorldOrigin[0], _imgWorldOrigin[1],
4061                            _imgWorldDims[0], _imgWorldDims[1]);
4062        resetAxes();
4063        break;
4064    case ORTHO:
4065        _renderer->GetActiveCamera()->ParallelProjectionOn();
4066        resetAxes();
4067        _renderer->ResetCamera(bounds);
4068        computeScreenWorldCoords();
4069        break;
4070    case PERSPECTIVE:
4071        _renderer->GetActiveCamera()->ParallelProjectionOff();
4072        resetAxes();
4073        _renderer->ResetCamera(bounds);
4074        computeScreenWorldCoords();
4075        break;
4076    default:
4077        ERROR("Unknown camera mode");
4078    }
4079#ifdef WANT_TRACE
4080    printCameraInfo(_renderer->GetActiveCamera());
4081#endif
4082}
4083
4084/**
4085 * \brief Print debugging info about a vtkCamera
4086 */
4087void Renderer::printCameraInfo(vtkCamera *camera)
4088{
4089    TRACE("Parallel Scale: %g, View angle: %g, Cam pos: %g %g %g, focal pt: %g %g %g, view up: %g %g %g, Clipping range: %g %g",
4090          camera->GetParallelScale(),
4091          camera->GetViewAngle(),
4092          camera->GetPosition()[0],
4093          camera->GetPosition()[1],
4094          camera->GetPosition()[2],
4095          camera->GetFocalPoint()[0],
4096          camera->GetFocalPoint()[1],
4097          camera->GetFocalPoint()[2],
4098          camera->GetViewUp()[0],
4099          camera->GetViewUp()[1],
4100          camera->GetViewUp()[2],
4101          camera->GetClippingRange()[0],
4102          camera->GetClippingRange()[1]);
4103}
4104
4105/**
4106 * \brief Set the RGB background color to render into the image
4107 */
4108void Renderer::setBackgroundColor(float color[3])
4109{
4110    _bgColor[0] = color[0];
4111    _bgColor[1] = color[1];
4112    _bgColor[2] = color[2];
4113    _renderer->SetBackground(_bgColor[0], _bgColor[1], _bgColor[2]);
4114    _needsRedraw = true;
4115}
4116
4117/**
4118 * \brief Set the opacity of the specified DataSet's associated graphics objects
4119 */
4120void Renderer::setOpacity(const DataSetId& id, double opacity)
4121{
4122    if (id.compare("all") == 0 || getContour2D(id) != NULL)
4123        setContour2DOpacity(id, opacity);
4124    if (id.compare("all") == 0 || getContour3D(id) != NULL)
4125        setContour3DOpacity(id, opacity);
4126    if (id.compare("all") == 0 || getGlyphs(id) != NULL)
4127        setGlyphsOpacity(id, opacity);
4128    if (id.compare("all") == 0 || getHeightMap(id) != NULL)
4129        setHeightMapOpacity(id, opacity);
4130    if (id.compare("all") == 0 || getPolyData(id) != NULL)
4131        setPolyDataOpacity(id, opacity);
4132    if (id.compare("all") == 0 || getPseudoColor(id) != NULL)
4133        setPseudoColorOpacity(id, opacity);
4134    if (id.compare("all") == 0 || getVolume(id) != NULL)
4135        setVolumeOpacity(id, opacity);
4136}
4137
4138/**
4139 * \brief Turn on/off rendering of the specified DataSet's associated graphics objects
4140 */
4141void Renderer::setVisibility(const DataSetId& id, bool state)
4142{
4143    DataSetHashmap::iterator itr;
4144
4145    bool doAll = false;
4146
4147    if (id.compare("all") == 0) {
4148        itr = _dataSets.begin();
4149        doAll = true;
4150    } else {
4151        itr = _dataSets.find(id);
4152    }
4153    if (itr == _dataSets.end()) {
4154        ERROR("Unknown dataset %s", id.c_str());
4155        return;
4156    }
4157
4158    do {
4159        itr->second->setVisibility(state);
4160    } while (doAll && ++itr != _dataSets.end());
4161
4162    if (id.compare("all") == 0 || getContour2D(id) != NULL)
4163        setContour2DVisibility(id, state);
4164    if (id.compare("all") == 0 || getContour3D(id) != NULL)
4165        setContour3DVisibility(id, state);
4166    if (id.compare("all") == 0 || getGlyphs(id) != NULL)
4167        setGlyphsVisibility(id, state);
4168    if (id.compare("all") == 0 || getHeightMap(id) != NULL)
4169        setHeightMapVisibility(id, state);
4170    if (id.compare("all") == 0 || getPolyData(id) != NULL)
4171        setPolyDataVisibility(id, state);
4172    if (id.compare("all") == 0 || getPseudoColor(id) != NULL)
4173        setPseudoColorVisibility(id, state);
4174    if (id.compare("all") == 0 || getVolume(id) != NULL)
4175        setVolumeVisibility(id, state);
4176}
4177
4178/**
4179 * \brief Set up clipping planes for image camera mode if needed
4180 */
4181void Renderer::setCameraClippingPlanes()
4182{
4183    /* XXX: Note that there appears to be a bug with setting the
4184     * clipping plane collection to NULL in the VTK Mappers --
4185     * the old clip planes are still applied.  The workaround here
4186     * is to keep the PlaneCollection and add/remove the planes
4187     * to/from the PlaneCollection as needed.
4188     */
4189    if (_cameraMode == IMAGE) {
4190        if (_activeClipPlanes->GetNumberOfItems() == 0) {
4191            for (int i = 0; i < 4; i++)
4192                _activeClipPlanes->AddItem(_clipPlanes[i]);
4193        }
4194    } else {
4195        if (_activeClipPlanes->GetNumberOfItems() > 0)
4196            _activeClipPlanes->RemoveAllItems();
4197    }
4198
4199    /* Ensure all Mappers are using the PlaneCollection
4200     * This will not change the state or timestamp of
4201     * Mappers already using the PlaneCollection
4202     */
4203    for (Contour2DHashmap::iterator itr = _contour2Ds.begin();
4204         itr != _contour2Ds.end(); ++itr) {
4205        itr->second->setClippingPlanes(_activeClipPlanes);
4206    }
4207    for (Contour3DHashmap::iterator itr = _contour3Ds.begin();
4208         itr != _contour3Ds.end(); ++itr) {
4209        itr->second->setClippingPlanes(_activeClipPlanes);
4210    }
4211    for (GlyphsHashmap::iterator itr = _glyphs.begin();
4212         itr != _glyphs.end(); ++itr) {
4213        itr->second->setClippingPlanes(_activeClipPlanes);
4214    }
4215    for (HeightMapHashmap::iterator itr = _heightMaps.begin();
4216         itr != _heightMaps.end(); ++itr) {
4217        itr->second->setClippingPlanes(_activeClipPlanes);
4218    }
4219    for (PolyDataHashmap::iterator itr = _polyDatas.begin();
4220         itr != _polyDatas.end(); ++itr) {
4221        itr->second->setClippingPlanes(_activeClipPlanes);
4222    }
4223    for (PseudoColorHashmap::iterator itr = _pseudoColors.begin();
4224         itr != _pseudoColors.end(); ++itr) {
4225        itr->second->setClippingPlanes(_activeClipPlanes);
4226    }
4227    for (VolumeHashmap::iterator itr = _volumes.begin();
4228         itr != _volumes.end(); ++itr) {
4229        itr->second->setClippingPlanes(_activeClipPlanes);
4230    }
4231}
4232
4233/**
4234 * \brief Control the use of the depth peeling algorithm for transparency
4235 */
4236void Renderer::setUseDepthPeeling(bool state)
4237{
4238    _renderer->SetUseDepthPeeling(state ? 1 : 0);
4239    _needsRedraw = true;
4240}
4241
4242/**
4243 * \brief Cause the rendering to render a new image if needed
4244 *
4245 * The _needsRedraw flag indicates if a state change has occured since
4246 * the last rendered frame
4247 */
4248bool Renderer::render()
4249{
4250    if (_needsRedraw) {
4251        setCameraClippingPlanes();
4252        _renderWindow->Render();
4253        _needsRedraw = false;
4254        return true;
4255    } else
4256        return false;
4257}
4258
4259/// Get the pixel width of the render window/image
4260int Renderer::getWindowWidth() const
4261{
4262    return _windowWidth;
4263}
4264
4265/// Get the pixel height of the render window/image
4266int Renderer::getWindowHeight() const
4267{
4268    return _windowHeight;
4269}
4270
4271/**
4272 * \brief Read back the rendered framebuffer image
4273 */
4274void Renderer::getRenderedFrame(vtkUnsignedCharArray *imgData)
4275{
4276#ifdef RENDER_TARGA
4277    _renderWindow->MakeCurrent();
4278    // Must clear previous errors first.
4279    while (glGetError() != GL_NO_ERROR){
4280        ;
4281    }
4282    int bytesPerPixel = TARGA_BYTES_PER_PIXEL;
4283    int size = bytesPerPixel * _windowWidth * _windowHeight;
4284
4285    if (imgData->GetMaxId() + 1 != size)
4286    {
4287        imgData->SetNumberOfComponents(bytesPerPixel);
4288        imgData->SetNumberOfValues(size);
4289    }
4290    glDisable(GL_TEXTURE_2D);
4291    if (_renderWindow->GetDoubleBuffer()) {
4292        glReadBuffer(static_cast<GLenum>(vtkOpenGLRenderWindow::SafeDownCast(_renderWindow)->GetBackLeftBuffer()));
4293    } else {
4294        glReadBuffer(static_cast<GLenum>(vtkOpenGLRenderWindow::SafeDownCast(_renderWindow)->GetFrontLeftBuffer()));
4295    }
4296    glPixelStorei(GL_PACK_ALIGNMENT, 1);
4297#ifdef WANT_TRACE
4298    struct timeval t1, t2;
4299    glFinish();
4300    gettimeofday(&t1, 0);
4301#endif
4302    if (bytesPerPixel == 4) {
4303        glReadPixels(0, 0, _windowWidth, _windowHeight, GL_BGRA,
4304                     GL_UNSIGNED_BYTE, imgData->GetPointer(0));
4305    } else {
4306        glReadPixels(0, 0, _windowWidth, _windowHeight, GL_BGR,
4307                     GL_UNSIGNED_BYTE, imgData->GetPointer(0));
4308    }
4309#ifdef WANT_TRACE
4310    gettimeofday(&t2, 0);
4311    static unsigned int numFrames = 0;
4312    static double accum = 0;
4313    numFrames++;
4314    accum += ELAPSED_TIME(t1, t2);
4315#endif
4316    TRACE("Readback time: %g ms", ELAPSED_TIME(t1, t2));
4317    TRACE("Readback avg: %g ms", accum/numFrames);
4318    if (glGetError() != GL_NO_ERROR) {
4319        ERROR("glReadPixels");
4320    }
4321#else
4322    _renderWindow->GetPixelData(0, 0, _windowWidth-1, _windowHeight-1,
4323                                !_renderWindow->GetDoubleBuffer(), imgData);
4324#endif
4325    TRACE("Image data size: %d", imgData->GetSize());
4326}
4327
4328/**
4329 * \brief Get nearest data value given display coordinates x,y
4330 *
4331 * Note: no interpolation is performed on data
4332 */
4333double Renderer::getDataValueAtPixel(const DataSetId& id, int x, int y)
4334{
4335    vtkSmartPointer<vtkCoordinate> coord = vtkSmartPointer<vtkCoordinate>::New();
4336    coord->SetCoordinateSystemToDisplay();
4337    coord->SetValue(x, y, 0);
4338    double *worldCoords = coord->GetComputedWorldValue(_renderer);
4339
4340    TRACE("Pixel coords: %d, %d\nWorld coords: %.12e, %.12e, %12e", x, y,
4341          worldCoords[0],
4342          worldCoords[1],
4343          worldCoords[2]);
4344
4345    return getDataValue(id, worldCoords[0], worldCoords[1], worldCoords[2]);
4346}
4347
4348/**
4349 * \brief Get nearest data value given world coordinates x,y,z
4350 *
4351 * Note: no interpolation is performed on data
4352 */
4353double Renderer::getDataValue(const DataSetId& id, double x, double y, double z)
4354{
4355    DataSet *ds = getDataSet(id);
4356    if (ds == NULL)
4357        return 0;
4358    vtkDataSet *vtkds = ds->getVtkDataSet();
4359    vtkIdType pt = vtkds->FindPoint(x, y, z);
4360    return vtkds->GetPointData()->GetScalars()->GetComponent(pt, 0);
4361}
Note: See TracBrowser for help on using the repository browser.