WMIsosurfaceRaytracer.cpp 14.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
//---------------------------------------------------------------------------
//
// Project: OpenWalnut ( http://www.openwalnut.org )
//
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
// For more information see http://www.openwalnut.org/copying
//
// This file is part of OpenWalnut.
//
// OpenWalnut is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// OpenWalnut is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------

#include <string>
26
#include <utility>
27 28

#include <osg/Geode>
Mathias Goldau's avatar
[STYLE]  
Mathias Goldau committed
29
#include <osg/Group>
30
#include <osg/Material>
Mathias Goldau's avatar
[STYLE]  
Mathias Goldau committed
31
#include <osg/ShapeDrawable>
32 33
#include <osg/StateAttribute>

34 35 36 37
#include "core/common/WColor.h"
#include "core/common/WPropertyHelper.h"
#include "core/dataHandler/WDataSetScalar.h"
#include "core/dataHandler/WDataSetVector.h"
38
#include "core/dataHandler/WDataTexture3D.h"
39 40 41 42 43 44 45 46 47 48 49 50 51 52
#include "core/graphicsEngine/WGEColormapping.h"
#include "core/graphicsEngine/WGEGeodeUtils.h"
#include "core/graphicsEngine/WGEManagedGroupNode.h"
#include "core/graphicsEngine/WGEUtils.h"
#include "core/graphicsEngine/WGETextureUtils.h"
#include "core/graphicsEngine/shaders/WGEPropertyUniform.h"
#include "core/graphicsEngine/shaders/WGEShader.h"
#include "core/graphicsEngine/shaders/WGEShaderDefineOptions.h"
#include "core/graphicsEngine/shaders/WGEShaderPropertyDefineOptions.h"
#include "core/graphicsEngine/shaders/WGEShaderPropertyDefine.h"
#include "core/graphicsEngine/postprocessing/WGEPostprocessingNode.h"
#include "core/graphicsEngine/WGERequirement.h"
#include "core/graphicsEngine/callbacks/WGENodeMaskCallback.h"
#include "core/kernel/WKernel.h"
53
#include "WMIsosurfaceRaytracer.xpm"
Mathias Goldau's avatar
[STYLE]  
Mathias Goldau committed
54
#include "WMIsosurfaceRaytracer.h"
55

56 57 58
// This line is needed by the module loader to actually find your module.
W_LOADABLE_MODULE( WMIsosurfaceRaytracer )

59
WMIsosurfaceRaytracer::WMIsosurfaceRaytracer():
60
    WModule()
61
{
62
    // Initialize members
63 64
}

65
WMIsosurfaceRaytracer::~WMIsosurfaceRaytracer()
66
{
67
    // Cleanup!
68 69
}

70
boost::shared_ptr< WModule > WMIsosurfaceRaytracer::factory() const
71
{
72
    return boost::shared_ptr< WModule >( new WMIsosurfaceRaytracer() );
73 74
}

75
const char** WMIsosurfaceRaytracer::getXPMIcon() const
Alexander Wiebel's avatar
Alexander Wiebel committed
76
{
77
    return isosurfaceraytracer_xpm;
Alexander Wiebel's avatar
Alexander Wiebel committed
78 79
}

80
const std::string WMIsosurfaceRaytracer::getName() const
81
{
82
    // Specify your module name here. This name must be UNIQUE!
83
    return "Isosurface Raytracer";
84 85
}

86
const std::string WMIsosurfaceRaytracer::getDescription() const
87
{
88
    // Specify your module description here. Be detailed. This text is read by the user.
89
    return "This module shows a fast raytraced isosurface of the specified scalar dataset.";
90 91
}

