1
//---------------------------------------------------------------------------
3
// Project: OpenWalnut ( http://www.openwalnut.org )
5
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
6
// For more information see http://www.openwalnut.org/copying
8
// This file is part of OpenWalnut.
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.
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.
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/>.
23
//---------------------------------------------------------------------------
30
#include <osg/BoundingSphere>
33
#include <osg/Geometry>
34
#include <osg/Drawable>
36
#include "core/common/WPropertyHelper.h"
37
#include "core/common/math/WMath.h"
38
#include "core/dataHandler/WDataHandler.h"
39
#include "core/dataHandler/WDataTexture3D.h"
40
#include "core/dataHandler/WGridRegular3D.h"
41
#include "core/graphicsEngine/WGEColormapping.h"
42
#include "core/graphicsEngine/WGEGeodeUtils.h"
43
#include "core/graphicsEngine/WGETextureUtils.h"
44
#include "core/graphicsEngine/callbacks/WGELinearTranslationCallback.h"
45
#include "core/graphicsEngine/callbacks/WGENodeMaskCallback.h"
46
#include "core/graphicsEngine/offscreen/WGEOffscreenRenderNode.h"
47
#include "core/graphicsEngine/offscreen/WGEOffscreenRenderPass.h"
48
#include "core/graphicsEngine/shaders/WGEPropertyUniform.h"
49
#include "core/graphicsEngine/shaders/WGEShader.h"
50
#include "core/graphicsEngine/shaders/WGEShaderDefineOptions.h"
51
#include "core/graphicsEngine/shaders/WGEShaderPropertyDefineOptions.h"
52
#include "core/kernel/WKernel.h"
54
#include "WMImageSpaceLIC.h"
55
#include "WMImageSpaceLIC.xpm"
57
// 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.
58
W_LOADABLE_MODULE( WMImageSpaceLIC )
60
WMImageSpaceLIC::WMImageSpaceLIC():
65
WMImageSpaceLIC::~WMImageSpaceLIC()
70
boost::shared_ptr< WModule > WMImageSpaceLIC::factory() const
72
return boost::shared_ptr< WModule >( new WMImageSpaceLIC() );
75
const char** WMImageSpaceLIC::getXPMIcon() const
77
return WMImageSpaceLIC_xpm;
80
const std::string WMImageSpaceLIC::getName() const
82
return "Image Space LIC";
85
const std::string WMImageSpaceLIC::getDescription() const
87
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"
88
"leave it unspecified to use slices.";
91
void WMImageSpaceLIC::connectors()
94
m_vectorsIn = WModuleInputData< WDataSetVector >::createAndAdd( shared_from_this(), "vectors", "The vector dataset."
95
"Needs to be in the same grid as the mesh." );
97
m_scalarIn = WModuleInputData< WDataSetScalar >::createAndAdd( shared_from_this(), "scalars", "The scalar dataset."
98
"Needs to be in the same grid as the mesh." );
101
m_meshIn = WModuleInputData< WTriangleMesh >::createAndAdd( shared_from_this(), "surface", "The optional surface to use." );
103
// call WModule's initialization
104
WModule::connectors();
107
void WMImageSpaceLIC::properties()
109
m_propCondition = boost::shared_ptr< WCondition >( new WCondition() );
111
m_geometryGroup = m_properties->addPropertyGroup( "Geometry", "Selection of used geometry to apply LIC to." );
113
m_useSlices = m_geometryGroup->addProperty( "Use Slices", "Show vectors on slices.", true, m_propCondition );
115
m_sliceGroup = m_geometryGroup->addPropertyGroup( "Slices", "Slice based LIC." );
118
// Flags denoting whether the glyphs should be shown on the specific slice
119
m_showonX = m_sliceGroup->addProperty( "Show Sagittal", "Show vectors on sagittal slice.", true );
120
m_showonY = m_sliceGroup->addProperty( "Show Coronal", "Show vectors on coronal slice.", true );
121
m_showonZ = m_sliceGroup->addProperty( "Show Axial", "Show vectors on axial slice.", true );
123
// The slice positions. These get update externally.
124
// TODO(all): this should somehow be connected to the nav slices.
125
m_xPos = m_sliceGroup->addProperty( "Sagittal Position", "Slice X position.", 80 );
126
m_yPos = m_sliceGroup->addProperty( "Coronal Position", "Slice Y position.", 100 );
127
m_zPos = m_sliceGroup->addProperty( "Axial Position", "Slice Z position.", 80 );
129
m_xPos->setMax( 159 );
131
m_yPos->setMax( 199 );
133
m_zPos->setMax( 159 );
135
m_licGroup = m_properties->addPropertyGroup( "LIC", "LIC properties." );
137
m_useLight = m_licGroup->addProperty( "Use Light", "Check to enable lightning using the Phong model.", false );
139
m_useEdges = m_licGroup->addProperty( "Edges", "Check to enable blending in edges.", true );
140
m_useHighContrast = m_licGroup->addProperty( "High Contrast", "Use an extremely increased contrast.", false );
142
m_cmapRatio = m_licGroup->addProperty( "Ratio Colormap to LIC", "Blending ratio between LIC and colormap.", 0.5 );
143
m_cmapRatio->setMin( 0.0 );
144
m_cmapRatio->setMax( 1.0 );
146
m_advancedLicGroup = m_properties->addPropertyGroup( "Advanced", "More advanced LIC properties." );
148
m_showHUD = m_advancedLicGroup->addProperty( "Show HUD", "Check to enable the debugging texture HUD.", false );
149
m_useDepthCueing = m_advancedLicGroup->addProperty( "Depth Cueing", "Use depth as additional cue? Mostly useful for isosurfaces.",
151
m_3dNoise = m_advancedLicGroup->addProperty( "Use 3D noise", "Use 3D noise? This provides better coherence during transformation of "
152
"the geometry but might introduce resolution problems.", false );
153
m_3dNoiseRes = m_advancedLicGroup->addProperty( "3D Noise Resolution", "The 3D noise is of 128^3 pixels size. This scaler allows "
154
"modification of this size.", 3.0 );
155
m_3dNoiseRes->setMin( 1 );
156
m_3dNoiseRes->setMax( 10 );
158
m_3dNoiseAutoRes = m_advancedLicGroup->addProperty( "3D Noise Auto-Resolution", "If checked, the resolution of the 3D noise gets calculated "
159
"automatically according to the screen size. If disabled, the user can zoom into the LIC.",
162
m_numIters = m_advancedLicGroup->addProperty( "Number of Iterations", "How much iterations along a streamline should be done per frame.",
164
m_numIters->setMin( 1 );
165
m_numIters->setMax( 100 );
167
// call WModule's initialization
168
WModule::properties();
171
void WMImageSpaceLIC::initOSG( boost::shared_ptr< WGridRegular3D > grid, boost::shared_ptr< WTriangleMesh > mesh )
173
// remove the old slices
176
if( mesh && !m_useSlices->get( true ) )
178
// we have a mesh and want to use it
179
// create geometry and geode
180
osg::Geometry* surfaceGeometry = new osg::Geometry();
181
osg::ref_ptr< osg::Geode > surfaceGeode = osg::ref_ptr< osg::Geode >( new osg::Geode );
183
surfaceGeometry->setVertexArray( mesh->getVertexArray() );
184
osg::DrawElementsUInt* surfaceElement;
185
surfaceElement = new osg::DrawElementsUInt( osg::PrimitiveSet::TRIANGLES, 0 );
186
std::vector< size_t > tris = mesh->getTriangles();
187
surfaceElement->reserve( tris.size() );
188
for( unsigned int vertId = 0; vertId < tris.size(); ++vertId )
190
surfaceElement->push_back( tris[vertId] );
192
surfaceGeometry->addPrimitiveSet( surfaceElement );
195
surfaceGeometry->setNormalArray( mesh->getVertexNormalArray() );
196
surfaceGeometry->setNormalBinding( osg::Geometry::BIND_PER_VERTEX );
198
// texture coordinates
199
surfaceGeometry->setTexCoordArray( 0, mesh->getTextureCoordinateArray() );
202
surfaceGeode->addDrawable( surfaceGeometry );
203
m_output->insert( surfaceGeode );
205
else if( !mesh && !m_useSlices->get( true ) )
207
warnLog() << "No surface connected to input but surface render mode enabled. Nothing rendered.";
211
// create a new geode containing the slices
212
osg::ref_ptr< osg::Node > xSlice = wge::genFinitePlane( grid->getOrigin(), grid->getNbCoordsY() * grid->getDirectionY(),
213
grid->getNbCoordsZ() * grid->getDirectionZ() );
215
osg::ref_ptr< osg::Node > ySlice = wge::genFinitePlane( grid->getOrigin(), grid->getNbCoordsX() * grid->getDirectionX(),
216
grid->getNbCoordsZ() * grid->getDirectionZ() );
218
osg::ref_ptr< osg::Node > zSlice = wge::genFinitePlane( grid->getOrigin(), grid->getNbCoordsX() * grid->getDirectionX(),
219
grid->getNbCoordsY() * grid->getDirectionY() );
221
// The movement of the slice is done in the shader. An alternative would be WGELinearTranslationCallback but there, the needed matrix is
222
// not available in the shader
223
osg::StateSet* ss = xSlice->getOrCreateStateSet();
224
ss->addUniform( new WGEPropertyUniform< WPropInt >( "u_vertexShift", m_xPos ) );
225
ss->addUniform( new osg::Uniform( "u_vertexShiftDirection", grid->getDirectionX().as< osg::Vec3f >() ) ); // the axis to move along
226
ss = ySlice->getOrCreateStateSet();
227
ss->addUniform( new WGEPropertyUniform< WPropInt >( "u_vertexShift", m_yPos ) );
228
ss->addUniform( new osg::Uniform( "u_vertexShiftDirection", grid->getDirectionY().as< osg::Vec3f >() ) ); // the axis to move along
229
ss = zSlice->getOrCreateStateSet();
230
ss->addUniform( new WGEPropertyUniform< WPropInt >( "u_vertexShift", m_zPos ) );
231
ss->addUniform( new osg::Uniform( "u_vertexShiftDirection", grid->getDirectionZ().as< osg::Vec3f >() ) ); // the axis to move along
233
// set callbacks for en-/disabling the nodes
234
xSlice->addUpdateCallback( new WGENodeMaskCallback( m_showonX ) );
235
ySlice->addUpdateCallback( new WGENodeMaskCallback( m_showonY ) );
236
zSlice->addUpdateCallback( new WGENodeMaskCallback( m_showonZ ) );
239
xSlice->setCullingActive( false );
240
ySlice->setCullingActive( false );
241
zSlice->setCullingActive( false );
243
// add the transformation nodes to the output group
244
m_output->insert( xSlice );
245
m_output->insert( ySlice );
246
m_output->insert( zSlice );
247
m_output->dirtyBound();
251
void WMImageSpaceLIC::moduleMain()
253
// get notified about data changes
254
m_moduleState.setResetable( true, true );
255
m_moduleState.add( m_vectorsIn->getDataChangedCondition() );
256
m_moduleState.add( m_scalarIn->getDataChangedCondition() );
257
m_moduleState.add( m_meshIn->getDataChangedCondition() );
258
// Remember the condition provided to some properties in properties()? The condition can now be used with this condition set.
259
m_moduleState.add( m_propCondition );
261
/////////////////////////////////////////////////////////////////////////////////////////////////////////
262
// Preparation 1: create noise texture
263
/////////////////////////////////////////////////////////////////////////////////////////////////////////
265
// we need a noise texture with a sufficient resolution. Create it.
266
const size_t resX = 1024;
268
// finally, create a texture from the image
269
osg::ref_ptr< osg::Texture2D > randTexture = wge::genWhiteNoiseTexture( resX, resX, 1 );
271
// create a 3D texture too. This allows transformation-invariant noise but is prone to flickering artifacts due to down/upscaling
272
osg::ref_ptr< osg::Texture3D > rand3DTexture = wge::genWhiteNoiseTexture( 128, 128, 128, 1 );
273
rand3DTexture->setWrap( osg::Texture::WRAP_S, osg::Texture::REPEAT );
274
rand3DTexture->setWrap( osg::Texture::WRAP_T, osg::Texture::REPEAT );
275
rand3DTexture->setWrap( osg::Texture::WRAP_R, osg::Texture::REPEAT );
276
WGEShaderPreprocessor::SPtr define3dNoise( new WGEShaderPropertyDefineOptions< WPropBool >( m_3dNoise, "NOISE3D_DISABLED", "NOISE3D_ENABLED" ) );
281
/////////////////////////////////////////////////////////////////////////////////////////////////////////
282
// Preparation 2: initialize offscreen renderer and hardwire it
283
/////////////////////////////////////////////////////////////////////////////////////////////////////////
285
// create the root node for all the geometry
286
m_output = osg::ref_ptr< WGEManagedGroupNode > ( new WGEManagedGroupNode( m_active ) );
288
// the WGEOffscreenRenderNode manages each of the render-passes for us
289
osg::ref_ptr< WGEOffscreenRenderNode > offscreen = new WGEOffscreenRenderNode(
290
WKernel::getRunningKernel()->getGraphicsEngine()->getViewer()->getCamera()
293
// allow en-/disabling the HUD:
294
offscreen->getTextureHUD()->addUpdateCallback( new WGENodeMaskCallback( m_showHUD ) );
296
// setup all the passes needed for image space advection
297
osg::ref_ptr< WGEShader > transformationShader = new WGEShader( "WMImageSpaceLIC-Transformation", m_localPath );
298
WGEShaderDefineOptions::SPtr availableDataDefines = WGEShaderDefineOptions::SPtr( new WGEShaderDefineOptions( "SCALARDATA", "VECTORDATA" ) );
299
transformationShader->addPreprocessor( availableDataDefines );
300
transformationShader->addPreprocessor( define3dNoise );
301
transformationShader->addPreprocessor( WGEShaderPreprocessor::SPtr(
302
new WGEShaderPropertyDefineOptions< WPropBool >( m_3dNoiseAutoRes, "NOISE3DAUTORES_DISABLED", "NOISE3DAUTORES_ENABLED" )
305
osg::ref_ptr< WGEOffscreenRenderPass > transformation = offscreen->addGeometryRenderPass(
307
transformationShader,
310
transformation->bind( rand3DTexture, 1 );
311
// apply colormapping to transformation
312
WGEColormapping::apply( transformation, transformationShader, 2 );
314
osg::ref_ptr< WGEShader > edgeShader = new WGEShader( "WMImageSpaceLIC-Edge", m_localPath );
315
osg::ref_ptr< WGEOffscreenRenderPass > edgeDetection = offscreen->addTextureProcessingPass(
319
edgeShader->addPreprocessor( define3dNoise );
321
// 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
322
// produces A as output. This way we can use A as input for the next step (clipping and blending).
323
osg::ref_ptr< WGEOffscreenRenderPass > advection = offscreen->addTextureProcessingPass(
324
new WGEShader( "WMImageSpaceLIC-Advection", m_localPath ),
328
// finally, put it back on screen, clip it, color it and apply depth buffer to on-screen buffer
329
osg::ref_ptr< WGEOffscreenRenderPass > clipBlend = offscreen->addFinalOnScreenPass(
330
new WGEShader( "WMImageSpaceLIC-ClipBlend", m_localPath ),
334
// hardwire the textures to use for each pass:
336
// Transformation Pass, needs Geometry
337
// * Creates 2D projected Vectors in RG
340
osg::ref_ptr< osg::Texture2D > transformationOut1 = transformation->attach( osg::Camera::COLOR_BUFFER0 );
341
osg::ref_ptr< osg::Texture2D > transformationColormapped = transformation->attach( osg::Camera::COLOR_BUFFER1 );
342
osg::ref_ptr< osg::Texture2D > transformationDepth = transformation->attach( osg::Camera::DEPTH_BUFFER );
344
transformation->addUniform( new WGEPropertyUniform< WPropDouble >( "u_noise3DResoultuion", m_3dNoiseRes ) );
346
// Edge Detection Pass, needs Depth as input
349
// * Un-advected Noise in B
350
osg::ref_ptr< osg::Texture2D > edgeDetectionOut1 = edgeDetection->attach( osg::Camera::COLOR_BUFFER0 );
351
edgeDetection->bind( transformationDepth, 0 );
352
edgeDetection->bind( randTexture, 1 );
353
edgeDetection->bind( transformationOut1, 2 );
355
// Advection Pass, needs edges and projected vectors as well as noise texture
356
// * Advected noise in luminance channel
357
osg::ref_ptr< osg::Texture2D > advectionOutA = advection->attach( osg::Camera::COLOR_BUFFER0, GL_LUMINANCE );
358
advection->bind( transformationOut1, 0 );
359
advection->bind( edgeDetectionOut1, 1 );
361
// advection needs some uniforms controlled by properties
362
osg::ref_ptr< osg::Uniform > numIters = new WGEPropertyUniform< WPropInt >( "u_numIter", m_numIters );
363
advection->addUniform( numIters );
365
// Final clipping and blending phase, needs Advected Noise, Edges, Depth and Light
366
clipBlend->bind( advectionOutA, 0 );
367
clipBlend->bind( edgeDetectionOut1, 1 );
368
clipBlend->bind( transformationColormapped, 2 );
369
// final pass needs some uniforms controlled by properties
370
clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useEdges", m_useEdges ) );
371
clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useLight", m_useLight ) );
372
clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useDepthCueing", m_useDepthCueing ) );
373
clipBlend->addUniform( new WGEPropertyUniform< WPropBool >( "u_useHighContrast", m_useHighContrast ) );
374
clipBlend->addUniform( new WGEPropertyUniform< WPropDouble >( "u_cmapRatio", m_cmapRatio ) );
376
/////////////////////////////////////////////////////////////////////////////////////////////////////////
378
/////////////////////////////////////////////////////////////////////////////////////////////////////////
381
while( !m_shutdownFlag() )
383
debugLog() << "Waiting ...";
384
m_moduleState.wait();
386
// woke up since the module is requested to finish?
387
if( m_shutdownFlag() )
392
// To query whether an input was updated, simply ask the input:
393
bool dataUpdated = m_vectorsIn->handledUpdate() || m_scalarIn->handledUpdate() || m_meshIn->handledUpdate();
394
bool propertyUpdated = m_useSlices->changed();
395
boost::shared_ptr< WDataSetVector > dataSetVec = m_vectorsIn->getData();
396
boost::shared_ptr< WDataSetScalar > dataSetScal = m_scalarIn->getData();
397
boost::shared_ptr< WTriangleMesh > mesh = m_meshIn->getData();
399
bool dataValid = ( dataSetVec || dataSetScal );
401
// is data valid? If not, remove graphics
404
debugLog() << "Resetting.";
405
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( offscreen );
409
// something interesting for us?
410
if( dataValid && !dataUpdated && !propertyUpdated )
415
// ensure it gets added
416
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( offscreen );
417
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( offscreen );
419
// prefer vector dataset if existing
422
debugLog() << "Using vector data";
424
// get grid and prepare OSG
425
boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( dataSetVec->getGrid() );
426
m_xPos->setMax( grid->getNbCoordsX() - 1 );
427
m_yPos->setMax( grid->getNbCoordsY() - 1 );
428
m_zPos->setMax( grid->getNbCoordsZ() - 1 );
429
initOSG( grid, mesh );
431
// prepare offscreen render chain
432
availableDataDefines->activateOption( 1 ); // vector input
433
transformation->bind( dataSetVec->getTexture(), 0 );
435
else if( dataSetScal )
437
debugLog() << "Using scalar data";
439
// get grid and prepare OSG
440
boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( dataSetScal->getGrid() );
441
m_xPos->setMax( grid->getNbCoordsX() - 1 );
442
m_yPos->setMax( grid->getNbCoordsY() - 1 );
443
m_zPos->setMax( grid->getNbCoordsZ() - 1 );
444
initOSG( grid, mesh );
446
// prepare offscreen render chain
447
availableDataDefines->activateOption( 0 ); // scalar input
448
transformation->bind( dataSetScal->getTexture(), 0 );
451
debugLog() << "Done";
456
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( offscreen );