Put method const back on method signature line
[SARndbox.git] / Sandbox.cpp
blob1b2212200e53d47bc4fbdbb78ec499d4158caab5
1 /***********************************************************************
2 Sandbox - Vrui application to drive an augmented reality sandbox.
3 Copyright (c) 2012-2018 Oliver Kreylos
4 Copyright (c) 2019,2020 Scottsdale Community College
6 This file is part of the Augmented Reality Sandbox (SARndbox).
8 The Augmented Reality Sandbox is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
13 The Augmented Reality Sandbox is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License along
19 with the Augmented Reality Sandbox; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 ***********************************************************************/
23 #include "Sandbox.h"
25 #include <ctype.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <string>
33 #include <vector>
34 #include <stdexcept>
35 #include <iostream>
36 #include <Misc/SizedTypes.h>
37 #include <Misc/SelfDestructPointer.h>
38 #include <Misc/FixedArray.h>
39 #include <Misc/FunctionCalls.h>
40 #include <Misc/FileNameExtensions.h>
41 #include <Misc/StandardValueCoders.h>
42 #include <Misc/ArrayValueCoders.h>
43 #include <Misc/ConfigurationFile.h>
44 #include <IO/File.h>
45 #include <IO/ValueSource.h>
46 #include <Cluster/OpenPipe.h>
47 #include <Math/Math.h>
48 #include <Math/Constants.h>
49 #include <Math/Interval.h>
50 #include <Math/MathValueCoders.h>
51 #include <Geometry/Point.h>
52 #include <Geometry/AffineCombiner.h>
53 #include <Geometry/HVector.h>
54 #include <Geometry/Plane.h>
55 #include <Geometry/LinearUnit.h>
56 #include <Geometry/GeometryValueCoders.h>
57 #include <Geometry/OutputOperators.h>
58 #include <GL/gl.h>
59 #include <GL/GLMaterialTemplates.h>
60 #include <GL/GLColorMap.h>
61 #include <GL/GLLightTracker.h>
62 #include <GL/Extensions/GLEXTFramebufferObject.h>
63 #include <GL/Extensions/GLARBTextureRectangle.h>
64 #include <GL/Extensions/GLARBTextureFloat.h>
65 #include <GL/Extensions/GLARBTextureRg.h>
66 #include <GL/Extensions/GLARBDepthTexture.h>
67 #include <GL/Extensions/GLARBShaderObjects.h>
68 #include <GL/Extensions/GLARBVertexShader.h>
69 #include <GL/Extensions/GLARBFragmentShader.h>
70 #include <GL/Extensions/GLARBMultitexture.h>
71 #include <GL/GLContextData.h>
72 #include <GL/GLGeometryWrappers.h>
73 #include <GL/GLTransformationWrappers.h>
74 #include <GLMotif/StyleSheet.h>
75 #include <GLMotif/WidgetManager.h>
76 #include <GLMotif/PopupMenu.h>
77 #include <GLMotif/Menu.h>
78 #include <GLMotif/PopupWindow.h>
79 #include <GLMotif/Margin.h>
80 #include <GLMotif/Label.h>
81 #include <GLMotif/TextField.h>
82 #include <Vrui/Vrui.h>
83 #include <Vrui/CoordinateManager.h>
84 #include <Vrui/Lightsource.h>
85 #include <Vrui/LightsourceManager.h>
86 #include <Vrui/Viewer.h>
87 #include <Vrui/ToolManager.h>
88 #include <Vrui/DisplayState.h>
89 #include <Vrui/OpenFile.h>
90 #include <Kinect/FileFrameSource.h>
91 #include <Kinect/MultiplexedFrameSource.h>
92 #include <Kinect/DirectFrameSource.h>
93 #include <Kinect/OpenDirectFrameSource.h>
95 #define SAVEDEPTH 0
97 #if SAVEDEPTH
98 #include <Images/RGBImage.h>
99 #include <Images/WriteImageFile.h>
100 #endif
102 #include "FrameFilter.h"
103 #include "DepthImageRenderer.h"
104 #include "ElevationColorMap.h"
105 #include "DEM.h"
106 #include "SurfaceRenderer.h"
107 #include "WaterTable2.h"
108 #include "HandExtractor.h"
109 #include "WaterRenderer.h"
110 #include "GlobalWaterTool.h"
111 #include "LocalWaterTool.h"
112 #include "DEMTool.h"
113 #include "BathymetrySaverTool.h"
115 #include "Config.h"
117 /**********************************
118 Methods of class Sandbox::DataItem:
119 **********************************/
121 Sandbox::DataItem::DataItem(void)
122 : waterTableTime(0.0),
123 shadowFramebufferObject(0), shadowDepthTextureObject(0) {
124 /* Check if all required extensions are supported: */
125 bool supported = GLEXTFramebufferObject::isSupported();
126 supported = supported && GLARBTextureRectangle::isSupported();
127 supported = supported && GLARBTextureFloat::isSupported();
128 supported = supported && GLARBTextureRg::isSupported();
129 supported = supported && GLARBDepthTexture::isSupported();
130 supported = supported && GLARBShaderObjects::isSupported();
131 supported = supported && GLARBVertexShader::isSupported();
132 supported = supported && GLARBFragmentShader::isSupported();
133 supported = supported && GLARBMultitexture::isSupported();
134 if(!supported)
135 Misc::throwStdErr("Sandbox: Not all required extensions are supported by local OpenGL");
137 /* Initialize all required extensions: */
138 GLEXTFramebufferObject::initExtension();
139 GLARBTextureRectangle::initExtension();
140 GLARBTextureFloat::initExtension();
141 GLARBTextureRg::initExtension();
142 GLARBDepthTexture::initExtension();
143 GLARBShaderObjects::initExtension();
144 GLARBVertexShader::initExtension();
145 GLARBFragmentShader::initExtension();
146 GLARBMultitexture::initExtension();
149 Sandbox::DataItem::~DataItem(void) {
150 /* Delete all shaders, buffers, and texture objects: */
151 glDeleteFramebuffersEXT(1, &shadowFramebufferObject);
152 glDeleteTextures(1, &shadowDepthTextureObject);
155 /****************************************
156 Methods of class Sandbox::RenderSettings:
157 ****************************************/
159 Sandbox::RenderSettings::RenderSettings(void)
160 : fixProjectorView(false), projectorTransform(PTransform::identity),
161 projectorTransformValid(false),
162 hillshade(false), surfaceMaterial(GLMaterial::Color(1.0f, 1.0f, 1.0f)),
163 useShadows(false),
164 elevationColorMap(0),
165 useContourLines(true), contourLineSpacing(0.75f),
166 renderWaterSurface(false), waterOpacity(2.0f),
167 surfaceRenderer(0), waterRenderer(0) {
168 /* Load the default projector transformation: */
169 loadProjectorTransform(CONFIG_DEFAULTPROJECTIONMATRIXFILENAME);
172 Sandbox::RenderSettings::RenderSettings(const Sandbox::RenderSettings& source)
173 : fixProjectorView(source.fixProjectorView), projectorTransform(source.projectorTransform),
174 projectorTransformValid(source.projectorTransformValid),
175 hillshade(source.hillshade), surfaceMaterial(source.surfaceMaterial),
176 useShadows(source.useShadows),
177 elevationColorMap(source.elevationColorMap != 0 ? new ElevationColorMap(
178 *source.elevationColorMap) : 0),
179 useContourLines(source.useContourLines), contourLineSpacing(source.contourLineSpacing),
180 renderWaterSurface(source.renderWaterSurface), waterOpacity(source.waterOpacity),
181 surfaceRenderer(0), waterRenderer(0) {
184 Sandbox::RenderSettings::~RenderSettings(void) {
185 delete surfaceRenderer;
186 delete waterRenderer;
187 delete elevationColorMap;
190 void Sandbox::RenderSettings::loadProjectorTransform(const char* projectorTransformName) {
191 std::string fullProjectorTransformName;
192 try {
193 /* Open the projector transformation file: */
194 if(projectorTransformName[0] == '/') {
195 /* Use the absolute file name directly: */
196 fullProjectorTransformName = projectorTransformName;
197 } else {
198 /* Assemble a file name relative to the configuration file directory: */
199 fullProjectorTransformName = CONFIG_CONFIGDIR;
200 fullProjectorTransformName.push_back('/');
201 fullProjectorTransformName.append(projectorTransformName);
203 IO::FilePtr projectorTransformFile = Vrui::openFile(fullProjectorTransformName.c_str(),
204 IO::File::ReadOnly);
205 projectorTransformFile->setEndianness(Misc::LittleEndian);
207 /* Read the projector transformation matrix from the binary file: */
208 Misc::Float64 pt[16];
209 projectorTransformFile->read(pt, 16);
210 projectorTransform = PTransform::fromRowMajor(pt);
212 projectorTransformValid = true;
213 } catch(const std::runtime_error& err) {
214 /* Print an error message and disable calibrated projections: */
215 std::cerr << "Unable to load projector transformation from file " << fullProjectorTransformName <<
216 " due to exception " << err.what() << std::endl;
217 projectorTransformValid = false;
221 void Sandbox::RenderSettings::loadHeightMap(const char* heightMapName) {
222 try {
223 /* Load the elevation color map of the given name: */
224 ElevationColorMap* newElevationColorMap = new ElevationColorMap(heightMapName);
226 /* Delete the previous elevation color map and assign the new one: */
227 delete elevationColorMap;
228 elevationColorMap = newElevationColorMap;
229 } catch(const std::runtime_error& err) {
230 std::cerr << "Ignoring height map due to exception " << err.what() << std::endl;
234 /************************
235 Methods of class Sandbox:
236 ************************/
238 void Sandbox::rawDepthFrameDispatcher(const Kinect::FrameBuffer& frameBuffer) {
239 /* Pass the received frame to the frame filter and the hand extractor: */
240 if(frameFilter != 0 && !pauseUpdates)
241 frameFilter->receiveRawFrame(frameBuffer);
242 if(handExtractor != 0)
243 handExtractor->receiveRawFrame(frameBuffer);
246 void Sandbox::receiveFilteredFrame(const Kinect::FrameBuffer& frameBuffer) {
247 /* Put the new frame into the frame input buffer: */
248 filteredFrames.postNewValue(frameBuffer);
250 /* Wake up the foreground thread: */
251 Vrui::requestUpdate();
254 void Sandbox::toggleDEM(DEM* dem) {
255 /* Check if this is the active DEM: */
256 if(activeDem == dem) {
257 /* Deactivate the currently active DEM: */
258 activeDem = 0;
259 } else {
260 /* Activate this DEM: */
261 activeDem = dem;
264 /* Enable DEM matching in all surface renderers that use a fixed projector matrix, i.e., in all physical sandboxes: */
265 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
266 rsIt != renderSettings.end(); ++rsIt)
267 if(rsIt->fixProjectorView)
268 rsIt->surfaceRenderer->setDem(activeDem);
271 void Sandbox::addWater(GLContextData& contextData) const {
272 /* Check if the most recent rain object list is not empty: */
273 if(handExtractor != 0 && !handExtractor->getLockedExtractedHands().empty()) {
274 /* Render all rain objects into the water table: */
275 glPushAttrib(GL_ENABLE_BIT);
276 glDisable(GL_CULL_FACE);
278 /* Create a local coordinate frame to render rain disks: */
279 Vector z = waterTable->getBaseTransform().inverseTransform(Vector(0, 0, 1));
280 Vector x = Geometry::normal(z);
281 Vector y = Geometry::cross(z, x);
282 x.normalize();
283 y.normalize();
285 for(HandExtractor::HandList::const_iterator hIt = handExtractor->getLockedExtractedHands().begin();
286 hIt != handExtractor->getLockedExtractedHands().end(); ++hIt) {
287 glVertexAttrib1fARB(1, hIt->direction * (rainStrength / waterSpeed));
288 /* Render a rain disk approximating the hand: */
289 glBegin(GL_POLYGON);
290 for(int i = 0; i < 32; ++i) {
291 Scalar angle = Scalar(2) * Math::Constants<Scalar>::pi * Scalar(i) / Scalar(32);
292 glVertex(hIt->center + x * (Math::cos(angle)*hIt->radius * 0.75) + y * (Math::sin(
293 angle)*hIt->radius * 0.75));
295 glEnd();
298 glPopAttrib();
302 void Sandbox::addContourLabel(GLContextData& contextData) const {
304 ContourLabel* labIt = new ContourLabel();
305 labIt->center = Point(0.0f, 0.0f, -105.0f);
306 labIt->radius = 10.0;
307 labIt->elevation = 1234;
309 glPushAttrib(GL_ENABLE_BIT);
310 glDisable(GL_CULL_FACE);
312 Vector z = Vector(0, 0, 1);
313 Vector x = Geometry::normal(z);
314 Vector y = Geometry::cross(z, x);
315 x.normalize();
316 y.normalize();
318 glVertexAttrib1fARB(1, labIt->elevation);
320 glBegin(GL_POLYGON);
321 for(int i = 0; i < 32; ++i) {
322 Scalar angle = Scalar(2) * Math::Constants<Scalar>::pi * Scalar(i) / Scalar(32);
323 glVertex(labIt->center + x * (Math::cos(angle)*labIt->radius * 0.75) + y * (Math::sin(
324 angle)*labIt->radius * 0.75));
326 glEnd();
328 glPopAttrib();
331 void Sandbox::pauseUpdatesCallback(GLMotif::ToggleButton::ValueChangedCallbackData* cbData) {
332 pauseUpdates = cbData->set;
335 void Sandbox::showWaterControlDialogCallback(Misc::CallbackData* cbData) {
336 Vrui::popupPrimaryWidget(waterControlDialog);
339 void Sandbox::waterSpeedSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
340 cbData) {
341 waterSpeed = cbData->value;
344 void Sandbox::waterMaxStepsSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
345 cbData) {
346 waterMaxSteps = int(Math::floor(cbData->value + 0.5));
349 void Sandbox::waterAttenuationSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
350 cbData) {
351 waterTable->setAttenuation(GLfloat(1.0 - cbData->value));
354 GLMotif::PopupMenu* Sandbox::createMainMenu(void) {
355 /* Create a popup shell to hold the main menu: */
356 GLMotif::PopupMenu* mainMenuPopup = new GLMotif::PopupMenu("MainMenuPopup",
357 Vrui::getWidgetManager());
358 mainMenuPopup->setTitle("AR Sandbox");
360 /* Create the main menu itself: */
361 GLMotif::Menu* mainMenu = new GLMotif::Menu("MainMenu", mainMenuPopup, false);
363 /* Create a button to pause topography updates: */
364 pauseUpdatesToggle = new GLMotif::ToggleButton("PauseUpdatesToggle", mainMenu, "Pause Topography");
365 pauseUpdatesToggle->setToggle(false);
366 pauseUpdatesToggle->getValueChangedCallbacks().add(this, &Sandbox::pauseUpdatesCallback);
368 if(waterTable != 0) {
369 /* Create a button to show the water control dialog: */
370 GLMotif::Button* showWaterControlDialogButton = new GLMotif::Button("ShowWaterControlDialogButton",
371 mainMenu, "Show Water Simulation Control");
372 showWaterControlDialogButton->getSelectCallbacks().add(this,
373 &Sandbox::showWaterControlDialogCallback);
376 /* Finish building the main menu: */
377 mainMenu->manageChild();
379 return mainMenuPopup;
382 GLMotif::PopupWindow* Sandbox::createWaterControlDialog(void) {
383 const GLMotif::StyleSheet& ss = *Vrui::getWidgetManager()->getStyleSheet();
385 /* Create a popup window shell: */
386 GLMotif::PopupWindow* waterControlDialogPopup = new GLMotif::PopupWindow("WaterControlDialogPopup",
387 Vrui::getWidgetManager(), "Water Simulation Control");
388 waterControlDialogPopup->setCloseButton(true);
389 waterControlDialogPopup->setResizableFlags(true, false);
390 waterControlDialogPopup->popDownOnClose();
392 GLMotif::RowColumn* waterControlDialog = new GLMotif::RowColumn("WaterControlDialog",
393 waterControlDialogPopup, false);
394 waterControlDialog->setOrientation(GLMotif::RowColumn::VERTICAL);
395 waterControlDialog->setPacking(GLMotif::RowColumn::PACK_TIGHT);
396 waterControlDialog->setNumMinorWidgets(2);
398 new GLMotif::Label("WaterSpeedLabel", waterControlDialog, "Speed");
400 waterSpeedSlider = new GLMotif::TextFieldSlider("WaterSpeedSlider", waterControlDialog, 8,
401 ss.fontHeight * 10.0f);
402 waterSpeedSlider->getTextField()->setFieldWidth(7);
403 waterSpeedSlider->getTextField()->setPrecision(4);
404 waterSpeedSlider->getTextField()->setFloatFormat(GLMotif::TextField::SMART);
405 waterSpeedSlider->setSliderMapping(GLMotif::TextFieldSlider::EXP10);
406 waterSpeedSlider->setValueRange(0.001, 10.0, 0.05);
407 waterSpeedSlider->getSlider()->addNotch(0.0f);
408 waterSpeedSlider->setValue(waterSpeed);
409 waterSpeedSlider->getValueChangedCallbacks().add(this, &Sandbox::waterSpeedSliderCallback);
411 new GLMotif::Label("WaterMaxStepsLabel", waterControlDialog, "Max Steps");
413 waterMaxStepsSlider = new GLMotif::TextFieldSlider("WaterMaxStepsSlider", waterControlDialog, 8,
414 ss.fontHeight * 10.0f);
415 waterMaxStepsSlider->getTextField()->setFieldWidth(7);
416 waterMaxStepsSlider->getTextField()->setPrecision(0);
417 waterMaxStepsSlider->getTextField()->setFloatFormat(GLMotif::TextField::FIXED);
418 waterMaxStepsSlider->setSliderMapping(GLMotif::TextFieldSlider::LINEAR);
419 waterMaxStepsSlider->setValueType(GLMotif::TextFieldSlider::UINT);
420 waterMaxStepsSlider->setValueRange(0, 200, 1);
421 waterMaxStepsSlider->setValue(waterMaxSteps);
422 waterMaxStepsSlider->getValueChangedCallbacks().add(this, &Sandbox::waterMaxStepsSliderCallback);
424 new GLMotif::Label("FrameRateLabel", waterControlDialog, "Frame Rate");
426 GLMotif::Margin* frameRateMargin = new GLMotif::Margin("FrameRateMargin", waterControlDialog,
427 false);
428 frameRateMargin->setAlignment(GLMotif::Alignment::LEFT);
430 frameRateTextField = new GLMotif::TextField("FrameRateTextField", frameRateMargin, 8);
431 frameRateTextField->setFieldWidth(7);
432 frameRateTextField->setPrecision(2);
433 frameRateTextField->setFloatFormat(GLMotif::TextField::FIXED);
434 frameRateTextField->setValue(0.0);
436 frameRateMargin->manageChild();
438 new GLMotif::Label("WaterAttenuationLabel", waterControlDialog, "Attenuation");
440 waterAttenuationSlider = new GLMotif::TextFieldSlider("WaterAttenuationSlider", waterControlDialog,
441 8, ss.fontHeight * 10.0f);
442 waterAttenuationSlider->getTextField()->setFieldWidth(7);
443 waterAttenuationSlider->getTextField()->setPrecision(5);
444 waterAttenuationSlider->getTextField()->setFloatFormat(GLMotif::TextField::SMART);
445 waterAttenuationSlider->setSliderMapping(GLMotif::TextFieldSlider::EXP10);
446 waterAttenuationSlider->setValueRange(0.001, 1.0, 0.01);
447 waterAttenuationSlider->getSlider()->addNotch(Math::log10(1.0 - double(
448 waterTable->getAttenuation())));
449 waterAttenuationSlider->setValue(1.0 - double(waterTable->getAttenuation()));
450 waterAttenuationSlider->getValueChangedCallbacks().add(this,
451 &Sandbox::waterAttenuationSliderCallback);
453 waterControlDialog->manageChild();
455 return waterControlDialogPopup;
458 namespace {
460 /****************
461 Helper functions:
462 ****************/
464 void printUsage(void) {
465 std::cout << "Usage: SARndbox [option 1] ... [option n]" << std::endl;
466 std::cout << " Options:" << std::endl;
467 std::cout << " -h" << std::endl;
468 std::cout << " Prints this help message" << std::endl;
469 std::cout << " -c <camera index>" << std::endl;
470 std::cout << " Selects the local 3D camera of the given index (0: first camera" << std::endl;
471 std::cout << " on USB bus)" << std::endl;
472 std::cout << " Default: 0" << std::endl;
473 std::cout << " -f <frame file name prefix>" << std::endl;
474 std::cout << " Reads a pre-recorded 3D video stream from a pair of color/depth" << std::endl;
475 std::cout << " files of the given file name prefix" << std::endl;
476 std::cout << " -s <scale factor>" << std::endl;
477 std::cout << " Scale factor from real sandbox to simulated terrain" << std::endl;
478 std::cout << " Default: 100.0 (1:100 scale, 1cm in sandbox is 1m in terrain" << std::endl;
479 std::cout << " -slf <sandbox layout file name>" << std::endl;
480 std::cout << " Loads the sandbox layout file of the given name" << std::endl;
481 std::cout << " Default: " << CONFIG_CONFIGDIR << '/' << CONFIG_DEFAULTBOXLAYOUTFILENAME <<
482 std::endl;
483 std::cout << " -er <min elevation> <max elevation>" << std::endl;
484 std::cout << " Sets the range of valid sand surface elevations relative to the" << std::endl;
485 std::cout << " ground plane in cm" << std::endl;
486 std::cout << " Default: Range of elevation color map" << std::endl;
487 std::cout << " -hmp <x> <y> <z> <offset>" << std::endl;
488 std::cout << " Sets an explicit base plane equation to use for height color mapping" <<
489 std::endl;
490 std::cout << " -nas <num averaging slots>" << std::endl;
491 std::cout << " Sets the number of averaging slots in the frame filter; latency is" <<
492 std::endl;
493 std::cout << " <num averaging slots> * 1/30 s" << std::endl;
494 std::cout << " Default: 30" << std::endl;
495 std::cout << " -sp <min num samples> <max variance>" << std::endl;
496 std::cout << " Sets the frame filter parameters minimum number of valid samples" << std::endl;
497 std::cout << " and maximum sample variance before convergence" << std::endl;
498 std::cout << " Default: 10 2" << std::endl;
499 std::cout << " -he <hysteresis envelope>" << std::endl;
500 std::cout << " Sets the size of the hysteresis envelope used for jitter removal" << std::endl;
501 std::cout << " Default: 0.1" << std::endl;
502 std::cout << " -wts <water grid width> <water grid height>" << std::endl;
503 std::cout << " Sets the width and height of the water flow simulation grid" << std::endl;
504 std::cout << " Default: 640 480" << std::endl;
505 std::cout << " -ws <water speed> <water max steps>" << std::endl;
506 std::cout << " Sets the relative speed of the water simulation and the maximum" << std::endl;
507 std::cout << " number of simulation steps per frame" << std::endl;
508 std::cout << " Default: 1.0 30" << std::endl;
509 std::cout << " -rer <min rain elevation> <max rain elevation>" << std::endl;
510 std::cout << " Sets the elevation range of the rain cloud level relative to the" << std::endl;
511 std::cout << " ground plane in cm" << std::endl;
512 std::cout << " Default: Above range of elevation color map" << std::endl;
513 std::cout << " -rs <rain strength>" << std::endl;
514 std::cout << " Sets the strength of global or local rainfall in cm/s" << std::endl;
515 std::cout << " Default: 0.25" << std::endl;
516 std::cout << " -evr <evaporation rate>" << std::endl;
517 std::cout << " Water evaporation rate in cm/s" << std::endl;
518 std::cout << " Default: 0.0" << std::endl;
519 std::cout << " -dds <DEM distance scale>" << std::endl;
520 std::cout << " DEM matching distance scale factor in cm" << std::endl;
521 std::cout << " Default: 1.0" << std::endl;
522 std::cout << " -wi <window index>" << std::endl;
523 std::cout << " Sets the zero-based index of the display window to which the" << std::endl;
524 std::cout << " following rendering settings are applied" << std::endl;
525 std::cout << " Default: 0" << std::endl;
526 std::cout << " -fpv [projector transform file name]" << std::endl;
527 std::cout << " Fixes the navigation transformation so that Kinect camera and" << std::endl;
528 std::cout << " projector are aligned, as defined by the projector transform file" << std::endl;
529 std::cout << " of the given name" << std::endl;
530 std::cout << " Default projector transform file name: " << CONFIG_CONFIGDIR << '/' <<
531 CONFIG_DEFAULTPROJECTIONMATRIXFILENAME << std::endl;
532 std::cout << " -nhs" << std::endl;
533 std::cout << " Disables hill shading" << std::endl;
534 std::cout << " -uhs" << std::endl;
535 std::cout << " Enables hill shading" << std::endl;
536 std::cout << " -ns" << std::endl;
537 std::cout << " Disables shadows" << std::endl;
538 std::cout << " -us" << std::endl;
539 std::cout << " Enables shadows" << std::endl;
540 std::cout << " -nhm" << std::endl;
541 std::cout << " Disables elevation color mapping" << std::endl;
542 std::cout << " -uhm [elevation color map file name]" << std::endl;
543 std::cout << " Enables elevation color mapping and loads the elevation color map from" <<
544 std::endl;
545 std::cout << " the file of the given name" << std::endl;
546 std::cout << " Default elevation color map file name: " << CONFIG_CONFIGDIR << '/' <<
547 CONFIG_DEFAULTHEIGHTCOLORMAPFILENAME << std::endl;
548 std::cout << " -ncl" << std::endl;
549 std::cout << " Disables topographic contour lines" << std::endl;
550 std::cout << " -ucl [contour line spacing]" << std::endl;
551 std::cout << " Enables topographic contour lines and sets the elevation distance between" <<
552 std::endl;
553 std::cout << " adjacent contour lines to the given value in cm" << std::endl;
554 std::cout << " Default contour line spacing: 0.75" << std::endl;
555 std::cout << " -rws" << std::endl;
556 std::cout << " Renders water surface as geometric surface" << std::endl;
557 std::cout << " -rwt" << std::endl;
558 std::cout << " Renders water surface as texture" << std::endl;
559 std::cout << " -wo <water opacity>" << std::endl;
560 std::cout << " Sets the water depth at which water appears opaque in cm" << std::endl;
561 std::cout << " Default: 2.0" << std::endl;
562 std::cout << " -cp <control pipe name>" << std::endl;
563 std::cout << " Sets the name of a named POSIX pipe from which to read control commands" <<
564 std::endl;
569 Sandbox::Sandbox(int& argc, char**& argv)
570 : Vrui::Application(argc, argv),
571 camera(0), pixelDepthCorrection(0),
572 frameFilter(0), pauseUpdates(false),
573 depthImageRenderer(0),
574 waterTable(0),
575 handExtractor(0), addWaterFunction(0), addWaterFunctionRegistered(false),
576 sun(0),
577 activeDem(0),
578 mainMenu(0), pauseUpdatesToggle(0), waterControlDialog(0),
579 waterSpeedSlider(0), waterMaxStepsSlider(0), frameRateTextField(0), waterAttenuationSlider(0),
580 controlPipeFd(-1) {
581 /* Read the sandbox's default configuration parameters: */
582 std::string sandboxConfigFileName = CONFIG_CONFIGDIR;
583 sandboxConfigFileName.push_back('/');
584 sandboxConfigFileName.append(CONFIG_DEFAULTCONFIGFILENAME);
585 Misc::ConfigurationFile sandboxConfigFile(sandboxConfigFileName.c_str());
586 Misc::ConfigurationFileSection cfg = sandboxConfigFile.getSection("/SARndbox");
587 unsigned int cameraIndex = cfg.retrieveValue<int>("./cameraIndex", 0);
588 std::string cameraConfiguration = cfg.retrieveString("./cameraConfiguration", "Camera");
589 double scale = cfg.retrieveValue<double>("./scaleFactor", 100.0);
590 std::string sandboxLayoutFileName = CONFIG_CONFIGDIR;
591 sandboxLayoutFileName.push_back('/');
592 sandboxLayoutFileName.append(CONFIG_DEFAULTBOXLAYOUTFILENAME);
593 sandboxLayoutFileName = cfg.retrieveString("./sandboxLayoutFileName", sandboxLayoutFileName);
594 Math::Interval<double> elevationRange =
595 cfg.retrieveValue<Math::Interval<double> >("./elevationRange", Math::Interval<double>(-1000.0,
596 1000.0));
597 bool haveHeightMapPlane = cfg.hasTag("./heightMapPlane");
598 Plane heightMapPlane;
599 if(haveHeightMapPlane)
600 heightMapPlane = cfg.retrieveValue<Plane>("./heightMapPlane");
601 unsigned int numAveragingSlots = cfg.retrieveValue<unsigned int>("./numAveragingSlots", 30);
602 unsigned int minNumSamples = cfg.retrieveValue<unsigned int>("./minNumSamples", 10);
603 unsigned int maxVariance = cfg.retrieveValue<unsigned int>("./maxVariance", 2);
604 float hysteresis = cfg.retrieveValue<float>("./hysteresis", 0.1f);
605 Misc::FixedArray<unsigned int, 2> wtSize;
606 wtSize[0] = 640;
607 wtSize[1] = 480;
608 wtSize = cfg.retrieveValue<Misc::FixedArray<unsigned int, 2> >("./waterTableSize", wtSize);
609 waterSpeed = cfg.retrieveValue<double>("./waterSpeed", 1.0);
610 waterMaxSteps = cfg.retrieveValue<unsigned int>("./waterMaxSteps", 30U);
611 Math::Interval<double> rainElevationRange =
612 cfg.retrieveValue<Math::Interval<double> >("./rainElevationRange", Math::Interval<double>(-1000.0,
613 1000.0));
614 rainStrength = cfg.retrieveValue<GLfloat>("./rainStrength", 0.25f);
615 double evaporationRate = cfg.retrieveValue<double>("./evaporationRate", 0.0);
616 float demDistScale = cfg.retrieveValue<float>("./demDistScale", 1.0f);
617 std::string controlPipeName = cfg.retrieveString("./controlPipeName", "");
619 /* Process command line parameters: */
620 bool printHelp = false;
621 const char* frameFilePrefix = 0;
622 const char* kinectServerName = 0;
623 int windowIndex = 0;
624 renderSettings.push_back(RenderSettings());
625 for(int i = 1; i < argc; ++i) {
626 if(argv[i][0] == '-') {
627 if(strcasecmp(argv[i] + 1, "h") == 0)
628 printHelp = true;
629 else if(strcasecmp(argv[i] + 1, "c") == 0) {
630 ++i;
631 cameraIndex = atoi(argv[i]);
632 } else if(strcasecmp(argv[i] + 1, "f") == 0) {
633 ++i;
634 frameFilePrefix = argv[i];
635 } else if(strcasecmp(argv[i] + 1, "p") == 0) {
636 ++i;
637 kinectServerName = argv[i];
638 } else if(strcasecmp(argv[i] + 1, "s") == 0) {
639 ++i;
640 scale = atof(argv[i]);
641 } else if(strcasecmp(argv[i] + 1, "slf") == 0) {
642 ++i;
643 sandboxLayoutFileName = argv[i];
644 } else if(strcasecmp(argv[i] + 1, "er") == 0) {
645 ++i;
646 double elevationMin = atof(argv[i]);
647 ++i;
648 double elevationMax = atof(argv[i]);
649 elevationRange = Math::Interval<double>(elevationMin, elevationMax);
650 } else if(strcasecmp(argv[i] + 1, "hmp") == 0) {
651 /* Read height mapping plane coefficients: */
652 haveHeightMapPlane = true;
653 double hmp[4];
654 for(int j = 0; j < 4; ++j) {
655 ++i;
656 hmp[j] = atof(argv[i]);
658 heightMapPlane = Plane(Plane::Vector(hmp), hmp[3]);
659 heightMapPlane.normalize();
660 } else if(strcasecmp(argv[i] + 1, "nas") == 0) {
661 ++i;
662 numAveragingSlots = atoi(argv[i]);
663 } else if(strcasecmp(argv[i] + 1, "sp") == 0) {
664 ++i;
665 minNumSamples = atoi(argv[i]);
666 ++i;
667 maxVariance = atoi(argv[i]);
668 } else if(strcasecmp(argv[i] + 1, "he") == 0) {
669 ++i;
670 hysteresis = float(atof(argv[i]));
671 } else if(strcasecmp(argv[i] + 1, "wts") == 0) {
672 for(int j = 0; j < 2; ++j) {
673 ++i;
674 wtSize[j] = (unsigned int)(atoi(argv[i]));
676 } else if(strcasecmp(argv[i] + 1, "ws") == 0) {
677 ++i;
678 waterSpeed = atof(argv[i]);
679 ++i;
680 waterMaxSteps = atoi(argv[i]);
681 } else if(strcasecmp(argv[i] + 1, "rer") == 0) {
682 ++i;
683 double rainElevationMin = atof(argv[i]);
684 ++i;
685 double rainElevationMax = atof(argv[i]);
686 rainElevationRange = Math::Interval<double>(rainElevationMin, rainElevationMax);
687 } else if(strcasecmp(argv[i] + 1, "rs") == 0) {
688 ++i;
689 rainStrength = GLfloat(atof(argv[i]));
690 } else if(strcasecmp(argv[i] + 1, "evr") == 0) {
691 ++i;
692 evaporationRate = atof(argv[i]);
693 } else if(strcasecmp(argv[i] + 1, "dds") == 0) {
694 ++i;
695 demDistScale = float(atof(argv[i]));
696 } else if(strcasecmp(argv[i] + 1, "wi") == 0) {
697 ++i;
698 windowIndex = atoi(argv[i]);
700 /* Extend the list of render settings if an index beyond the end is selected: */
701 while(int(renderSettings.size()) <= windowIndex)
702 renderSettings.push_back(renderSettings.back());
704 /* Disable fixed projector view on the new render settings: */
705 renderSettings.back().fixProjectorView = false;
706 } else if(strcasecmp(argv[i] + 1, "fpv") == 0) {
707 renderSettings.back().fixProjectorView = true;
708 if(i + 1 < argc && argv[i + 1][0] != '-') {
709 /* Load the projector transformation file specified in the next argument: */
710 ++i;
711 renderSettings.back().loadProjectorTransform(argv[i]);
713 } else if(strcasecmp(argv[i] + 1, "nhs") == 0)
714 renderSettings.back().hillshade = false;
715 else if(strcasecmp(argv[i] + 1, "uhs") == 0)
716 renderSettings.back().hillshade = true;
717 else if(strcasecmp(argv[i] + 1, "ns") == 0)
718 renderSettings.back().useShadows = false;
719 else if(strcasecmp(argv[i] + 1, "us") == 0)
720 renderSettings.back().useShadows = true;
721 else if(strcasecmp(argv[i] + 1, "nhm") == 0) {
722 delete renderSettings.back().elevationColorMap;
723 renderSettings.back().elevationColorMap = 0;
724 } else if(strcasecmp(argv[i] + 1, "uhm") == 0) {
725 if(i + 1 < argc && argv[i + 1][0] != '-') {
726 /* Load the height color map file specified in the next argument: */
727 ++i;
728 renderSettings.back().loadHeightMap(argv[i]);
729 } else {
730 /* Load the default height color map: */
731 renderSettings.back().loadHeightMap(CONFIG_DEFAULTHEIGHTCOLORMAPFILENAME);
733 } else if(strcasecmp(argv[i] + 1, "ncl") == 0)
734 renderSettings.back().useContourLines = false;
735 else if(strcasecmp(argv[i] + 1, "ucl") == 0) {
736 renderSettings.back().useContourLines = true;
737 if(i + 1 < argc && argv[i + 1][0] != '-') {
738 /* Read the contour line spacing: */
739 ++i;
740 renderSettings.back().contourLineSpacing = GLfloat(atof(argv[i]));
742 } else if(strcasecmp(argv[i] + 1, "rws") == 0)
743 renderSettings.back().renderWaterSurface = true;
744 else if(strcasecmp(argv[i] + 1, "rwt") == 0)
745 renderSettings.back().renderWaterSurface = false;
746 else if(strcasecmp(argv[i] + 1, "wo") == 0) {
747 ++i;
748 renderSettings.back().waterOpacity = GLfloat(atof(argv[i]));
749 } else if(strcasecmp(argv[i] + 1, "cp") == 0) {
750 ++i;
751 controlPipeName = argv[i];
752 } else
753 std::cerr << "Ignoring unrecognized command line switch " << argv[i] << std::endl;
757 /* Print usage help if requested: */
758 if(printHelp)
759 printUsage();
761 if(frameFilePrefix != 0) {
762 /* Open the selected pre-recorded 3D video files: */
763 std::string colorFileName = frameFilePrefix;
764 colorFileName.append(".color");
765 std::string depthFileName = frameFilePrefix;
766 depthFileName.append(".depth");
767 camera = new Kinect::FileFrameSource(Vrui::openFile(colorFileName.c_str()),
768 Vrui::openFile(depthFileName.c_str()));
769 } else if(kinectServerName != 0) {
770 /* Split the server name into host name and port: */
771 const char* colonPtr = 0;
772 for(const char* snPtr = kinectServerName; *snPtr != '\0'; ++snPtr)
773 if(*snPtr == ':')
774 colonPtr = snPtr;
775 std::string hostName;
776 int port;
777 if(colonPtr != 0) {
778 /* Extract host name and port: */
779 hostName = std::string(kinectServerName, colonPtr);
780 port = atoi(colonPtr + 1);
781 } else {
782 /* Use complete host name and default port: */
783 hostName = kinectServerName;
784 port = 26000;
787 /* Open a multiplexed frame source for the given server host name and port number: */
788 Kinect::MultiplexedFrameSource* source = Kinect::MultiplexedFrameSource::create(
789 Cluster::openTCPPipe(Vrui::getClusterMultiplexer(), hostName.c_str(), port));
791 /* Use the server's first component stream as the camera device: */
792 camera = source->getStream(0);
793 } else {
794 /* Open the 3D camera device of the selected index: */
795 Kinect::DirectFrameSource* realCamera = Kinect::openDirectFrameSource(cameraIndex);
796 Misc::ConfigurationFileSection cameraConfigurationSection = cfg.getSection(
797 cameraConfiguration.c_str());
798 realCamera->configure(cameraConfigurationSection);
799 camera = realCamera;
801 for(int i = 0; i < 2; ++i)
802 frameSize[i] = camera->getActualFrameSize(Kinect::FrameSource::DEPTH)[i];
804 /* Get the camera's per-pixel depth correction parameters and evaluate it on the depth frame's pixel grid: */
805 Kinect::FrameSource::DepthCorrection* depthCorrection = camera->getDepthCorrectionParameters();
806 if(depthCorrection != 0) {
807 pixelDepthCorrection = depthCorrection->getPixelCorrection(frameSize);
808 delete depthCorrection;
809 } else {
810 /* Create dummy per-pixel depth correction parameters: */
811 pixelDepthCorrection = new PixelDepthCorrection[frameSize[1]*frameSize[0]];
812 PixelDepthCorrection* pdcPtr = pixelDepthCorrection;
813 for(unsigned int y = 0; y < frameSize[1]; ++y)
814 for(unsigned int x = 0; x < frameSize[0]; ++x, ++pdcPtr) {
815 pdcPtr->scale = 1.0f;
816 pdcPtr->offset = 0.0f;
820 /* Get the camera's intrinsic parameters: */
821 cameraIps = camera->getIntrinsicParameters();
823 /* Read the sandbox layout file: */
824 Geometry::Plane<double, 3> basePlane;
825 Geometry::Point<double, 3> basePlaneCorners[4];
827 IO::ValueSource layoutSource(Vrui::openFile(sandboxLayoutFileName.c_str()));
828 layoutSource.skipWs();
830 /* Read the base plane equation: */
831 std::string s = layoutSource.readLine();
832 basePlane = Misc::ValueCoder<Geometry::Plane<double, 3> >::decode(s.c_str(),
833 s.c_str() + s.length());
834 basePlane.normalize();
836 /* Read the corners of the base quadrilateral and project them into the base plane: */
837 for(int i = 0; i < 4; ++i) {
838 layoutSource.skipWs();
839 s = layoutSource.readLine();
840 basePlaneCorners[i] = basePlane.project(Misc::ValueCoder<Geometry::Point<double, 3> >::decode(
841 s.c_str(), s.c_str() + s.length()));
845 /* Limit the valid elevation range to the intersection of the extents of all height color maps: */
846 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
847 rsIt != renderSettings.end(); ++rsIt)
848 if(rsIt->elevationColorMap != 0) {
849 Math::Interval<double> mapRange(rsIt->elevationColorMap->getScalarRangeMin(),
850 rsIt->elevationColorMap->getScalarRangeMax());
851 elevationRange.intersectInterval(mapRange);
854 /* Scale all sizes by the given scale factor: */
855 double sf = scale / 100.0; // Scale factor from cm to final units
856 for(int i = 0; i < 3; ++i)
857 for(int j = 0; j < 4; ++j)
858 cameraIps.depthProjection.getMatrix()(i, j) *= sf;
859 basePlane = Geometry::Plane<double, 3>(basePlane.getNormal(), basePlane.getOffset() * sf);
860 for(int i = 0; i < 4; ++i)
861 for(int j = 0; j < 3; ++j)
862 basePlaneCorners[i][j] *= sf;
863 if(elevationRange != Math::Interval<double>::full)
864 elevationRange *= sf;
865 if(rainElevationRange != Math::Interval<double>::full)
866 rainElevationRange *= sf;
867 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
868 rsIt != renderSettings.end(); ++rsIt) {
869 if(rsIt->elevationColorMap != 0)
870 rsIt->elevationColorMap->setScalarRange(rsIt->elevationColorMap->getScalarRangeMin()*sf,
871 rsIt->elevationColorMap->getScalarRangeMax()*sf);
872 rsIt->contourLineSpacing *= sf;
873 rsIt->waterOpacity /= sf;
874 for(int i = 0; i < 4; ++i)
875 rsIt->projectorTransform.getMatrix()(i, 3) *= sf;
877 rainStrength *= sf;
878 evaporationRate *= sf;
879 demDistScale *= sf;
881 /* Create the frame filter object: */
882 frameFilter = new FrameFilter(frameSize, numAveragingSlots, pixelDepthCorrection,
883 cameraIps.depthProjection, basePlane);
884 frameFilter->setValidElevationInterval(cameraIps.depthProjection, basePlane,
885 elevationRange.getMin(), elevationRange.getMax());
886 frameFilter->setStableParameters(minNumSamples, maxVariance);
887 frameFilter->setHysteresis(hysteresis);
888 frameFilter->setSpatialFilter(true);
889 frameFilter->setOutputFrameFunction(Misc::createFunctionCall(this,
890 &Sandbox::receiveFilteredFrame));
892 if(waterSpeed > 0.0) {
893 /* Create the hand extractor object: */
894 handExtractor = new HandExtractor(frameSize, pixelDepthCorrection, cameraIps.depthProjection);
897 /* Start streaming depth frames: */
898 camera->startStreaming(0, Misc::createFunctionCall(this, &Sandbox::rawDepthFrameDispatcher));
900 /* Create the depth image renderer: */
901 depthImageRenderer = new DepthImageRenderer(frameSize);
902 depthImageRenderer->setIntrinsics(cameraIps);
903 depthImageRenderer->setBasePlane(basePlane);
906 /* Calculate the transformation from camera space to sandbox space: */
907 ONTransform::Vector z = basePlane.getNormal();
908 ONTransform::Vector x = (basePlaneCorners[1] - basePlaneCorners[0]) +
909 (basePlaneCorners[3] - basePlaneCorners[2]);
910 ONTransform::Vector y = z ^ x;
911 boxTransform = ONTransform::rotate(Geometry::invert(ONTransform::Rotation::fromBaseVectors(x, y)));
912 ONTransform::Point center = Geometry::mid(Geometry::mid(basePlaneCorners[0], basePlaneCorners[1]),
913 Geometry::mid(basePlaneCorners[2], basePlaneCorners[3]));
914 boxTransform *= ONTransform::translateToOriginFrom(center);
916 /* Calculate the size of the sandbox area: */
917 boxSize = Geometry::dist(center, basePlaneCorners[0]);
918 for(int i = 1; i < 4; ++i)
919 boxSize = Math::max(boxSize, Geometry::dist(center, basePlaneCorners[i]));
922 /* Calculate a bounding box around all potential surfaces: */
923 bbox = Box::empty;
924 for(int i = 0; i < 4; ++i) {
925 bbox.addPoint(basePlaneCorners[i] + basePlane.getNormal()*elevationRange.getMin());
926 bbox.addPoint(basePlaneCorners[i] + basePlane.getNormal()*elevationRange.getMax());
929 if(waterSpeed > 0.0) {
930 /* Initialize the water flow simulator: */
931 waterTable = new WaterTable2(wtSize[0], wtSize[1], depthImageRenderer, basePlaneCorners);
932 waterTable->setElevationRange(elevationRange.getMin(), rainElevationRange.getMax());
933 waterTable->setWaterDeposit(evaporationRate);
935 /* Register a render function with the water table: */
936 addWaterFunction = Misc::createFunctionCall(this, &Sandbox::addWater);
937 waterTable->addRenderFunction(addWaterFunction);
938 addWaterFunctionRegistered = true;
941 /* Initialize all surface renderers: */
942 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
943 rsIt != renderSettings.end(); ++rsIt) {
944 /* Calculate the texture mapping plane for this renderer's height map: */
945 if(rsIt->elevationColorMap != 0) {
946 if(haveHeightMapPlane)
947 rsIt->elevationColorMap->calcTexturePlane(heightMapPlane);
948 else
949 rsIt->elevationColorMap->calcTexturePlane(depthImageRenderer);
952 /* Initialize the surface renderer: */
953 rsIt->surfaceRenderer = new SurfaceRenderer(depthImageRenderer);
954 rsIt->surfaceRenderer->setDrawContourLines(rsIt->useContourLines);
955 rsIt->surfaceRenderer->setContourLineDistance(rsIt->contourLineSpacing);
956 rsIt->surfaceRenderer->setElevationColorMap(rsIt->elevationColorMap);
957 rsIt->surfaceRenderer->setIlluminate(rsIt->hillshade);
958 if(waterTable != 0) {
959 if(rsIt->renderWaterSurface) {
960 /* Create a water renderer: */
961 rsIt->waterRenderer = new WaterRenderer(waterTable);
962 } else {
963 rsIt->surfaceRenderer->setWaterTable(waterTable);
964 rsIt->surfaceRenderer->setAdvectWaterTexture(true);
965 rsIt->surfaceRenderer->setWaterOpacity(rsIt->waterOpacity);
968 rsIt->surfaceRenderer->setDemDistScale(demDistScale);
971 #if 0
972 /* Create a fixed-position light source: */
973 sun = Vrui::getLightsourceManager()->createLightsource(true);
974 for(int i = 0; i < Vrui::getNumViewers(); ++i)
975 Vrui::getViewer(i)->setHeadlightState(false);
976 sun->enable();
977 sun->getLight().position = GLLight::Position(1, 0, 1, 0);
978 #endif
980 /* Create the GUI: */
981 mainMenu = createMainMenu();
982 Vrui::setMainMenu(mainMenu);
983 if(waterTable != 0)
984 waterControlDialog = createWaterControlDialog();
986 /* Initialize the custom tool classes: */
987 GlobalWaterTool::initClass(*Vrui::getToolManager());
988 LocalWaterTool::initClass(*Vrui::getToolManager());
989 DEMTool::initClass(*Vrui::getToolManager());
990 if(waterTable != 0)
991 BathymetrySaverTool::initClass(waterTable, *Vrui::getToolManager());
992 addEventTool("Pause Topography", 0, 0);
994 if(!controlPipeName.empty()) {
995 /* Open the control pipe in non-blocking mode: */
996 controlPipeFd = open(controlPipeName.c_str(), O_RDONLY | O_NONBLOCK);
997 if(controlPipeFd < 0)
998 std::cerr << "Unable to open control pipe " << controlPipeName << "; ignoring" << std::endl;
1001 /* Inhibit the screen saver: */
1002 Vrui::inhibitScreenSaver();
1004 /* Set the linear unit to support proper scaling: */
1005 Vrui::getCoordinateManager()->setUnit(Geometry::LinearUnit(Geometry::LinearUnit::METER,
1006 scale / 100.0));
1009 Sandbox::~Sandbox(void) {
1010 /* Stop streaming depth frames: */
1011 camera->stopStreaming();
1012 delete camera;
1013 delete frameFilter;
1015 /* Delete helper objects: */
1016 delete waterTable;
1017 delete depthImageRenderer;
1018 delete handExtractor;
1019 delete addWaterFunction;
1020 delete[] pixelDepthCorrection;
1022 delete mainMenu;
1023 delete waterControlDialog;
1025 close(controlPipeFd);
1028 void Sandbox::toolDestructionCallback(Vrui::ToolManager::ToolDestructionCallbackData* cbData) {
1029 /* Check if the destroyed tool is the active DEM tool: */
1030 if(activeDem == dynamic_cast<DEM*>(cbData->tool)) {
1031 /* Deactivate the active DEM tool: */
1032 activeDem = 0;
1036 namespace {
1038 /****************
1039 Helper functions:
1040 ****************/
1042 std::vector<std::string> tokenizeLine(const char*& buffer) {
1043 std::vector<std::string> result;
1045 /* Skip initial whitespace but not end-of-line: */
1046 const char* bPtr = buffer;
1047 while(*bPtr != '\0' && *bPtr != '\n' && isspace(*bPtr))
1048 ++bPtr;
1050 /* Extract white-space separated tokens until a newline or end-of-string are encountered: */
1051 while(*bPtr != '\0' && *bPtr != '\n') {
1052 /* Remember the start of the current token: */
1053 const char* tokenStart = bPtr;
1055 /* Find the end of the current token: */
1056 while(*bPtr != '\0' && !isspace(*bPtr))
1057 ++bPtr;
1059 /* Extract the token: */
1060 result.push_back(std::string(tokenStart, bPtr));
1062 /* Skip whitespace but not end-of-line: */
1063 while(*bPtr != '\0' && *bPtr != '\n' && isspace(*bPtr))
1064 ++bPtr;
1067 /* Skip end-of-line: */
1068 if(*bPtr == '\n')
1069 ++bPtr;
1071 /* Set the start of the next line and return the token list: */
1072 buffer = bPtr;
1073 return result;
1076 bool isToken(const std::string& token, const char* pattern) {
1077 return strcasecmp(token.c_str(), pattern) == 0;
1082 void Sandbox::frame(void) {
1083 /* Check if the filtered frame has been updated: */
1084 if(filteredFrames.lockNewValue()) {
1085 /* Update the depth image renderer's depth image: */
1086 depthImageRenderer->setDepthImage(filteredFrames.getLockedValue());
1089 if(handExtractor != 0) {
1090 /* Lock the most recent extracted hand list: */
1091 handExtractor->lockNewExtractedHands();
1093 #if 0
1095 /* Register/unregister the rain rendering function based on whether hands have been detected: */
1096 bool registerWaterFunction = !handExtractor->getLockedExtractedHands().empty();
1097 if(addWaterFunctionRegistered != registerWaterFunction) {
1098 if(registerWaterFunction)
1099 waterTable->addRenderFunction(addWaterFunction);
1100 else
1101 waterTable->removeRenderFunction(addWaterFunction);
1102 addWaterFunctionRegistered = registerWaterFunction;
1105 #endif
1108 /* Update all surface renderers: */
1109 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1110 rsIt != renderSettings.end(); ++rsIt)
1111 rsIt->surfaceRenderer->setAnimationTime(Vrui::getApplicationTime());
1113 /* Check if there is a control command on the control pipe: */
1114 if(controlPipeFd >= 0) {
1115 /* Try reading a chunk of data (will fail with EAGAIN if no data due to non-blocking access): */
1116 char commandBuffer[1024];
1117 ssize_t readResult = read(controlPipeFd, commandBuffer, sizeof(commandBuffer) - 1);
1118 if(readResult > 0) {
1119 commandBuffer[readResult] = '\0';
1121 /* Extract commands line-by-line: */
1122 const char* cPtr = commandBuffer;
1123 while(*cPtr != '\0') {
1124 /* Split the current line into tokens and skip empty lines: */
1125 std::vector<std::string> tokens = tokenizeLine(cPtr);
1126 if(tokens.empty())
1127 continue;
1129 /* Parse the command: */
1130 if(isToken(tokens[0], "waterSpeed")) {
1131 if(tokens.size() == 2) {
1132 waterSpeed = atof(tokens[1].c_str());
1133 if(waterSpeedSlider != 0)
1134 waterSpeedSlider->setValue(waterSpeed);
1135 } else
1136 std::cerr << "Wrong number of arguments for waterSpeed control pipe command" << std::endl;
1137 } else if(isToken(tokens[0], "waterMaxSteps")) {
1138 if(tokens.size() == 2) {
1139 waterMaxSteps = atoi(tokens[1].c_str());
1140 if(waterMaxStepsSlider != 0)
1141 waterMaxStepsSlider->setValue(waterMaxSteps);
1142 } else
1143 std::cerr << "Wrong number of arguments for waterMaxSteps control pipe command" << std::endl;
1144 } else if(isToken(tokens[0], "waterAttenuation")) {
1145 if(tokens.size() == 2) {
1146 double attenuation = atof(tokens[1].c_str());
1147 if(waterTable != 0)
1148 waterTable->setAttenuation(GLfloat(1.0 - attenuation));
1149 if(waterAttenuationSlider != 0)
1150 waterAttenuationSlider->setValue(attenuation);
1151 } else
1152 std::cerr << "Wrong number of arguments for waterAttenuation control pipe command" << std::endl;
1153 } else if(isToken(tokens[0], "colorMap")) {
1154 if(tokens.size() == 2) {
1155 try {
1156 /* Update all height color maps: */
1157 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1158 rsIt != renderSettings.end(); ++rsIt)
1159 if(rsIt->elevationColorMap != 0)
1160 rsIt->elevationColorMap->load(tokens[1].c_str());
1161 } catch(const std::runtime_error& err) {
1162 std::cerr << "Cannot read height color map " << tokens[1] << " due to exception " << err.what() <<
1163 std::endl;
1165 } else
1166 std::cerr << "Wrong number of arguments for colorMap control pipe command" << std::endl;
1167 } else if(isToken(tokens[0], "heightMapPlane")) {
1168 if(tokens.size() == 5) {
1169 /* Read the height map plane equation: */
1170 double hmp[4];
1171 for(int i = 0; i < 4; ++i)
1172 hmp[i] = atof(tokens[1 + i].c_str());
1173 Plane heightMapPlane = Plane(Plane::Vector(hmp), hmp[3]);
1174 heightMapPlane.normalize();
1176 /* Override the height mapping planes of all elevation color maps: */
1177 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1178 rsIt != renderSettings.end(); ++rsIt)
1179 if(rsIt->elevationColorMap != 0)
1180 rsIt->elevationColorMap->calcTexturePlane(heightMapPlane);
1181 } else
1182 std::cerr << "Wrong number of arguments for heightMapPlane control pipe command" << std::endl;
1183 } else if(isToken(tokens[0], "dippingBed")) {
1184 if(tokens.size() == 2 && isToken(tokens[1], "off")) {
1185 /* Disable dipping bed rendering on all surface renderers: */
1186 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1187 rsIt != renderSettings.end(); ++rsIt)
1188 rsIt->surfaceRenderer->setDrawDippingBed(false);
1189 } else if(tokens.size() == 5) {
1190 /* Read the dipping bed plane equation: */
1191 GLfloat dbp[4];
1192 for(int i = 0; i < 4; ++i)
1193 dbp[i] = GLfloat(atof(tokens[1 + i].c_str()));
1194 SurfaceRenderer::Plane dippingBedPlane = SurfaceRenderer::Plane(SurfaceRenderer::Plane::Vector(
1195 dbp), dbp[3]);
1196 dippingBedPlane.normalize();
1198 /* Enable dipping bed rendering and set the dipping bed plane equation on all surface renderers: */
1199 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1200 rsIt != renderSettings.end(); ++rsIt) {
1201 rsIt->surfaceRenderer->setDrawDippingBed(true);
1202 rsIt->surfaceRenderer->setDippingBedPlane(dippingBedPlane);
1204 } else
1205 std::cerr << "Wrong number of arguments for dippingBed control pipe command" << std::endl;
1206 } else if(isToken(tokens[0], "foldedDippingBed")) {
1207 if(tokens.size() == 6) {
1208 /* Read the dipping bed coefficients: */
1209 GLfloat dbc[5];
1210 for(int i = 0; i < 5; ++i)
1211 dbc[i] = GLfloat(atof(tokens[1 + i].c_str()));
1213 /* Enable dipping bed rendering and set the dipping bed coefficients on all surface renderers: */
1214 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1215 rsIt != renderSettings.end(); ++rsIt) {
1216 rsIt->surfaceRenderer->setDrawDippingBed(true);
1217 rsIt->surfaceRenderer->setDippingBedCoeffs(dbc);
1219 } else
1220 std::cerr << "Wrong number of arguments for foldedDippingBed control pipe command" << std::endl;
1221 } else if(isToken(tokens[0], "dippingBedThickness")) {
1222 if(tokens.size() == 2) {
1223 /* Read the dipping bed thickness: */
1224 float dippingBedThickness = float(atof(tokens[1].c_str()));
1226 /* Set the dipping bed thickness on all surface renderers: */
1227 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1228 rsIt != renderSettings.end(); ++rsIt)
1229 rsIt->surfaceRenderer->setDippingBedThickness(dippingBedThickness);
1230 } else
1231 std::cerr << "Wrong number of arguments for dippingBedThickness control pipe command" << std::endl;
1232 } else
1233 std::cerr << "Unrecognized control pipe command " << tokens[0] << std::endl;
1238 if(frameRateTextField != 0 && Vrui::getWidgetManager()->isVisible(waterControlDialog)) {
1239 /* Update the frame rate display: */
1240 frameRateTextField->setValue(1.0 / Vrui::getCurrentFrameTime());
1243 if(pauseUpdates)
1244 Vrui::scheduleUpdate(Vrui::getApplicationTime() + 1.0 / 30.0);
1247 void Sandbox::display(GLContextData& contextData) const {
1248 /* Get the data item: */
1249 DataItem* dataItem = contextData.retrieveDataItem<DataItem>(this);
1251 /* Get the rendering settings for this window: */
1252 const Vrui::DisplayState& ds = Vrui::getDisplayState(contextData);
1253 const Vrui::VRWindow* window = ds.window;
1254 int windowIndex;
1255 for(windowIndex = 0; windowIndex < Vrui::getNumWindows()
1256 && window != Vrui::getWindow(windowIndex); ++windowIndex)
1258 const RenderSettings& rs = windowIndex < int(renderSettings.size()) ? renderSettings[windowIndex] :
1259 renderSettings.back();
1261 /* Check if the water simulation state needs to be updated: */
1262 if(waterTable != 0 && dataItem->waterTableTime != Vrui::getApplicationTime()) {
1263 /* Update the water table's bathymetry grid: */
1264 waterTable->updateBathymetry(contextData);
1266 /* Run the water flow simulation's main pass: */
1267 GLfloat totalTimeStep = GLfloat(Vrui::getFrameTime() * waterSpeed);
1268 unsigned int numSteps = 0;
1269 while(numSteps < waterMaxSteps - 1U && totalTimeStep > 1.0e-8f) {
1270 /* Run with a self-determined time step to maintain stability: */
1271 waterTable->setMaxStepSize(totalTimeStep);
1272 GLfloat timeStep = waterTable->runSimulationStep(false, contextData);
1273 totalTimeStep -= timeStep;
1274 ++numSteps;
1276 #if 0
1277 if(totalTimeStep > 1.0e-8f) {
1278 std::cout << '.' << std::flush;
1279 /* Force the final step to avoid simulation slow-down: */
1280 waterTable->setMaxStepSize(totalTimeStep);
1281 GLfloat timeStep = waterTable->runSimulationStep(true, contextData);
1282 totalTimeStep -= timeStep;
1283 ++numSteps;
1285 #else
1286 if(totalTimeStep > 1.0e-8f)
1287 std::cout << "Ran out of time by " << totalTimeStep << std::endl;
1288 #endif
1290 /* Mark the water simulation state as up-to-date for this frame: */
1291 dataItem->waterTableTime = Vrui::getApplicationTime();
1294 /* Calculate the projection matrix: */
1295 PTransform projection = ds.projection;
1296 if(rs.fixProjectorView && rs.projectorTransformValid) {
1297 /* Use the projector transformation instead: */
1298 projection = rs.projectorTransform;
1300 /* Multiply with the inverse modelview transformation so that lighting still works as usual: */
1301 projection *= Geometry::invert(ds.modelviewNavigational);
1304 addContourLabel(contextData);
1306 if(rs.hillshade) {
1307 /* Set the surface material: */
1308 glMaterial(GLMaterialEnums::FRONT, rs.surfaceMaterial);
1311 #if 0
1312 if(rs.hillshade && rs.useShadows) {
1313 /* Set up OpenGL state: */
1314 glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT | GL_POLYGON_BIT);
1316 GLLightTracker& lt = *contextData.getLightTracker();
1318 /* Save the currently-bound frame buffer and viewport: */
1319 GLint currentFrameBuffer;
1320 glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &currentFrameBuffer);
1321 GLint currentViewport[4];
1322 glGetIntegerv(GL_VIEWPORT, currentViewport);
1324 /*******************************************************************
1325 First rendering pass: Global ambient illumination only
1326 *******************************************************************/
1328 /* Draw the surface mesh: */
1329 surfaceRenderer->glRenderGlobalAmbientHeightMap(dataItem->heightColorMapObject, contextData);
1331 /*******************************************************************
1332 Second rendering pass: Add local illumination for every light source
1333 *******************************************************************/
1335 /* Enable additive rendering: */
1336 glEnable(GL_BLEND);
1337 glBlendFunc(GL_ONE, GL_ONE);
1338 glDepthFunc(GL_LEQUAL);
1339 glDepthMask(GL_FALSE);
1341 for(int lightSourceIndex = 0; lightSourceIndex < lt.getMaxNumLights(); ++lightSourceIndex)
1342 if(lt.getLightState(lightSourceIndex).isEnabled()) {
1343 /***************************************************************
1344 First step: Render to the light source's shadow map
1345 ***************************************************************/
1347 /* Set up OpenGL state to render to the shadow map: */
1348 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dataItem->shadowFramebufferObject);
1349 glViewport(0, 0, dataItem->shadowBufferSize[0], dataItem->shadowBufferSize[1]);
1350 glDepthMask(GL_TRUE);
1351 glClear(GL_DEPTH_BUFFER_BIT);
1352 glCullFace(GL_FRONT);
1354 /*************************************************************
1355 Calculate the shadow projection matrix:
1356 *************************************************************/
1358 /* Get the light source position in eye space: */
1359 Geometry::HVector<float, 3> lightPosEc;
1360 glGetLightfv(GL_LIGHT0 + lightSourceIndex, GL_POSITION, lightPosEc.getComponents());
1362 /* Transform the light source position to camera space: */
1363 Vrui::ONTransform::HVector lightPosCc = Vrui::getDisplayState(
1364 contextData).modelviewNavigational.inverseTransform(Vrui::ONTransform::HVector(lightPosEc));
1366 /* Calculate the direction vector from the center of the bounding box to the light source: */
1367 Point bboxCenter = Geometry::mid(bbox.min, bbox.max);
1368 Vrui::Vector lightDirCc = Vrui::Vector(lightPosCc.getComponents()) - Vrui::Vector(
1369 bboxCenter.getComponents()) * lightPosCc[3];
1371 /* Build a transformation that aligns the light direction with the positive z axis: */
1372 Vrui::ONTransform shadowModelview = Vrui::ONTransform::rotate(Vrui::Rotation::rotateFromTo(
1373 lightDirCc, Vrui::Vector(0, 0, 1)));
1374 shadowModelview *= Vrui::ONTransform::translateToOriginFrom(bboxCenter);
1376 /* Create a projection matrix, based on whether the light is positional or directional: */
1377 PTransform shadowProjection(0.0);
1378 if(lightPosEc[3] != 0.0f) {
1379 /* Modify the modelview transformation such that the light source is at the origin: */
1380 shadowModelview.leftMultiply(Vrui::ONTransform::translate(Vrui::Vector(0, 0, -lightDirCc.mag())));
1382 /***********************************************************
1383 Create a perspective projection:
1384 ***********************************************************/
1386 /* Calculate the perspective bounding box of the surface bounding box in eye space: */
1387 Box pBox = Box::empty;
1388 for(int i = 0; i < 8; ++i) {
1389 Point bc = shadowModelview.transform(bbox.getVertex(i));
1390 pBox.addPoint(Point(-bc[0] / bc[2], -bc[1] / bc[2], -bc[2]));
1393 /* Upload the frustum matrix: */
1394 double l = pBox.min[0] * pBox.min[2];
1395 double r = pBox.max[0] * pBox.min[2];
1396 double b = pBox.min[1] * pBox.min[2];
1397 double t = pBox.max[1] * pBox.min[2];
1398 double n = pBox.min[2];
1399 double f = pBox.max[2];
1400 shadowProjection.getMatrix()(0, 0) = 2.0 * n / (r - l);
1401 shadowProjection.getMatrix()(0, 2) = (r + l) / (r - l);
1402 shadowProjection.getMatrix()(1, 1) = 2.0 * n / (t - b);
1403 shadowProjection.getMatrix()(1, 2) = (t + b) / (t - b);
1404 shadowProjection.getMatrix()(2, 2) = -(f + n) / (f - n);
1405 shadowProjection.getMatrix()(2, 3) = -2.0 * f * n / (f - n);
1406 shadowProjection.getMatrix()(3, 2) = -1.0;
1407 } else {
1408 /***********************************************************
1409 Create a perspective projection:
1410 ***********************************************************/
1412 /* Transform the bounding box with the modelview transformation: */
1413 Box bboxEc = bbox;
1414 bboxEc.transform(shadowModelview);
1416 /* Upload the ortho matrix: */
1417 double l = bboxEc.min[0];
1418 double r = bboxEc.max[0];
1419 double b = bboxEc.min[1];
1420 double t = bboxEc.max[1];
1421 double n = -bboxEc.max[2];
1422 double f = -bboxEc.min[2];
1423 shadowProjection.getMatrix()(0, 0) = 2.0 / (r - l);
1424 shadowProjection.getMatrix()(0, 3) = -(r + l) / (r - l);
1425 shadowProjection.getMatrix()(1, 1) = 2.0 / (t - b);
1426 shadowProjection.getMatrix()(1, 3) = -(t + b) / (t - b);
1427 shadowProjection.getMatrix()(2, 2) = -2.0 / (f - n);
1428 shadowProjection.getMatrix()(2, 3) = -(f + n) / (f - n);
1429 shadowProjection.getMatrix()(3, 3) = 1.0;
1432 /* Multiply the shadow modelview matrix onto the shadow projection matrix: */
1433 shadowProjection *= shadowModelview;
1435 /* Draw the surface into the shadow buffer: */
1436 surfaceRenderer->glRenderDepthOnly(shadowProjection, contextData);
1438 /* Reset OpenGL state: */
1439 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFrameBuffer);
1440 glViewport(currentViewport[0], currentViewport[1], currentViewport[2], currentViewport[3]);
1441 glCullFace(GL_BACK);
1442 glDepthMask(GL_FALSE);
1444 #if SAVEDEPTH
1445 /* Save the depth image: */
1447 glBindTexture(GL_TEXTURE_2D, dataItem->shadowDepthTextureObject);
1448 GLfloat* depthTextureImage = new
1449 GLfloat[dataItem->shadowBufferSize[1]*dataItem->shadowBufferSize[0]];
1450 glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depthTextureImage);
1451 glBindTexture(GL_TEXTURE_2D, 0);
1452 Images::RGBImage dti(dataItem->shadowBufferSize[0], dataItem->shadowBufferSize[1]);
1453 GLfloat* dtiPtr = depthTextureImage;
1454 Images::RGBImage::Color* ciPtr = dti.modifyPixels();
1455 for(int y = 0; y < dataItem->shadowBufferSize[1]; ++y)
1456 for(int x = 0; x < dataItem->shadowBufferSize[0]; ++x, ++dtiPtr, ++ciPtr) {
1457 GLColor<GLfloat, 3> tc(*dtiPtr, *dtiPtr, *dtiPtr);
1458 *ciPtr = tc;
1460 delete[] depthTextureImage;
1461 Images::writeImageFile(dti, "DepthImage.png");
1463 #endif
1465 /* Draw the surface using the shadow texture: */
1466 rs.surfaceRenderer->glRenderShadowedIlluminatedHeightMap(dataItem->heightColorMapObject,
1467 dataItem->shadowDepthTextureObject, shadowProjection, contextData);
1470 /* Reset OpenGL state: */
1471 glPopAttrib();
1472 } else
1473 #endif
1475 /* Render the surface in a single pass: */
1476 rs.surfaceRenderer->renderSinglePass(ds.viewport, projection, ds.modelviewNavigational,
1477 contextData);
1480 if(rs.waterRenderer != 0) {
1481 /* Draw the water surface: */
1482 glMaterialAmbientAndDiffuse(GLMaterialEnums::FRONT, GLColor<GLfloat, 4>(0.0f, 0.5f, 0.8f));
1483 glMaterialSpecular(GLMaterialEnums::FRONT, GLColor<GLfloat, 4>(1.0f, 1.0f, 1.0f));
1484 glMaterialShininess(GLMaterialEnums::FRONT, 64.0f);
1485 rs.waterRenderer->render(projection, ds.modelviewNavigational, contextData);
1489 void Sandbox::resetNavigation(void) {
1490 /* Construct a navigation transformation to center the sandbox area in the display, facing the viewer, with the long sandbox axis facing to the right: */
1491 Vrui::NavTransform nav = Vrui::NavTransform::translateFromOriginTo(Vrui::getDisplayCenter());
1492 nav *= Vrui::NavTransform::scale(Vrui::getDisplaySize() / boxSize);
1493 Vrui::Vector y = Vrui::getUpDirection();
1494 Vrui::Vector z = Vrui::getForwardDirection();
1495 Vrui::Vector x = z ^ y;
1496 nav *= Vrui::NavTransform::rotate(Vrui::Rotation::fromBaseVectors(x, y));
1497 nav *= boxTransform;
1498 Vrui::setNavigationTransformation(nav);
1501 void Sandbox::eventCallback(Vrui::Application::EventID eventId,
1502 Vrui::InputDevice::ButtonCallbackData* cbData) {
1503 if(cbData->newButtonState) {
1504 switch(eventId) {
1505 case 0:
1506 /* Invert the current pause setting: */
1507 pauseUpdates = !pauseUpdates;
1509 /* Update the main menu toggle: */
1510 pauseUpdatesToggle->setToggle(pauseUpdates);
1512 break;
1517 void Sandbox::initContext(GLContextData& contextData) const {
1518 /* Create a data item and add it to the context: */
1519 DataItem* dataItem = new DataItem;
1520 contextData.addDataItem(this, dataItem);
1523 /* Save the currently bound frame buffer: */
1524 GLint currentFrameBuffer;
1525 glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &currentFrameBuffer);
1527 /* Set the default shadow buffer size: */
1528 dataItem->shadowBufferSize[0] = 1024;
1529 dataItem->shadowBufferSize[1] = 1024;
1531 /* Generate the shadow rendering frame buffer: */
1532 glGenFramebuffersEXT(1, &dataItem->shadowFramebufferObject);
1533 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dataItem->shadowFramebufferObject);
1535 /* Generate a depth texture for shadow rendering: */
1536 glGenTextures(1, &dataItem->shadowDepthTextureObject);
1537 glBindTexture(GL_TEXTURE_2D, dataItem->shadowDepthTextureObject);
1538 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1539 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1540 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1541 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1542 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
1543 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
1544 glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
1545 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24_ARB, dataItem->shadowBufferSize[0],
1546 dataItem->shadowBufferSize[1], 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
1547 glBindTexture(GL_TEXTURE_2D, 0);
1549 /* Attach the depth texture to the frame buffer object: */
1550 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D,
1551 dataItem->shadowDepthTextureObject, 0);
1552 glDrawBuffer(GL_NONE);
1553 glReadBuffer(GL_NONE);
1554 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFrameBuffer);
1558 VRUI_APPLICATION_RUN(Sandbox)