92
void WMIsosurfaceRaytracer::connectors()
93 94
{
    // DVR needs one input: the scalar dataset
95
    m_input = boost::shared_ptr< WModuleInputData < WDataSetScalar  > >(
96
        new WModuleInputData< WDataSetScalar >( shared_from_this(), "in", "The scalar dataset shown using isosurface." )
97 98 99 100 101
    );

    // As properties, every connector needs to be added to the list of connectors.
    addConnector( m_input );

102 103 104
    // Optional: the gradient field
    m_gradients = WModuleInputData< WDataSetVector >::createAndAdd( shared_from_this(), "gradients", "The gradient field of the dataset to display" );

105 106 107 108
    // call WModules initialization
    WModule::connectors();
}

109
void WMIsosurfaceRaytracer::properties()
110 111 112
{
    // Initialize the properties
    m_propCondition = boost::shared_ptr< WCondition >( new WCondition() );
113

114
    m_isoValue      = m_properties->addProperty( "Isovalue",         "The isovalue used whenever the isosurface Mode is turned on.",
115 116
                                                                      128.0 );

117
    m_isoColor      = m_properties->addProperty( "Iso color",        "The color to blend the isosurface with.", WColor( 1.0, 1.0, 1.0, 1.0 ),
118
                      m_propCondition );
119

120
    m_stepCount     = m_properties->addProperty( "Step count",       "The number of steps to walk along the ray during raycasting. A low value "
121 122 123
                                                                      "may cause artifacts whilst a high value slows down rendering.", 250 );
    m_stepCount->setMin( 1 );
    m_stepCount->setMax( 1000 );
124

125 126 127 128 129
    m_epsilon       = m_properties->addProperty( "Epsilon",           "The value defines the precision of iso-value checking. The lower the "
                                                                      "value, the higher the precision.", 0.1 );
    m_epsilon->setMin( 0.0 );
    m_epsilon->setMax( 1.0 );

130 131 132 133
    m_alpha         = m_properties->addProperty( "Opacity %",        "The opacity in %. Transparency = 1 - Opacity.", 1.0 );
    m_alpha->setMin( 0.0 );
    m_alpha->setMax( 1.0 );

134
    m_colormapRatio = m_properties->addProperty( "Colormap ratio",   "The intensity of the colormap in contrast to surface shading.", 0.5 );
135 136
    m_colormapRatio->setMin( 0.0 );
    m_colormapRatio->setMax( 1.0 );
137

138
    m_phongShading  = m_properties->addProperty( "Phong shading", "If enabled, Phong shading gets applied on a per-pixel basis.", true );
139

140
    m_cortexMode    = m_properties->addProperty( "Emphasize cortex", "Emphasize the cortex while inner parts ar not that well lighten.", false );
141

142
    m_stochasticJitter = m_properties->addProperty( "Stochastic jitter", "Improves image quality at low sampling rates but introduces slight "
143 144
                                                                         "noise effect.", true );

145
    m_borderClip = m_properties->addProperty( "Border clip", "If enabled, a certain area on the volume boundary can be clipped. This is useful "
146 147
                                                             "for noise and non-peeled data but will consume a lot of GPU power.", false );

148
    m_borderClipDistance = m_properties->addProperty( "Border clip distance", "The distance that should be ignored.", 0.05 );
149 150 151
    m_borderClipDistance->setMin( 0.0 );
    m_borderClipDistance->setMax( 0.1 );

152
    WModule::properties();
153 154
}

155 156 157 158 159
void WMIsosurfaceRaytracer::requirements()
{
    m_requirements.push_back( new WGERequirement() );
}

