Add ContourLineExtractor to Sandbox
[SARndbox.git] / Sandbox.cpp
blobd805c10f3443c06c59728f6cbb9575a49fef240f
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 "ContourLineExtractor.h"
109 #include "HandExtractor.h"
110 #include "WaterRenderer.h"
111 #include "GlobalWaterTool.h"
112 #include "LocalWaterTool.h"
113 #include "DEMTool.h"
114 #include "BathymetrySaverTool.h"
116 #include "Config.h"
118 /**********************************
119 Methods of class Sandbox::DataItem:
120 **********************************/
122 Sandbox::DataItem::DataItem(void)
123 : waterTableTime(0.0),
124 shadowFramebufferObject(0), shadowDepthTextureObject(0) {
125 /* Check if all required extensions are supported: */
126 bool supported = GLEXTFramebufferObject::isSupported();
127 supported = supported && GLARBTextureRectangle::isSupported();
128 supported = supported && GLARBTextureFloat::isSupported();
129 supported = supported && GLARBTextureRg::isSupported();
130 supported = supported && GLARBDepthTexture::isSupported();
131 supported = supported && GLARBShaderObjects::isSupported();
132 supported = supported && GLARBVertexShader::isSupported();
133 supported = supported && GLARBFragmentShader::isSupported();
134 supported = supported && GLARBMultitexture::isSupported();
135 if(!supported)
136 Misc::throwStdErr("Sandbox: Not all required extensions are supported by local OpenGL");
138 /* Initialize all required extensions: */
139 GLEXTFramebufferObject::initExtension();
140 GLARBTextureRectangle::initExtension();
141 GLARBTextureFloat::initExtension();
142 GLARBTextureRg::initExtension();
143 GLARBDepthTexture::initExtension();
144 GLARBShaderObjects::initExtension();
145 GLARBVertexShader::initExtension();
146 GLARBFragmentShader::initExtension();
147 GLARBMultitexture::initExtension();
150 Sandbox::DataItem::~DataItem(void) {
151 /* Delete all shaders, buffers, and texture objects: */
152 glDeleteFramebuffersEXT(1, &shadowFramebufferObject);
153 glDeleteTextures(1, &shadowDepthTextureObject);
156 /****************************************
157 Methods of class Sandbox::RenderSettings:
158 ****************************************/
160 Sandbox::RenderSettings::RenderSettings(void)
161 : fixProjectorView(false), projectorTransform(PTransform::identity),
162 projectorTransformValid(false),
163 hillshade(false), surfaceMaterial(GLMaterial::Color(1.0f, 1.0f, 1.0f)),
164 useShadows(false),
165 elevationColorMap(0),
166 useContourLines(true), contourLineSpacing(0.75f),
167 renderWaterSurface(false), waterOpacity(2.0f),
168 surfaceRenderer(0), waterRenderer(0) {
169 /* Load the default projector transformation: */
170 loadProjectorTransform(CONFIG_DEFAULTPROJECTIONMATRIXFILENAME);
173 Sandbox::RenderSettings::RenderSettings(const Sandbox::RenderSettings& source)
174 : fixProjectorView(source.fixProjectorView), projectorTransform(source.projectorTransform),
175 projectorTransformValid(source.projectorTransformValid),
176 hillshade(source.hillshade), surfaceMaterial(source.surfaceMaterial),
177 useShadows(source.useShadows),
178 elevationColorMap(source.elevationColorMap != 0 ? new ElevationColorMap(
179 *source.elevationColorMap) : 0),
180 useContourLines(source.useContourLines), contourLineSpacing(source.contourLineSpacing),
181 renderWaterSurface(source.renderWaterSurface), waterOpacity(source.waterOpacity),
182 surfaceRenderer(0), waterRenderer(0) {
185 Sandbox::RenderSettings::~RenderSettings(void) {
186 delete surfaceRenderer;
187 delete waterRenderer;
188 delete elevationColorMap;
191 void Sandbox::RenderSettings::loadProjectorTransform(const char* projectorTransformName) {
192 std::string fullProjectorTransformName;
193 try {
194 /* Open the projector transformation file: */
195 if(projectorTransformName[0] == '/') {
196 /* Use the absolute file name directly: */
197 fullProjectorTransformName = projectorTransformName;
198 } else {
199 /* Assemble a file name relative to the configuration file directory: */
200 fullProjectorTransformName = CONFIG_CONFIGDIR;
201 fullProjectorTransformName.push_back('/');
202 fullProjectorTransformName.append(projectorTransformName);
204 IO::FilePtr projectorTransformFile = Vrui::openFile(fullProjectorTransformName.c_str(),
205 IO::File::ReadOnly);
206 projectorTransformFile->setEndianness(Misc::LittleEndian);
208 /* Read the projector transformation matrix from the binary file: */
209 Misc::Float64 pt[16];
210 projectorTransformFile->read(pt, 16);
211 projectorTransform = PTransform::fromRowMajor(pt);
213 projectorTransformValid = true;
214 } catch(const std::runtime_error& err) {
215 /* Print an error message and disable calibrated projections: */
216 std::cerr << "Unable to load projector transformation from file " << fullProjectorTransformName <<
217 " due to exception " << err.what() << std::endl;
218 projectorTransformValid = false;
222 void Sandbox::RenderSettings::loadHeightMap(const char* heightMapName) {
223 try {
224 /* Load the elevation color map of the given name: */
225 ElevationColorMap* newElevationColorMap = new ElevationColorMap(heightMapName);
227 /* Delete the previous elevation color map and assign the new one: */
228 delete elevationColorMap;
229 elevationColorMap = newElevationColorMap;
230 } catch(const std::runtime_error& err) {
231 std::cerr << "Ignoring height map due to exception " << err.what() << std::endl;
235 /************************
236 Methods of class Sandbox:
237 ************************/
239 void Sandbox::rawDepthFrameDispatcher(const Kinect::FrameBuffer& frameBuffer) {
240 /* Pass the received frame to the frame filter and the hand extractor: */
241 if(frameFilter != 0 && !pauseUpdates)
242 frameFilter->receiveRawFrame(frameBuffer);
243 if(contourLineExtractor != 0)
244 contourLineExtractor->receiveRawFrame(frameBuffer);
245 if(handExtractor != 0)
246 handExtractor->receiveRawFrame(frameBuffer);
249 void Sandbox::receiveFilteredFrame(const Kinect::FrameBuffer& frameBuffer) {
250 /* Put the new frame into the frame input buffer: */
251 filteredFrames.postNewValue(frameBuffer);
253 /* Wake up the foreground thread: */
254 Vrui::requestUpdate();
257 void Sandbox::toggleDEM(DEM* dem) {
258 /* Check if this is the active DEM: */
259 if(activeDem == dem) {
260 /* Deactivate the currently active DEM: */
261 activeDem = 0;
262 } else {
263 /* Activate this DEM: */
264 activeDem = dem;
267 /* Enable DEM matching in all surface renderers that use a fixed projector matrix, i.e., in all physical sandboxes: */
268 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
269 rsIt != renderSettings.end(); ++rsIt)
270 if(rsIt->fixProjectorView)
271 rsIt->surfaceRenderer->setDem(activeDem);
274 void Sandbox::addWater(GLContextData& contextData) const {
275 /* Check if the most recent rain object list is not empty: */
276 if(handExtractor != 0 && !handExtractor->getLockedExtractedHands().empty()) {
277 /* Render all rain objects into the water table: */
278 glPushAttrib(GL_ENABLE_BIT);
279 glDisable(GL_CULL_FACE);
281 /* Create a local coordinate frame to render rain disks: */
282 Vector z = waterTable->getBaseTransform().inverseTransform(Vector(0, 0, 1));
283 Vector x = Geometry::normal(z);
284 Vector y = Geometry::cross(z, x);
285 x.normalize();
286 y.normalize();
288 for(HandExtractor::HandList::const_iterator hIt = handExtractor->getLockedExtractedHands().begin();
289 hIt != handExtractor->getLockedExtractedHands().end(); ++hIt) {
290 glVertexAttrib1fARB(1, hIt->direction * (rainStrength / waterSpeed));
291 /* Render a rain disk approximating the hand: */
292 glBegin(GL_POLYGON);
293 for(int i = 0; i < 32; ++i) {
294 Scalar angle = Scalar(2) * Math::Constants<Scalar>::pi * Scalar(i) / Scalar(32);
295 glVertex(hIt->center + x * (Math::cos(angle)*hIt->radius * 0.75) + y * (Math::sin(
296 angle)*hIt->radius * 0.75));
298 glEnd();
301 glPopAttrib();
305 void Sandbox::addContourLabel(GLContextData& contextData) const {
307 // if(contourLineExtractor != 0 && !contourLineExtractor->getLockedExtractedContourLines().empty()) {
308 ContourLineExtractor::ContourLabel* labIt = new ContourLineExtractor::ContourLabel();
309 labIt->center = Point(0.0f, 0.0f, -105.0f);
310 labIt->radius = 10.0;
311 labIt->elevation = 1234;
313 glPushAttrib(GL_ENABLE_BIT);
314 glDisable(GL_CULL_FACE);
316 Vector z = Vector(0, 0, 1);
317 Vector x = Geometry::normal(z);
318 Vector y = Geometry::cross(z, x);
319 x.normalize();
320 y.normalize();
322 glVertexAttrib1fARB(1, labIt->elevation);
324 glBegin(GL_POLYGON);
325 for(int i = 0; i < 32; ++i) {
326 Scalar angle = Scalar(2) * Math::Constants<Scalar>::pi * Scalar(i) / Scalar(32);
327 glVertex(labIt->center +
328 x * (Math::cos(angle) * labIt->radius * 3.0 * 0.125) +
329 y * (Math::sin(angle) * labIt->radius * 0.125));
331 glEnd();
333 glPopAttrib();
334 // }
337 void Sandbox::pauseUpdatesCallback(GLMotif::ToggleButton::ValueChangedCallbackData* cbData) {
338 pauseUpdates = cbData->set;
341 void Sandbox::showWaterControlDialogCallback(Misc::CallbackData* cbData) {
342 Vrui::popupPrimaryWidget(waterControlDialog);
345 void Sandbox::waterSpeedSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
346 cbData) {
347 waterSpeed = cbData->value;
350 void Sandbox::waterMaxStepsSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
351 cbData) {
352 waterMaxSteps = int(Math::floor(cbData->value + 0.5));
355 void Sandbox::waterAttenuationSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
356 cbData) {
357 waterTable->setAttenuation(GLfloat(1.0 - cbData->value));
360 GLMotif::PopupMenu* Sandbox::createMainMenu(void) {
361 /* Create a popup shell to hold the main menu: */
362 GLMotif::PopupMenu* mainMenuPopup = new GLMotif::PopupMenu("MainMenuPopup",
363 Vrui::getWidgetManager());
364 mainMenuPopup->setTitle("AR Sandbox");
366 /* Create the main menu itself: */
367 GLMotif::Menu* mainMenu = new GLMotif::Menu("MainMenu", mainMenuPopup, false);
369 /* Create a button to pause topography updates: */
370 pauseUpdatesToggle = new GLMotif::ToggleButton("PauseUpdatesToggle", mainMenu, "Pause Topography");
371 pauseUpdatesToggle->setToggle(false);
372 pauseUpdatesToggle->getValueChangedCallbacks().add(this, &Sandbox::pauseUpdatesCallback);
374 if(waterTable != 0) {
375 /* Create a button to show the water control dialog: */
376 GLMotif::Button* showWaterControlDialogButton = new GLMotif::Button("ShowWaterControlDialogButton",
377 mainMenu, "Show Water Simulation Control");
378 showWaterControlDialogButton->getSelectCallbacks().add(this,
379 &Sandbox::showWaterControlDialogCallback);
382 /* Finish building the main menu: */
383 mainMenu->manageChild();
385 return mainMenuPopup;
388 GLMotif::PopupWindow* Sandbox::createWaterControlDialog(void) {
389 const GLMotif::StyleSheet& ss = *Vrui::getWidgetManager()->getStyleSheet();
391 /* Create a popup window shell: */
392 GLMotif::PopupWindow* waterControlDialogPopup = new GLMotif::PopupWindow("WaterControlDialogPopup",
393 Vrui::getWidgetManager(), "Water Simulation Control");
394 waterControlDialogPopup->setCloseButton(true);
395 waterControlDialogPopup->setResizableFlags(true, false);
396 waterControlDialogPopup->popDownOnClose();
398 GLMotif::RowColumn* waterControlDialog = new GLMotif::RowColumn("WaterControlDialog",
399 waterControlDialogPopup, false);
400 waterControlDialog->setOrientation(GLMotif::RowColumn::VERTICAL);
401 waterControlDialog->setPacking(GLMotif::RowColumn::PACK_TIGHT);
402 waterControlDialog->setNumMinorWidgets(2);
404 new GLMotif::Label("WaterSpeedLabel", waterControlDialog, "Speed");
406 waterSpeedSlider = new GLMotif::TextFieldSlider("WaterSpeedSlider", waterControlDialog, 8,
407 ss.fontHeight * 10.0f);
408 waterSpeedSlider->getTextField()->setFieldWidth(7);
409 waterSpeedSlider->getTextField()->setPrecision(4);
410 waterSpeedSlider->getTextField()->setFloatFormat(GLMotif::TextField::SMART);
411 waterSpeedSlider->setSliderMapping(GLMotif::TextFieldSlider::EXP10);
412 waterSpeedSlider->setValueRange(0.001, 10.0, 0.05);
413 waterSpeedSlider->getSlider()->addNotch(0.0f);
414 waterSpeedSlider->setValue(waterSpeed);
415 waterSpeedSlider->getValueChangedCallbacks().add(this, &Sandbox::waterSpeedSliderCallback);
417 new GLMotif::Label("WaterMaxStepsLabel", waterControlDialog, "Max Steps");
419 waterMaxStepsSlider = new GLMotif::TextFieldSlider("WaterMaxStepsSlider", waterControlDialog, 8,
420 ss.fontHeight * 10.0f);
421 waterMaxStepsSlider->getTextField()->setFieldWidth(7);
422 waterMaxStepsSlider->getTextField()->setPrecision(0);
423 waterMaxStepsSlider->getTextField()->setFloatFormat(GLMotif::TextField::FIXED);
424 waterMaxStepsSlider->setSliderMapping(GLMotif::TextFieldSlider::LINEAR);
425 waterMaxStepsSlider->setValueType(GLMotif::TextFieldSlider::UINT);
426 waterMaxStepsSlider->setValueRange(0, 200, 1);
427 waterMaxStepsSlider->setValue(waterMaxSteps);
428 waterMaxStepsSlider->getValueChangedCallbacks().add(this, &Sandbox::waterMaxStepsSliderCallback);
430 new GLMotif::Label("FrameRateLabel", waterControlDialog, "Frame Rate");
432 GLMotif::Margin* frameRateMargin = new GLMotif::Margin("FrameRateMargin", waterControlDialog,
433 false);
434 frameRateMargin->setAlignment(GLMotif::Alignment::LEFT);
436 frameRateTextField = new GLMotif::TextField("FrameRateTextField", frameRateMargin, 8);
437 frameRateTextField->setFieldWidth(7);
438 frameRateTextField->setPrecision(2);
439 frameRateTextField->setFloatFormat(GLMotif::TextField::FIXED);
440 frameRateTextField->setValue(0.0);
442 frameRateMargin->manageChild();
444 new GLMotif::Label("WaterAttenuationLabel", waterControlDialog, "Attenuation");
446 waterAttenuationSlider = new GLMotif::TextFieldSlider("WaterAttenuationSlider", waterControlDialog,
447 8, ss.fontHeight * 10.0f);
448 waterAttenuationSlider->getTextField()->setFieldWidth(7);
449 waterAttenuationSlider->getTextField()->setPrecision(5);
450 waterAttenuationSlider->getTextField()->setFloatFormat(GLMotif::TextField::SMART);
451 waterAttenuationSlider->setSliderMapping(GLMotif::TextFieldSlider::EXP10);
452 waterAttenuationSlider->setValueRange(0.001, 1.0, 0.01);
453 waterAttenuationSlider->getSlider()->addNotch(Math::log10(1.0 - double(
454 waterTable->getAttenuation())));
455 waterAttenuationSlider->setValue(1.0 - double(waterTable->getAttenuation()));
456 waterAttenuationSlider->getValueChangedCallbacks().add(this,
457 &Sandbox::waterAttenuationSliderCallback);
459 waterControlDialog->manageChild();
461 return waterControlDialogPopup;
464 namespace {
466 /****************
467 Helper functions:
468 ****************/
470 void printUsage(void) {
471 std::cout << "Usage: SARndbox [option 1] ... [option n]" << std::endl;
472 std::cout << " Options:" << std::endl;
473 std::cout << " -h" << std::endl;
474 std::cout << " Prints this help message" << std::endl;
475 std::cout << " -c <camera index>" << std::endl;
476 std::cout << " Selects the local 3D camera of the given index (0: first camera" << std::endl;
477 std::cout << " on USB bus)" << std::endl;
478 std::cout << " Default: 0" << std::endl;
479 std::cout << " -f <frame file name prefix>" << std::endl;
480 std::cout << " Reads a pre-recorded 3D video stream from a pair of color/depth" << std::endl;
481 std::cout << " files of the given file name prefix" << std::endl;
482 std::cout << " -s <scale factor>" << std::endl;
483 std::cout << " Scale factor from real sandbox to simulated terrain" << std::endl;
484 std::cout << " Default: 100.0 (1:100 scale, 1cm in sandbox is 1m in terrain" << std::endl;
485 std::cout << " -slf <sandbox layout file name>" << std::endl;
486 std::cout << " Loads the sandbox layout file of the given name" << std::endl;
487 std::cout << " Default: " << CONFIG_CONFIGDIR << '/' << CONFIG_DEFAULTBOXLAYOUTFILENAME <<
488 std::endl;
489 std::cout << " -er <min elevation> <max elevation>" << std::endl;
490 std::cout << " Sets the range of valid sand surface elevations relative to the" << std::endl;
491 std::cout << " ground plane in cm" << std::endl;
492 std::cout << " Default: Range of elevation color map" << std::endl;
493 std::cout << " -hmp <x> <y> <z> <offset>" << std::endl;
494 std::cout << " Sets an explicit base plane equation to use for height color mapping" <<
495 std::endl;
496 std::cout << " -nas <num averaging slots>" << std::endl;
497 std::cout << " Sets the number of averaging slots in the frame filter; latency is" <<
498 std::endl;
499 std::cout << " <num averaging slots> * 1/30 s" << std::endl;
500 std::cout << " Default: 30" << std::endl;
501 std::cout << " -sp <min num samples> <max variance>" << std::endl;
502 std::cout << " Sets the frame filter parameters minimum number of valid samples" << std::endl;
503 std::cout << " and maximum sample variance before convergence" << std::endl;
504 std::cout << " Default: 10 2" << std::endl;
505 std::cout << " -he <hysteresis envelope>" << std::endl;
506 std::cout << " Sets the size of the hysteresis envelope used for jitter removal" << std::endl;
507 std::cout << " Default: 0.1" << std::endl;
508 std::cout << " -wts <water grid width> <water grid height>" << std::endl;
509 std::cout << " Sets the width and height of the water flow simulation grid" << std::endl;
510 std::cout << " Default: 640 480" << std::endl;
511 std::cout << " -ws <water speed> <water max steps>" << std::endl;
512 std::cout << " Sets the relative speed of the water simulation and the maximum" << std::endl;
513 std::cout << " number of simulation steps per frame" << std::endl;
514 std::cout << " Default: 1.0 30" << std::endl;
515 std::cout << " -rer <min rain elevation> <max rain elevation>" << std::endl;
516 std::cout << " Sets the elevation range of the rain cloud level relative to the" << std::endl;
517 std::cout << " ground plane in cm" << std::endl;
518 std::cout << " Default: Above range of elevation color map" << std::endl;
519 std::cout << " -rs <rain strength>" << std::endl;
520 std::cout << " Sets the strength of global or local rainfall in cm/s" << std::endl;
521 std::cout << " Default: 0.25" << std::endl;
522 std::cout << " -evr <evaporation rate>" << std::endl;
523 std::cout << " Water evaporation rate in cm/s" << std::endl;
524 std::cout << " Default: 0.0" << std::endl;
525 std::cout << " -dds <DEM distance scale>" << std::endl;
526 std::cout << " DEM matching distance scale factor in cm" << std::endl;
527 std::cout << " Default: 1.0" << std::endl;
528 std::cout << " -wi <window index>" << std::endl;
529 std::cout << " Sets the zero-based index of the display window to which the" << std::endl;
530 std::cout << " following rendering settings are applied" << std::endl;
531 std::cout << " Default: 0" << std::endl;
532 std::cout << " -fpv [projector transform file name]" << std::endl;
533 std::cout << " Fixes the navigation transformation so that Kinect camera and" << std::endl;
534 std::cout << " projector are aligned, as defined by the projector transform file" << std::endl;
535 std::cout << " of the given name" << std::endl;
536 std::cout << " Default projector transform file name: " << CONFIG_CONFIGDIR << '/' <<
537 CONFIG_DEFAULTPROJECTIONMATRIXFILENAME << std::endl;
538 std::cout << " -nhs" << std::endl;
539 std::cout << " Disables hill shading" << std::endl;
540 std::cout << " -uhs" << std::endl;
541 std::cout << " Enables hill shading" << std::endl;
542 std::cout << " -ns" << std::endl;
543 std::cout << " Disables shadows" << std::endl;
544 std::cout << " -us" << std::endl;
545 std::cout << " Enables shadows" << std::endl;
546 std::cout << " -nhm" << std::endl;
547 std::cout << " Disables elevation color mapping" << std::endl;
548 std::cout << " -uhm [elevation color map file name]" << std::endl;
549 std::cout << " Enables elevation color mapping and loads the elevation color map from" <<
550 std::endl;
551 std::cout << " the file of the given name" << std::endl;
552 std::cout << " Default elevation color map file name: " << CONFIG_CONFIGDIR << '/' <<
553 CONFIG_DEFAULTHEIGHTCOLORMAPFILENAME << std::endl;
554 std::cout << " -ncl" << std::endl;
555 std::cout << " Disables topographic contour lines" << std::endl;
556 std::cout << " -ucl [contour line spacing]" << std::endl;
557 std::cout << " Enables topographic contour lines and sets the elevation distance between" <<
558 std::endl;
559 std::cout << " adjacent contour lines to the given value in cm" << std::endl;
560 std::cout << " Default contour line spacing: 0.75" << std::endl;
561 std::cout << " -rws" << std::endl;
562 std::cout << " Renders water surface as geometric surface" << std::endl;
563 std::cout << " -rwt" << std::endl;
564 std::cout << " Renders water surface as texture" << std::endl;
565 std::cout << " -wo <water opacity>" << std::endl;
566 std::cout << " Sets the water depth at which water appears opaque in cm" << std::endl;
567 std::cout << " Default: 2.0" << std::endl;
568 std::cout << " -cp <control pipe name>" << std::endl;
569 std::cout << " Sets the name of a named POSIX pipe from which to read control commands" <<
570 std::endl;
575 Sandbox::Sandbox(int& argc, char**& argv)
576 : Vrui::Application(argc, argv),
577 camera(0), pixelDepthCorrection(0),
578 frameFilter(0), pauseUpdates(false),
579 depthImageRenderer(0),
580 waterTable(0),
581 contourLineExtractor(0),
582 handExtractor(0),
583 addWaterFunction(0),
584 addWaterFunctionRegistered(false),
585 sun(0),
586 activeDem(0),
587 mainMenu(0), pauseUpdatesToggle(0), waterControlDialog(0),
588 waterSpeedSlider(0), waterMaxStepsSlider(0), frameRateTextField(0), waterAttenuationSlider(0),
589 controlPipeFd(-1) {
590 /* Read the sandbox's default configuration parameters: */
591 std::string sandboxConfigFileName = CONFIG_CONFIGDIR;
592 sandboxConfigFileName.push_back('/');
593 sandboxConfigFileName.append(CONFIG_DEFAULTCONFIGFILENAME);
594 Misc::ConfigurationFile sandboxConfigFile(sandboxConfigFileName.c_str());
595 Misc::ConfigurationFileSection cfg = sandboxConfigFile.getSection("/SARndbox");
596 unsigned int cameraIndex = cfg.retrieveValue<int>("./cameraIndex", 0);
597 std::string cameraConfiguration = cfg.retrieveString("./cameraConfiguration", "Camera");
598 double scale = cfg.retrieveValue<double>("./scaleFactor", 100.0);
599 std::string sandboxLayoutFileName = CONFIG_CONFIGDIR;
600 sandboxLayoutFileName.push_back('/');
601 sandboxLayoutFileName.append(CONFIG_DEFAULTBOXLAYOUTFILENAME);
602 sandboxLayoutFileName = cfg.retrieveString("./sandboxLayoutFileName", sandboxLayoutFileName);
603 Math::Interval<double> elevationRange =
604 cfg.retrieveValue<Math::Interval<double> >("./elevationRange", Math::Interval<double>(-1000.0,
605 1000.0));
606 bool haveHeightMapPlane = cfg.hasTag("./heightMapPlane");
607 Plane heightMapPlane;
608 if(haveHeightMapPlane)
609 heightMapPlane = cfg.retrieveValue<Plane>("./heightMapPlane");
610 unsigned int numAveragingSlots = cfg.retrieveValue<unsigned int>("./numAveragingSlots", 30);
611 unsigned int minNumSamples = cfg.retrieveValue<unsigned int>("./minNumSamples", 10);
612 unsigned int maxVariance = cfg.retrieveValue<unsigned int>("./maxVariance", 2);
613 float hysteresis = cfg.retrieveValue<float>("./hysteresis", 0.1f);
614 Misc::FixedArray<unsigned int, 2> wtSize;
615 wtSize[0] = 640;
616 wtSize[1] = 480;
617 wtSize = cfg.retrieveValue<Misc::FixedArray<unsigned int, 2> >("./waterTableSize", wtSize);
618 waterSpeed = cfg.retrieveValue<double>("./waterSpeed", 1.0);
619 waterMaxSteps = cfg.retrieveValue<unsigned int>("./waterMaxSteps", 30U);
620 Math::Interval<double> rainElevationRange =
621 cfg.retrieveValue<Math::Interval<double> >("./rainElevationRange", Math::Interval<double>(-1000.0,
622 1000.0));
623 rainStrength = cfg.retrieveValue<GLfloat>("./rainStrength", 0.25f);
624 double evaporationRate = cfg.retrieveValue<double>("./evaporationRate", 0.0);
625 float demDistScale = cfg.retrieveValue<float>("./demDistScale", 1.0f);
626 std::string controlPipeName = cfg.retrieveString("./controlPipeName", "");
628 /* Process command line parameters: */
629 bool printHelp = false;
630 const char* frameFilePrefix = 0;
631 const char* kinectServerName = 0;
632 int windowIndex = 0;
633 renderSettings.push_back(RenderSettings());
634 for(int i = 1; i < argc; ++i) {
635 if(argv[i][0] == '-') {
636 if(strcasecmp(argv[i] + 1, "h") == 0)
637 printHelp = true;
638 else if(strcasecmp(argv[i] + 1, "c") == 0) {
639 ++i;
640 cameraIndex = atoi(argv[i]);
641 } else if(strcasecmp(argv[i] + 1, "f") == 0) {
642 ++i;
643 frameFilePrefix = argv[i];
644 } else if(strcasecmp(argv[i] + 1, "p") == 0) {
645 ++i;
646 kinectServerName = argv[i];
647 } else if(strcasecmp(argv[i] + 1, "s") == 0) {
648 ++i;
649 scale = atof(argv[i]);
650 } else if(strcasecmp(argv[i] + 1, "slf") == 0) {
651 ++i;
652 sandboxLayoutFileName = argv[i];
653 } else if(strcasecmp(argv[i] + 1, "er") == 0) {
654 ++i;
655 double elevationMin = atof(argv[i]);
656 ++i;
657 double elevationMax = atof(argv[i]);
658 elevationRange = Math::Interval<double>(elevationMin, elevationMax);
659 } else if(strcasecmp(argv[i] + 1, "hmp") == 0) {
660 /* Read height mapping plane coefficients: */
661 haveHeightMapPlane = true;
662 double hmp[4];
663 for(int j = 0; j < 4; ++j) {
664 ++i;
665 hmp[j] = atof(argv[i]);
667 heightMapPlane = Plane(Plane::Vector(hmp), hmp[3]);
668 heightMapPlane.normalize();
669 } else if(strcasecmp(argv[i] + 1, "nas") == 0) {
670 ++i;
671 numAveragingSlots = atoi(argv[i]);
672 } else if(strcasecmp(argv[i] + 1, "sp") == 0) {
673 ++i;
674 minNumSamples = atoi(argv[i]);
675 ++i;
676 maxVariance = atoi(argv[i]);
677 } else if(strcasecmp(argv[i] + 1, "he") == 0) {
678 ++i;
679 hysteresis = float(atof(argv[i]));
680 } else if(strcasecmp(argv[i] + 1, "wts") == 0) {
681 for(int j = 0; j < 2; ++j) {
682 ++i;
683 wtSize[j] = (unsigned int)(atoi(argv[i]));
685 } else if(strcasecmp(argv[i] + 1, "ws") == 0) {
686 ++i;
687 waterSpeed = atof(argv[i]);
688 ++i;
689 waterMaxSteps = atoi(argv[i]);
690 } else if(strcasecmp(argv[i] + 1, "rer") == 0) {
691 ++i;
692 double rainElevationMin = atof(argv[i]);
693 ++i;
694 double rainElevationMax = atof(argv[i]);
695 rainElevationRange = Math::Interval<double>(rainElevationMin, rainElevationMax);
696 } else if(strcasecmp(argv[i] + 1, "rs") == 0) {
697 ++i;
698 rainStrength = GLfloat(atof(argv[i]));
699 } else if(strcasecmp(argv[i] + 1, "evr") == 0) {
700 ++i;
701 evaporationRate = atof(argv[i]);
702 } else if(strcasecmp(argv[i] + 1, "dds") == 0) {
703 ++i;
704 demDistScale = float(atof(argv[i]));
705 } else if(strcasecmp(argv[i] + 1, "wi") == 0) {
706 ++i;
707 windowIndex = atoi(argv[i]);
709 /* Extend the list of render settings if an index beyond the end is selected: */
710 while(int(renderSettings.size()) <= windowIndex)
711 renderSettings.push_back(renderSettings.back());
713 /* Disable fixed projector view on the new render settings: */
714 renderSettings.back().fixProjectorView = false;
715 } else if(strcasecmp(argv[i] + 1, "fpv") == 0) {
716 renderSettings.back().fixProjectorView = true;
717 if(i + 1 < argc && argv[i + 1][0] != '-') {
718 /* Load the projector transformation file specified in the next argument: */
719 ++i;
720 renderSettings.back().loadProjectorTransform(argv[i]);
722 } else if(strcasecmp(argv[i] + 1, "nhs") == 0)
723 renderSettings.back().hillshade = false;
724 else if(strcasecmp(argv[i] + 1, "uhs") == 0)
725 renderSettings.back().hillshade = true;
726 else if(strcasecmp(argv[i] + 1, "ns") == 0)
727 renderSettings.back().useShadows = false;
728 else if(strcasecmp(argv[i] + 1, "us") == 0)
729 renderSettings.back().useShadows = true;
730 else if(strcasecmp(argv[i] + 1, "nhm") == 0) {
731 delete renderSettings.back().elevationColorMap;
732 renderSettings.back().elevationColorMap = 0;
733 } else if(strcasecmp(argv[i] + 1, "uhm") == 0) {
734 if(i + 1 < argc && argv[i + 1][0] != '-') {
735 /* Load the height color map file specified in the next argument: */
736 ++i;
737 renderSettings.back().loadHeightMap(argv[i]);
738 } else {
739 /* Load the default height color map: */
740 renderSettings.back().loadHeightMap(CONFIG_DEFAULTHEIGHTCOLORMAPFILENAME);
742 } else if(strcasecmp(argv[i] + 1, "ncl") == 0)
743 renderSettings.back().useContourLines = false;
744 else if(strcasecmp(argv[i] + 1, "ucl") == 0) {
745 renderSettings.back().useContourLines = true;
746 if(i + 1 < argc && argv[i + 1][0] != '-') {
747 /* Read the contour line spacing: */
748 ++i;
749 renderSettings.back().contourLineSpacing = GLfloat(atof(argv[i]));
751 } else if(strcasecmp(argv[i] + 1, "rws") == 0)
752 renderSettings.back().renderWaterSurface = true;
753 else if(strcasecmp(argv[i] + 1, "rwt") == 0)
754 renderSettings.back().renderWaterSurface = false;
755 else if(strcasecmp(argv[i] + 1, "wo") == 0) {
756 ++i;
757 renderSettings.back().waterOpacity = GLfloat(atof(argv[i]));
758 } else if(strcasecmp(argv[i] + 1, "cp") == 0) {
759 ++i;
760 controlPipeName = argv[i];
761 } else
762 std::cerr << "Ignoring unrecognized command line switch " << argv[i] << std::endl;
766 /* Print usage help if requested: */
767 if(printHelp)
768 printUsage();
770 if(frameFilePrefix != 0) {
771 /* Open the selected pre-recorded 3D video files: */
772 std::string colorFileName = frameFilePrefix;
773 colorFileName.append(".color");
774 std::string depthFileName = frameFilePrefix;
775 depthFileName.append(".depth");
776 camera = new Kinect::FileFrameSource(Vrui::openFile(colorFileName.c_str()),
777 Vrui::openFile(depthFileName.c_str()));
778 } else if(kinectServerName != 0) {
779 /* Split the server name into host name and port: */
780 const char* colonPtr = 0;
781 for(const char* snPtr = kinectServerName; *snPtr != '\0'; ++snPtr)
782 if(*snPtr == ':')
783 colonPtr = snPtr;
784 std::string hostName;
785 int port;
786 if(colonPtr != 0) {
787 /* Extract host name and port: */
788 hostName = std::string(kinectServerName, colonPtr);
789 port = atoi(colonPtr + 1);
790 } else {
791 /* Use complete host name and default port: */
792 hostName = kinectServerName;
793 port = 26000;
796 /* Open a multiplexed frame source for the given server host name and port number: */
797 Kinect::MultiplexedFrameSource* source = Kinect::MultiplexedFrameSource::create(
798 Cluster::openTCPPipe(Vrui::getClusterMultiplexer(), hostName.c_str(), port));
800 /* Use the server's first component stream as the camera device: */
801 camera = source->getStream(0);
802 } else {
803 /* Open the 3D camera device of the selected index: */
804 Kinect::DirectFrameSource* realCamera = Kinect::openDirectFrameSource(cameraIndex);
805 Misc::ConfigurationFileSection cameraConfigurationSection = cfg.getSection(
806 cameraConfiguration.c_str());
807 realCamera->configure(cameraConfigurationSection);
808 camera = realCamera;
810 for(int i = 0; i < 2; ++i)
811 frameSize[i] = camera->getActualFrameSize(Kinect::FrameSource::DEPTH)[i];
813 /* Get the camera's per-pixel depth correction parameters and evaluate it on the depth frame's pixel grid: */
814 Kinect::FrameSource::DepthCorrection* depthCorrection = camera->getDepthCorrectionParameters();
815 if(depthCorrection != 0) {
816 pixelDepthCorrection = depthCorrection->getPixelCorrection(frameSize);
817 delete depthCorrection;
818 } else {
819 /* Create dummy per-pixel depth correction parameters: */
820 pixelDepthCorrection = new PixelDepthCorrection[frameSize[1]*frameSize[0]];
821 PixelDepthCorrection* pdcPtr = pixelDepthCorrection;
822 for(unsigned int y = 0; y < frameSize[1]; ++y)
823 for(unsigned int x = 0; x < frameSize[0]; ++x, ++pdcPtr) {
824 pdcPtr->scale = 1.0f;
825 pdcPtr->offset = 0.0f;
829 /* Get the camera's intrinsic parameters: */
830 cameraIps = camera->getIntrinsicParameters();
832 /* Read the sandbox layout file: */
833 Geometry::Plane<double, 3> basePlane;
834 Geometry::Point<double, 3> basePlaneCorners[4];
836 IO::ValueSource layoutSource(Vrui::openFile(sandboxLayoutFileName.c_str()));
837 layoutSource.skipWs();
839 /* Read the base plane equation: */
840 std::string s = layoutSource.readLine();
841 basePlane = Misc::ValueCoder<Geometry::Plane<double, 3> >::decode(s.c_str(),
842 s.c_str() + s.length());
843 basePlane.normalize();
845 /* Read the corners of the base quadrilateral and project them into the base plane: */
846 for(int i = 0; i < 4; ++i) {
847 layoutSource.skipWs();
848 s = layoutSource.readLine();
849 basePlaneCorners[i] = basePlane.project(Misc::ValueCoder<Geometry::Point<double, 3> >::decode(
850 s.c_str(), s.c_str() + s.length()));
854 /* Limit the valid elevation range to the intersection of the extents of all height color maps: */
855 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
856 rsIt != renderSettings.end(); ++rsIt)
857 if(rsIt->elevationColorMap != 0) {
858 Math::Interval<double> mapRange(rsIt->elevationColorMap->getScalarRangeMin(),
859 rsIt->elevationColorMap->getScalarRangeMax());
860 elevationRange.intersectInterval(mapRange);
863 /* Scale all sizes by the given scale factor: */
864 double sf = scale / 100.0; // Scale factor from cm to final units
865 for(int i = 0; i < 3; ++i)
866 for(int j = 0; j < 4; ++j)
867 cameraIps.depthProjection.getMatrix()(i, j) *= sf;
868 basePlane = Geometry::Plane<double, 3>(basePlane.getNormal(), basePlane.getOffset() * sf);
869 for(int i = 0; i < 4; ++i)
870 for(int j = 0; j < 3; ++j)
871 basePlaneCorners[i][j] *= sf;
872 if(elevationRange != Math::Interval<double>::full)
873 elevationRange *= sf;
874 if(rainElevationRange != Math::Interval<double>::full)
875 rainElevationRange *= sf;
876 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
877 rsIt != renderSettings.end(); ++rsIt) {
878 if(rsIt->elevationColorMap != 0)
879 rsIt->elevationColorMap->setScalarRange(rsIt->elevationColorMap->getScalarRangeMin()*sf,
880 rsIt->elevationColorMap->getScalarRangeMax()*sf);
881 rsIt->contourLineSpacing *= sf;
882 rsIt->waterOpacity /= sf;
883 for(int i = 0; i < 4; ++i)
884 rsIt->projectorTransform.getMatrix()(i, 3) *= sf;
886 rainStrength *= sf;
887 evaporationRate *= sf;
888 demDistScale *= sf;
890 /* Create the frame filter object: */
891 frameFilter = new FrameFilter(frameSize, numAveragingSlots, pixelDepthCorrection,
892 cameraIps.depthProjection, basePlane);
893 frameFilter->setValidElevationInterval(cameraIps.depthProjection, basePlane,
894 elevationRange.getMin(), elevationRange.getMax());
895 frameFilter->setStableParameters(minNumSamples, maxVariance);
896 frameFilter->setHysteresis(hysteresis);
897 frameFilter->setSpatialFilter(true);
898 frameFilter->setOutputFrameFunction(Misc::createFunctionCall(this,
899 &Sandbox::receiveFilteredFrame));
901 if(waterSpeed > 0.0) {
902 /* Create the hand extractor object: */
903 handExtractor = new HandExtractor(frameSize, pixelDepthCorrection, cameraIps.depthProjection);
906 contourLineExtractor = new ContourLineExtractor(frameSize, pixelDepthCorrection, cameraIps.depthProjection);
908 /* Start streaming depth frames: */
909 camera->startStreaming(0, Misc::createFunctionCall(this, &Sandbox::rawDepthFrameDispatcher));
911 /* Create the depth image renderer: */
912 depthImageRenderer = new DepthImageRenderer(frameSize);
913 depthImageRenderer->setIntrinsics(cameraIps);
914 depthImageRenderer->setBasePlane(basePlane);
917 /* Calculate the transformation from camera space to sandbox space: */
918 ONTransform::Vector z = basePlane.getNormal();
919 ONTransform::Vector x = (basePlaneCorners[1] - basePlaneCorners[0]) +
920 (basePlaneCorners[3] - basePlaneCorners[2]);
921 ONTransform::Vector y = z ^ x;
922 boxTransform = ONTransform::rotate(Geometry::invert(ONTransform::Rotation::fromBaseVectors(x, y)));
923 ONTransform::Point center = Geometry::mid(Geometry::mid(basePlaneCorners[0], basePlaneCorners[1]),
924 Geometry::mid(basePlaneCorners[2], basePlaneCorners[3]));
925 boxTransform *= ONTransform::translateToOriginFrom(center);
927 /* Calculate the size of the sandbox area: */
928 boxSize = Geometry::dist(center, basePlaneCorners[0]);
929 for(int i = 1; i < 4; ++i)
930 boxSize = Math::max(boxSize, Geometry::dist(center, basePlaneCorners[i]));
933 /* Calculate a bounding box around all potential surfaces: */
934 bbox = Box::empty;
935 for(int i = 0; i < 4; ++i) {
936 bbox.addPoint(basePlaneCorners[i] + basePlane.getNormal()*elevationRange.getMin());
937 bbox.addPoint(basePlaneCorners[i] + basePlane.getNormal()*elevationRange.getMax());
940 if(waterSpeed > 0.0) {
941 /* Initialize the water flow simulator: */
942 waterTable = new WaterTable2(wtSize[0], wtSize[1], depthImageRenderer, basePlaneCorners);
943 waterTable->setElevationRange(elevationRange.getMin(), rainElevationRange.getMax());
944 waterTable->setWaterDeposit(evaporationRate);
946 /* Register a render function with the water table: */
947 addWaterFunction = Misc::createFunctionCall(this, &Sandbox::addWater);
948 waterTable->addRenderFunction(addWaterFunction);
949 addWaterFunctionRegistered = true;
952 /* Initialize all surface renderers: */
953 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
954 rsIt != renderSettings.end(); ++rsIt) {
955 /* Calculate the texture mapping plane for this renderer's height map: */
956 if(rsIt->elevationColorMap != 0) {
957 if(haveHeightMapPlane)
958 rsIt->elevationColorMap->calcTexturePlane(heightMapPlane);
959 else
960 rsIt->elevationColorMap->calcTexturePlane(depthImageRenderer);
963 /* Initialize the surface renderer: */
964 rsIt->surfaceRenderer = new SurfaceRenderer(depthImageRenderer);
965 rsIt->surfaceRenderer->setDrawContourLines(rsIt->useContourLines);
966 rsIt->surfaceRenderer->setContourLineDistance(rsIt->contourLineSpacing);
967 rsIt->surfaceRenderer->setElevationColorMap(rsIt->elevationColorMap);
968 rsIt->surfaceRenderer->setIlluminate(rsIt->hillshade);
969 if(waterTable != 0) {
970 if(rsIt->renderWaterSurface) {
971 /* Create a water renderer: */
972 rsIt->waterRenderer = new WaterRenderer(waterTable);
973 } else {
974 rsIt->surfaceRenderer->setWaterTable(waterTable);
975 rsIt->surfaceRenderer->setAdvectWaterTexture(true);
976 rsIt->surfaceRenderer->setWaterOpacity(rsIt->waterOpacity);
979 rsIt->surfaceRenderer->setDemDistScale(demDistScale);
982 #if 0
983 /* Create a fixed-position light source: */
984 sun = Vrui::getLightsourceManager()->createLightsource(true);
985 for(int i = 0; i < Vrui::getNumViewers(); ++i)
986 Vrui::getViewer(i)->setHeadlightState(false);
987 sun->enable();
988 sun->getLight().position = GLLight::Position(1, 0, 1, 0);
989 #endif
991 /* Create the GUI: */
992 mainMenu = createMainMenu();
993 Vrui::setMainMenu(mainMenu);
994 if(waterTable != 0)
995 waterControlDialog = createWaterControlDialog();
997 /* Initialize the custom tool classes: */
998 GlobalWaterTool::initClass(*Vrui::getToolManager());
999 LocalWaterTool::initClass(*Vrui::getToolManager());
1000 DEMTool::initClass(*Vrui::getToolManager());
1001 if(waterTable != 0)
1002 BathymetrySaverTool::initClass(waterTable, *Vrui::getToolManager());
1003 addEventTool("Pause Topography", 0, 0);
1005 if(!controlPipeName.empty()) {
1006 /* Open the control pipe in non-blocking mode: */
1007 controlPipeFd = open(controlPipeName.c_str(), O_RDONLY | O_NONBLOCK);
1008 if(controlPipeFd < 0)
1009 std::cerr << "Unable to open control pipe " << controlPipeName << "; ignoring" << std::endl;
1012 /* Inhibit the screen saver: */
1013 Vrui::inhibitScreenSaver();
1015 /* Set the linear unit to support proper scaling: */
1016 Vrui::getCoordinateManager()->setUnit(Geometry::LinearUnit(Geometry::LinearUnit::METER,
1017 scale / 100.0));
1020 Sandbox::~Sandbox(void) {
1021 /* Stop streaming depth frames: */
1022 camera->stopStreaming();
1023 delete camera;
1024 delete frameFilter;
1026 /* Delete helper objects: */
1027 delete waterTable;
1028 delete depthImageRenderer;
1029 delete contourLineExtractor;
1030 delete handExtractor;
1031 delete addWaterFunction;
1032 delete[] pixelDepthCorrection;
1034 delete mainMenu;
1035 delete waterControlDialog;
1037 close(controlPipeFd);
1040 void Sandbox::toolDestructionCallback(Vrui::ToolManager::ToolDestructionCallbackData* cbData) {
1041 /* Check if the destroyed tool is the active DEM tool: */
1042 if(activeDem == dynamic_cast<DEM*>(cbData->tool)) {
1043 /* Deactivate the active DEM tool: */
1044 activeDem = 0;
1048 namespace {
1050 /****************
1051 Helper functions:
1052 ****************/
1054 std::vector<std::string> tokenizeLine(const char*& buffer) {
1055 std::vector<std::string> result;
1057 /* Skip initial whitespace but not end-of-line: */
1058 const char* bPtr = buffer;
1059 while(*bPtr != '\0' && *bPtr != '\n' && isspace(*bPtr))
1060 ++bPtr;
1062 /* Extract white-space separated tokens until a newline or end-of-string are encountered: */
1063 while(*bPtr != '\0' && *bPtr != '\n') {
1064 /* Remember the start of the current token: */
1065 const char* tokenStart = bPtr;
1067 /* Find the end of the current token: */
1068 while(*bPtr != '\0' && !isspace(*bPtr))
1069 ++bPtr;
1071 /* Extract the token: */
1072 result.push_back(std::string(tokenStart, bPtr));
1074 /* Skip whitespace but not end-of-line: */
1075 while(*bPtr != '\0' && *bPtr != '\n' && isspace(*bPtr))
1076 ++bPtr;
1079 /* Skip end-of-line: */
1080 if(*bPtr == '\n')
1081 ++bPtr;
1083 /* Set the start of the next line and return the token list: */
1084 buffer = bPtr;
1085 return result;
1088 bool isToken(const std::string& token, const char* pattern) {
1089 return strcasecmp(token.c_str(), pattern) == 0;
1094 void Sandbox::frame(void) {
1095 /* Check if the filtered frame has been updated: */
1096 if(filteredFrames.lockNewValue()) {
1097 /* Update the depth image renderer's depth image: */
1098 depthImageRenderer->setDepthImage(filteredFrames.getLockedValue());
1101 if(contourLineExtractor != 0) {
1102 /* Lock the most recent extracted contour line list: */
1103 contourLineExtractor->lockNewExtractedContourLines();
1106 if(handExtractor != 0) {
1107 /* Lock the most recent extracted hand list: */
1108 handExtractor->lockNewExtractedHands();
1110 #if 0
1112 /* Register/unregister the rain rendering function based on whether hands have been detected: */
1113 bool registerWaterFunction = !handExtractor->getLockedExtractedHands().empty();
1114 if(addWaterFunctionRegistered != registerWaterFunction) {
1115 if(registerWaterFunction)
1116 waterTable->addRenderFunction(addWaterFunction);
1117 else
1118 waterTable->removeRenderFunction(addWaterFunction);
1119 addWaterFunctionRegistered = registerWaterFunction;
1122 #endif
1125 /* Update all surface renderers: */
1126 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1127 rsIt != renderSettings.end(); ++rsIt)
1128 rsIt->surfaceRenderer->setAnimationTime(Vrui::getApplicationTime());
1130 /* Check if there is a control command on the control pipe: */
1131 if(controlPipeFd >= 0) {
1132 /* Try reading a chunk of data (will fail with EAGAIN if no data due to non-blocking access): */
1133 char commandBuffer[1024];
1134 ssize_t readResult = read(controlPipeFd, commandBuffer, sizeof(commandBuffer) - 1);
1135 if(readResult > 0) {
1136 commandBuffer[readResult] = '\0';
1138 /* Extract commands line-by-line: */
1139 const char* cPtr = commandBuffer;
1140 while(*cPtr != '\0') {
1141 /* Split the current line into tokens and skip empty lines: */
1142 std::vector<std::string> tokens = tokenizeLine(cPtr);
1143 if(tokens.empty())
1144 continue;
1146 /* Parse the command: */
1147 if(isToken(tokens[0], "waterSpeed")) {
1148 if(tokens.size() == 2) {
1149 waterSpeed = atof(tokens[1].c_str());
1150 if(waterSpeedSlider != 0)
1151 waterSpeedSlider->setValue(waterSpeed);
1152 } else
1153 std::cerr << "Wrong number of arguments for waterSpeed control pipe command" << std::endl;
1154 } else if(isToken(tokens[0], "waterMaxSteps")) {
1155 if(tokens.size() == 2) {
1156 waterMaxSteps = atoi(tokens[1].c_str());
1157 if(waterMaxStepsSlider != 0)
1158 waterMaxStepsSlider->setValue(waterMaxSteps);
1159 } else
1160 std::cerr << "Wrong number of arguments for waterMaxSteps control pipe command" << std::endl;
1161 } else if(isToken(tokens[0], "waterAttenuation")) {
1162 if(tokens.size() == 2) {
1163 double attenuation = atof(tokens[1].c_str());
1164 if(waterTable != 0)
1165 waterTable->setAttenuation(GLfloat(1.0 - attenuation));
1166 if(waterAttenuationSlider != 0)
1167 waterAttenuationSlider->setValue(attenuation);
1168 } else
1169 std::cerr << "Wrong number of arguments for waterAttenuation control pipe command" << std::endl;
1170 } else if(isToken(tokens[0], "colorMap")) {
1171 if(tokens.size() == 2) {
1172 try {
1173 /* Update all height color maps: */
1174 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1175 rsIt != renderSettings.end(); ++rsIt)
1176 if(rsIt->elevationColorMap != 0)
1177 rsIt->elevationColorMap->load(tokens[1].c_str());
1178 } catch(const std::runtime_error& err) {
1179 std::cerr << "Cannot read height color map " << tokens[1] << " due to exception " << err.what() <<
1180 std::endl;
1182 } else
1183 std::cerr << "Wrong number of arguments for colorMap control pipe command" << std::endl;
1184 } else if(isToken(tokens[0], "heightMapPlane")) {
1185 if(tokens.size() == 5) {
1186 /* Read the height map plane equation: */
1187 double hmp[4];
1188 for(int i = 0; i < 4; ++i)
1189 hmp[i] = atof(tokens[1 + i].c_str());
1190 Plane heightMapPlane = Plane(Plane::Vector(hmp), hmp[3]);
1191 heightMapPlane.normalize();
1193 /* Override the height mapping planes of all elevation color maps: */
1194 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1195 rsIt != renderSettings.end(); ++rsIt)
1196 if(rsIt->elevationColorMap != 0)
1197 rsIt->elevationColorMap->calcTexturePlane(heightMapPlane);
1198 } else
1199 std::cerr << "Wrong number of arguments for heightMapPlane control pipe command" << std::endl;
1200 } else if(isToken(tokens[0], "dippingBed")) {
1201 if(tokens.size() == 2 && isToken(tokens[1], "off")) {
1202 /* Disable dipping bed rendering on all surface renderers: */
1203 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1204 rsIt != renderSettings.end(); ++rsIt)
1205 rsIt->surfaceRenderer->setDrawDippingBed(false);
1206 } else if(tokens.size() == 5) {
1207 /* Read the dipping bed plane equation: */
1208 GLfloat dbp[4];
1209 for(int i = 0; i < 4; ++i)
1210 dbp[i] = GLfloat(atof(tokens[1 + i].c_str()));
1211 SurfaceRenderer::Plane dippingBedPlane = SurfaceRenderer::Plane(SurfaceRenderer::Plane::Vector(
1212 dbp), dbp[3]);
1213 dippingBedPlane.normalize();
1215 /* Enable dipping bed rendering and set the dipping bed plane equation on all surface renderers: */
1216 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1217 rsIt != renderSettings.end(); ++rsIt) {
1218 rsIt->surfaceRenderer->setDrawDippingBed(true);
1219 rsIt->surfaceRenderer->setDippingBedPlane(dippingBedPlane);
1221 } else
1222 std::cerr << "Wrong number of arguments for dippingBed control pipe command" << std::endl;
1223 } else if(isToken(tokens[0], "foldedDippingBed")) {
1224 if(tokens.size() == 6) {
1225 /* Read the dipping bed coefficients: */
1226 GLfloat dbc[5];
1227 for(int i = 0; i < 5; ++i)
1228 dbc[i] = GLfloat(atof(tokens[1 + i].c_str()));
1230 /* Enable dipping bed rendering and set the dipping bed coefficients on all surface renderers: */
1231 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1232 rsIt != renderSettings.end(); ++rsIt) {
1233 rsIt->surfaceRenderer->setDrawDippingBed(true);
1234 rsIt->surfaceRenderer->setDippingBedCoeffs(dbc);
1236 } else
1237 std::cerr << "Wrong number of arguments for foldedDippingBed control pipe command" << std::endl;
1238 } else if(isToken(tokens[0], "dippingBedThickness")) {
1239 if(tokens.size() == 2) {
1240 /* Read the dipping bed thickness: */
1241 float dippingBedThickness = float(atof(tokens[1].c_str()));
1243 /* Set the dipping bed thickness on all surface renderers: */
1244 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1245 rsIt != renderSettings.end(); ++rsIt)
1246 rsIt->surfaceRenderer->setDippingBedThickness(dippingBedThickness);
1247 } else
1248 std::cerr << "Wrong number of arguments for dippingBedThickness control pipe command" << std::endl;
1249 } else
1250 std::cerr << "Unrecognized control pipe command " << tokens[0] << std::endl;
1255 if(frameRateTextField != 0 && Vrui::getWidgetManager()->isVisible(waterControlDialog)) {
1256 /* Update the frame rate display: */
1257 frameRateTextField->setValue(1.0 / Vrui::getCurrentFrameTime());
1260 if(pauseUpdates)
1261 Vrui::scheduleUpdate(Vrui::getApplicationTime() + 1.0 / 30.0);
1264 void Sandbox::display(GLContextData& contextData) const {
1265 /* Get the data item: */
1266 DataItem* dataItem = contextData.retrieveDataItem<DataItem>(this);
1268 /* Get the rendering settings for this window: */
1269 const Vrui::DisplayState& ds = Vrui::getDisplayState(contextData);
1270 const Vrui::VRWindow* window = ds.window;
1271 int windowIndex;
1272 for(windowIndex = 0; windowIndex < Vrui::getNumWindows()
1273 && window != Vrui::getWindow(windowIndex); ++windowIndex)
1275 const RenderSettings& rs = windowIndex < int(renderSettings.size()) ? renderSettings[windowIndex] :
1276 renderSettings.back();
1278 /* Check if the water simulation state needs to be updated: */
1279 if(waterTable != 0 && dataItem->waterTableTime != Vrui::getApplicationTime()) {
1280 /* Update the water table's bathymetry grid: */
1281 waterTable->updateBathymetry(contextData);
1283 /* Run the water flow simulation's main pass: */
1284 GLfloat totalTimeStep = GLfloat(Vrui::getFrameTime() * waterSpeed);
1285 unsigned int numSteps = 0;
1286 while(numSteps < waterMaxSteps - 1U && totalTimeStep > 1.0e-8f) {
1287 /* Run with a self-determined time step to maintain stability: */
1288 waterTable->setMaxStepSize(totalTimeStep);
1289 GLfloat timeStep = waterTable->runSimulationStep(false, contextData);
1290 totalTimeStep -= timeStep;
1291 ++numSteps;
1293 #if 0
1294 if(totalTimeStep > 1.0e-8f) {
1295 std::cout << '.' << std::flush;
1296 /* Force the final step to avoid simulation slow-down: */
1297 waterTable->setMaxStepSize(totalTimeStep);
1298 GLfloat timeStep = waterTable->runSimulationStep(true, contextData);
1299 totalTimeStep -= timeStep;
1300 ++numSteps;
1302 #else
1303 if(totalTimeStep > 1.0e-8f)
1304 std::cout << "Ran out of time by " << totalTimeStep << std::endl;
1305 #endif
1307 /* Mark the water simulation state as up-to-date for this frame: */
1308 dataItem->waterTableTime = Vrui::getApplicationTime();
1311 /* Calculate the projection matrix: */
1312 PTransform projection = ds.projection;
1313 if(rs.fixProjectorView && rs.projectorTransformValid) {
1314 /* Use the projector transformation instead: */
1315 projection = rs.projectorTransform;
1317 /* Multiply with the inverse modelview transformation so that lighting still works as usual: */
1318 projection *= Geometry::invert(ds.modelviewNavigational);
1321 addContourLabel(contextData);
1323 if(rs.hillshade) {
1324 /* Set the surface material: */
1325 glMaterial(GLMaterialEnums::FRONT, rs.surfaceMaterial);
1328 #if 0
1329 if(rs.hillshade && rs.useShadows) {
1330 /* Set up OpenGL state: */
1331 glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT | GL_POLYGON_BIT);
1333 GLLightTracker& lt = *contextData.getLightTracker();
1335 /* Save the currently-bound frame buffer and viewport: */
1336 GLint currentFrameBuffer;
1337 glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &currentFrameBuffer);
1338 GLint currentViewport[4];
1339 glGetIntegerv(GL_VIEWPORT, currentViewport);
1341 /*******************************************************************
1342 First rendering pass: Global ambient illumination only
1343 *******************************************************************/
1345 /* Draw the surface mesh: */
1346 surfaceRenderer->glRenderGlobalAmbientHeightMap(dataItem->heightColorMapObject, contextData);
1348 /*******************************************************************
1349 Second rendering pass: Add local illumination for every light source
1350 *******************************************************************/
1352 /* Enable additive rendering: */
1353 glEnable(GL_BLEND);
1354 glBlendFunc(GL_ONE, GL_ONE);
1355 glDepthFunc(GL_LEQUAL);
1356 glDepthMask(GL_FALSE);
1358 for(int lightSourceIndex = 0; lightSourceIndex < lt.getMaxNumLights(); ++lightSourceIndex)
1359 if(lt.getLightState(lightSourceIndex).isEnabled()) {
1360 /***************************************************************
1361 First step: Render to the light source's shadow map
1362 ***************************************************************/
1364 /* Set up OpenGL state to render to the shadow map: */
1365 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dataItem->shadowFramebufferObject);
1366 glViewport(0, 0, dataItem->shadowBufferSize[0], dataItem->shadowBufferSize[1]);
1367 glDepthMask(GL_TRUE);
1368 glClear(GL_DEPTH_BUFFER_BIT);
1369 glCullFace(GL_FRONT);
1371 /*************************************************************
1372 Calculate the shadow projection matrix:
1373 *************************************************************/
1375 /* Get the light source position in eye space: */
1376 Geometry::HVector<float, 3> lightPosEc;
1377 glGetLightfv(GL_LIGHT0 + lightSourceIndex, GL_POSITION, lightPosEc.getComponents());
1379 /* Transform the light source position to camera space: */
1380 Vrui::ONTransform::HVector lightPosCc = Vrui::getDisplayState(
1381 contextData).modelviewNavigational.inverseTransform(Vrui::ONTransform::HVector(lightPosEc));
1383 /* Calculate the direction vector from the center of the bounding box to the light source: */
1384 Point bboxCenter = Geometry::mid(bbox.min, bbox.max);
1385 Vrui::Vector lightDirCc = Vrui::Vector(lightPosCc.getComponents()) - Vrui::Vector(
1386 bboxCenter.getComponents()) * lightPosCc[3];
1388 /* Build a transformation that aligns the light direction with the positive z axis: */
1389 Vrui::ONTransform shadowModelview = Vrui::ONTransform::rotate(Vrui::Rotation::rotateFromTo(
1390 lightDirCc, Vrui::Vector(0, 0, 1)));
1391 shadowModelview *= Vrui::ONTransform::translateToOriginFrom(bboxCenter);
1393 /* Create a projection matrix, based on whether the light is positional or directional: */
1394 PTransform shadowProjection(0.0);
1395 if(lightPosEc[3] != 0.0f) {
1396 /* Modify the modelview transformation such that the light source is at the origin: */
1397 shadowModelview.leftMultiply(Vrui::ONTransform::translate(Vrui::Vector(0, 0, -lightDirCc.mag())));
1399 /***********************************************************
1400 Create a perspective projection:
1401 ***********************************************************/
1403 /* Calculate the perspective bounding box of the surface bounding box in eye space: */
1404 Box pBox = Box::empty;
1405 for(int i = 0; i < 8; ++i) {
1406 Point bc = shadowModelview.transform(bbox.getVertex(i));
1407 pBox.addPoint(Point(-bc[0] / bc[2], -bc[1] / bc[2], -bc[2]));
1410 /* Upload the frustum matrix: */
1411 double l = pBox.min[0] * pBox.min[2];
1412 double r = pBox.max[0] * pBox.min[2];
1413 double b = pBox.min[1] * pBox.min[2];
1414 double t = pBox.max[1] * pBox.min[2];
1415 double n = pBox.min[2];
1416 double f = pBox.max[2];
1417 shadowProjection.getMatrix()(0, 0) = 2.0 * n / (r - l);
1418 shadowProjection.getMatrix()(0, 2) = (r + l) / (r - l);
1419 shadowProjection.getMatrix()(1, 1) = 2.0 * n / (t - b);
1420 shadowProjection.getMatrix()(1, 2) = (t + b) / (t - b);
1421 shadowProjection.getMatrix()(2, 2) = -(f + n) / (f - n);
1422 shadowProjection.getMatrix()(2, 3) = -2.0 * f * n / (f - n);
1423 shadowProjection.getMatrix()(3, 2) = -1.0;
1424 } else {
1425 /***********************************************************
1426 Create a perspective projection:
1427 ***********************************************************/
1429 /* Transform the bounding box with the modelview transformation: */
1430 Box bboxEc = bbox;
1431 bboxEc.transform(shadowModelview);
1433 /* Upload the ortho matrix: */
1434 double l = bboxEc.min[0];
1435 double r = bboxEc.max[0];
1436 double b = bboxEc.min[1];
1437 double t = bboxEc.max[1];
1438 double n = -bboxEc.max[2];
1439 double f = -bboxEc.min[2];
1440 shadowProjection.getMatrix()(0, 0) = 2.0 / (r - l);
1441 shadowProjection.getMatrix()(0, 3) = -(r + l) / (r - l);
1442 shadowProjection.getMatrix()(1, 1) = 2.0 / (t - b);
1443 shadowProjection.getMatrix()(1, 3) = -(t + b) / (t - b);
1444 shadowProjection.getMatrix()(2, 2) = -2.0 / (f - n);
1445 shadowProjection.getMatrix()(2, 3) = -(f + n) / (f - n);
1446 shadowProjection.getMatrix()(3, 3) = 1.0;
1449 /* Multiply the shadow modelview matrix onto the shadow projection matrix: */
1450 shadowProjection *= shadowModelview;
1452 /* Draw the surface into the shadow buffer: */
1453 surfaceRenderer->glRenderDepthOnly(shadowProjection, contextData);
1455 /* Reset OpenGL state: */
1456 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFrameBuffer);
1457 glViewport(currentViewport[0], currentViewport[1], currentViewport[2], currentViewport[3]);
1458 glCullFace(GL_BACK);
1459 glDepthMask(GL_FALSE);
1461 #if SAVEDEPTH
1462 /* Save the depth image: */
1464 glBindTexture(GL_TEXTURE_2D, dataItem->shadowDepthTextureObject);
1465 GLfloat* depthTextureImage = new
1466 GLfloat[dataItem->shadowBufferSize[1]*dataItem->shadowBufferSize[0]];
1467 glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depthTextureImage);
1468 glBindTexture(GL_TEXTURE_2D, 0);
1469 Images::RGBImage dti(dataItem->shadowBufferSize[0], dataItem->shadowBufferSize[1]);
1470 GLfloat* dtiPtr = depthTextureImage;
1471 Images::RGBImage::Color* ciPtr = dti.modifyPixels();
1472 for(int y = 0; y < dataItem->shadowBufferSize[1]; ++y)
1473 for(int x = 0; x < dataItem->shadowBufferSize[0]; ++x, ++dtiPtr, ++ciPtr) {
1474 GLColor<GLfloat, 3> tc(*dtiPtr, *dtiPtr, *dtiPtr);
1475 *ciPtr = tc;
1477 delete[] depthTextureImage;
1478 Images::writeImageFile(dti, "DepthImage.png");
1480 #endif
1482 /* Draw the surface using the shadow texture: */
1483 rs.surfaceRenderer->glRenderShadowedIlluminatedHeightMap(dataItem->heightColorMapObject,
1484 dataItem->shadowDepthTextureObject, shadowProjection, contextData);
1487 /* Reset OpenGL state: */
1488 glPopAttrib();
1489 } else
1490 #endif
1492 /* Render the surface in a single pass: */
1493 rs.surfaceRenderer->renderSinglePass(ds.viewport, projection, ds.modelviewNavigational,
1494 contextData);
1497 if(rs.waterRenderer != 0) {
1498 /* Draw the water surface: */
1499 glMaterialAmbientAndDiffuse(GLMaterialEnums::FRONT, GLColor<GLfloat, 4>(0.0f, 0.5f, 0.8f));
1500 glMaterialSpecular(GLMaterialEnums::FRONT, GLColor<GLfloat, 4>(1.0f, 1.0f, 1.0f));
1501 glMaterialShininess(GLMaterialEnums::FRONT, 64.0f);
1502 rs.waterRenderer->render(projection, ds.modelviewNavigational, contextData);
1506 void Sandbox::resetNavigation(void) {
1507 /* Construct a navigation transformation to center the sandbox area in the display, facing the viewer, with the long sandbox axis facing to the right: */
1508 Vrui::NavTransform nav = Vrui::NavTransform::translateFromOriginTo(Vrui::getDisplayCenter());
1509 nav *= Vrui::NavTransform::scale(Vrui::getDisplaySize() / boxSize);
1510 Vrui::Vector y = Vrui::getUpDirection();
1511 Vrui::Vector z = Vrui::getForwardDirection();
1512 Vrui::Vector x = z ^ y;
1513 nav *= Vrui::NavTransform::rotate(Vrui::Rotation::fromBaseVectors(x, y));
1514 nav *= boxTransform;
1515 Vrui::setNavigationTransformation(nav);
1518 void Sandbox::eventCallback(Vrui::Application::EventID eventId,
1519 Vrui::InputDevice::ButtonCallbackData* cbData) {
1520 if(cbData->newButtonState) {
1521 switch(eventId) {
1522 case 0:
1523 /* Invert the current pause setting: */
1524 pauseUpdates = !pauseUpdates;
1526 /* Update the main menu toggle: */
1527 pauseUpdatesToggle->setToggle(pauseUpdates);
1529 break;
1534 void Sandbox::initContext(GLContextData& contextData) const {
1535 /* Create a data item and add it to the context: */
1536 DataItem* dataItem = new DataItem;
1537 contextData.addDataItem(this, dataItem);
1540 /* Save the currently bound frame buffer: */
1541 GLint currentFrameBuffer;
1542 glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &currentFrameBuffer);
1544 /* Set the default shadow buffer size: */
1545 dataItem->shadowBufferSize[0] = 1024;
1546 dataItem->shadowBufferSize[1] = 1024;
1548 /* Generate the shadow rendering frame buffer: */
1549 glGenFramebuffersEXT(1, &dataItem->shadowFramebufferObject);
1550 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dataItem->shadowFramebufferObject);
1552 /* Generate a depth texture for shadow rendering: */
1553 glGenTextures(1, &dataItem->shadowDepthTextureObject);
1554 glBindTexture(GL_TEXTURE_2D, dataItem->shadowDepthTextureObject);
1555 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1556 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1557 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1558 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1559 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
1560 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
1561 glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
1562 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24_ARB, dataItem->shadowBufferSize[0],
1563 dataItem->shadowBufferSize[1], 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
1564 glBindTexture(GL_TEXTURE_2D, 0);
1566 /* Attach the depth texture to the frame buffer object: */
1567 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D,
1568 dataItem->shadowDepthTextureObject, 0);
1569 glDrawBuffer(GL_NONE);
1570 glReadBuffer(GL_NONE);
1571 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFrameBuffer);
1575 VRUI_APPLICATION_RUN(Sandbox)