WMImageSpaceLIC.cpp 18.7 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 <cmath>
26
#include <cstdlib>
27 28 29
#include <vector>
#include <string>

30 31 32 33 34
#include <osg/Vec3>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Drawable>

35 36
#include "../../common/WPropertyHelper.h"
#include "../../common/math/WMath.h"
37
#include "../../common/math/WPlane.h"
38
#include "../../dataHandler/WDataHandler.h"
39
#include "../../dataHandler/WDataTexture3D_2.h"
40 41 42 43
#include "../../dataHandler/WGridRegular3D.h"
#include "../../graphicsEngine/WGEColormapping.h"
#include "../../graphicsEngine/WGEGeodeUtils.h"
#include "../../graphicsEngine/WGETextureUtils.h"
44
#include "../../graphicsEngine/callbacks/WGELinearTranslationCallback.h"
45
#include "../../graphicsEngine/callbacks/WGENodeMaskCallback.h"
46
#include "../../graphicsEngine/callbacks/WGEShaderAnimationCallback.h"
47
#include "../../graphicsEngine/offscreen/WGEOffscreenRenderNode.h"
48
#include "../../graphicsEngine/offscreen/WGEOffscreenRenderPass.h"
49
#include "../../graphicsEngine/shaders/WGEPropertyUniform.h"
50 51 52
#include "../../graphicsEngine/shaders/WGEShader.h"
#include "../../graphicsEngine/shaders/WGEShaderDefineOptions.h"
#include "../../kernel/WKernel.h"
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95

#include "WMImageSpaceLIC.h"
#include "WMImageSpaceLIC.xpm"

// This line is needed by the module loader to actually find your module. You need to add this to your module too. Do NOT add a ";" here.
W_LOADABLE_MODULE( WMImageSpaceLIC )

WMImageSpaceLIC::WMImageSpaceLIC():
    WModule()
{
}

WMImageSpaceLIC::~WMImageSpaceLIC()
{
    // Cleanup!
}

boost::shared_ptr< WModule > WMImageSpaceLIC::factory() const
{
    return boost::shared_ptr< WModule >( new WMImageSpaceLIC() );
}

const char** WMImageSpaceLIC::getXPMIcon() const
{
    return WMImageSpaceLIC_xpm;
}

const std::string WMImageSpaceLIC::getName() const
{
    return "Image Space LIC";
}

const std::string WMImageSpaceLIC::getDescription() const
{
    return "This module takes an vector dataset and creates an LIC-like texture on an arbitrary surface. You can specify the surface as input or"
           "leave it unspecified to use slices.";
}

void WMImageSpaceLIC::connectors()
{
    // vector input
    m_vectorsIn = WModuleInputData< WDataSetVector >::createAndAdd( shared_from_this(), "vectors", "The vector dataset."
                                                                                                    "Needs to be in the same grid as the mesh." );
96 97 98
    // scalar input
    m_scalarIn = WModuleInputData< WDataSetScalar >::createAndAdd( shared_from_this(), "scalars", "The scalar dataset."
                                                                                                    "Needs to be in the same grid as the mesh." );
99 100

    // mesh input
101
    m_meshIn = WModuleInputData< WTriangleMesh >::createAndAdd( shared_from_this(), "surface", "The optional surface to use." );
102 103 104 105 106 107 108 109 110

    // call WModule's initialization
    WModule::connectors();
}