160
void WMIsosurfaceRaytracer::moduleMain()
161
{
162
    m_shader = osg::ref_ptr< WGEShader > ( new WGEShader( "WMIsosurfaceRaytracer", m_localPath ) );
163 164 165 166 167
    m_shader->addPreprocessor( WGEShaderPreprocessor::SPtr(
        new WGEShaderPropertyDefineOptions< WPropBool >( m_cortexMode, "CORTEXMODE_DISABLED", "CORTEXMODE_ENABLED" ) )
    );
    m_shader->addPreprocessor( WGEShaderPreprocessor::SPtr(
        new WGEShaderPropertyDefineOptions< WPropBool >( m_stochasticJitter, "STOCHASTICJITTER_DISABLED", "STOCHASTICJITTER_ENABLED" ) )
168
    );
169 170 171
    m_shader->addPreprocessor( WGEShaderPreprocessor::SPtr(
        new WGEShaderPropertyDefine< WPropDouble >( "ISO_EPSILON", m_epsilon ) )
    );
172 173
    m_shader->addPreprocessor( WGEShaderPreprocessor::SPtr(
        new WGEShaderPropertyDefineOptions< WPropBool >( m_phongShading, "PHONGSHADING_DISABLED", "PHONGSHADING_ENABLED" ) )
174
    );
175
    WGEShaderDefineSwitch::SPtr gradTexEnableDefine = m_shader->setDefine( "GRADIENTTEXTURE_ENABLED" );
176 177 178
    m_shader->addPreprocessor( WGEShaderPreprocessor::SPtr(
        new WGEShaderPropertyDefineOptions< WPropBool >( m_borderClip, "BORDERCLIP_DISABLED", "BORDERCLIP_ENABLED" ) )
    );
179

180
    // let the main loop awake if the data changes or the properties changed.
181 182
    m_moduleState.setResetable( true, true );
    m_moduleState.add( m_input->getDataChangedCondition() );
183
    m_moduleState.add( m_gradients->getDataChangedCondition() );
184
    m_moduleState.add( m_propCondition );
185

186
    // Signal ready state.
187
    ready();
188
    debugLog() << "Module is now ready.";
189

190
    // create the root node containing the transformation and geometry
191
    osg::ref_ptr< WGEGroupNode > rootNode = new WGEGroupNode();
192 193 194 195 196

    // create the post-processing node which actually does the nice stuff to the rendered image
    osg::ref_ptr< WGEPostprocessingNode > postNode = new WGEPostprocessingNode(
        WKernel::getRunningKernel()->getGraphicsEngine()->getViewer()->getCamera()
    );
197
    postNode->addUpdateCallback( new WGENodeMaskCallback( m_active ) ); // disable the postNode with m_active
198 199 200 201 202 203 204

    // provide the properties of the post-processor to the user
    m_properties->addProperty( postNode->getProperties() );

    // insert it
    postNode->insert( rootNode, m_shader );
    bool postNodeInserted = false;
205

206 207 208
    // 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
    // connectors or on changed in your properties.
    debugLog() << "Entering main loop";
209
    while( !m_shutdownFlag() )
210
    {
211 212 213 214 215
        // Now, the moduleState variable comes into play. The module can wait for the condition, which gets fired whenever the input receives data
        // or an property changes. The main loop now waits until something happens.
        debugLog() << "Waiting ...";
        m_moduleState.wait();

216
        // quit if requested
217
        if( m_shutdownFlag() )
218 219 220 221
        {
            break;
        }

222
        // was there an update?
223
        bool dataUpdated = m_input->updated() || m_gradients->updated();
224 225
        boost::shared_ptr< WDataSetScalar > dataSet = m_input->getData();
        bool dataValid   = ( dataSet );
226

227
        // valid data available?
228
        if( !dataValid )
229 230 231 232 233
        {
            // remove renderings if no data is available anymore
            rootNode->clear();
        }

234
        // m_isoColor or shading changed
235
        if( m_isoColor->changed() )
236 237
        {
            // a new color requires the proxy geometry to be rebuild as we store it as color in this geometry
238
            dataUpdated = true;
239 240
        }

241
        // As the data has changed, we need to recreate the texture.
242
        if( dataUpdated && dataValid )
243 244 245
        {
            debugLog() << "Data changed. Uploading new data as texture.";

246 247
            m_isoValue->setMin( dataSet->getTexture()->minimum()->get() );
            m_isoValue->setMax( dataSet->getTexture()->scale()->get() + dataSet->getTexture()->minimum()->get() );
248
            m_isoValue->setRecommendedValue( dataSet->getTexture()->minimum()->get() + ( 0.5 * dataSet->getTexture()->scale()->get() ) );
249

250
            // First, grab the grid
251
            boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( dataSet->getGrid() );
252
            if( !grid )
253 254 255 256 257
            {
                errorLog() << "The dataset does not provide a regular grid. Ignoring dataset.";
                continue;
            }

258
            // use the OSG Shapes, create unit cube
259 260
            WBoundingBox bb( WPosition( 0.0, 0.0, 0.0 ),
                WPosition( grid->getNbCoordsX() - 1, grid->getNbCoordsY() - 1, grid->getNbCoordsZ() - 1 ) );
261
            osg::ref_ptr< osg::Node > cube = wge::generateSolidBoundingBoxNode( bb, m_isoColor->get( true ) );
262 263
            cube->asTransform()->getChild( 0 )->setName( "_DVR Proxy Cube" ); // Be aware that this name is used in the pick handler.
                                                                              // because of the underscore in front it won't be picked
264
            // we also set the grid's transformation here
265
            rootNode->setMatrix( static_cast< WMatrix4d >( grid->getTransform() ) );
266

267
            // bind the texture to the node
268
            osg::StateSet* rootState = cube->getOrCreateStateSet();
269 270 271 272 273

            // enable transparency
            wge::enableTransparency( cube );

            // bind the data texture
274
            osg::ref_ptr< WGETexture3D > texture3D = dataSet->getTexture();
275
            texture3D->bind( cube );
276

277
            ////////////////////////////////////////////////////////////////////////////////////////////////////
278
            // setup all those uniforms and additional textures
279 280
            ////////////////////////////////////////////////////////////////////////////////////////////////////

281 282 283 284
            rootState->addUniform( new WGEPropertyUniform< WPropDouble >( "u_isovalue", m_isoValue ) );
            rootState->addUniform( new WGEPropertyUniform< WPropInt >( "u_steps", m_stepCount ) );
            rootState->addUniform( new WGEPropertyUniform< WPropDouble >( "u_alpha", m_alpha ) );
            rootState->addUniform( new WGEPropertyUniform< WPropDouble >( "u_colormapRatio", m_colormapRatio ) );
285
            rootState->addUniform( new WGEPropertyUniform< WPropDouble >( "u_borderClipDistance", m_borderClipDistance ) );
286 287
            // Stochastic jitter?
            const size_t size = 64;
288
            osg::ref_ptr< WGETexture2D > randTex = wge::genWhiteNoiseTexture( size, size, 1 );
289 290
            wge::bindTexture( cube, randTex, 1 );

291 292
            // if there is a gradient field available -> apply as texture too
            boost::shared_ptr< WDataSetVector > gradients = m_gradients->getData();
293
            if( gradients )
294 295 296 297
            {
                debugLog() << "Uploading specified gradient field.";

                // bind the texture to the node
298
                osg::ref_ptr< WDataTexture3D > gradTexture3D = gradients->getTexture();
299 300 301 302 303 304 305 306 307
                wge::bindTexture( cube, gradTexture3D, 2, "u_gradients" );
                gradTexEnableDefine->setActive( true );
            }
            else
            {
                gradTexEnableDefine->setActive( false ); // disable gradient texture
            }
            WGEColormapping::apply( cube, grid->getTransformationMatrix(), m_shader, 3 );

308
            // update node
309
            debugLog() << "Adding new rendering.";
310 311 312 313
            rootNode->clear();
            rootNode->insert( cube );
            // 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
            // 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.
314
            if( !postNodeInserted )
315
            {
316 317
                postNodeInserted = true;
                WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( postNode );
318
            }
319
        }
320
    }
321 322 323

    // At this point, the container managing this module signalled to shutdown. The main loop has ended and you should clean up. Always remove
    // allocated memory and remove all OSG nodes.
324
    WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( postNode );
325 326
}