OpenWalnut  1.5.0dev
WMDirectVolumeRendering.cpp
1 //---------------------------------------------------------------------------
2 //
3 // Project: OpenWalnut ( http://www.openwalnut.org )
4 //
5 // Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
6 // For more information see http://www.openwalnut.org/copying
7 //
8 // This file is part of OpenWalnut.
9 //
10 // OpenWalnut is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // OpenWalnut is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
22 //
23 //---------------------------------------------------------------------------
24 
25 #include <algorithm>
26 #include <cmath>
27 #include <memory>
28 #include <string>
29 #include <utility>
30 
31 #include <osg/Geode>
32 #include <osg/Group>
33 #include <osg/Material>
34 #include <osg/ShapeDrawable>
35 #include <osg/StateAttribute>
36 #include <osgDB/ReadFile>
37 #include <osgDB/WriteFile>
38 
39 #include "WMDirectVolumeRendering.h"
40 #include "WMDirectVolumeRendering.xpm"
41 #include "core/common/WColor.h"
42 #include "core/common/WPropertyHelper.h"
43 #include "core/dataHandler/WDataSetScalar.h"
44 #include "core/graphicsEngine/WGEColormapping.h"
45 #include "core/graphicsEngine/WGEGeodeUtils.h"
46 #include "core/graphicsEngine/WGEManagedGroupNode.h"
47 #include "core/graphicsEngine/WGERequirement.h"
48 #include "core/graphicsEngine/WGETextureUtils.h"
49 #include "core/graphicsEngine/WGEUtils.h"
50 #include "core/graphicsEngine/shaders/WGEShader.h"
51 #include "core/graphicsEngine/shaders/WGEShaderDefine.h"
52 #include "core/graphicsEngine/shaders/WGEShaderDefineOptions.h"
53 #include "core/kernel/WKernel.h"
54 
55 // This line is needed by the module loader to actually find your module.
56 W_LOADABLE_MODULE( WMDirectVolumeRendering )
57 
59  WModule()
60 {
61  // Initialize members
62 }
63 
65 {
66  // Cleanup!
67 }
68 
69 std::shared_ptr< WModule > WMDirectVolumeRendering::factory() const
70 {
71  return std::shared_ptr< WModule >( new WMDirectVolumeRendering() );
72 }
73 
75 {
76  return WMDirectVolumeRendering_xpm;
77 }
78 
79 const std::string WMDirectVolumeRendering::getName() const
80 {
81  // Specify your module name here. This name must be UNIQUE!
82  return "Direct Volume Rendering";
83 }
84 
85 const std::string WMDirectVolumeRendering::getDescription() const
86 {
87  // Specify your module description here. Be detailed. This text is read by the user.
88  return "Direct volume rendering of regular volumetric data.";
89 }
90 
92 {
93  // DVR needs one input: the scalar dataset
94  m_input = WModuleInputData< WDataSetScalar >::createAndAdd( shared_from_this(), "scalar data", "The scalar dataset." );
95 
96  // The transfer function for our DVR
97  m_transferFunction = WModuleInputData< WDataSetSingle >::createAndAdd( shared_from_this(), "transfer function", "The 1D transfer function." );
98 
99  // Optional: the gradient field
101  "gradients", "<u>Optional:</u> Gradient field of the dataset to display." );
102 
103  // call WModules initialization
105 }
106 
108 {
109  // Initialize the properties
110  m_propCondition = std::shared_ptr< WCondition >( new WCondition() );
111 
112  m_samples = m_properties->addProperty( "Sample count", "The number of samples to walk along the ray during raycasting. A low value "
113  "may cause artifacts whilst a high value slows down rendering.", 256 );
114  m_samples->setMin( 1 );
115  m_samples->setMax( 5000 );
116 
117  // illumination model
118  m_localIlluminationSelections = std::shared_ptr< WItemSelection >( new WItemSelection() );
119  m_localIlluminationSelections->addItem( "No Local Illumination", "Volume Renderer only uses the classified voxel color." );
120  m_localIlluminationSelections->addItem( "Blinn-Phong", "Blinn-Phong lighting is used for shading each classified voxel." );
121  m_localIlluminationAlgo = m_properties->addProperty( "Local illumination model", "The illumination algorithm to use.",
122  m_localIlluminationSelections->getSelectorFirst(), m_propCondition );
123 
126 
127  // additional artifact removal methods
128  m_improvementGroup = m_properties->addPropertyGroup( "Improvements", "Methods for improving image quality. Most of these methods imply "
129  "additional calculation/texture overhead and therefore slow down rendering." );
130 
131  m_stochasticJitterEnabled = m_improvementGroup->addProperty( "Stochastic jitter", "With stochastic jitter, wood-grain artifacts can be "
132  "removed with the cost of possible noise artifacts.", true,
133  m_propCondition );
134 
135  m_opacityCorrectionEnabled = m_improvementGroup->addProperty( "Opacity correction", "If enabled, opacities are assumed to be relative to the "
136  "sample count. If disabled, changing the sample count "
137  "varies brightness of the image.", true,
138  m_propCondition );
139 
140  m_maximumIntensityProjectionEnabled = m_improvementGroup->addProperty( "MIP", "If enabled, MIP is used.", false,
141  m_propCondition );
142 
143  m_depthProjectionEnabled = m_improvementGroup->addProperty( "Depth projection", "If enabled, depth projection mode is used", false,
144  m_propCondition );
145 
147 }
148 
150 {
151  m_requirements.push_back( new WGERequirement() );
152 }
153 
154 /**
155  * Generates a white noise texture with given resolution.
156  *
157  * \param resX the resolution
158  *
159  * \return a image with resX*resX resolution.
160  */
161 osg::ref_ptr< osg::Image > genWhiteNoise( size_t resX )
162 {
163  std::srand( time( 0 ) );
164 
165  osg::ref_ptr< osg::Image > randImage = new osg::Image();
166  randImage->allocateImage( resX, resX, 1, GL_LUMINANCE, GL_UNSIGNED_BYTE );
167  unsigned char *randomLuminance = randImage->data(); // should be 4 megs
168  for( unsigned int x = 0; x < resX; x++ )
169  {
170  for( unsigned int y = 0; y < resX; y++ )
171  {
172  // - stylechecker says "use rand_r" but I am not sure about portability.
173  unsigned char r = ( unsigned char )( std::rand() % 255 ); // NOLINT
174  randomLuminance[ ( y * resX ) + x ] = r;
175  }
176  }
177 
178  return randImage;
179 }
180 
182 {
183  m_shader = osg::ref_ptr< WGEShader > ( new WGEShader( "WMDirectVolumeRendering", m_localPath ) );
184  // setup all the defines needed
185 
186  // local illumination model
188  new WGEShaderDefineOptions( "LOCALILLUMINATION_NONE", "LOCALILLUMINATION_PHONG" )
189  );
190  m_shader->addPreprocessor( illuminationAlgoDefines );
191 
192  // gradient texture settings
193  WGEShaderDefine< std::string >::SPtr gradTexSamplerDefine = m_shader->setDefine( "GRADIENTTEXTURE_SAMPLER", std::string( "tex1" ) );
194  WGEShaderDefineSwitch::SPtr gradTexEnableDefine = m_shader->setDefine( "GRADIENTTEXTURE_ENABLED" );
195 
196  // transfer function texture settings
197  WGEShaderDefine< std::string >::SPtr tfTexSamplerDefine = m_shader->setDefine( "TRANSFERFUNCTION_SAMPLER", std::string( "tex2" ) );
198  WGEShaderDefineSwitch::SPtr tfTexEnableDefine = m_shader->setDefine( "TRANSFERFUNCTION_ENABLED" );
199 
200  // jitter
201  WGEShaderDefine< std::string >::SPtr jitterSamplerDefine = m_shader->setDefine( "JITTERTEXTURE_SAMPLER", std::string( "tex3" ) );
202  WGEShaderDefine< int >::SPtr jitterSizeXDefine = m_shader->setDefine( "JITTERTEXTURE_SIZEX", 0 );
203  WGEShaderDefineSwitch::SPtr jitterEnable = m_shader->setDefine( "JITTERTEXTURE_ENABLED" );
204 
205  // opacity correction enabled?
206  WGEShaderDefineSwitch::SPtr opacityCorrectionEnableDefine = m_shader->setDefine( "OPACITYCORRECTION_ENABLED" );
207 
208  WGEShaderDefineSwitch::SPtr maximumIntensityProjectionEnabledDefine = m_shader->setDefine( "MIP_ENABLED" );
209  WGEShaderDefineSwitch::SPtr depthProjectionEnabledDefine = m_shader->setDefine( "DEPTH_PROJECTION_ENABLED" );
210 
211  // the texture used for the transfer function
212  osg::ref_ptr< osg::Texture1D > tfTexture = new osg::Texture1D();
213  osg::ref_ptr< osg::Image > tfImage = new osg::Image();
214  bool updateTF = false; // if true, update of TF is enforced
215 
216  // let the main loop awake if the data changes or the properties changed.
217  m_moduleState.setResetable( true, true );
218  m_moduleState.add( m_transferFunction->getDataChangedCondition() );
219  m_moduleState.add( m_input->getDataChangedCondition() );
220  m_moduleState.add( m_gradients->getDataChangedCondition() );
222 
223  // Signal ready state.
224  ready();
225  debugLog() << "Module is now ready.";
226 
227  osg::ref_ptr< WGEManagedGroupNode > rootNode = new WGEManagedGroupNode( m_active );
228  bool rootInserted = false;
229 
230  // Normally, you will have a loop which runs as long as the module should not shutdown. In this loop you can react on changing data on input
231  // connectors or on changed in your properties.
232  debugLog() << "Entering main loop";
233  while( !m_shutdownFlag() )
234  {
235  // Now, the moduleState variable comes into play. The module can wait for the condition, which gets fired whenever the input receives data
236  // or an property changes. The main loop now waits until something happens.
238 
239  // quit if requested
240  if( m_shutdownFlag() )
241  {
242  break;
243  }
244 
245  // was there an update?
246  bool dataUpdated = m_input->updated() || m_gradients->updated();
247  std::shared_ptr< WDataSetScalar > dataSet = m_input->getData();
248  bool dataValid = ( dataSet != NULL );
249  bool propUpdated = m_localIlluminationAlgo->changed() || m_stochasticJitterEnabled->changed() || m_opacityCorrectionEnabled->changed() ||
251 
252 
253  // reset module in case of invalid data. This accounts only for the scalar field input
254  if( !dataValid )
255  {
256  cube.release();
257  debugLog() << "Resetting.";
258  rootNode->clear();
259  continue;
260  }
261 
262  // As the data has changed, we need to recreate the texture.
263  if( ( propUpdated || dataUpdated ) && dataValid )
264  {
265  debugLog() << "Data changed. Uploading new data as texture.";
266 
267  // there are several updates. Clear the root node and later on insert the new rendering.
268  rootNode->clear();
269 
270  // First, grab the grid
271  std::shared_ptr< WGridRegular3D > grid = std::dynamic_pointer_cast< WGridRegular3D >( dataSet->getGrid() );
272  if( !grid )
273  {
274  errorLog() << "The dataset does not provide a regular grid. Ignoring dataset.";
275  continue;
276  }
277 
278  // use the OSG Shapes, create unit cube
279  WBoundingBox bb( WPosition( 0.0, 0.0, 0.0 ),
280  WPosition( grid->getNbCoordsX() - 1, grid->getNbCoordsY() - 1, grid->getNbCoordsZ() - 1 ) );
281  cube = wge::generateSolidBoundingBoxNode( bb, WColor( 1.0, 1.0, 1.0, 1.0 ) );
282  cube->asTransform()->getChild( 0 )->setName( "_DVR Proxy Cube" ); // Be aware that this name is used in the pick handler.
283  // because of the underscore in front it won't be picked
284  // we also set the grid's transformation here
285  rootNode->setMatrix( static_cast< WMatrix4d >( grid->getTransform() ) );
286 
287  m_shader->apply( cube );
288 
289  // bind the texture to the node
290  osg::ref_ptr< WDataTexture3D > texture3D = dataSet->getTexture();
291  wge::bindTexture( cube, texture3D, 0, "u_volume" );
292 
293  ////////////////////////////////////////////////////////////////////////////////////////////////////
294  // setup illumination
295  ////////////////////////////////////////////////////////////////////////////////////////////////////
296 
297  // enable transparency
298  osg::StateSet* rootState = cube->getOrCreateStateSet();
299  rootState->setMode( GL_BLEND, osg::StateAttribute::ON );
300  rootState->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
301 
302  // set proper illumination define
303  illuminationAlgoDefines->activateOption( m_localIlluminationAlgo->get( true ).getItemIndexOfSelected( 0 ) );
304 
305  // if there is a gradient field available -> apply as texture too
306  std::shared_ptr< WDataSetVector > gradients = m_gradients->getData();
307  if( gradients )
308  {
309  debugLog() << "Uploading specified gradient field.";
310 
311  // bind the texture to the node
312  osg::ref_ptr< WDataTexture3D > gradTexture3D = gradients->getTexture();
313  wge::bindTexture( cube, gradTexture3D, 1, "u_gradients" );
314  gradTexEnableDefine->setActive( true );
315  }
316  else
317  {
318  gradTexEnableDefine->setActive( false ); // disable gradient texture
319  }
320 
321  ////////////////////////////////////////////////////////////////////////////////////////////////////
322  // stochastic jittering texture
323  ////////////////////////////////////////////////////////////////////////////////////////////////////
324 
325  // create some random noise
326  jitterSamplerDefine->setActive( false );
327  jitterEnable->setActive( false );
328  if( m_stochasticJitterEnabled->get( true ) )
329  {
330  const size_t size = 64;
331  osg::ref_ptr< WGETexture2D > randTexture = new WGETexture2D( genWhiteNoise( size ) );
332  randTexture->setFilterMinMag( osg::Texture2D::NEAREST );
333  randTexture->setWrapSTR( osg::Texture2D::REPEAT );
334  wge::bindTexture( cube, randTexture, 2, "u_jitter" );
335  jitterSamplerDefine->setActive( true );
336  jitterEnable->setActive( true );
337  jitterSizeXDefine->setValue( size );
338  }
339 
340  ////////////////////////////////////////////////////////////////////////////////////////////////////
341  // transfer function texture
342  ////////////////////////////////////////////////////////////////////////////////////////////////////
343 
344  osg::ref_ptr< osg::Texture1D > tfTexture = new osg::Texture1D();
345  tfTexture->setDataVariance( osg::Object::DYNAMIC );
346  // create some ramp as default
347  {
348  int resX = 32;
349  tfImage->allocateImage( resX, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE );
350  unsigned char *data = tfImage->data(); // should be 4 megs
351  for( int x = 0; x < resX; x++ )
352  {
353  unsigned char r = ( unsigned char )( 0.1 * 255.0 * static_cast< float >( x ) / static_cast< float >( resX ) );
354  data[ 4 * x + 0 ] = 255;
355  data[ 4 * x + 1 ] = 255;
356  data[ 4 * x + 2 ] = 255;
357  data[ 4 * x + 3 ] = r;
358  }
359  }
360 
361  tfTexture->setImage( tfImage );
362  wge::bindTexture( cube, tfTexture, 3, "u_transferFunction" );
363  // permanently enable the TF texture. As we have no alternative way to set the TF, always use a TF texture
364  tfTexEnableDefine->setActive( true );
365 
366  ////////////////////////////////////////////////////////////////////////////////////////////////////
367  // opacity correction
368  ////////////////////////////////////////////////////////////////////////////////////////////////////
369 
370  if( m_opacityCorrectionEnabled->get( true ) )
371  {
372  opacityCorrectionEnableDefine->setActive( true );
373  }
374  else
375  {
376  opacityCorrectionEnableDefine->setActive( false );
377  }
378 
379  ////////////////////////////////////////////////////////////////////////////////////////////////////
380  // maximum intensity projection (MIP)
381  ////////////////////////////////////////////////////////////////////////////////////////////////////
382 
383  if( m_maximumIntensityProjectionEnabled->get( true ) )
384  {
385  maximumIntensityProjectionEnabledDefine->setActive( true );
386  }
387  else
388  {
389  maximumIntensityProjectionEnabledDefine->setActive( false );
390  }
391 
392  ////////////////////////////////////////////////////////////////////////////////////////////////////
393  // depth projection
394  ////////////////////////////////////////////////////////////////////////////////////////////////////
395 
396  if( m_depthProjectionEnabled->get( true ) )
397  {
398  depthProjectionEnabledDefine->setActive( true );
399  }
400  else
401  {
402  depthProjectionEnabledDefine->setActive( false );
403  }
404 
405  ////////////////////////////////////////////////////////////////////////////////////////////////////
406  // setup all those uniforms
407  ////////////////////////////////////////////////////////////////////////////////////////////////////
408 
409  rootState->addUniform( new WGEPropertyUniform< WPropInt >( "u_samples", m_samples ) );
410 
411  ////////////////////////////////////////////////////////////////////////////////////////////////////
412  // build spatial search structure
413  ////////////////////////////////////////////////////////////////////////////////////////////////////
414 
415  // update node
416  debugLog() << "Adding new rendering.";
417  rootNode->insert( cube );
418  // insert root node if needed. This way, we ensure that the root node gets added only if the proxy cube has been added AND the bbox
419  // can be calculated properly by the OSG to ensure the proxy cube is centered in the scene if no other item has been added earlier.
420  if( !rootInserted )
421  {
422  rootInserted = true;
423  WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( rootNode );
424  }
425 
426  updateTF = true;
427  }
428 
429  ////////////////////////////////////////////////////////////////////////////////////////////////////
430  // load transfer function
431  ////////////////////////////////////////////////////////////////////////////////////////////////////
432 
433  if( ( updateTF || propUpdated || m_transferFunction->updated() ) && dataValid && cube )
434  {
435  updateTF = false;
436  std::shared_ptr< WDataSetSingle > dataSet = m_transferFunction->getData();
437  if( !dataSet )
438  {
439  debugLog() << "no data set?";
440  }
441  else
442  {
443  WAssert( dataSet, "data set" );
444  std::shared_ptr< WValueSetBase > valueSet = dataSet->getValueSet();
445  WAssert( valueSet, "value set" );
446  std::shared_ptr< WValueSet< unsigned char > > cvalueSet( std::dynamic_pointer_cast<WValueSet< unsigned char> >( valueSet ) );
447  if( !cvalueSet )
448  {
449  debugLog() << "invalid type";
450  }
451  else
452  {
453  size_t tfsize = cvalueSet->rawSize();
454 
455  // create image and copy the TF
456  tfImage->allocateImage( tfsize/4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE );
457  tfImage->setInternalTextureFormat( GL_RGBA );
458  unsigned char* data = reinterpret_cast< unsigned char* >( tfImage->data() );
459  std::copy( cvalueSet->rawData(), &cvalueSet->rawData()[ tfsize ], data );
460 
461  // force OpenGl to use the new texture
462  tfTexture->dirtyTextureObject();
463  }
464  }
465  }
466  }
467 
468  // At this point, the container managing this module signalled to shutdown. The main loop has ended and you should clean up. Always remove
469  // allocated memory and remove all OSG nodes.
470  WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( rootNode );
471 }
virtual void wait() const
Wait for the condition.
void setResetable(bool resetable=true, bool autoReset=true)
Sets the resetable flag.
virtual void add(std::shared_ptr< WCondition > condition)
Adds another condition to the set of conditions to wait for.
Class to encapsulate boost::condition_variable_any.
Definition: WCondition.h:42
This class adds some convenience methods to WGEGroupNode.
Class implementing a uniform which can be controlled by a property instance.
This requirement ensures an up and running WGE.
This GLSL preprocessor is able to set one define from a list of defines depending on the active optio...
std::shared_ptr< WGEShaderDefineOptions > SPtr
Shared pointer for this class.
void setValue(const ValueType &value)
Sets the new value for this define.
std::shared_ptr< WGEShaderDefine< ValueType > > SPtr
Shared pointer for this class.
void setActive(bool active=true)
(De-)activates the preprocessor.
Class encapsulating the OSG Program class for a more convenient way of adding and modifying shader.
Definition: WGEShader.h:48
This calls serves a simple purpose: have a texture and its scaling information together which allows ...
Definition: WGETexture.h:53
A class containing a list of named items.
static WKernel * getRunningKernel()
Returns pointer to the currently running kernel.
Definition: WKernel.cpp:117
std::shared_ptr< WGraphicsEngine > getGraphicsEngine() const
Returns pointer to currently running instance of graphics engine.
Definition: WKernel.cpp:122
This module is a basic volume renderer.
virtual std::shared_ptr< WModule > factory() const
Due to the prototype design pattern used to build modules, this method returns a new instance of this...
WMDirectVolumeRendering()
Default constructor.
std::shared_ptr< WModuleInputData< WDataSetScalar > > m_input
An input connector used to get datasets from other modules.
WPropBool m_depthProjectionEnabled
If active, color coding is done by depth projection mode where the intensity is taken from the alpha ...
WPropBool m_maximumIntensityProjectionEnabled
If active, Maximum intensity projection is used based on the alpha value of the points.
WPropBool m_opacityCorrectionEnabled
If active, the opacity of the classified fragment gets scaled according to sample count to ensure rel...
virtual const std::string getDescription() const
Gives back a description of this module.
std::shared_ptr< WItemSelection > m_localIlluminationSelections
The available shading algorithms.
WPropGroup m_improvementGroup
All properties for those nice improvement methods.
WPropInt m_samples
The number of samples to walk along the ray.
std::shared_ptr< WCondition > m_propCondition
A condition used to notify about changes in several properties.
virtual const char ** getXPMIcon() const
Get the icon for this module in XPM format.
virtual void moduleMain()
Entry point after loading the module.
virtual ~WMDirectVolumeRendering()
Destructor.
osg::ref_ptr< osg::Node > cube
the main geometry node
WPropSelection m_localIlluminationAlgo
The actually selected shading algorithm.
std::shared_ptr< WModuleInputData< WDataSetVector > > m_gradients
The gradient field input.
osg::ref_ptr< WGEShader > m_shader
the DVR shader.
virtual void connectors()
Initialize the connectors this module is using.
std::shared_ptr< WModuleInputData< WDataSetSingle > > m_transferFunction
The transfer function as an input data set.
virtual const std::string getName() const
Gives back the name of this module.
WPropBool m_stochasticJitterEnabled
If true, stochastic jittering is used for image quality improvement.
virtual void properties()
Initialize the properties for this module.
virtual void requirements()
Initialize requirements for this module.
static PtrType createAndAdd(std::shared_ptr< WModule > module, std::string name="", std::string description="")
Convenience method to create a new instance of this in data connector with proper type and add it to ...
Class representing a single module of OpenWalnut.
Definition: WModule.h:72
boost::filesystem::path m_localPath
The path where the module binary resides in.
Definition: WModule.h:734
Requirements m_requirements
The list of requirements.
Definition: WModule.h:754
virtual void properties()
Initialize properties in this function.
Definition: WModule.cpp:212
wlog::WStreamedLogger debugLog() const
Logger instance for comfortable debug logging.
Definition: WModule.cpp:575
std::shared_ptr< WProperties > m_properties
The property object for the module.
Definition: WModule.h:640
void ready()
Call this whenever your module is ready and can react on property changes.
Definition: WModule.cpp:505
WConditionSet m_moduleState
The internal state of the module.
Definition: WModule.h:703
wlog::WStreamedLogger errorLog() const
Logger instance for comfortable error logging.
Definition: WModule.cpp:570
WPropBool m_active
True whenever the module should be active.
Definition: WModule.h:723
virtual void connectors()
Initialize connectors in this function.
Definition: WModule.cpp:208
This only is a 3d double vector.
WBoolFlag m_shutdownFlag
Condition getting fired whenever the thread should quit.
Base Class for all value set types.
Definition: WValueSet.h:47
void addTo(WPropSelection prop)
Add the PC_NOTEMPTY constraint to the property.
void addTo(WPropSelection prop)
Add the PC_SELECTONLYONE constraint to the property.
void bindTexture(osg::ref_ptr< osg::Node > node, osg::ref_ptr< WDataTexture3D > texture, size_t unit=0, std::string prefix="")
Binds the specified texture to the specified unit.
osg::ref_ptr< osg::Node > generateSolidBoundingBoxNode(const WBoundingBox &bb, const WColor &color, bool threeDTexCoords=true)
Generates an OSG node for the specified bounding box.