void WMImageSpaceLIC::properties()
{
    m_propCondition = boost::shared_ptr< WCondition >( new WCondition() );

111 112 113 114 115
    m_geometryGroup   = m_properties->addPropertyGroup( "Geometry",  "Selection of used geometry to apply LIC to." );

    m_useSlices       = m_geometryGroup->addProperty( "Use Slices", "Show vectors on slices.", true, m_propCondition );

    m_sliceGroup      = m_geometryGroup->addPropertyGroup( "Slices",  "Slice based LIC." );
116

117 118
    // enable slices
    // Flags denoting whether the glyphs should be shown on the specific slice
119 120 121
    m_showonX        = m_sliceGroup->addProperty( "Show Sagittal", "Show vectors on sagittal slice.", true );
    m_showonY        = m_sliceGroup->addProperty( "Show Coronal", "Show vectors on coronal slice.", true );
    m_showonZ        = m_sliceGroup->addProperty( "Show Axial", "Show vectors on axial slice.", true );
122 123 124

    // The slice positions. These get update externally.
    // TODO(all): this should somehow be connected to the nav slices.
125 126 127
    m_xPos           = m_sliceGroup->addProperty( "Sagittal Position", "Slice X position.", 80 );
    m_yPos           = m_sliceGroup->addProperty( "Coronal Position", "Slice Y position.", 100 );
    m_zPos           = m_sliceGroup->addProperty( "Axial Position", "Slice Z position.", 80 );
128 129 130 131 132 133 134
    m_xPos->setMin( 0 );
    m_xPos->setMax( 159 );
    m_yPos->setMin( 0 );
    m_yPos->setMax( 199 );
    m_zPos->setMin( 0 );
    m_zPos->setMax( 159 );

135 136
    m_licGroup      = m_properties->addPropertyGroup( "LIC",  "LIC properties." );

137
    // show hud?
138
    m_showHUD        = m_licGroup->addProperty( "Show HUD", "Check to enable the debugging texture HUD.", false );
139

140
    m_useLight       = m_licGroup->addProperty( "Use Light", "Check to enable lightning using the Phong model.", false );
141

142
    m_useEdges       = m_licGroup->addProperty( "Edges", "Check to enable blending in edges.", true );
143 144 145
    m_useDepthCueing = m_licGroup->addProperty( "Depth Cueing", "Use depth as additional cue? Blends in the depth. Mostly useful for isosurfaces.",
                                                false );
    m_useHighContrast = m_licGroup->addProperty( "High Contrast", "Use an extremely increased contrast.", false );
146

147
    m_numIters     = m_licGroup->addProperty( "Number of Iterations", "How much iterations along a streamline should be done per frame.", 30 );
148
    m_numIters->setMin( 1 );
Sebastian Eichelbaum's avatar
Sebastian Eichelbaum committed
149
    m_numIters->setMax( 100 );
150

151 152 153 154
    m_cmapRatio    = m_licGroup->addProperty( "Ratio Colormap to LIC", "Blending ratio between LIC and colormap.", 0.5 );
    m_cmapRatio->setMin( 0.0 );
    m_cmapRatio->setMax( 1.0 );

155 156 157 158
    // call WModule's initialization
    WModule::properties();
}

159
void WMImageSpaceLIC::initOSG( boost::shared_ptr< WGridRegular3D > grid, boost::shared_ptr< WTriangleMesh > mesh )
160 161
{
    // remove the old slices
162 163 164 165 166
    m_output->clear();

    if ( mesh && !m_useSlices->get( true ) )
    {
        // we have a mesh and want to use it
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
        // create geometry and geode
        osg::Geometry* surfaceGeometry = new osg::Geometry();
        osg::ref_ptr< osg::Geode > surfaceGeode = osg::ref_ptr< osg::Geode >( new osg::Geode );

        surfaceGeometry->setVertexArray( mesh->getVertexArray() );
        osg::DrawElementsUInt* surfaceElement;
        surfaceElement = new osg::DrawElementsUInt( osg::PrimitiveSet::TRIANGLES, 0 );
        std::vector< size_t > tris = mesh->getTriangles();
        surfaceElement->reserve( tris.size() );
        for( unsigned int vertId = 0; vertId < tris.size(); ++vertId )
        {
            surfaceElement->push_back( tris[vertId] );
        }
        surfaceGeometry->addPrimitiveSet( surfaceElement );

        // normals
        surfaceGeometry->setNormalArray( mesh->getVertexNormalArray() );
        surfaceGeometry->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );

        // texture coordinates
187
        surfaceGeometry->setTexCoordArray( 0, mesh->getTextureCoordinateArray() );
188 189 190

        // render
        surfaceGeode->addDrawable( surfaceGeometry );
Sebastian Eichelbaum's avatar
Sebastian Eichelbaum committed
191
        m_output->insert( surfaceGeode );
192
    }
