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