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