//---------------------------------------------------------------------------
//
// 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 .
//
//---------------------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#include
#include
#include "../../common/WPropertyHelper.h"
#include "../../common/math/WMath.h"
#include "../../common/math/WPlane.h"
#include "../../dataHandler/WDataHandler.h"
#include "../../dataHandler/WDataTexture3D_2.h"
#include "../../dataHandler/WGridRegular3D.h"
#include "../../graphicsEngine/WGEColormapping.h"
#include "../../graphicsEngine/WGEGeodeUtils.h"
#include "../../graphicsEngine/WGETextureUtils.h"
#include "../../graphicsEngine/callbacks/WGELinearTranslationCallback.h"
#include "../../graphicsEngine/callbacks/WGENodeMaskCallback.h"
#include "../../graphicsEngine/callbacks/WGEShaderAnimationCallback.h"
#include "../../graphicsEngine/offscreen/WGEOffscreenRenderNode.h"
#include "../../graphicsEngine/offscreen/WGEOffscreenRenderPass.h"
#include "../../graphicsEngine/shaders/WGEPropertyUniform.h"
#include "../../graphicsEngine/shaders/WGEShader.h"
#include "../../graphicsEngine/shaders/WGEShaderDefineOptions.h"
#include "../../kernel/WKernel.h"
#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." );
// scalar input
m_scalarIn = WModuleInputData< WDataSetScalar >::createAndAdd( shared_from_this(), "scalars", "The scalar dataset."
"Needs to be in the same grid as the mesh." );
// mesh input
m_meshIn = WModuleInputData< WTriangleMesh >::createAndAdd( shared_from_this(), "surface", "The optional surface to use." );
// call WModule's initialization
WModule::connectors();
}
void WMImageSpaceLIC::properties()
{
m_propCondition = boost::shared_ptr< WCondition >( new WCondition() );
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." );
// enable slices
// Flags denoting whether the glyphs should be shown on the specific slice
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 );
// The slice positions. These get update externally.
// TODO(all): this should somehow be connected to the nav slices.
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 );
m_xPos->setMin( 0 );
m_xPos->setMax( 159 );
m_yPos->setMin( 0 );
m_yPos->setMax( 199 );
m_zPos->setMin( 0 );
m_zPos->setMax( 159 );
m_licGroup = m_properties->addPropertyGroup( "LIC", "LIC properties." );
// show hud?
m_showHUD = m_licGroup->addProperty( "Show HUD", "Check to enable the debugging texture HUD.", false );
m_useLight = m_licGroup->addProperty( "Use Light", "Check to enable lightning using the Phong model.", false );
m_useEdges = m_licGroup->addProperty( "Edges", "Check to enable blending in edges.", true );
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 );
m_numIters = m_licGroup->addProperty( "Number of Iterations", "How much iterations along a streamline should be done per frame.", 30 );
m_numIters->setMin( 1 );
m_numIters->setMax( 100 );
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 );
// call WModule's initialization
WModule::properties();
}
void WMImageSpaceLIC::initOSG( boost::shared_ptr< WGridRegular3D > grid, boost::shared_ptr< WTriangleMesh > mesh )
{
// remove the old slices
m_output->clear();
if ( mesh && !m_useSlices->get( true ) )
{
// we have a mesh and want to use it
// 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
surfaceGeometry->setTexCoordArray( 0, mesh->getTextureCoordinateArray() );
// render
surfaceGeode->addDrawable( surfaceGeometry );
m_output->insert( surfaceGeode );
}
else if ( !mesh && !m_useSlices->get( true ) )
{
warnLog() << "No surface connected to input but surface render mode enabled. Nothing rendered.";
}
else
{
// we want the tex matrix for each slice to be modified too,
osg::ref_ptr< osg::TexMat > texMat;
// create a new geode containing the slices
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
// add the transformation nodes to the output group
m_output->insert( xSlice );
m_output->insert( ySlice );
m_output->insert( zSlice );
}
}
void WMImageSpaceLIC::moduleMain()
{
// get notified about data changes
m_moduleState.setResetable( true, true );
m_moduleState.add( m_vectorsIn->getDataChangedCondition() );
m_moduleState.add( m_scalarIn->getDataChangedCondition() );
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 );
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Preparation 1: create noise texture
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// we need a noise texture with a sufficient resolution. Create it.
const size_t resX = 1024;
// finally, create a texture from the image
osg::ref_ptr< osg::Texture2D > randTexture = wge::genWhiteNoiseTexture( resX, resX, 1 );
// done.
ready();
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Preparation 2: initialize offscreen renderer and hardwire it
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// create the root node for all the geometry
m_output = osg::ref_ptr< WGEManagedGroupNode > ( new WGEManagedGroupNode( m_active ) );
// the WGEOffscreenRenderNode manages each of the render-passes for us
osg::ref_ptr< WGEOffscreenRenderNode > offscreen = new WGEOffscreenRenderNode(
WKernel::getRunningKernel()->getGraphicsEngine()->getViewer()->getCamera()
);
// allow en-/disabling the HUD:
offscreen->getTextureHUD()->addUpdateCallback( new WGENodeMaskCallback( m_showHUD ) );
// setup all the passes needed for image space advection
osg::ref_ptr< WGEShader > transformationShader = new WGEShader( "WMImageSpaceLIC-Transformation", m_localPath );
WGEShaderDefineOptions::SPtr availableDataDefines = WGEShaderDefineOptions::SPtr( new WGEShaderDefineOptions( "SCALARDATA", "VECTORDATA" ) );
transformationShader->addPreprocessor( availableDataDefines );
osg::ref_ptr< WGEOffscreenRenderPass > transformation = offscreen->addGeometryRenderPass(
m_output,
transformationShader,
"Transformation"
);
// apply colormapping to transformation
WGEColormapping::apply( transformation, transformationShader, 1 );
osg::ref_ptr< WGEOffscreenRenderPass > edgeDetection = offscreen->addTextureProcessingPass(
new WGEShader( "WMImageSpaceLIC-Edge", m_localPath ),
"Edge Detection"
);
// 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).
osg::ref_ptr< WGEOffscreenRenderPass > advection = offscreen->addTextureProcessingPass(
new WGEShader( "WMImageSpaceLIC-Advection", m_localPath ),
"Advection"
);
// 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(
new WGEShader( "WMImageSpaceLIC-ClipBlend", m_localPath ),
"Clip & Blend"
);
// 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 );
osg::ref_ptr< osg::Texture2D > transformationColormapped = transformation->attach( osg::Camera::COLOR_BUFFER1 );
osg::ref_ptr< osg::Texture2D > transformationDepth = transformation->attach( osg::Camera::DEPTH_BUFFER );
// Edge Detection Pass, needs Depth as input
// * Edges in R
// * Depth in G
// * Un-advected Noise in B
osg::ref_ptr< osg::Texture2D > edgeDetectionOut1 = edgeDetection->attach( osg::Camera::COLOR_BUFFER0 );
edgeDetection->bind( transformationDepth, 0 );
edgeDetection->bind( randTexture, 1 );
edgeDetection->bind( transformationOut1, 2 );
// Advection Pass, needs edges and projected vectors as well as noise texture
// * Advected noise in luminance channel
osg::ref_ptr< osg::Texture2D > advectionOutA = advection->attach( osg::Camera::COLOR_BUFFER0, GL_LUMINANCE );
advection->bind( transformationOut1, 0 );
advection->bind( edgeDetectionOut1, 1 );
// advection needs some uniforms controlled by properties
osg::ref_ptr< osg::Uniform > numIters = new WGEPropertyUniform< WPropInt >( "u_numIter", m_numIters );
advection->addUniform( numIters );
// Final clipping and blending phase, needs Advected Noise, Edges, Depth and Light
clipBlend->bind( advectionOutA, 0 );
clipBlend->bind( edgeDetectionOut1, 1 );
clipBlend->bind( transformationColormapped, 2 );
// 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 ) );
clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useDepthCueing", m_useDepthCueing ) );
clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useHighContrast", m_useHighContrast ) );
clipBlend->addUniform( new WGEPropertyUniform< WPropDouble >( "u_cmapRatio", m_cmapRatio ) );
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Main loop
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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:
bool dataUpdated = m_vectorsIn->handledUpdate() || m_scalarIn->handledUpdate() || m_meshIn->handledUpdate();
bool propertyUpdated = m_useSlices->changed();
boost::shared_ptr< WDataSetVector > dataSetVec = m_vectorsIn->getData();
boost::shared_ptr< WDataSetScalar > dataSetScal = m_scalarIn->getData();
boost::shared_ptr< WTriangleMesh > mesh = m_meshIn->getData();
bool dataValid = ( dataSetVec || dataSetScal );
// 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 )
{
continue;
}
// ensure it gets added
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( offscreen );
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( offscreen );
// 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 );
initOSG( grid, mesh );
// prepare offscreen render chain
edgeDetection->bind( randTexture, 1 );
availableDataDefines->activateOption( 1 ); // vector input
transformation->bind( dataSetVec->getTexture2(), 0 );
}
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 );
initOSG( grid, mesh );
// prepare offscreen render chain
edgeDetection->bind( randTexture, 1 );
availableDataDefines->activateOption( 0 ); // scalar input
transformation->bind( dataSetScal->getTexture2(), 0 );
}
debugLog() << "Done";
}
// clean up
offscreen->clear();
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( offscreen );
}