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