temp commit
[SARndbox.git] / Sandbox.cpp
blob6443b34c8997e672c8690dc634782ca39b362176
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 glColor4f(1.0, 1.0, 1.0, 1.0);
326 int sides = 4;
327 for ( int i = 0; i < sides; ++i ) {
328 Scalar angle = Scalar(2) * Math::Constants<Scalar>::pi * Scalar(i) / Scalar(sides);
329 glVertex(labIt->center +
330 x * (Math::cos(angle) * labIt->radius * 3.0 * 0.25) +
331 y * (Math::sin(angle) * labIt->radius * 0.25));
333 glEnd();
335 glPopAttrib();
336 // }
339 void Sandbox::pauseUpdatesCallback(GLMotif::ToggleButton::ValueChangedCallbackData* cbData) {
340 pauseUpdates = cbData->set;
343 void Sandbox::showWaterControlDialogCallback(Misc::CallbackData* cbData) {
344 Vrui::popupPrimaryWidget(waterControlDialog);
347 void Sandbox::waterSpeedSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
348 cbData) {
349 waterSpeed = cbData->value;
352 void Sandbox::waterMaxStepsSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
353 cbData) {
354 waterMaxSteps = int(Math::floor(cbData->value + 0.5));
357 void Sandbox::waterAttenuationSliderCallback(GLMotif::TextFieldSlider::ValueChangedCallbackData*
358 cbData) {
359 waterTable->setAttenuation(GLfloat(1.0 - cbData->value));
362 GLMotif::PopupMenu* Sandbox::createMainMenu(void) {
363 /* Create a popup shell to hold the main menu: */
364 GLMotif::PopupMenu* mainMenuPopup = new GLMotif::PopupMenu("MainMenuPopup",
365 Vrui::getWidgetManager());
366 mainMenuPopup->setTitle("AR Sandbox");
368 /* Create the main menu itself: */
369 GLMotif::Menu* mainMenu = new GLMotif::Menu("MainMenu", mainMenuPopup, false);
371 /* Create a button to pause topography updates: */
372 pauseUpdatesToggle = new GLMotif::ToggleButton("PauseUpdatesToggle", mainMenu, "Pause Topography");
373 pauseUpdatesToggle->setToggle(false);
374 pauseUpdatesToggle->getValueChangedCallbacks().add(this, &Sandbox::pauseUpdatesCallback);
376 if(waterTable != 0) {
377 /* Create a button to show the water control dialog: */
378 GLMotif::Button* showWaterControlDialogButton = new GLMotif::Button("ShowWaterControlDialogButton",
379 mainMenu, "Show Water Simulation Control");
380 showWaterControlDialogButton->getSelectCallbacks().add(this,
381 &Sandbox::showWaterControlDialogCallback);
384 /* Finish building the main menu: */
385 mainMenu->manageChild();
387 return mainMenuPopup;
390 GLMotif::PopupWindow* Sandbox::createWaterControlDialog(void) {
391 const GLMotif::StyleSheet& ss = *Vrui::getWidgetManager()->getStyleSheet();
393 /* Create a popup window shell: */
394 GLMotif::PopupWindow* waterControlDialogPopup = new GLMotif::PopupWindow("WaterControlDialogPopup",
395 Vrui::getWidgetManager(), "Water Simulation Control");
396 waterControlDialogPopup->setCloseButton(true);
397 waterControlDialogPopup->setResizableFlags(true, false);
398 waterControlDialogPopup->popDownOnClose();
400 GLMotif::RowColumn* waterControlDialog = new GLMotif::RowColumn("WaterControlDialog",
401 waterControlDialogPopup, false);
402 waterControlDialog->setOrientation(GLMotif::RowColumn::VERTICAL);
403 waterControlDialog->setPacking(GLMotif::RowColumn::PACK_TIGHT);
404 waterControlDialog->setNumMinorWidgets(2);
406 new GLMotif::Label("WaterSpeedLabel", waterControlDialog, "Speed");
408 waterSpeedSlider = new GLMotif::TextFieldSlider("WaterSpeedSlider", waterControlDialog, 8,
409 ss.fontHeight * 10.0f);
410 waterSpeedSlider->getTextField()->setFieldWidth(7);
411 waterSpeedSlider->getTextField()->setPrecision(4);
412 waterSpeedSlider->getTextField()->setFloatFormat(GLMotif::TextField::SMART);
413 waterSpeedSlider->setSliderMapping(GLMotif::TextFieldSlider::EXP10);
414 waterSpeedSlider->setValueRange(0.001, 10.0, 0.05);
415 waterSpeedSlider->getSlider()->addNotch(0.0f);
416 waterSpeedSlider->setValue(waterSpeed);
417 waterSpeedSlider->getValueChangedCallbacks().add(this, &Sandbox::waterSpeedSliderCallback);
419 new GLMotif::Label("WaterMaxStepsLabel", waterControlDialog, "Max Steps");
421 waterMaxStepsSlider = new GLMotif::TextFieldSlider("WaterMaxStepsSlider", waterControlDialog, 8,
422 ss.fontHeight * 10.0f);
423 waterMaxStepsSlider->getTextField()->setFieldWidth(7);
424 waterMaxStepsSlider->getTextField()->setPrecision(0);
425 waterMaxStepsSlider->getTextField()->setFloatFormat(GLMotif::TextField::FIXED);
426 waterMaxStepsSlider->setSliderMapping(GLMotif::TextFieldSlider::LINEAR);
427 waterMaxStepsSlider->setValueType(GLMotif::TextFieldSlider::UINT);
428 waterMaxStepsSlider->setValueRange(0, 200, 1);
429 waterMaxStepsSlider->setValue(waterMaxSteps);
430 waterMaxStepsSlider->getValueChangedCallbacks().add(this, &Sandbox::waterMaxStepsSliderCallback);
432 new GLMotif::Label("FrameRateLabel", waterControlDialog, "Frame Rate");
434 GLMotif::Margin* frameRateMargin = new GLMotif::Margin("FrameRateMargin", waterControlDialog,
435 false);
436 frameRateMargin->setAlignment(GLMotif::Alignment::LEFT);
438 frameRateTextField = new GLMotif::TextField("FrameRateTextField", frameRateMargin, 8);
439 frameRateTextField->setFieldWidth(7);
440 frameRateTextField->setPrecision(2);
441 frameRateTextField->setFloatFormat(GLMotif::TextField::FIXED);
442 frameRateTextField->setValue(0.0);
444 frameRateMargin->manageChild();
446 new GLMotif::Label("WaterAttenuationLabel", waterControlDialog, "Attenuation");
448 waterAttenuationSlider = new GLMotif::TextFieldSlider("WaterAttenuationSlider", waterControlDialog,
449 8, ss.fontHeight * 10.0f);
450 waterAttenuationSlider->getTextField()->setFieldWidth(7);
451 waterAttenuationSlider->getTextField()->setPrecision(5);
452 waterAttenuationSlider->getTextField()->setFloatFormat(GLMotif::TextField::SMART);
453 waterAttenuationSlider->setSliderMapping(GLMotif::TextFieldSlider::EXP10);
454 waterAttenuationSlider->setValueRange(0.001, 1.0, 0.01);
455 waterAttenuationSlider->getSlider()->addNotch(Math::log10(1.0 - double(
456 waterTable->getAttenuation())));
457 waterAttenuationSlider->setValue(1.0 - double(waterTable->getAttenuation()));
458 waterAttenuationSlider->getValueChangedCallbacks().add(this,
459 &Sandbox::waterAttenuationSliderCallback);
461 waterControlDialog->manageChild();
463 return waterControlDialogPopup;
466 namespace {
468 /****************
469 Helper functions:
470 ****************/
472 void printUsage(void) {
473 std::cout << "Usage: SARndbox [option 1] ... [option n]" << std::endl;
474 std::cout << " Options:" << std::endl;
475 std::cout << " -h" << std::endl;
476 std::cout << " Prints this help message" << std::endl;
477 std::cout << " -c <camera index>" << std::endl;
478 std::cout << " Selects the local 3D camera of the given index (0: first camera" << std::endl;
479 std::cout << " on USB bus)" << std::endl;
480 std::cout << " Default: 0" << std::endl;
481 std::cout << " -f <frame file name prefix>" << std::endl;
482 std::cout << " Reads a pre-recorded 3D video stream from a pair of color/depth" << std::endl;
483 std::cout << " files of the given file name prefix" << std::endl;
484 std::cout << " -s <scale factor>" << std::endl;
485 std::cout << " Scale factor from real sandbox to simulated terrain" << std::endl;
486 std::cout << " Default: 100.0 (1:100 scale, 1cm in sandbox is 1m in terrain" << std::endl;
487 std::cout << " -slf <sandbox layout file name>" << std::endl;
488 std::cout << " Loads the sandbox layout file of the given name" << std::endl;
489 std::cout << " Default: " << CONFIG_CONFIGDIR << '/' << CONFIG_DEFAULTBOXLAYOUTFILENAME <<
490 std::endl;
491 std::cout << " -er <min elevation> <max elevation>" << std::endl;
492 std::cout << " Sets the range of valid sand surface elevations relative to the" << std::endl;
493 std::cout << " ground plane in cm" << std::endl;
494 std::cout << " Default: Range of elevation color map" << std::endl;
495 std::cout << " -hmp <x> <y> <z> <offset>" << std::endl;
496 std::cout << " Sets an explicit base plane equation to use for height color mapping" <<
497 std::endl;
498 std::cout << " -nas <num averaging slots>" << std::endl;
499 std::cout << " Sets the number of averaging slots in the frame filter; latency is" <<
500 std::endl;
501 std::cout << " <num averaging slots> * 1/30 s" << std::endl;
502 std::cout << " Default: 30" << std::endl;
503 std::cout << " -sp <min num samples> <max variance>" << std::endl;
504 std::cout << " Sets the frame filter parameters minimum number of valid samples" << std::endl;
505 std::cout << " and maximum sample variance before convergence" << std::endl;
506 std::cout << " Default: 10 2" << std::endl;
507 std::cout << " -he <hysteresis envelope>" << std::endl;
508 std::cout << " Sets the size of the hysteresis envelope used for jitter removal" << std::endl;
509 std::cout << " Default: 0.1" << std::endl;
510 std::cout << " -wts <water grid width> <water grid height>" << std::endl;
511 std::cout << " Sets the width and height of the water flow simulation grid" << std::endl;
512 std::cout << " Default: 640 480" << std::endl;
513 std::cout << " -ws <water speed> <water max steps>" << std::endl;
514 std::cout << " Sets the relative speed of the water simulation and the maximum" << std::endl;
515 std::cout << " number of simulation steps per frame" << std::endl;
516 std::cout << " Default: 1.0 30" << std::endl;
517 std::cout << " -rer <min rain elevation> <max rain elevation>" << std::endl;
518 std::cout << " Sets the elevation range of the rain cloud level relative to the" << std::endl;
519 std::cout << " ground plane in cm" << std::endl;
520 std::cout << " Default: Above range of elevation color map" << std::endl;
521 std::cout << " -rs <rain strength>" << std::endl;
522 std::cout << " Sets the strength of global or local rainfall in cm/s" << std::endl;
523 std::cout << " Default: 0.25" << std::endl;
524 std::cout << " -evr <evaporation rate>" << std::endl;
525 std::cout << " Water evaporation rate in cm/s" << std::endl;
526 std::cout << " Default: 0.0" << std::endl;
527 std::cout << " -dds <DEM distance scale>" << std::endl;
528 std::cout << " DEM matching distance scale factor in cm" << std::endl;
529 std::cout << " Default: 1.0" << std::endl;
530 std::cout << " -wi <window index>" << std::endl;
531 std::cout << " Sets the zero-based index of the display window to which the" << std::endl;
532 std::cout << " following rendering settings are applied" << std::endl;
533 std::cout << " Default: 0" << std::endl;
534 std::cout << " -fpv [projector transform file name]" << std::endl;
535 std::cout << " Fixes the navigation transformation so that Kinect camera and" << std::endl;
536 std::cout << " projector are aligned, as defined by the projector transform file" << std::endl;
537 std::cout << " of the given name" << std::endl;
538 std::cout << " Default projector transform file name: " << CONFIG_CONFIGDIR << '/' <<
539 CONFIG_DEFAULTPROJECTIONMATRIXFILENAME << std::endl;
540 std::cout << " -nhs" << std::endl;
541 std::cout << " Disables hill shading" << std::endl;
542 std::cout << " -uhs" << std::endl;
543 std::cout << " Enables hill shading" << std::endl;
544 std::cout << " -ns" << std::endl;
545 std::cout << " Disables shadows" << std::endl;
546 std::cout << " -us" << std::endl;
547 std::cout << " Enables shadows" << std::endl;
548 std::cout << " -nhm" << std::endl;
549 std::cout << " Disables elevation color mapping" << std::endl;
550 std::cout << " -uhm [elevation color map file name]" << std::endl;
551 std::cout << " Enables elevation color mapping and loads the elevation color map from" <<
552 std::endl;
553 std::cout << " the file of the given name" << std::endl;
554 std::cout << " Default elevation color map file name: " << CONFIG_CONFIGDIR << '/' <<
555 CONFIG_DEFAULTHEIGHTCOLORMAPFILENAME << std::endl;
556 std::cout << " -ncl" << std::endl;
557 std::cout << " Disables topographic contour lines" << std::endl;
558 std::cout << " -ucl [contour line spacing]" << std::endl;
559 std::cout << " Enables topographic contour lines and sets the elevation distance between" <<
560 std::endl;
561 std::cout << " adjacent contour lines to the given value in cm" << std::endl;
562 std::cout << " Default contour line spacing: 0.75" << std::endl;
563 std::cout << " -rws" << std::endl;
564 std::cout << " Renders water surface as geometric surface" << std::endl;
565 std::cout << " -rwt" << std::endl;
566 std::cout << " Renders water surface as texture" << std::endl;
567 std::cout << " -wo <water opacity>" << std::endl;
568 std::cout << " Sets the water depth at which water appears opaque in cm" << std::endl;
569 std::cout << " Default: 2.0" << std::endl;
570 std::cout << " -cp <control pipe name>" << std::endl;
571 std::cout << " Sets the name of a named POSIX pipe from which to read control commands" <<
572 std::endl;
577 Sandbox::Sandbox(int& argc, char**& argv)
578 : Vrui::Application(argc, argv),
579 camera(0), pixelDepthCorrection(0),
580 frameFilter(0), pauseUpdates(false),
581 depthImageRenderer(0),
582 waterTable(0),
583 contourLineExtractor(0),
584 handExtractor(0),
585 addWaterFunction(0),
586 addWaterFunctionRegistered(false),
587 sun(0),
588 activeDem(0),
589 mainMenu(0), pauseUpdatesToggle(0), waterControlDialog(0),
590 waterSpeedSlider(0), waterMaxStepsSlider(0), frameRateTextField(0), waterAttenuationSlider(0),
591 controlPipeFd(-1) {
592 /* Read the sandbox's default configuration parameters: */
593 std::string sandboxConfigFileName = CONFIG_CONFIGDIR;
594 sandboxConfigFileName.push_back('/');
595 sandboxConfigFileName.append(CONFIG_DEFAULTCONFIGFILENAME);
596 Misc::ConfigurationFile sandboxConfigFile(sandboxConfigFileName.c_str());
597 Misc::ConfigurationFileSection cfg = sandboxConfigFile.getSection("/SARndbox");
598 unsigned int cameraIndex = cfg.retrieveValue<int>("./cameraIndex", 0);
599 std::string cameraConfiguration = cfg.retrieveString("./cameraConfiguration", "Camera");
600 double scale = cfg.retrieveValue<double>("./scaleFactor", 100.0);
601 std::string sandboxLayoutFileName = CONFIG_CONFIGDIR;
602 sandboxLayoutFileName.push_back('/');
603 sandboxLayoutFileName.append(CONFIG_DEFAULTBOXLAYOUTFILENAME);
604 sandboxLayoutFileName = cfg.retrieveString("./sandboxLayoutFileName", sandboxLayoutFileName);
605 Math::Interval<double> elevationRange =
606 cfg.retrieveValue<Math::Interval<double> >("./elevationRange", Math::Interval<double>(-1000.0,
607 1000.0));
608 bool haveHeightMapPlane = cfg.hasTag("./heightMapPlane");
609 Plane heightMapPlane;
610 if(haveHeightMapPlane)
611 heightMapPlane = cfg.retrieveValue<Plane>("./heightMapPlane");
612 unsigned int numAveragingSlots = cfg.retrieveValue<unsigned int>("./numAveragingSlots", 30);
613 unsigned int minNumSamples = cfg.retrieveValue<unsigned int>("./minNumSamples", 10);
614 unsigned int maxVariance = cfg.retrieveValue<unsigned int>("./maxVariance", 2);
615 float hysteresis = cfg.retrieveValue<float>("./hysteresis", 0.1f);
616 Misc::FixedArray<unsigned int, 2> wtSize;
617 wtSize[0] = 640;
618 wtSize[1] = 480;
619 wtSize = cfg.retrieveValue<Misc::FixedArray<unsigned int, 2> >("./waterTableSize", wtSize);
620 waterSpeed = cfg.retrieveValue<double>("./waterSpeed", 1.0);
621 waterMaxSteps = cfg.retrieveValue<unsigned int>("./waterMaxSteps", 30U);
622 Math::Interval<double> rainElevationRange =
623 cfg.retrieveValue<Math::Interval<double> >("./rainElevationRange", Math::Interval<double>(-1000.0,
624 1000.0));
625 rainStrength = cfg.retrieveValue<GLfloat>("./rainStrength", 0.25f);
626 double evaporationRate = cfg.retrieveValue<double>("./evaporationRate", 0.0);
627 float demDistScale = cfg.retrieveValue<float>("./demDistScale", 1.0f);
628 std::string controlPipeName = cfg.retrieveString("./controlPipeName", "");
630 /* Process command line parameters: */
631 bool printHelp = false;
632 const char* frameFilePrefix = 0;
633 const char* kinectServerName = 0;
634 int windowIndex = 0;
635 renderSettings.push_back(RenderSettings());
636 for(int i = 1; i < argc; ++i) {
637 if(argv[i][0] == '-') {
638 if(strcasecmp(argv[i] + 1, "h") == 0)
639 printHelp = true;
640 else if(strcasecmp(argv[i] + 1, "c") == 0) {
641 ++i;
642 cameraIndex = atoi(argv[i]);
643 } else if(strcasecmp(argv[i] + 1, "f") == 0) {
644 ++i;
645 frameFilePrefix = argv[i];
646 } else if(strcasecmp(argv[i] + 1, "p") == 0) {
647 ++i;
648 kinectServerName = argv[i];
649 } else if(strcasecmp(argv[i] + 1, "s") == 0) {
650 ++i;
651 scale = atof(argv[i]);
652 } else if(strcasecmp(argv[i] + 1, "slf") == 0) {
653 ++i;
654 sandboxLayoutFileName = argv[i];
655 } else if(strcasecmp(argv[i] + 1, "er") == 0) {
656 ++i;
657 double elevationMin = atof(argv[i]);
658 ++i;
659 double elevationMax = atof(argv[i]);
660 elevationRange = Math::Interval<double>(elevationMin, elevationMax);
661 } else if(strcasecmp(argv[i] + 1, "hmp") == 0) {
662 /* Read height mapping plane coefficients: */
663 haveHeightMapPlane = true;
664 double hmp[4];
665 for(int j = 0; j < 4; ++j) {
666 ++i;
667 hmp[j] = atof(argv[i]);
669 heightMapPlane = Plane(Plane::Vector(hmp), hmp[3]);
670 heightMapPlane.normalize();
671 } else if(strcasecmp(argv[i] + 1, "nas") == 0) {
672 ++i;
673 numAveragingSlots = atoi(argv[i]);
674 } else if(strcasecmp(argv[i] + 1, "sp") == 0) {
675 ++i;
676 minNumSamples = atoi(argv[i]);
677 ++i;
678 maxVariance = atoi(argv[i]);
679 } else if(strcasecmp(argv[i] + 1, "he") == 0) {
680 ++i;
681 hysteresis = float(atof(argv[i]));
682 } else if(strcasecmp(argv[i] + 1, "wts") == 0) {
683 for(int j = 0; j < 2; ++j) {
684 ++i;
685 wtSize[j] = (unsigned int)(atoi(argv[i]));
687 } else if(strcasecmp(argv[i] + 1, "ws") == 0) {
688 ++i;
689 waterSpeed = atof(argv[i]);
690 ++i;
691 waterMaxSteps = atoi(argv[i]);
692 } else if(strcasecmp(argv[i] + 1, "rer") == 0) {
693 ++i;
694 double rainElevationMin = atof(argv[i]);
695 ++i;
696 double rainElevationMax = atof(argv[i]);
697 rainElevationRange = Math::Interval<double>(rainElevationMin, rainElevationMax);
698 } else if(strcasecmp(argv[i] + 1, "rs") == 0) {
699 ++i;
700 rainStrength = GLfloat(atof(argv[i]));
701 } else if(strcasecmp(argv[i] + 1, "evr") == 0) {
702 ++i;
703 evaporationRate = atof(argv[i]);
704 } else if(strcasecmp(argv[i] + 1, "dds") == 0) {
705 ++i;
706 demDistScale = float(atof(argv[i]));
707 } else if(strcasecmp(argv[i] + 1, "wi") == 0) {
708 ++i;
709 windowIndex = atoi(argv[i]);
711 /* Extend the list of render settings if an index beyond the end is selected: */
712 while(int(renderSettings.size()) <= windowIndex)
713 renderSettings.push_back(renderSettings.back());
715 /* Disable fixed projector view on the new render settings: */
716 renderSettings.back().fixProjectorView = false;
717 } else if(strcasecmp(argv[i] + 1, "fpv") == 0) {
718 renderSettings.back().fixProjectorView = true;
719 if(i + 1 < argc && argv[i + 1][0] != '-') {
720 /* Load the projector transformation file specified in the next argument: */
721 ++i;
722 renderSettings.back().loadProjectorTransform(argv[i]);
724 } else if(strcasecmp(argv[i] + 1, "nhs") == 0)
725 renderSettings.back().hillshade = false;
726 else if(strcasecmp(argv[i] + 1, "uhs") == 0)
727 renderSettings.back().hillshade = true;
728 else if(strcasecmp(argv[i] + 1, "ns") == 0)
729 renderSettings.back().useShadows = false;
730 else if(strcasecmp(argv[i] + 1, "us") == 0)
731 renderSettings.back().useShadows = true;
732 else if(strcasecmp(argv[i] + 1, "nhm") == 0) {
733 delete renderSettings.back().elevationColorMap;
734 renderSettings.back().elevationColorMap = 0;
735 } else if(strcasecmp(argv[i] + 1, "uhm") == 0) {
736 if(i + 1 < argc && argv[i + 1][0] != '-') {
737 /* Load the height color map file specified in the next argument: */
738 ++i;
739 renderSettings.back().loadHeightMap(argv[i]);
740 } else {
741 /* Load the default height color map: */
742 renderSettings.back().loadHeightMap(CONFIG_DEFAULTHEIGHTCOLORMAPFILENAME);
744 } else if(strcasecmp(argv[i] + 1, "ncl") == 0)
745 renderSettings.back().useContourLines = false;
746 else if(strcasecmp(argv[i] + 1, "ucl") == 0) {
747 renderSettings.back().useContourLines = true;
748 if(i + 1 < argc && argv[i + 1][0] != '-') {
749 /* Read the contour line spacing: */
750 ++i;
751 renderSettings.back().contourLineSpacing = GLfloat(atof(argv[i]));
753 } else if(strcasecmp(argv[i] + 1, "rws") == 0)
754 renderSettings.back().renderWaterSurface = true;
755 else if(strcasecmp(argv[i] + 1, "rwt") == 0)
756 renderSettings.back().renderWaterSurface = false;
757 else if(strcasecmp(argv[i] + 1, "wo") == 0) {
758 ++i;
759 renderSettings.back().waterOpacity = GLfloat(atof(argv[i]));
760 } else if(strcasecmp(argv[i] + 1, "cp") == 0) {
761 ++i;
762 controlPipeName = argv[i];
763 } else
764 std::cerr << "Ignoring unrecognized command line switch " << argv[i] << std::endl;
768 /* Print usage help if requested: */
769 if(printHelp)
770 printUsage();
772 if(frameFilePrefix != 0) {
773 /* Open the selected pre-recorded 3D video files: */
774 std::string colorFileName = frameFilePrefix;
775 colorFileName.append(".color");
776 std::string depthFileName = frameFilePrefix;
777 depthFileName.append(".depth");
778 camera = new Kinect::FileFrameSource(Vrui::openFile(colorFileName.c_str()),
779 Vrui::openFile(depthFileName.c_str()));
780 } else if(kinectServerName != 0) {
781 /* Split the server name into host name and port: */
782 const char* colonPtr = 0;
783 for(const char* snPtr = kinectServerName; *snPtr != '\0'; ++snPtr)
784 if(*snPtr == ':')
785 colonPtr = snPtr;
786 std::string hostName;
787 int port;
788 if(colonPtr != 0) {
789 /* Extract host name and port: */
790 hostName = std::string(kinectServerName, colonPtr);
791 port = atoi(colonPtr + 1);
792 } else {
793 /* Use complete host name and default port: */
794 hostName = kinectServerName;
795 port = 26000;
798 /* Open a multiplexed frame source for the given server host name and port number: */
799 Kinect::MultiplexedFrameSource* source = Kinect::MultiplexedFrameSource::create(
800 Cluster::openTCPPipe(Vrui::getClusterMultiplexer(), hostName.c_str(), port));
802 /* Use the server's first component stream as the camera device: */
803 camera = source->getStream(0);
804 } else {
805 /* Open the 3D camera device of the selected index: */
806 Kinect::DirectFrameSource* realCamera = Kinect::openDirectFrameSource(cameraIndex);
807 Misc::ConfigurationFileSection cameraConfigurationSection = cfg.getSection(
808 cameraConfiguration.c_str());
809 realCamera->configure(cameraConfigurationSection);
810 camera = realCamera;
812 for(int i = 0; i < 2; ++i)
813 frameSize[i] = camera->getActualFrameSize(Kinect::FrameSource::DEPTH)[i];
815 /* Get the camera's per-pixel depth correction parameters and evaluate it on the depth frame's pixel grid: */
816 Kinect::FrameSource::DepthCorrection* depthCorrection = camera->getDepthCorrectionParameters();
817 if(depthCorrection != 0) {
818 pixelDepthCorrection = depthCorrection->getPixelCorrection(frameSize);
819 delete depthCorrection;
820 } else {
821 /* Create dummy per-pixel depth correction parameters: */
822 pixelDepthCorrection = new PixelDepthCorrection[frameSize[1]*frameSize[0]];
823 PixelDepthCorrection* pdcPtr = pixelDepthCorrection;
824 for(unsigned int y = 0; y < frameSize[1]; ++y)
825 for(unsigned int x = 0; x < frameSize[0]; ++x, ++pdcPtr) {
826 pdcPtr->scale = 1.0f;
827 pdcPtr->offset = 0.0f;
831 /* Get the camera's intrinsic parameters: */
832 cameraIps = camera->getIntrinsicParameters();
834 /* Read the sandbox layout file: */
835 Geometry::Plane<double, 3> basePlane;
836 Geometry::Point<double, 3> basePlaneCorners[4];
838 IO::ValueSource layoutSource(Vrui::openFile(sandboxLayoutFileName.c_str()));
839 layoutSource.skipWs();
841 /* Read the base plane equation: */
842 std::string s = layoutSource.readLine();
843 basePlane = Misc::ValueCoder<Geometry::Plane<double, 3> >::decode(s.c_str(),
844 s.c_str() + s.length());
845 basePlane.normalize();
847 /* Read the corners of the base quadrilateral and project them into the base plane: */
848 for(int i = 0; i < 4; ++i) {
849 layoutSource.skipWs();
850 s = layoutSource.readLine();
851 basePlaneCorners[i] = basePlane.project(Misc::ValueCoder<Geometry::Point<double, 3> >::decode(
852 s.c_str(), s.c_str() + s.length()));
856 /* Limit the valid elevation range to the intersection of the extents of all height color maps: */
857 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
858 rsIt != renderSettings.end(); ++rsIt)
859 if(rsIt->elevationColorMap != 0) {
860 Math::Interval<double> mapRange(rsIt->elevationColorMap->getScalarRangeMin(),
861 rsIt->elevationColorMap->getScalarRangeMax());
862 elevationRange.intersectInterval(mapRange);
865 /* Scale all sizes by the given scale factor: */
866 double sf = scale / 100.0; // Scale factor from cm to final units
867 for(int i = 0; i < 3; ++i)
868 for(int j = 0; j < 4; ++j)
869 cameraIps.depthProjection.getMatrix()(i, j) *= sf;
870 basePlane = Geometry::Plane<double, 3>(basePlane.getNormal(), basePlane.getOffset() * sf);
871 for(int i = 0; i < 4; ++i)
872 for(int j = 0; j < 3; ++j)
873 basePlaneCorners[i][j] *= sf;
874 if(elevationRange != Math::Interval<double>::full)
875 elevationRange *= sf;
876 if(rainElevationRange != Math::Interval<double>::full)
877 rainElevationRange *= sf;
878 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
879 rsIt != renderSettings.end(); ++rsIt) {
880 if(rsIt->elevationColorMap != 0)
881 rsIt->elevationColorMap->setScalarRange(rsIt->elevationColorMap->getScalarRangeMin()*sf,
882 rsIt->elevationColorMap->getScalarRangeMax()*sf);
883 rsIt->contourLineSpacing *= sf;
884 rsIt->waterOpacity /= sf;
885 for(int i = 0; i < 4; ++i)
886 rsIt->projectorTransform.getMatrix()(i, 3) *= sf;
888 rainStrength *= sf;
889 evaporationRate *= sf;
890 demDistScale *= sf;
892 /* Create the frame filter object: */
893 frameFilter = new FrameFilter(frameSize, numAveragingSlots, pixelDepthCorrection,
894 cameraIps.depthProjection, basePlane);
895 frameFilter->setValidElevationInterval(cameraIps.depthProjection, basePlane,
896 elevationRange.getMin(), elevationRange.getMax());
897 frameFilter->setStableParameters(minNumSamples, maxVariance);
898 frameFilter->setHysteresis(hysteresis);
899 frameFilter->setSpatialFilter(true);
900 frameFilter->setOutputFrameFunction(Misc::createFunctionCall(this,
901 &Sandbox::receiveFilteredFrame));
903 if(waterSpeed > 0.0) {
904 /* Create the hand extractor object: */
905 handExtractor = new HandExtractor(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 contourLineExtractor = new ContourLineExtractor(frameSize, pixelDepthCorrection, cameraIps.depthProjection);
934 contourLineExtractor->setBasePlane(basePlane);
936 /* Calculate a bounding box around all potential surfaces: */
937 bbox = Box::empty;
938 for(int i = 0; i < 4; ++i) {
939 bbox.addPoint(basePlaneCorners[i] + basePlane.getNormal()*elevationRange.getMin());
940 bbox.addPoint(basePlaneCorners[i] + basePlane.getNormal()*elevationRange.getMax());
943 if(waterSpeed > 0.0) {
944 /* Initialize the water flow simulator: */
945 waterTable = new WaterTable2(wtSize[0], wtSize[1], depthImageRenderer, basePlaneCorners);
946 waterTable->setElevationRange(elevationRange.getMin(), rainElevationRange.getMax());
947 waterTable->setWaterDeposit(evaporationRate);
949 /* Register a render function with the water table: */
950 addWaterFunction = Misc::createFunctionCall(this, &Sandbox::addWater);
951 waterTable->addRenderFunction(addWaterFunction);
952 addWaterFunctionRegistered = true;
955 /* Initialize all surface renderers: */
956 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
957 rsIt != renderSettings.end(); ++rsIt) {
958 /* Calculate the texture mapping plane for this renderer's height map: */
959 if(rsIt->elevationColorMap != 0) {
960 if(haveHeightMapPlane)
961 rsIt->elevationColorMap->calcTexturePlane(heightMapPlane);
962 else
963 rsIt->elevationColorMap->calcTexturePlane(depthImageRenderer);
966 /* Initialize the surface renderer: */
967 rsIt->surfaceRenderer = new SurfaceRenderer(depthImageRenderer);
968 rsIt->surfaceRenderer->setDrawContourLines(rsIt->useContourLines);
969 rsIt->surfaceRenderer->setContourLineDistance(rsIt->contourLineSpacing);
970 rsIt->surfaceRenderer->setElevationColorMap(rsIt->elevationColorMap);
971 rsIt->surfaceRenderer->setIlluminate(rsIt->hillshade);
972 if(waterTable != 0) {
973 if(rsIt->renderWaterSurface) {
974 /* Create a water renderer: */
975 rsIt->waterRenderer = new WaterRenderer(waterTable);
976 } else {
977 rsIt->surfaceRenderer->setWaterTable(waterTable);
978 rsIt->surfaceRenderer->setAdvectWaterTexture(true);
979 rsIt->surfaceRenderer->setWaterOpacity(rsIt->waterOpacity);
982 rsIt->surfaceRenderer->setDemDistScale(demDistScale);
985 #if 0
986 /* Create a fixed-position light source: */
987 sun = Vrui::getLightsourceManager()->createLightsource(true);
988 for(int i = 0; i < Vrui::getNumViewers(); ++i)
989 Vrui::getViewer(i)->setHeadlightState(false);
990 sun->enable();
991 sun->getLight().position = GLLight::Position(1, 0, 1, 0);
992 #endif
994 /* Create the GUI: */
995 mainMenu = createMainMenu();
996 Vrui::setMainMenu(mainMenu);
997 if(waterTable != 0)
998 waterControlDialog = createWaterControlDialog();
1000 /* Initialize the custom tool classes: */
1001 GlobalWaterTool::initClass(*Vrui::getToolManager());
1002 LocalWaterTool::initClass(*Vrui::getToolManager());
1003 DEMTool::initClass(*Vrui::getToolManager());
1004 if(waterTable != 0)
1005 BathymetrySaverTool::initClass(waterTable, *Vrui::getToolManager());
1006 addEventTool("Pause Topography", 0, 0);
1008 if(!controlPipeName.empty()) {
1009 /* Open the control pipe in non-blocking mode: */
1010 controlPipeFd = open(controlPipeName.c_str(), O_RDONLY | O_NONBLOCK);
1011 if(controlPipeFd < 0)
1012 std::cerr << "Unable to open control pipe " << controlPipeName << "; ignoring" << std::endl;
1015 /* Inhibit the screen saver: */
1016 Vrui::inhibitScreenSaver();
1018 /* Set the linear unit to support proper scaling: */
1019 Vrui::getCoordinateManager()->setUnit(Geometry::LinearUnit(Geometry::LinearUnit::METER,
1020 scale / 100.0));
1023 Sandbox::~Sandbox(void) {
1024 /* Stop streaming depth frames: */
1025 camera->stopStreaming();
1026 delete camera;
1027 delete frameFilter;
1029 /* Delete helper objects: */
1030 delete waterTable;
1031 delete depthImageRenderer;
1032 delete contourLineExtractor;
1033 delete handExtractor;
1034 delete addWaterFunction;
1035 delete[] pixelDepthCorrection;
1037 delete mainMenu;
1038 delete waterControlDialog;
1040 close(controlPipeFd);
1043 void Sandbox::toolDestructionCallback(Vrui::ToolManager::ToolDestructionCallbackData* cbData) {
1044 /* Check if the destroyed tool is the active DEM tool: */
1045 if(activeDem == dynamic_cast<DEM*>(cbData->tool)) {
1046 /* Deactivate the active DEM tool: */
1047 activeDem = 0;
1051 namespace {
1053 /****************
1054 Helper functions:
1055 ****************/
1057 std::vector<std::string> tokenizeLine(const char*& buffer) {
1058 std::vector<std::string> result;
1060 /* Skip initial whitespace but not end-of-line: */
1061 const char* bPtr = buffer;
1062 while(*bPtr != '\0' && *bPtr != '\n' && isspace(*bPtr))
1063 ++bPtr;
1065 /* Extract white-space separated tokens until a newline or end-of-string are encountered: */
1066 while(*bPtr != '\0' && *bPtr != '\n') {
1067 /* Remember the start of the current token: */
1068 const char* tokenStart = bPtr;
1070 /* Find the end of the current token: */
1071 while(*bPtr != '\0' && !isspace(*bPtr))
1072 ++bPtr;
1074 /* Extract the token: */
1075 result.push_back(std::string(tokenStart, bPtr));
1077 /* Skip whitespace but not end-of-line: */
1078 while(*bPtr != '\0' && *bPtr != '\n' && isspace(*bPtr))
1079 ++bPtr;
1082 /* Skip end-of-line: */
1083 if(*bPtr == '\n')
1084 ++bPtr;
1086 /* Set the start of the next line and return the token list: */
1087 buffer = bPtr;
1088 return result;
1091 bool isToken(const std::string& token, const char* pattern) {
1092 return strcasecmp(token.c_str(), pattern) == 0;
1097 void Sandbox::frame(void) {
1098 /* Check if the filtered frame has been updated: */
1099 if(filteredFrames.lockNewValue()) {
1100 /* Update the depth image renderer's depth image: */
1101 depthImageRenderer->setDepthImage(filteredFrames.getLockedValue());
1104 if(contourLineExtractor != 0) {
1105 /* Lock the most recent extracted contour line list: */
1106 contourLineExtractor->lockNewExtractedContourLines();
1109 if(handExtractor != 0) {
1110 /* Lock the most recent extracted hand list: */
1111 handExtractor->lockNewExtractedHands();
1113 #if 0
1115 /* Register/unregister the rain rendering function based on whether hands have been detected: */
1116 bool registerWaterFunction = !handExtractor->getLockedExtractedHands().empty();
1117 if(addWaterFunctionRegistered != registerWaterFunction) {
1118 if(registerWaterFunction)
1119 waterTable->addRenderFunction(addWaterFunction);
1120 else
1121 waterTable->removeRenderFunction(addWaterFunction);
1122 addWaterFunctionRegistered = registerWaterFunction;
1125 #endif
1128 /* Update all surface renderers: */
1129 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1130 rsIt != renderSettings.end(); ++rsIt)
1131 rsIt->surfaceRenderer->setAnimationTime(Vrui::getApplicationTime());
1133 /* Check if there is a control command on the control pipe: */
1134 if(controlPipeFd >= 0) {
1135 /* Try reading a chunk of data (will fail with EAGAIN if no data due to non-blocking access): */
1136 char commandBuffer[1024];
1137 ssize_t readResult = read(controlPipeFd, commandBuffer, sizeof(commandBuffer) - 1);
1138 if(readResult > 0) {
1139 commandBuffer[readResult] = '\0';
1141 /* Extract commands line-by-line: */
1142 const char* cPtr = commandBuffer;
1143 while(*cPtr != '\0') {
1144 /* Split the current line into tokens and skip empty lines: */
1145 std::vector<std::string> tokens = tokenizeLine(cPtr);
1146 if(tokens.empty())
1147 continue;
1149 /* Parse the command: */
1150 if(isToken(tokens[0], "waterSpeed")) {
1151 if(tokens.size() == 2) {
1152 waterSpeed = atof(tokens[1].c_str());
1153 if(waterSpeedSlider != 0)
1154 waterSpeedSlider->setValue(waterSpeed);
1155 } else
1156 std::cerr << "Wrong number of arguments for waterSpeed control pipe command" << std::endl;
1157 } else if(isToken(tokens[0], "waterMaxSteps")) {
1158 if(tokens.size() == 2) {
1159 waterMaxSteps = atoi(tokens[1].c_str());
1160 if(waterMaxStepsSlider != 0)
1161 waterMaxStepsSlider->setValue(waterMaxSteps);
1162 } else
1163 std::cerr << "Wrong number of arguments for waterMaxSteps control pipe command" << std::endl;
1164 } else if(isToken(tokens[0], "waterAttenuation")) {
1165 if(tokens.size() == 2) {
1166 double attenuation = atof(tokens[1].c_str());
1167 if(waterTable != 0)
1168 waterTable->setAttenuation(GLfloat(1.0 - attenuation));
1169 if(waterAttenuationSlider != 0)
1170 waterAttenuationSlider->setValue(attenuation);
1171 } else
1172 std::cerr << "Wrong number of arguments for waterAttenuation control pipe command" << std::endl;
1173 } else if(isToken(tokens[0], "colorMap")) {
1174 if(tokens.size() == 2) {
1175 try {
1176 /* Update all height color maps: */
1177 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1178 rsIt != renderSettings.end(); ++rsIt)
1179 if(rsIt->elevationColorMap != 0)
1180 rsIt->elevationColorMap->load(tokens[1].c_str());
1181 } catch(const std::runtime_error& err) {
1182 std::cerr << "Cannot read height color map " << tokens[1] << " due to exception " << err.what() <<
1183 std::endl;
1185 } else
1186 std::cerr << "Wrong number of arguments for colorMap control pipe command" << std::endl;
1187 } else if(isToken(tokens[0], "heightMapPlane")) {
1188 if(tokens.size() == 5) {
1189 /* Read the height map plane equation: */
1190 double hmp[4];
1191 for(int i = 0; i < 4; ++i)
1192 hmp[i] = atof(tokens[1 + i].c_str());
1193 Plane heightMapPlane = Plane(Plane::Vector(hmp), hmp[3]);
1194 heightMapPlane.normalize();
1196 /* Override the height mapping planes of all elevation color maps: */
1197 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1198 rsIt != renderSettings.end(); ++rsIt)
1199 if(rsIt->elevationColorMap != 0)
1200 rsIt->elevationColorMap->calcTexturePlane(heightMapPlane);
1201 } else
1202 std::cerr << "Wrong number of arguments for heightMapPlane control pipe command" << std::endl;
1203 } else if(isToken(tokens[0], "dippingBed")) {
1204 if(tokens.size() == 2 && isToken(tokens[1], "off")) {
1205 /* Disable dipping bed rendering on all surface renderers: */
1206 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1207 rsIt != renderSettings.end(); ++rsIt)
1208 rsIt->surfaceRenderer->setDrawDippingBed(false);
1209 } else if(tokens.size() == 5) {
1210 /* Read the dipping bed plane equation: */
1211 GLfloat dbp[4];
1212 for(int i = 0; i < 4; ++i)
1213 dbp[i] = GLfloat(atof(tokens[1 + i].c_str()));
1214 SurfaceRenderer::Plane dippingBedPlane = SurfaceRenderer::Plane(SurfaceRenderer::Plane::Vector(
1215 dbp), dbp[3]);
1216 dippingBedPlane.normalize();
1218 /* Enable dipping bed rendering and set the dipping bed plane equation on all surface renderers: */
1219 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1220 rsIt != renderSettings.end(); ++rsIt) {
1221 rsIt->surfaceRenderer->setDrawDippingBed(true);
1222 rsIt->surfaceRenderer->setDippingBedPlane(dippingBedPlane);
1224 } else
1225 std::cerr << "Wrong number of arguments for dippingBed control pipe command" << std::endl;
1226 } else if(isToken(tokens[0], "foldedDippingBed")) {
1227 if(tokens.size() == 6) {
1228 /* Read the dipping bed coefficients: */
1229 GLfloat dbc[5];
1230 for(int i = 0; i < 5; ++i)
1231 dbc[i] = GLfloat(atof(tokens[1 + i].c_str()));
1233 /* Enable dipping bed rendering and set the dipping bed coefficients on all surface renderers: */
1234 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1235 rsIt != renderSettings.end(); ++rsIt) {
1236 rsIt->surfaceRenderer->setDrawDippingBed(true);
1237 rsIt->surfaceRenderer->setDippingBedCoeffs(dbc);
1239 } else
1240 std::cerr << "Wrong number of arguments for foldedDippingBed control pipe command" << std::endl;
1241 } else if(isToken(tokens[0], "dippingBedThickness")) {
1242 if(tokens.size() == 2) {
1243 /* Read the dipping bed thickness: */
1244 float dippingBedThickness = float(atof(tokens[1].c_str()));
1246 /* Set the dipping bed thickness on all surface renderers: */
1247 for(std::vector<RenderSettings>::iterator rsIt = renderSettings.begin();
1248 rsIt != renderSettings.end(); ++rsIt)
1249 rsIt->surfaceRenderer->setDippingBedThickness(dippingBedThickness);
1250 } else
1251 std::cerr << "Wrong number of arguments for dippingBedThickness control pipe command" << std::endl;
1252 } else
1253 std::cerr << "Unrecognized control pipe command " << tokens[0] << std::endl;
1258 if(frameRateTextField != 0 && Vrui::getWidgetManager()->isVisible(waterControlDialog)) {
1259 /* Update the frame rate display: */
1260 frameRateTextField->setValue(1.0 / Vrui::getCurrentFrameTime());
1263 if(pauseUpdates)
1264 Vrui::scheduleUpdate(Vrui::getApplicationTime() + 1.0 / 30.0);
1267 void Sandbox::display(GLContextData& contextData) const {
1268 /* Get the data item: */
1269 DataItem* dataItem = contextData.retrieveDataItem<DataItem>(this);
1271 /* Get the rendering settings for this window: */
1272 const Vrui::DisplayState& ds = Vrui::getDisplayState(contextData);
1273 const Vrui::VRWindow* window = ds.window;
1274 int windowIndex;
1275 for(windowIndex = 0; windowIndex < Vrui::getNumWindows()
1276 && window != Vrui::getWindow(windowIndex); ++windowIndex)
1278 const RenderSettings& rs = windowIndex < int(renderSettings.size()) ? renderSettings[windowIndex] :
1279 renderSettings.back();
1281 /* Check if the water simulation state needs to be updated: */
1282 if(waterTable != 0 && dataItem->waterTableTime != Vrui::getApplicationTime()) {
1283 /* Update the water table's bathymetry grid: */
1284 waterTable->updateBathymetry(contextData);
1286 /* Run the water flow simulation's main pass: */
1287 GLfloat totalTimeStep = GLfloat(Vrui::getFrameTime() * waterSpeed);
1288 unsigned int numSteps = 0;
1289 while(numSteps < waterMaxSteps - 1U && totalTimeStep > 1.0e-8f) {
1290 /* Run with a self-determined time step to maintain stability: */
1291 waterTable->setMaxStepSize(totalTimeStep);
1292 GLfloat timeStep = waterTable->runSimulationStep(false, contextData);
1293 totalTimeStep -= timeStep;
1294 ++numSteps;
1296 #if 0
1297 if(totalTimeStep > 1.0e-8f) {
1298 std::cout << '.' << std::flush;
1299 /* Force the final step to avoid simulation slow-down: */
1300 waterTable->setMaxStepSize(totalTimeStep);
1301 GLfloat timeStep = waterTable->runSimulationStep(true, contextData);
1302 totalTimeStep -= timeStep;
1303 ++numSteps;
1305 #else
1306 if(totalTimeStep > 1.0e-8f)
1307 std::cout << "Ran out of time by " << totalTimeStep << std::endl;
1308 #endif
1310 /* Mark the water simulation state as up-to-date for this frame: */
1311 dataItem->waterTableTime = Vrui::getApplicationTime();
1314 /* Calculate the projection matrix: */
1315 PTransform projection = ds.projection;
1316 if(rs.fixProjectorView && rs.projectorTransformValid) {
1317 /* Use the projector transformation instead: */
1318 projection = rs.projectorTransform;
1320 /* Multiply with the inverse modelview transformation so that lighting still works as usual: */
1321 projection *= Geometry::invert(ds.modelviewNavigational);
1324 addContourLabel(contextData);
1326 if(rs.hillshade) {
1327 /* Set the surface material: */
1328 glMaterial(GLMaterialEnums::FRONT, rs.surfaceMaterial);
1331 #if 0
1332 if(rs.hillshade && rs.useShadows) {
1333 /* Set up OpenGL state: */
1334 glPushAttrib(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ENABLE_BIT | GL_POLYGON_BIT);
1336 GLLightTracker& lt = *contextData.getLightTracker();
1338 /* Save the currently-bound frame buffer and viewport: */
1339 GLint currentFrameBuffer;
1340 glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &currentFrameBuffer);
1341 GLint currentViewport[4];
1342 glGetIntegerv(GL_VIEWPORT, currentViewport);
1344 /*******************************************************************
1345 First rendering pass: Global ambient illumination only
1346 *******************************************************************/
1348 /* Draw the surface mesh: */
1349 surfaceRenderer->glRenderGlobalAmbientHeightMap(dataItem->heightColorMapObject, contextData);
1351 /*******************************************************************
1352 Second rendering pass: Add local illumination for every light source
1353 *******************************************************************/
1355 /* Enable additive rendering: */
1356 glEnable(GL_BLEND);
1357 glBlendFunc(GL_ONE, GL_ONE);
1358 glDepthFunc(GL_LEQUAL);
1359 glDepthMask(GL_FALSE);
1361 for(int lightSourceIndex = 0; lightSourceIndex < lt.getMaxNumLights(); ++lightSourceIndex)
1362 if(lt.getLightState(lightSourceIndex).isEnabled()) {
1363 /***************************************************************
1364 First step: Render to the light source's shadow map
1365 ***************************************************************/
1367 /* Set up OpenGL state to render to the shadow map: */
1368 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dataItem->shadowFramebufferObject);
1369 glViewport(0, 0, dataItem->shadowBufferSize[0], dataItem->shadowBufferSize[1]);
1370 glDepthMask(GL_TRUE);
1371 glClear(GL_DEPTH_BUFFER_BIT);
1372 glCullFace(GL_FRONT);
1374 /*************************************************************
1375 Calculate the shadow projection matrix:
1376 *************************************************************/
1378 /* Get the light source position in eye space: */
1379 Geometry::HVector<float, 3> lightPosEc;
1380 glGetLightfv(GL_LIGHT0 + lightSourceIndex, GL_POSITION, lightPosEc.getComponents());
1382 /* Transform the light source position to camera space: */
1383 Vrui::ONTransform::HVector lightPosCc = Vrui::getDisplayState(
1384 contextData).modelviewNavigational.inverseTransform(Vrui::ONTransform::HVector(lightPosEc));
1386 /* Calculate the direction vector from the center of the bounding box to the light source: */
1387 Point bboxCenter = Geometry::mid(bbox.min, bbox.max);
1388 Vrui::Vector lightDirCc = Vrui::Vector(lightPosCc.getComponents()) - Vrui::Vector(
1389 bboxCenter.getComponents()) * lightPosCc[3];
1391 /* Build a transformation that aligns the light direction with the positive z axis: */
1392 Vrui::ONTransform shadowModelview = Vrui::ONTransform::rotate(Vrui::Rotation::rotateFromTo(
1393 lightDirCc, Vrui::Vector(0, 0, 1)));
1394 shadowModelview *= Vrui::ONTransform::translateToOriginFrom(bboxCenter);
1396 /* Create a projection matrix, based on whether the light is positional or directional: */
1397 PTransform shadowProjection(0.0);
1398 if(lightPosEc[3] != 0.0f) {
1399 /* Modify the modelview transformation such that the light source is at the origin: */
1400 shadowModelview.leftMultiply(Vrui::ONTransform::translate(Vrui::Vector(0, 0, -lightDirCc.mag())));
1402 /***********************************************************
1403 Create a perspective projection:
1404 ***********************************************************/
1406 /* Calculate the perspective bounding box of the surface bounding box in eye space: */
1407 Box pBox = Box::empty;
1408 for(int i = 0; i < 8; ++i) {
1409 Point bc = shadowModelview.transform(bbox.getVertex(i));
1410 pBox.addPoint(Point(-bc[0] / bc[2], -bc[1] / bc[2], -bc[2]));
1413 /* Upload the frustum matrix: */
1414 double l = pBox.min[0] * pBox.min[2];
1415 double r = pBox.max[0] * pBox.min[2];
1416 double b = pBox.min[1] * pBox.min[2];
1417 double t = pBox.max[1] * pBox.min[2];
1418 double n = pBox.min[2];
1419 double f = pBox.max[2];
1420 shadowProjection.getMatrix()(0, 0) = 2.0 * n / (r - l);
1421 shadowProjection.getMatrix()(0, 2) = (r + l) / (r - l);
1422 shadowProjection.getMatrix()(1, 1) = 2.0 * n / (t - b);
1423 shadowProjection.getMatrix()(1, 2) = (t + b) / (t - b);
1424 shadowProjection.getMatrix()(2, 2) = -(f + n) / (f - n);
1425 shadowProjection.getMatrix()(2, 3) = -2.0 * f * n / (f - n);
1426 shadowProjection.getMatrix()(3, 2) = -1.0;
1427 } else {
1428 /***********************************************************
1429 Create a perspective projection:
1430 ***********************************************************/
1432 /* Transform the bounding box with the modelview transformation: */
1433 Box bboxEc = bbox;
1434 bboxEc.transform(shadowModelview);
1436 /* Upload the ortho matrix: */
1437 double l = bboxEc.min[0];
1438 double r = bboxEc.max[0];
1439 double b = bboxEc.min[1];
1440 double t = bboxEc.max[1];
1441 double n = -bboxEc.max[2];
1442 double f = -bboxEc.min[2];
1443 shadowProjection.getMatrix()(0, 0) = 2.0 / (r - l);
1444 shadowProjection.getMatrix()(0, 3) = -(r + l) / (r - l);
1445 shadowProjection.getMatrix()(1, 1) = 2.0 / (t - b);
1446 shadowProjection.getMatrix()(1, 3) = -(t + b) / (t - b);
1447 shadowProjection.getMatrix()(2, 2) = -2.0 / (f - n);
1448 shadowProjection.getMatrix()(2, 3) = -(f + n) / (f - n);
1449 shadowProjection.getMatrix()(3, 3) = 1.0;
1452 /* Multiply the shadow modelview matrix onto the shadow projection matrix: */
1453 shadowProjection *= shadowModelview;
1455 /* Draw the surface into the shadow buffer: */
1456 surfaceRenderer->glRenderDepthOnly(shadowProjection, contextData);
1458 /* Reset OpenGL state: */
1459 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFrameBuffer);
1460 glViewport(currentViewport[0], currentViewport[1], currentViewport[2], currentViewport[3]);
1461 glCullFace(GL_BACK);
1462 glDepthMask(GL_FALSE);
1464 #if SAVEDEPTH
1465 /* Save the depth image: */
1467 glBindTexture(GL_TEXTURE_2D, dataItem->shadowDepthTextureObject);
1468 GLfloat* depthTextureImage = new
1469 GLfloat[dataItem->shadowBufferSize[1]*dataItem->shadowBufferSize[0]];
1470 glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depthTextureImage);
1471 glBindTexture(GL_TEXTURE_2D, 0);
1472 Images::RGBImage dti(dataItem->shadowBufferSize[0], dataItem->shadowBufferSize[1]);
1473 GLfloat* dtiPtr = depthTextureImage;
1474 Images::RGBImage::Color* ciPtr = dti.modifyPixels();
1475 for(int y = 0; y < dataItem->shadowBufferSize[1]; ++y)
1476 for(int x = 0; x < dataItem->shadowBufferSize[0]; ++x, ++dtiPtr, ++ciPtr) {
1477 GLColor<GLfloat, 3> tc(*dtiPtr, *dtiPtr, *dtiPtr);
1478 *ciPtr = tc;
1480 delete[] depthTextureImage;
1481 Images::writeImageFile(dti, "DepthImage.png");
1483 #endif
1485 /* Draw the surface using the shadow texture: */
1486 rs.surfaceRenderer->glRenderShadowedIlluminatedHeightMap(dataItem->heightColorMapObject,
1487 dataItem->shadowDepthTextureObject, shadowProjection, contextData);
1490 /* Reset OpenGL state: */
1491 glPopAttrib();
1492 } else
1493 #endif
1495 /* Render the surface in a single pass: */
1496 rs.surfaceRenderer->renderSinglePass(ds.viewport, projection, ds.modelviewNavigational,
1497 contextData);
1500 if(rs.waterRenderer != 0) {
1501 /* Draw the water surface: */
1502 glMaterialAmbientAndDiffuse(GLMaterialEnums::FRONT, GLColor<GLfloat, 4>(0.0f, 0.5f, 0.8f));
1503 glMaterialSpecular(GLMaterialEnums::FRONT, GLColor<GLfloat, 4>(1.0f, 1.0f, 1.0f));
1504 glMaterialShininess(GLMaterialEnums::FRONT, 64.0f);
1505 rs.waterRenderer->render(projection, ds.modelviewNavigational, contextData);
1509 void Sandbox::resetNavigation(void) {
1510 /* Construct a navigation transformation to center the sandbox area in the display, facing the viewer, with the long sandbox axis facing to the right: */
1511 Vrui::NavTransform nav = Vrui::NavTransform::translateFromOriginTo(Vrui::getDisplayCenter());
1512 nav *= Vrui::NavTransform::scale(Vrui::getDisplaySize() / boxSize);
1513 Vrui::Vector y = Vrui::getUpDirection();
1514 Vrui::Vector z = Vrui::getForwardDirection();
1515 Vrui::Vector x = z ^ y;
1516 nav *= Vrui::NavTransform::rotate(Vrui::Rotation::fromBaseVectors(x, y));
1517 nav *= boxTransform;
1518 Vrui::setNavigationTransformation(nav);
1521 void Sandbox::eventCallback(Vrui::Application::EventID eventId,
1522 Vrui::InputDevice::ButtonCallbackData* cbData) {
1523 if(cbData->newButtonState) {
1524 switch(eventId) {
1525 case 0:
1526 /* Invert the current pause setting: */
1527 pauseUpdates = !pauseUpdates;
1529 /* Update the main menu toggle: */
1530 pauseUpdatesToggle->setToggle(pauseUpdates);
1532 break;
1537 void Sandbox::initContext(GLContextData& contextData) const {
1538 /* Create a data item and add it to the context: */
1539 DataItem* dataItem = new DataItem;
1540 contextData.addDataItem(this, dataItem);
1543 /* Save the currently bound frame buffer: */
1544 GLint currentFrameBuffer;
1545 glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &currentFrameBuffer);
1547 /* Set the default shadow buffer size: */
1548 dataItem->shadowBufferSize[0] = 1024;
1549 dataItem->shadowBufferSize[1] = 1024;
1551 /* Generate the shadow rendering frame buffer: */
1552 glGenFramebuffersEXT(1, &dataItem->shadowFramebufferObject);
1553 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dataItem->shadowFramebufferObject);
1555 /* Generate a depth texture for shadow rendering: */
1556 glGenTextures(1, &dataItem->shadowDepthTextureObject);
1557 glBindTexture(GL_TEXTURE_2D, dataItem->shadowDepthTextureObject);
1558 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1559 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1560 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1561 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1562 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
1563 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
1564 glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);
1565 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24_ARB, dataItem->shadowBufferSize[0],
1566 dataItem->shadowBufferSize[1], 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
1567 glBindTexture(GL_TEXTURE_2D, 0);
1569 /* Attach the depth texture to the frame buffer object: */
1570 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D,
1571 dataItem->shadowDepthTextureObject, 0);
1572 glDrawBuffer(GL_NONE);
1573 glReadBuffer(GL_NONE);
1574 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentFrameBuffer);
1578 VRUI_APPLICATION_RUN(Sandbox)