193 194 195 196
    else if ( !mesh && !m_useSlices->get( true ) )
    {
        warnLog() << "No surface connected to input but surface render mode enabled. Nothing rendered.";
    }
197 198 199 200
    else
    {
        // we want the tex matrix for each slice to be modified too,
        osg::ref_ptr< osg::TexMat > texMat;
201

202
        // create a new geode containing the slices
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
        osg::ref_ptr< osg::Node > xSlice = wge::genFinitePlane( grid->getOrigin(), grid->getNbCoordsY() * grid->getDirectionY(),
                                                                                   grid->getNbCoordsZ() * grid->getDirectionZ() );

        osg::ref_ptr< osg::Node > ySlice = wge::genFinitePlane( grid->getOrigin(), grid->getNbCoordsX() * grid->getDirectionX(),
                                                                                   grid->getNbCoordsZ() * grid->getDirectionZ() );

        osg::ref_ptr< osg::Node > zSlice =  wge::genFinitePlane( grid->getOrigin(), grid->getNbCoordsX() * grid->getDirectionX(),
                                                                                    grid->getNbCoordsY() * grid->getDirectionY() );

        // The movement of the slice is done in the shader. An alternative would be WGELinearTranslationCallback but there, the needed matrix is
        // not available in the shader
        osg::StateSet* ss = xSlice->getOrCreateStateSet();
        ss->addUniform( new WGEPropertyUniform< WPropInt >( "u_vertexShift", m_xPos ) );
        ss->addUniform( new osg::Uniform( "u_vertexShiftDirection", grid->getDirectionX() ) );  // the axis to move along
        ss = ySlice->getOrCreateStateSet();
        ss->addUniform( new WGEPropertyUniform< WPropInt >( "u_vertexShift", m_yPos ) );
        ss->addUniform( new osg::Uniform( "u_vertexShiftDirection", grid->getDirectionY() ) );  // the axis to move along
        ss = zSlice->getOrCreateStateSet();
        ss->addUniform( new WGEPropertyUniform< WPropInt >( "u_vertexShift", m_zPos ) );
        ss->addUniform( new osg::Uniform( "u_vertexShiftDirection", grid->getDirectionZ() ) );  // the axis to move along
223 224

        // add the transformation nodes to the output group
225 226 227
        m_output->insert( xSlice );
        m_output->insert( ySlice );
        m_output->insert( zSlice );
228
    }
229 230
}

231 232 233 234 235
void WMImageSpaceLIC::moduleMain()
{
    // get notified about data changes
    m_moduleState.setResetable( true, true );
    m_moduleState.add( m_vectorsIn->getDataChangedCondition() );
236
    m_moduleState.add( m_scalarIn->getDataChangedCondition() );
237 238 239 240
    m_moduleState.add( m_meshIn->getDataChangedCondition() );
    // Remember the condition provided to some properties in properties()? The condition can now be used with this condition set.
    m_moduleState.add( m_propCondition );

241 242 243 244 245
    /////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Preparation 1: create noise texture
    /////////////////////////////////////////////////////////////////////////////////////////////////////////

    // we need a noise texture with a sufficient resolution. Create it.
246
    const size_t resX = 1024;
247 248

    // finally, create a texture from the image
249
    osg::ref_ptr< osg::Texture2D > randTexture = wge::genWhiteNoiseTexture( resX, resX, 1 );
250
    // done.
251 252
    ready();

253 254 255 256
    /////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Preparation 2: initialize offscreen renderer and hardwire it
    /////////////////////////////////////////////////////////////////////////////////////////////////////////

257
    // create the root node for all the geometry
258
    m_output = osg::ref_ptr< WGEManagedGroupNode > ( new WGEManagedGroupNode( m_active ) );
259

260 261
    // the WGEOffscreenRenderNode manages each of the render-passes for us
    osg::ref_ptr< WGEOffscreenRenderNode > offscreen = new WGEOffscreenRenderNode(
262
        WKernel::getRunningKernel()->getGraphicsEngine()->getViewer()->getCamera()
263
    );
264

265 266 267
    // allow en-/disabling the HUD:
    offscreen->getTextureHUD()->addUpdateCallback( new WGENodeMaskCallback( m_showHUD ) );

268
    // setup all the passes needed for image space advection
269
    osg::ref_ptr< WGEShader > transformationShader = new WGEShader( "WMImageSpaceLIC-Transformation", m_localPath );
270
    WGEShaderDefineOptions::SPtr availableDataDefines = WGEShaderDefineOptions::SPtr( new WGEShaderDefineOptions( "SCALARDATA", "VECTORDATA" ) );
271
    transformationShader->addPreprocessor( availableDataDefines );
272 273
    osg::ref_ptr< WGEOffscreenRenderPass > transformation = offscreen->addGeometryRenderPass(
        m_output,
274
        transformationShader,
275 276
        "Transformation"
    );
277 278 279
    // apply colormapping to transformation
    WGEColormapping::apply( transformation, transformationShader, 1 );

280
    osg::ref_ptr< WGEOffscreenRenderPass > edgeDetection =  offscreen->addTextureProcessingPass(
281
        new WGEShader( "WMImageSpaceLIC-Edge", m_localPath ),
282 283
        "Edge Detection"
    );
284

285 286
    // we use two advection passes per frame as the input A of the first produces the output B whereas the second pass uses B as input and
    // produces A as output. This way we can use A as input for the next step (clipping and blending).
287
    osg::ref_ptr< WGEOffscreenRenderPass > advection =  offscreen->addTextureProcessingPass(
288
        new WGEShader( "WMImageSpaceLIC-Advection", m_localPath ),
289
        "Advection"
290 291 292 293
    );

    // finally, put it back on screen, clip it, color it and apply depth buffer to on-screen buffer
    osg::ref_ptr< WGEOffscreenRenderPass > clipBlend =  offscreen->addFinalOnScreenPass(
294
        new WGEShader( "WMImageSpaceLIC-ClipBlend", m_localPath ),
295 296 297
        "Clip & Blend"
    );

298 299 300 301 302 303 304
    // hardwire the textures to use for each pass:

    // Transformation Pass, needs Geometry
    //  * Creates 2D projected Vectors in RG
    //  * Lighting in B
    //  * Depth
    osg::ref_ptr< osg::Texture2D > transformationOut1  = transformation->attach( osg::Camera::COLOR_BUFFER0 );
305
    osg::ref_ptr< osg::Texture2D > transformationColormapped  = transformation->attach( osg::Camera::COLOR_BUFFER1 );
306 307
    osg::ref_ptr< osg::Texture2D > transformationDepth = transformation->attach( osg::Camera::DEPTH_BUFFER );

308
    // Edge Detection Pass, needs Depth as input
309 310 311
    //  * Edges in R
    //  * Depth in G
    //  * Un-advected Noise in B
312
    osg::ref_ptr< osg::Texture2D > edgeDetectionOut1 = edgeDetection->attach( osg::Camera::COLOR_BUFFER0 );
313 314
    edgeDetection->bind( transformationDepth, 0 );
    edgeDetection->bind( randTexture,         1 );
315
    edgeDetection->bind( transformationOut1,  2 );
316 317 318

    // Advection Pass, needs edges and projected vectors as well as noise texture
    //  * Advected noise in luminance channel
319 320 321
    osg::ref_ptr< osg::Texture2D > advectionOutA  = advection->attach( osg::Camera::COLOR_BUFFER0, GL_LUMINANCE );
    advection->bind( transformationOut1, 0 );
    advection->bind( edgeDetectionOut1,  1 );
322

323
    // advection needs some uniforms controlled by properties
Sebastian Eichelbaum's avatar
Sebastian Eichelbaum committed
324
    osg::ref_ptr< osg::Uniform > numIters = new WGEPropertyUniform< WPropInt >( "u_numIter", m_numIters );
325
    advection->addUniform( numIters );
326

327
    // Final clipping and blending phase, needs Advected Noise, Edges, Depth and Light
328
    clipBlend->bind( advectionOutA, 0 );
329
    clipBlend->bind( edgeDetectionOut1, 1 );
330
    clipBlend->bind( transformationColormapped, 2 );
331 332 333
    // final pass needs some uniforms controlled by properties
    clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useEdges", m_useEdges ) );
    clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useLight", m_useLight ) );
334 335
    clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useDepthCueing", m_useDepthCueing ) );
    clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useHighContrast", m_useHighContrast ) );
336
    clipBlend->addUniform( new WGEPropertyUniform< WPropDouble >( "u_cmapRatio", m_cmapRatio ) );
337 338 339 340

    /////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Main loop
    /////////////////////////////////////////////////////////////////////////////////////////////////////////
341

342 343 344 345 346 347 348 349 350 351 352 353 354
    // main loop
    while ( !m_shutdownFlag() )
    {
        debugLog() << "Waiting ...";
        m_moduleState.wait();

        // woke up since the module is requested to finish?
        if ( m_shutdownFlag() )
        {
            break;
        }

        // To query whether an input was updated, simply ask the input:
355 356
        bool dataUpdated = m_vectorsIn->handledUpdate() || m_scalarIn->handledUpdate() || m_meshIn->handledUpdate();
        bool propertyUpdated = m_useSlices->changed();
357 358
        boost::shared_ptr< WDataSetVector > dataSetVec = m_vectorsIn->getData();
        boost::shared_ptr< WDataSetScalar > dataSetScal = m_scalarIn->getData();
359
        boost::shared_ptr< WTriangleMesh > mesh = m_meshIn->getData();
360 361

        bool dataValid = ( dataSetVec || dataSetScal );
362 363 364 365 366 367 368 369 370 371 372

        // is data valid? If not, remove graphics
        if ( !dataValid )
        {
            debugLog() << "Resetting.";
            WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( offscreen );
            continue;
        }

        // something interesting for us?
        if ( dataValid && !dataUpdated && !propertyUpdated )
373 374 375 376
        {
            continue;
        }

377 378
        // ensure it gets added
        WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( offscreen );
379 380
        WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( offscreen );

381 382 383 384 385 386 387 388 389 390
        // prefer vector dataset if existing
        if ( dataSetVec )
        {
            debugLog() << "Using vector data";

            // get grid and prepare OSG
            boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( dataSetVec->getGrid() );
            m_xPos->setMax( grid->getNbCoordsX() - 1 );
            m_yPos->setMax( grid->getNbCoordsY() - 1 );
            m_zPos->setMax( grid->getNbCoordsZ() - 1 );
391
            initOSG( grid, mesh );
392 393 394

            // prepare offscreen render chain
            edgeDetection->bind( randTexture, 1 );
395
            availableDataDefines->activateOption( 1 );  // vector input
396
            transformation->bind( dataSetVec->getTexture2(), 0 );
397 398 399 400 401 402 403 404 405 406
        }
        else if ( dataSetScal )
        {
            debugLog() << "Using scalar data";

            // get grid and prepare OSG
            boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( dataSetScal->getGrid() );
            m_xPos->setMax( grid->getNbCoordsX() - 1 );
            m_yPos->setMax( grid->getNbCoordsY() - 1 );
            m_zPos->setMax( grid->getNbCoordsZ() - 1 );
407
            initOSG( grid, mesh );
408 409 410

            // prepare offscreen render chain
            edgeDetection->bind( randTexture, 1 );
411
            availableDataDefines->activateOption( 0 );  // scalar input
412
            transformation->bind( dataSetScal->getTexture2(), 0 );
413
        }
414

415 416
        debugLog() << "Done";
    }
417 418

    // clean up
419 420
    offscreen->clear();
    WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( offscreen );
421 422
}