diff --git a/include/osgShadow/ConvexPolyhedron b/include/osgShadow/ConvexPolyhedron new file mode 100644 index 000000000..bd6c7f7f3 --- /dev/null +++ b/include/osgShadow/ConvexPolyhedron @@ -0,0 +1,123 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#ifndef OSGSHADOW_CONVEXPOLYHEDRON +#define OSGSHADOW_CONVEXPOLYHEDRON 1 + +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// Class based on CustomPolytope defined and used in osgSim::OverlayNode.cpp. +// Honors should go to Robert Osfield for writing such useful piece of code. +// First incarnations of my ConvexPolyhedron were derived from CustomPolytope. +// Later I made a number of modifications aimed at improving convex hull +// precision of intersection & extrusion operations and ended up with code +// so mixed that I decided to rewrite it as separate class. +//////////////////////////////////////////////////////////////////////////////// + +namespace osgShadow { + +class OSGSHADOW_EXPORT ConvexPolyhedron +{ + public: + typedef std::vector Vertices; + + static const osg::Matrix & defaultMatrix; + + struct Face + { + std::string name; + osg::Plane plane; + Vertices vertices; + }; + + typedef std::list Faces; + Faces _faces; + + ConvexPolyhedron( void ) { } + + ConvexPolyhedron( const osg::Matrix& matrix, const osg::Matrix& inverse, + const osg::BoundingBox& bb = osg::BoundingBox(-1,-1,-1,1,1,1)); + + Face& createFace() { _faces.push_back(Face()); return _faces.back(); } + void clear() { _faces.clear(); } + + + void setToUnitFrustum(bool withNear=true, bool withFar=true); + void setToBoundingBox(const osg::BoundingBox& bb); + void transform(const osg::Matrix& matrix, const osg::Matrix& inverse); + void transformClip(const osg::Matrix& matrix, const osg::Matrix& inverse); + + + bool mergeFaces + ( const Face & face0, const Face & face1, Face & face ); + + void mergeCoplanarFaces( const double & plane_normal_dot_tolerance = 0.0, + const double & plane_distance_tolerance = 0.0 ); + + void removeDuplicateVertices( void ); + + + static int pointsColinear + ( const osg::Vec3d & va, const osg::Vec3d & vb, const osg::Vec3d & vc, + const double & edge_normal_dot_tolerance = 0.0, + const double & null_edge_length_tolerance = 0.0 ); + + static int isFacePolygonConvex( Face & face, bool ignoreCollinearVertices = true ); + + bool checkCoherency + ( bool checkForNonConvexPolys = false, const char * errorPrefix = NULL ); + + + void cut(const osg::Polytope& polytope); + + void cut(const ConvexPolyhedron& polytope); + + void cut(const osg::Plane& plane, const std::string& name=std::string()); + + void extrude( const osg::Vec3d & offset ); + + void translate( const osg::Vec3d & offset ); + + + void getPolytope(osg::Polytope& polytope) const; + void getPoints(Vertices& vertices) const; + osg::BoundingBox computeBoundingBox( const osg::Matrix & m = osgShadow::ConvexPolyhedron::defaultMatrix ) const; + + osg::Geometry* buildGeometry( const osg::Vec4d& colorOutline, + const osg::Vec4d& colorInside, + osg::Geometry* useGeometry = NULL ) const; + + + bool dumpGeometry( const Face * face = NULL, + const osg::Plane * plane = NULL, + ConvexPolyhedron * basehull = NULL, + const char * filename = "convexpolyhedron.osg", + const osg::Vec4d& colorOutline = osg::Vec4( 0,1,0,0.5 ), + const osg::Vec4d& colorInside = osg::Vec4( 0,1,0,0.25 ), + const osg::Vec4d& faceColorOutline = osg::Vec4( 0,0,1,0.5 ), + const osg::Vec4d& faceColorInside = osg::Vec4( 0,0,1,0.25 ), + const osg::Vec4d& planeColorOutline = osg::Vec4( 1,0,0,0.5 ), + const osg::Vec4d& planeColorInside = osg::Vec4( 1,0,0,0.25 ), + const osg::Vec4d& baseColorOutline = osg::Vec4( 0,0,0,0.5 ), + const osg::Vec4d& baseColorInside = osg::Vec4( 0,0,0,0.25 ) ) const; +}; + +} // namespace osgShadow + +#endif diff --git a/include/osgShadow/DebugShadowMap b/include/osgShadow/DebugShadowMap new file mode 100644 index 000000000..f76bf58e4 --- /dev/null +++ b/include/osgShadow/DebugShadowMap @@ -0,0 +1,178 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#ifndef OSGSHADOW_DEBUGSHADOWMAP +#define OSGSHADOW_DEBUGSHADOWMAP 1 + +#include +#include +#include +#include +#include +#include +#include + +namespace osgShadow { + +/** +Class used as a layer for debuging resources used by derived xxxShadowMap classes. +As designed by its base ViewDepndentShadowTechnique, DebugShadowMap serves mainly as container of +DebugShadowMap::ViewData objects. Most of the debuging support work is done by these objects. +DebugShadowMap technique only initializes them in initViewDependentData method. + +Debuging outputs present: + Shadow maps (pseudo colored to improve readability) + Shadow and related volumes (represented as convex polyhedra) +*/ + +class OSGSHADOW_EXPORT DebugShadowMap : public ViewDependentShadowTechnique +{ + public : + + /* + All classes stemming from ViewDependentShadowTechnique follow the same pattern. + + They are always based on some underlying level base Technique and they always + derive their ViewData from ViewData structure defined in underlying base Technique. + + I use some typedefs to make these inheritance patterns easier to declare/define. + */ + + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef DebugShadowMap ThisClass; + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef ViewDependentShadowTechnique BaseClass; + + /** Classic OSG constructor */ + DebugShadowMap(); + + /** Classic OSG cloning constructor */ + DebugShadowMap(const DebugShadowMap& dsm, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, DebugShadowMap ); + + /** Turn on/off debuging hud & rendering of debug volumes in main view */ + void setDebugDraw( bool draw ) { _doDebugDraw = draw; } + + /** Tell if debuging hud & rendering of debug volumes is active */ + bool getDebugDraw( void ) { return _doDebugDraw; } + + protected: + /** Classic protected OSG destructor */ + virtual ~DebugShadowMap(); + + osg::Vec2s _hudSize; + osg::Vec2s _hudOrigin; + osg::Vec2s _viewportSize; + osg::Vec2s _viewportOrigin; + osg::Vec2s _orthoSize; + osg::Vec2s _orthoOrigin; + + bool _doDebugDraw; + + osg::ref_ptr< osg::Shader > _depthColorFragmentShader; + + struct ViewData: public BaseClass::ViewData + { + /** + Texture used as ShadowMap - initialized by derived classes. + But it has to be defined now since DebugShadowMap::ViewData methods use it + */ + osg::ref_ptr< osg::Texture2D > _texture; + /** + Camera used to render ShadowMap - initialized by derived classes. + But it has to be defined now since DebugShadowMap::ViewData methods use it + */ + osg::ref_ptr< osg::Camera > _camera; + + osg::Matrix _viewProjection; + osg::Camera * _viewCamera; + + // Debug hud variables + + /** Coloring Shader used to present shadow depth map contents */ + osg::ref_ptr< osg::Shader > _depthColorFragmentShader; + + struct PolytopeGeometry { + + ConvexPolyhedron _polytope; + osg::ref_ptr< osg::Geometry > _geometry[2]; + osg::Vec4 _colorOutline; + osg::Vec4 _colorInside; + }; + + typedef std::map< std::string, PolytopeGeometry > PolytopeGeometryMap; + + osg::Vec2s _hudSize; + osg::Vec2s _hudOrigin; + osg::Vec2s _viewportSize; + osg::Vec2s _viewportOrigin; + osg::Vec2s _orthoSize; + osg::Vec2s _orthoOrigin; + + bool *_doDebugDrawPtr; + + PolytopeGeometryMap _polytopeGeometryMap; + osg::ref_ptr< osg::Geode > _geode[2]; + osg::ref_ptr< osg::MatrixTransform > _transform[2]; + + std::map< std::string, osg::Matrix > _matrixMap; + std::map< std::string, osg::Polytope > _polytopeMap; + std::map< std::string, osg::BoundingBox > _boundingBoxMap; + + osg::ref_ptr _cameraDebugHUD; + + bool getDebugDraw() { return *_doDebugDrawPtr; } + + virtual void init( ThisClass * st, osgUtil::CullVisitor * cv ); + + virtual void cull( ); + + virtual void createDebugHUD( void ); + + virtual void cullDebugGeometry( ); + + virtual void updateDebugGeometry( const osg::Camera * screenCam, + const osg::Camera * shadowCam ); + + void setDebugPolytope( const char * name, + const ConvexPolyhedron & polytope = *(ConvexPolyhedron*)( NULL ), + osg::Vec4 colorOutline = osg::Vec4(0,0,0,0), + osg::Vec4 colorInside = osg::Vec4(0,0,0,0) ); + + bool DebugBoundingBox( const osg::BoundingBox & bb, const char * name = "" ); + bool DebugPolytope( const osg::Polytope & p, const char * name = "" ); + bool DebugMatrix( const osg::Matrix & m, const char * name = "" ); + + static osg::Vec3d computeShadowTexelToPixelError + ( const osg::Matrix & mvpwView, + const osg::Matrix & mvpwShadow, + const osg::Vec3d & vWorld, + const osg::Vec3d & vDelta = osg::Vec3d( 0.01,0.01,0.01 ) ); + + static void displayShadowTexelToPixelErrors + ( const osg::Camera * viewCam, + const osg::Camera * shadowCam, + const ConvexPolyhedron * hull ); + }; + + META_ViewDependentShadowTechniqueData( ThisClass, ViewData ) +}; + +} // namespace osgShadow + +#endif diff --git a/include/osgShadow/LightSpacePerspectiveShadowMap b/include/osgShadow/LightSpacePerspectiveShadowMap new file mode 100644 index 000000000..9514667be --- /dev/null +++ b/include/osgShadow/LightSpacePerspectiveShadowMap @@ -0,0 +1,122 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + + +#ifndef OSGSHADOW_LIGHTSPACEPERSPECTIVESHADOWMAP +#define OSGSHADOW_LIGHTSPACEPERSPECTIVESHADOWMAP 1 + +#include +#include +#include + +namespace osgShadow { + +// Class implements +// "Light Space Perspective Shadow Maps" algorithm by +// Michael Wimmer, Daniel Scherzer, Werner Purgathofer +// http://www.cg.tuwien.ac.at/research/vr/lispsm/ + +class LispSM; + +class OSGSHADOW_EXPORT LightSpacePerspectiveShadowMapAlgorithm +{ + public: + LightSpacePerspectiveShadowMapAlgorithm(); + ~LightSpacePerspectiveShadowMapAlgorithm(); + + void operator() ( + const osgShadow::ConvexPolyhedron* hullShadowedView, + const osg::Camera* cameraMain, + osg::Camera* cameraShadow ) const; + + protected: + LispSM * lispsm; +}; + +// Optimized for draw traversal shadow bounds +class OSGSHADOW_EXPORT LightSpacePerspectiveShadowMapDB: public ProjectionShadowMap< MinimalDrawBoundsShadowMap, LightSpacePerspectiveShadowMapAlgorithm > +{ + public: + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef ProjectionShadowMap< MinimalDrawBoundsShadowMap, LightSpacePerspectiveShadowMapAlgorithm > BaseClass; + + /** Classic OSG constructor */ + LightSpacePerspectiveShadowMapDB() + { + } + + /** Classic OSG cloning constructor */ + LightSpacePerspectiveShadowMapDB( + const LightSpacePerspectiveShadowMapDB& copy, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop) + { + } + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, LightSpacePerspectiveShadowMapDB ); +}; + +// Optimized for cull traversal shadow bounds +class OSGSHADOW_EXPORT LightSpacePerspectiveShadowMapCB: public ProjectionShadowMap< MinimalCullBoundsShadowMap, LightSpacePerspectiveShadowMapAlgorithm > +{ + public: + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef ProjectionShadowMap< MinimalCullBoundsShadowMap, LightSpacePerspectiveShadowMapAlgorithm > BaseClass; + + /** Classic OSG constructor */ + LightSpacePerspectiveShadowMapCB() + { + } + + /** Classic OSG cloning constructor */ + LightSpacePerspectiveShadowMapCB( + const LightSpacePerspectiveShadowMapCB& copy, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop) + { + } + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, LightSpacePerspectiveShadowMapCB ); +}; + +// Optimized for view frustum bounds +class OSGSHADOW_EXPORT LightSpacePerspectiveShadowMapVB: public ProjectionShadowMap< MinimalShadowMap, LightSpacePerspectiveShadowMapAlgorithm > +{ + public: + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef ProjectionShadowMap< MinimalShadowMap, LightSpacePerspectiveShadowMapAlgorithm > BaseClass; + + /** Classic OSG constructor */ + LightSpacePerspectiveShadowMapVB() + { + } + + /** Classic OSG cloning constructor */ + LightSpacePerspectiveShadowMapVB( + const LightSpacePerspectiveShadowMapVB& copy, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop) + { + } + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, LightSpacePerspectiveShadowMapVB ); +}; + +typedef LightSpacePerspectiveShadowMapDB LightSpacePerspectiveShadowMap; + +} // namespace osgShadow + +#endif diff --git a/include/osgShadow/MinimalCullBoundsShadowMap b/include/osgShadow/MinimalCullBoundsShadowMap new file mode 100644 index 000000000..cac7e71d0 --- /dev/null +++ b/include/osgShadow/MinimalCullBoundsShadowMap @@ -0,0 +1,82 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#ifndef OSGSHADOW_MINIMALCULLBOUNDSSHADOWMAP +#define OSGSHADOW_MINIMALCULLBOUNDSSHADOWMAP 1 + +#include + +namespace osgShadow { + +class OSGSHADOW_EXPORT MinimalCullBoundsShadowMap + : public MinimalShadowMap +{ + public : + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef MinimalCullBoundsShadowMap ThisClass; + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef MinimalShadowMap BaseClass; + + /** Classic OSG constructor */ + MinimalCullBoundsShadowMap(); + + /** Classic OSG cloning constructor */ + MinimalCullBoundsShadowMap( + const MinimalCullBoundsShadowMap& mcbsm, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, MinimalCullBoundsShadowMap ); + + protected: + /** Classic protected OSG destructor */ + virtual ~MinimalCullBoundsShadowMap(void); + + struct ViewData: public MinimalShadowMap::ViewData + { + virtual void init( ThisClass * st, osgUtil::CullVisitor * cv ); + + virtual void cullShadowReceivingScene( ); + + virtual void aimShadowCastingCamera( const osg::Light *light, + const osg::Vec4 &worldLightPos, + const osg::Vec3 &worldLightDir, + const osg::Vec3 &worldLightUp + = osg::Vec3(0,1,0) ); + + typedef std::vector< osgUtil::RenderLeaf* > RenderLeafList; + + static unsigned RemoveOldRenderLeaves + ( RenderLeafList &rllNew, RenderLeafList &rllOld ); + + static unsigned RemoveIgnoredRenderLeaves( RenderLeafList &rll ); + + static osg::BoundingBox ComputeRenderLeavesBounds + ( RenderLeafList &rll, osg::Matrix & projectionToWorld ); + + static osg::BoundingBox ComputeRenderLeavesBounds + ( RenderLeafList &rll, osg::Matrix & projectionToWorld, osg::Polytope & polytope ); + + static void GetRenderLeaves + ( osgUtil::RenderBin *rb, RenderLeafList &rll ); + }; + + META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData ) +}; + +} // namespace osgShadow + +#endif diff --git a/include/osgShadow/MinimalDrawBoundsShadowMap b/include/osgShadow/MinimalDrawBoundsShadowMap new file mode 100644 index 000000000..3ed1d59cc --- /dev/null +++ b/include/osgShadow/MinimalDrawBoundsShadowMap @@ -0,0 +1,126 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + + +#ifndef OSGSHADOW_MINIMALDRAWBOUNDSSHADOWMAP +#define OSGSHADOW_MINIMALDRAWBOUNDSSHADOWMAP 1 + +#include + +namespace osgShadow { + +class OSGSHADOW_EXPORT MinimalDrawBoundsShadowMap + : public MinimalShadowMap +{ + public : + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef MinimalDrawBoundsShadowMap ThisClass; + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef MinimalShadowMap BaseClass; + + /** Classic OSG constructor */ + MinimalDrawBoundsShadowMap(); + + /** Classic OSG cloning constructor */ + MinimalDrawBoundsShadowMap( + const MinimalDrawBoundsShadowMap& mdbsm, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, MinimalDrawBoundsShadowMap ); + + protected: + /** Classic protected OSG destructor */ + virtual ~MinimalDrawBoundsShadowMap(void); + + struct ViewData: public BaseClass::ViewData + { + osg::ref_ptr< osg::RefMatrix > _projection; + osg::Vec2s _boundAnalysisSize; + osg::ref_ptr< osg::Image > _boundAnalysisImage; + osg::ref_ptr< osg::Texture2D > _boundAnalysisTexture; + osg::ref_ptr< osg::Camera > _boundAnalysisCamera; + osg::ref_ptr< osg::Camera > _mainCamera; + + void setShadowCameraProjectionMatrixPtr( osg::RefMatrix * projection ) + { _projection = projection; } + + osg::RefMatrix * getShadowCameraProjectionMatrixPtr( void ) + { return _projection.get(); } + + virtual void init( ThisClass * st, osgUtil::CullVisitor * cv ); + + virtual void cullShadowReceivingScene( ); + + virtual void createDebugHUD( ); + + virtual void recordShadowMapParams( ); + + virtual void cullBoundAnalysisScene( ); + + static osg::BoundingBox scanImage( const osg::Image * image, osg::Matrix m ); + + virtual void performBoundAnalysis( const osg::Camera& camera ); + + ViewData( void ): _boundAnalysisSize( 64, 64 ) {} + }; + + META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData ) + + + struct CameraPostDrawCallback : public osg::Camera::DrawCallback { + + CameraPostDrawCallback( ViewData * vd ): _vd( vd ) + { + } + + virtual void operator ()( const osg::Camera& camera ) const + { + if( _vd.valid() ) + _vd->performBoundAnalysis( camera ); + } + + osg::observer_ptr< ViewData > _vd; + }; + + struct CameraCullCallback: public osg::NodeCallback { + + CameraCullCallback(ViewData * vd, osg::NodeCallback * nc): _vd(vd), _nc(nc) + { + } + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + osgUtil::CullVisitor *cv = dynamic_cast< osgUtil::CullVisitor *>( nv ); + + if( _nc.valid() ) + _nc->operator()(node,nv); + else + traverse(node,nv); + + if( cv ) + _vd->recordShadowMapParams( ); + } + + protected: + osg::ref_ptr< osg::NodeCallback > _nc; + osg::observer_ptr< ViewData > _vd; + }; +}; + +} // namespace osgShadow + +#endif diff --git a/include/osgShadow/MinimalShadowMap b/include/osgShadow/MinimalShadowMap new file mode 100644 index 000000000..3bccbce58 --- /dev/null +++ b/include/osgShadow/MinimalShadowMap @@ -0,0 +1,156 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#ifndef OSGSHADOW_MINIMALSHADOWMAP +#define OSGSHADOW_MINIMALSHADOWMAP 1 + +#include + +namespace osgShadow { + +class OSGSHADOW_EXPORT MinimalShadowMap : public StandardShadowMap +{ + public : + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef MinimalShadowMap ThisClass; + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef StandardShadowMap BaseClass; + + /** Classic OSG constructor */ + MinimalShadowMap(); + + /** Classic OSG cloning constructor */ + MinimalShadowMap( + const MinimalShadowMap& msm, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, MinimalShadowMap ); + + void setModellingSpaceToWorldTransform( const osg::Matrix & modellingSpaceToWorld ) + { _modellingSpaceToWorld = modellingSpaceToWorld; } + + const osg::Matrix & getModellingSpaceToWorldTransform( void ) + { return _modellingSpaceToWorld; } + + float getMaxFarPlane( ) + { return _maxFarPlane; } + + void setMaxFarPlane( float maxFarPlane ) + { _maxFarPlane = maxFarPlane; } + + float getMinLightMargin( ) + { return _minLightMargin; } + + void setMinLightMargin( float minLightMargin ) + { _minLightMargin = minLightMargin; } + + enum ShadowReceivingCoarseBoundAccuracy { + EMPTY_BOX, + BOUNDING_SPHERE, + BOUNDING_BOX, + DEFAULT_ACCURACY = BOUNDING_BOX, + }; + + void setShadowReceivingCoarseBoundAccuracy + ( ShadowReceivingCoarseBoundAccuracy accuracy ) + { _shadowReceivingCoarseBoundAccuracy = accuracy; } + + ShadowReceivingCoarseBoundAccuracy + getShadowReceivingCoarseBoundAccuracy() + { return _shadowReceivingCoarseBoundAccuracy; } + + protected: + /** Classic protected OSG destructor */ + virtual ~MinimalShadowMap(void); + + protected: + // Matrix modellingSpaceToWorld and its inverse + // are used to define Modelling Space where shadowed scene drawables + // have minimal (smallest possible extent) bounding boxes. + + // Computing visible shadow range in this space + // allows for optimal use of ShadowMap resolution. + + // By default it is set to identity ie computations are in world space. + // But it should be set to ElipsoidModel::localToWorld + // when scene objects are put on earth ellipsoid surface. + + // Other scenarios are also possible for example when models are + // built in XZY space which would require identity matrix with swapped colums + + osg::Matrix _modellingSpaceToWorld; + float _maxFarPlane; + float _minLightMargin; + ShadowReceivingCoarseBoundAccuracy _shadowReceivingCoarseBoundAccuracy; + + struct ViewData: public BaseClass::ViewData + { + osg::Matrix *_modellingSpaceToWorldPtr; + float *_maxFarPlanePtr; + float *_minLightMarginPtr; + + ConvexPolyhedron _sceneReceivingShadowPolytope; + std::vector< osg::Vec3d > _sceneReceivingShadowPolytopePoints; + + osg::Matrix _clampedProjection; + + virtual void init( ThisClass * st, osgUtil::CullVisitor * cv ); + + virtual osg::BoundingBox computeShadowReceivingCoarseBounds( ); + + virtual void cullShadowReceivingScene( ); + + virtual void aimShadowCastingCamera( const osg::Light *light, + const osg::Vec4 &worldLightPos, + const osg::Vec3 &worldLightDir, + const osg::Vec3 &worldLightUp + = osg::Vec3(0,1,0) ); + + virtual void frameShadowCastingCamera + ( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass = 1 ); + + void cutScenePolytope( const osg::Matrix & matrix, + const osg::Matrix & inverse, + const osg::BoundingBox &bb = + osg::BoundingBox(-1,-1,-1,1,1,1) ); + + osg::BoundingBox computeScenePolytopeBounds + ( const osg::Matrix & m = *(osg::Matrix*)(NULL) ); + + // Utility methods for adjusting projection matrices + + // Modify projection matrix so that some output subrange + // is remapped to whole clip space (-1..1,-1..1,-1..1). + // Bit mask can be used to limit remaping to selected bounds only. + static void trimProjection + ( osg::Matrix & projection, osg::BoundingBox subrange, + unsigned int trimMask = (1|2|4|8|16|32) + /*1=left|2=right|4=bottom|8=top|16=near|32=far*/); + + static void clampProjection + ( osg::Matrix & projection, float n = 0, float f = FLT_MAX ); + + static void extendProjection + ( osg::Matrix & projection, osg::Viewport * viewport, const osg::Vec2& margin ); + }; + + META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData ) +}; + +} // namespace osgShadow + +#endif diff --git a/include/osgShadow/ProjectionShadowMap b/include/osgShadow/ProjectionShadowMap new file mode 100644 index 000000000..e9eece046 --- /dev/null +++ b/include/osgShadow/ProjectionShadowMap @@ -0,0 +1,82 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#ifndef OSGSHADOW_PROJECTIONSHADOWMAP +#define OSGSHADOW_PROJECTIONSHADOWMAP 1 + +#include + +namespace osgShadow { + +template< typename MinimalBoundsBaseClass, typename ShadowProjectionAlgorithmClass > +class OSGSHADOW_EXPORT ProjectionShadowMap : public MinimalBoundsBaseClass +{ + public : + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef MinimalBoundsBaseClass BaseClass; + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef ProjectionShadowMap ThisClass; + + /** Classic OSG constructor */ + ProjectionShadowMap() : BaseClass() + { + } + + /** Classic OSG cloning constructor */ + ProjectionShadowMap( + const ProjectionShadowMap& copy, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) : BaseClass(copy,copyop) + { + } + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, ProjectionShadowMap ); + + protected: + + /** Classic protected OSG destructor */ + virtual ~ProjectionShadowMap(void) + { + } + + struct ViewData: public BaseClass::ViewData, + public ShadowProjectionAlgorithmClass + { + #if 0 + virtual void init( ThisClass * st, osgUtil::CullVisitor * cv ); + { + BaseClass::ViewData::init( st, cv ); + } + #endif + + virtual void frameShadowCastingCamera + ( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass = 1 ) + { + // Force dependent name lookup + ShadowProjectionAlgorithmClass::operator() + ( &this->_sceneReceivingShadowPolytope, cameraMain, cameraShadow ); + + // DebugBoundingBox( computeScenePolytopeBounds(), "ProjectionShadowMap" ); + BaseClass::ViewData::frameShadowCastingCamera( cameraMain, cameraShadow, pass ); + } + }; + + META_ViewDependentShadowTechniqueData( ThisClass, typename ThisClass::ViewData ) +}; + +} // namespace osgShadow + +#endif diff --git a/include/osgShadow/StandardShadowMap b/include/osgShadow/StandardShadowMap new file mode 100644 index 000000000..ef3f5cf5b --- /dev/null +++ b/include/osgShadow/StandardShadowMap @@ -0,0 +1,195 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#ifndef OSGSHADOW_STANDARDSHADOWMAP +#define OSGSHADOW_STANDARDSHADOWMAP 1 + +#include + +namespace osgShadow { + +class OSGSHADOW_EXPORT StandardShadowMap : public DebugShadowMap +{ + public : + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef StandardShadowMap ThisClass; + /** Convenient typedef used in definition of ViewData struct and methods */ + typedef DebugShadowMap BaseClass; + + /** Classic OSG constructor */ + StandardShadowMap(); + + /** Classic OSG cloning constructor */ + StandardShadowMap(const StandardShadowMap& ssm, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, StandardShadowMap ); + + void setBaseTextureUnit( unsigned int unit ) + { _baseTextureUnit = unit; dirty(); } + + unsigned int getBaseTextureUnit( void ) + { return _baseTextureUnit; } + + void setShadowTextureUnit( unsigned int unit ) + { _shadowTextureUnit = unit; dirty(); } + + unsigned int getShadowTextureUnit( void ) + { return _shadowTextureUnit; } + + // Texture Indices are changed by search and replace on shader source + // Carefully order these calls when changing both base and shadow indices + // In worst case when intend to swap indices + // one will have to call these methods more than once + // with one extra pass to change index to unused value to avoid + // unwanted superfluous replace: + // + // Example: Imagine we want to swap base(0) and shadow(1) indices: + // We have to do an extra step to make sure both do not end up as 1 + // + // // initialy change base to something else than 1 + // setBaseTextureCoordIndex( 100 ); + // // now search and replace all gl_TexCord[1] to gl_TexCord[0] + // setShadowTextureCoordIndex( 0 ); + // // finally change base from 100 to 0 + // setBaseTextureCoordIndex( 1 ); + + void setBaseTextureCoordIndex( unsigned int index ) + { updateTextureCoordIndices( _baseTextureCoordIndex, index ); + _baseTextureCoordIndex = index; } + + unsigned int getBaseTextureCoordIndex( void ) + { return _baseTextureCoordIndex; } + + // Texture Indices are changed by search and replace on shader source + // Look at the comment above setBaseTextureCoordIndex + + void setShadowTextureCoordIndex( unsigned int index ) + { updateTextureCoordIndices( _shadowTextureCoordIndex, index ); + _shadowTextureCoordIndex = index; } + + unsigned int getShadowTextureCoordIndex( void ) + { return _shadowTextureCoordIndex; } + + void setTextureSize( const osg::Vec2s& textureSize ) + { _textureSize = textureSize; dirty(); } + + osg::Vec2s getTextureSize( ) + { return _textureSize; } + + void setLight( osg::Light* light ) + { _light = light; } + + osg::Light* getLight( void ) + { return _light.get(); } + + osg::Shader * getShadowVertexShader() + { return _shadowVertexShader.get(); } + + osg::Shader * getShadowFragmentShader() + { return _shadowFragmentShader.get(); } + + osg::Shader * getMainVertexShader( ) + { return _mainVertexShader.get(); } + + osg::Shader * getMainFragmentShader( ) + { return _mainFragmentShader.get(); } + + void setShadowVertexShader( osg::Shader * shader ) + { _shadowVertexShader = shader; } + + void setShadowFragmentShader( osg::Shader * shader ) + { _shadowFragmentShader = shader; } + + void setMainVertexShader( osg::Shader * shader ) + { _mainVertexShader = shader; } + + void setMainFragmentShader( osg::Shader * shader ) + { _mainFragmentShader = shader; } + + protected: + /** Classic protected OSG destructor */ + virtual ~StandardShadowMap(void); + + virtual void updateTextureCoordIndices + ( unsigned int baseTexCoordIndex, unsigned int shadowTexCoordIndex ); + + virtual void searchAndReplaceShaderSource + ( osg::Shader*, std::string fromString, std::string toString ); + + osg::ref_ptr< osg::Shader > _mainVertexShader; + osg::ref_ptr< osg::Shader > _mainFragmentShader; + osg::ref_ptr< osg::Shader > _shadowVertexShader; + osg::ref_ptr< osg::Shader > _shadowFragmentShader; + + osg::ref_ptr< osg::Light > _light; + float _polygonOffsetFactor; + float _polygonOffsetUnits; + osg::Vec2s _textureSize; + unsigned int _baseTextureUnit; + unsigned int _shadowTextureUnit; + unsigned int _baseTextureCoordIndex; + unsigned int _shadowTextureCoordIndex; + + struct ViewData: public BaseClass::ViewData + { + osg::ref_ptr< osg::Light > * _lightPtr; + unsigned int * _baseTextureUnitPtr; + unsigned int * _shadowTextureUnitPtr; + + // ShadowMap texture is defined by base DebugShadowMap + // osg::ref_ptr _texture; + + // ShadowMap camera is defined by base DebugShadowMap + // osg::ref_ptr _camera; + + osg::ref_ptr _texgen; + osg::ref_ptr _stateset; + + virtual void init( ThisClass * st, osgUtil::CullVisitor * cv ); + + virtual void cull( ); + + virtual void aimShadowCastingCamera( + const osg::BoundingSphere &bounds, + const osg::Light *light, + const osg::Vec4 &worldLightPos, + const osg::Vec3 &worldLightDir, + const osg::Vec3 &worldLightUp = osg::Vec3(0,1,0) ); + + virtual void cullShadowReceivingScene( ); + + virtual void cullShadowCastingScene( ); + + virtual void addShadowReceivingTexGen( ); + + virtual const osg::Light* selectLight( osg::Vec4 &viewLightPos, + osg::Vec3 &viewLightDir ); + + virtual void aimShadowCastingCamera( const osg::Light *light, + const osg::Vec4 &worldLightPos, + const osg::Vec3 &worldLightDir, + const osg::Vec3 &worldLightUp + = osg::Vec3(0,1,0) ); + }; + + META_ViewDependentShadowTechniqueData( ThisClass, ThisClass::ViewData ) +}; + +} // namespace osgShadow + +#endif diff --git a/include/osgShadow/ViewDependentShadowTechnique b/include/osgShadow/ViewDependentShadowTechnique new file mode 100644 index 000000000..211f7bb2f --- /dev/null +++ b/include/osgShadow/ViewDependentShadowTechnique @@ -0,0 +1,229 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#ifndef OSGSHADOW_VIEWDEPENDENTSHADOWTECHINIQUE +#define OSGSHADOW_VIEWDEPENDENTSHADOWTECHINIQUE 1 + +#include +#include +#include + +namespace osgShadow { +/** + META_ViewDependentShadowTechniqueData macro defines initViewDependentData + method used by derived shadow techniques to initialize their specific + ViewData objects. initViewDependentData will be called from + ViewDependentShadowTechnique base class to init derived class +*/ +#define META_ViewDependentShadowTechniqueData( ShadowTechnique, TechniqueData )\ +virtual ViewDependentShadowTechnique::ViewData * initViewDependentData \ +( osgUtil::CullVisitor *cv, ViewDependentShadowTechnique::ViewData * vd ) \ +{ \ + TechniqueData* td = dynamic_cast( vd ); \ + if ( !td ) td = new TechniqueData; \ + td->init( this, cv ); \ + return td; \ +} + +/** + ViewDependentShadowTechnique is a base class for all + View Dependent Shadow techniques. It defines fundamental object structure + and methods to manage separate shadow resources for each view of the scene. + By view we understand osg::View or SceneView instance and their associated + Camera. Typical osg application has one or more such views. View Dependent + Shadow techniques manage shadow generation for them. + + View Dependent Shadow techniques are used to optimize shadow algorithms for + part of the scene seen on the view. If rest of the scene is behind view + frustum, there is no sense in computing shadows for it. Since in practice we + often use 3d databases extending far beyond current camera frustum View + Dependent Shadow approach may produce much better shadows. + + The other goal is to provide framework for thread safe rendering of + the shadows. It allows to use shadows with different OSG threading models. + + Conceptually ViewDependentShadowTechnique is similar to osgSim::OverlayNode. + Its a container class for number of ViewData (or ViewData derived) objects + doing actual shadow work for each of the scene views. + + But ViewDependentShadowTechnique is intended as foundation layer for all + derived classes so in some way it extends osgSim::OverlayNode approach a bit. + + HOW IT WORKS: + + ViewDependendentShadowTechnique is derived from osgShadow::ShadowTechnique + and as such overrides virtual methods of osgShadow::ShadowTechnique. + But most of the shadow dirty work is done by ViewData objects, + ViewDependendentShadowTechnique::cull is the only osgShadow::ShadowTechnique + method where ViewDependendentShadowTechnique does something significant: + + What ViewDependentShadowTechnique::cull( CullVisitor & cv ) does ? + It identifies View. CullVisitor ptr is used as View identificator. + In practice we do not check and interpret what are actual Views and SceneViews + set up by application. We focus on Camera and CullVisitors as a identificators + of views. We can safely do this because each such view will have associated + unique CullVisitor used to cull the scene in every frame. + + Based on CullVisitor ptr passed to cull method, associated Technique::ViewData + object is created (if neccessary) and then seleced. Then control is passed to + this ViewData object. So, each view has its associated unique ViewData + (or derived) object performing dirty work of shadow resources management and + shadow generation for the view. + + To support creation of classes derived from ViewDependentShadowTechnique it + was neccessary to provide mechanism to override ViewData and allow for + initialization of new derived ViewData objects. Creation and initialization + is performed when ViewDependendentShadowTechnique::cull gets called with + CullVistor ptr which does not yet have associated ViewData object. When it + happens, virtual initViewDependentData method is called to give + derived techniques a chance to allocate and iniitalize its specific + resources as new ViewData derived instance. In practice initViewDependentData + in derived techniques should look the same as in base class so as a convenience + it was defined as META_ViewDependentShadowTechnique macro. Derived techniques + use this macro to override initViewDependentData method for their usage. + + After ViewData derived object is construted and selected, control is passed + to this object by call to virtual ViewData::cull method. The rest of work + is the done by this object. ViewDependentShadowTechnique::ViewData is intended + as a base class so it does nothing. In practice the rest of dirty work will + do new ViewData classes implemented in derived techniques. +*/ +class OSGSHADOW_EXPORT ViewDependentShadowTechnique + : public osgShadow::ShadowTechnique +{ + public: + /** + osgShadow::ShadowTechnique equivalent methods for view dependent techniques + */ + + /** Classic OSG constructor */ + ViewDependentShadowTechnique( void ); + + /** Classic OSG cloning constructor */ + ViewDependentShadowTechnique( + const ViewDependentShadowTechnique& vdst, + const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY ); + + + /** Declaration of standard OSG object methods */ + META_Object( ViewDependentShadow, ViewDependentShadowTechnique ); + + /** Dirty view data bits and force update of view data resources */ + virtual void dirty(); + + /** Initialize the ShadowedScene and some data structures.*/ + virtual void init(); + + /** Run the update traversal of the ShadowedScene and update any local cached data structures.*/ + virtual void update(osg::NodeVisitor& nv); + + /** Run the cull traversal of the ShadowedScene and set up the rendering for this ShadowTechnique.*/ + virtual void cull(osgUtil::CullVisitor& cv); + + /** Clean scene graph from any shadow technique specific nodes, state and drawables.*/ + virtual void cleanSceneGraph(); + + /** Traverse shadow scene graph.*/ + virtual void traverse(osg::NodeVisitor& nv); + + protected: + /** Classic protected OSG destructor */ + ~ViewDependentShadowTechnique( void ); + + /** + Base container class for view dependent shadow resources. + Techniques based on ViewDependentShadowTechnique will usually define + similar struct and derive it from ViewData to contain their specufic resources. + */ + struct ViewData: public osg::Referenced + { + /** + Method called upon ViewData instance to initialize internal variables + */ + virtual void init + ( ViewDependentShadowTechnique *st, osgUtil::CullVisitor *cv ); + + /** + Method called by ViewDependentShadowTechnique to allow ViewData + do the hard work computing shadows for its associated view + */ + virtual void cull(); + + /** + Dirty is called by parent ViewDependentShadowTechnique to force + update of resources after some of them were modified in parent technique + */ + virtual void dirty( bool flag ); + + /** + Simple constructor zeroing all variables. + */ + ViewData(): _dirty( true ), _cv( NULL ), _st( NULL ) { }; + + /** + Mutex used to guard _dirty flag from override in case when parent technique calls + dirty() simultaneously with ViewData while it is updating resources inside init method. + */ + OpenThreads::Mutex _mutex; + + /** + View's CullVisitor associated with this ViewData instance + */ + osg::observer_ptr< osgUtil::CullVisitor > _cv; + + /** + Parent ViewDependentShadowTechnique + */ + osg::observer_ptr< ViewDependentShadowTechnique > _st; + + /** + Dirty flag tells this instance to update its resources + */ + bool _dirty; + }; + + /** + Map of view dependent data per view cull visitor (CVs are used as indices) + ViewDependentShadowTechnique uses this map to find VieData for each cull vitior + */ + + typedef std::map< osg::ref_ptr< osgUtil::CullVisitor >, + osg::ref_ptr< ViewData > > ViewDataMap; + + ViewDataMap _viewDataMap; + + + /** + Mutex used to serialize accesses to ViewDataMap + */ + OpenThreads::Mutex _viewDataMapMutex; + + /** Return view dependent data for the cull visitor */ + virtual ViewDependentShadowTechnique::ViewData * getViewDependentData( osgUtil::CullVisitor * cv ); + + /** Define view dependent data for the cull visitor */ + virtual void setViewDependentData( osgUtil::CullVisitor * cv, ViewDependentShadowTechnique::ViewData * data ); + + /** + Declare standard initViewDependentData method. + */ + META_ViewDependentShadowTechniqueData( ViewDependentShadowTechnique, ViewData ) +}; + + +} // namespace osgShadow + +#endif diff --git a/src/osgShadow/CMakeLists.txt b/src/osgShadow/CMakeLists.txt index 8d5d18096..b7637a0c7 100644 --- a/src/osgShadow/CMakeLists.txt +++ b/src/osgShadow/CMakeLists.txt @@ -18,6 +18,16 @@ SET(LIB_PUBLIC_HEADERS ${HEADER_PATH}/SoftShadowMap ${HEADER_PATH}/ParallelSplitShadowMap ${HEADER_PATH}/Version + + ${HEADER_PATH}/ConvexPolyhedron + ${HEADER_PATH}/DebugShadowMap + ${HEADER_PATH}/LightSpacePerspectiveShadowMap + ${HEADER_PATH}/MinimalCullBoundsShadowMap + ${HEADER_PATH}/MinimalDrawBoundsShadowMap + ${HEADER_PATH}/MinimalShadowMap + ${HEADER_PATH}/ProjectionShadowMap + ${HEADER_PATH}/StandardShadowMap + ${HEADER_PATH}/ViewDependentShadowTechnique ) # FIXME: For OS X, need flag for Framework or dylib @@ -33,6 +43,14 @@ ADD_LIBRARY(${LIB_NAME} SoftShadowMap.cpp ParallelSplitShadowMap.cpp Version.cpp + ConvexPolyhedron.cpp + DebugShadowMap.cpp + LightSpacePerspectiveShadowMap.cpp + MinimalCullBoundsShadowMap.cpp + MinimalDrawBoundsShadowMap.cpp + MinimalShadowMap.cpp + StandardShadowMap.cpp + ViewDependentShadowTechnique.cpp ) LINK_INTERNAL(${LIB_NAME} diff --git a/src/osgShadow/ConvexPolyhedron.cpp b/src/osgShadow/ConvexPolyhedron.cpp new file mode 100644 index 000000000..436861376 --- /dev/null +++ b/src/osgShadow/ConvexPolyhedron.cpp @@ -0,0 +1,1876 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace osgShadow; + + +#if defined( DEBUG ) || defined( _DEBUG ) || defined( _DEBUG_ ) + +// #define MAKE_CHECKS 0 + +#endif + +#if MAKE_CHECKS + +inline std::ostream & DEBUG_NOTIFY( void ) +{ + return osg::notify( osg::WARN ); +} +#define WARN DEBUG_NOTIFY() + +#define CONVEX_POLYHEDRON_CHECK_COHERENCY 1 +#define CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA 2 +#define CONVEX_POLYHEDRON_DUMP_ON_INCOHERENT_DATA 2 +#define CONVEX_POLYHEDRON_WARN_ON_BAD_POLYGON 1 +#define CONVEX_POLYHEDRON_DUMP_ON_BAD_POLYGON 1 +#define CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON 1 +#define CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON 1 +#define CONVEX_POLYHEDRON_WARN_ON_INCORRECT_FACE_CUT 1 +#define CONVEX_POLYHEDRON_DUMP_ON_INCORRECT_FACE_CUT 1 + + +#else + +#define WARN osg::notify( osg::WARN ) + +#define CONVEX_POLYHEDRON_CHECK_COHERENCY 0 +#define CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA 0 +#define CONVEX_POLYHEDRON_DUMP_ON_INCOHERENT_DATA 0 +#define CONVEX_POLYHEDRON_WARN_ON_BAD_POLYGON 0 +#define CONVEX_POLYHEDRON_DUMP_ON_BAD_POLYGON 0 +#define CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON 0 +#define CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON 0 +#define CONVEX_POLYHEDRON_WARN_ON_INCORRECT_FACE_CUT 0 +#define CONVEX_POLYHEDRON_DUMP_ON_INCORRECT_FACE_CUT 0 + +#endif + +const osg::Matrix & ConvexPolyhedron::defaultMatrix = *(osg::Matrix*)NULL; +static const double epsi = pow( 2.0, -20.0 ); //circa 8 times float prec(~2^-23) +static const double plane_hull_tolerance = 1.0e-5; +static const double point_plane_tolerance = 0; +static const double point_point_tolerance = 0; +static const double point_point_equivalence = 0; + +// Tim Moore modifications for GCC 4.3 August 15, 2008 +// they correspond to Adrian Egli tweaks for VS 7.3 on August 19, 2008 +namespace +{ +typedef std::vector< double > Distances; +typedef std::vector< osg::Vec4d > Points; + +// Auxilliary params contined per face +struct FaceDistances +{ + ConvexPolyhedron::Faces::iterator itr; + Points points; + Distances distances; + int below, above, on; +}; +} // namespace + +ConvexPolyhedron::ConvexPolyhedron( const osg::Matrix& matrix, + const osg::Matrix& inverse, + const osg::BoundingBox& bb ) +{ + setToBoundingBox( bb ); + + if( &matrix != &defaultMatrix && &inverse != &defaultMatrix ) + transform( matrix, inverse ); + else if( &matrix != &defaultMatrix && &inverse == &defaultMatrix ) + transform( matrix, osg::Matrix::inverse( matrix ) ); + else if( &matrix == &defaultMatrix && &inverse != &defaultMatrix ) + transform( osg::Matrix::inverse( inverse ), matrix ); +} + +void ConvexPolyhedron::setToUnitFrustum(bool withNear, bool withFar) +{ + const osg::Vec3d v000(-1.0,-1.0,-1.0); + const osg::Vec3d v010(-1.0,1.0,-1.0); + const osg::Vec3d v001(-1.0,-1.0,1.0); + const osg::Vec3d v011(-1.0,1.0,1.0); + const osg::Vec3d v100(1.0,-1.0,-1.0); + const osg::Vec3d v110(1.0,1.0,-1.0); + const osg::Vec3d v101(1.0,-1.0,1.0); + const osg::Vec3d v111(1.0,1.0,1.0); + + _faces.clear(); + + { // left plane. + Face& face = createFace(); + face.name = "left"; + face.plane.set(1.0,0.0,0.0,1.0); + face.vertices.push_back(v000); + face.vertices.push_back(v001); + face.vertices.push_back(v011); + face.vertices.push_back(v010); + } + + { // right plane. + Face& face = createFace(); + face.name = "right"; + face.plane.set(-1.0,0.0,0.0,1.0); + face.vertices.push_back(v100); + face.vertices.push_back(v110); + face.vertices.push_back(v111); + face.vertices.push_back(v101); + } + + { // bottom plane. + Face& face = createFace(); + face.name = "bottom"; + face.plane.set(0.0,1.0,0.0,1.0); + face.vertices.push_back(v000); + face.vertices.push_back(v100); + face.vertices.push_back(v101); + face.vertices.push_back(v001); + } + + { // top plane. + Face& face = createFace(); + face.name = "top"; + face.plane.set(0.0,-1.0,0.0,1.0); + face.vertices.push_back(v010); + face.vertices.push_back(v011); + face.vertices.push_back(v111); + face.vertices.push_back(v110); + } + + if (withNear) + { // near plane + Face& face = createFace(); + face.name = "near"; + face.plane.set(0.0,0.0,1.0,1.0); + face.vertices.push_back(v000); + face.vertices.push_back(v010); + face.vertices.push_back(v110); + face.vertices.push_back(v100); + } + + if (withFar) + { // far plane + Face& face = createFace(); + face.name = "far"; + face.plane.set(0.0,0.0,-1.0,1.0); + face.vertices.push_back(v001); + face.vertices.push_back(v101); + face.vertices.push_back(v111); + face.vertices.push_back(v011); + } +} + +void ConvexPolyhedron::setToBoundingBox(const osg::BoundingBox& bb) +{ + _faces.clear(); + + // Ignore invalid and one dimensional boxes + if( bb._min[0] >= bb._max[0] || + bb._min[1] >= bb._max[1] || + bb._min[2] >= bb._max[2] ) return; + + const osg::Vec3d v000(bb.xMin(),bb.yMin(), bb.zMin()); + const osg::Vec3d v010(bb.xMin(),bb.yMax(), bb.zMin()); + const osg::Vec3d v001(bb.xMin(),bb.yMin(), bb.zMax()); + const osg::Vec3d v011(bb.xMin(),bb.yMax(), bb.zMax()); + const osg::Vec3d v100(bb.xMax(),bb.yMin(), bb.zMin()); + const osg::Vec3d v110(bb.xMax(),bb.yMax(), bb.zMin()); + const osg::Vec3d v101(bb.xMax(),bb.yMin(), bb.zMax()); + const osg::Vec3d v111(bb.xMax(),bb.yMax(), bb.zMax()); + + + + { // x min plane + Face& face = createFace(); + face.name = "xMin"; + face.plane.set(1.0,0.0,0.0,-bb.xMin()); + face.vertices.push_back(v000); + face.vertices.push_back(v001); + face.vertices.push_back(v011); + face.vertices.push_back(v010); + } + + { // x max plane. + Face& face = createFace(); + face.name = "xMax"; + face.plane.set(-1.0,0.0,0.0,bb.xMax()); + face.vertices.push_back(v100); + face.vertices.push_back(v110); + face.vertices.push_back(v111); + face.vertices.push_back(v101); + } + + { // y min plane. + Face& face = createFace(); + face.name = "yMin"; + face.plane.set(0.0,1.0,0.0,-bb.yMin()); + face.vertices.push_back(v000); + face.vertices.push_back(v100); + face.vertices.push_back(v101); + face.vertices.push_back(v001); + } + + { // y max plane. + Face& face = createFace(); + face.name = "yMax"; + face.plane.set(0.0,-1.0,0.0,bb.yMax()); + face.vertices.push_back(v010); + face.vertices.push_back(v011); + face.vertices.push_back(v111); + face.vertices.push_back(v110); + } + { // z min plane + Face& face = createFace(); + face.name = "zMin"; + face.plane.set(0.0,0.0,1.0,-bb.zMin()); + face.vertices.push_back(v000); + face.vertices.push_back(v010); + face.vertices.push_back(v110); + face.vertices.push_back(v100); + } + + { // z max plane + Face& face = createFace(); + face.name = "zMax"; + face.plane.set(0.0,0.0,-1.0,bb.zMax()); + face.vertices.push_back(v001); + face.vertices.push_back(v101); + face.vertices.push_back(v111); + face.vertices.push_back(v011); + } +} + +void ConvexPolyhedron::transform(const osg::Matrix& matrix, const osg::Matrix& inverse) +{ + bool requires_infinite_plane_clip = false; + + ConvexPolyhedron cp = *this; + for(Faces::iterator itr = _faces.begin(); + itr != _faces.end() && !requires_infinite_plane_clip; + ++itr) + { + Face& face = *itr; + face.plane.transformProvidingInverse(inverse); + for(Vertices::iterator vitr = face.vertices.begin(); + vitr != face.vertices.end(); + ++vitr) + { + osg::Vec4d v( *vitr, 1.0 ); + v = v * matrix; + + if( v[3] <= 0 ) { + requires_infinite_plane_clip = true; + break; + } + + vitr->set( v[0]/v[3], v[1]/v[3], v[2]/v[3] ); + } + } + + if( requires_infinite_plane_clip ) { + *this = cp; + transformClip( matrix, inverse ); +// cp.dumpGeometry(&cp._faces.back(),&cp._faces.back().plane,this); + } + + // Perpective transforms and lack of precision + // occasionaly cause removal of some points + + removeDuplicateVertices( ); + + checkCoherency( true, "ConvexPolyhedron::transform" ); +} + +void ConvexPolyhedron::transformClip(const osg::Matrix& matrix, const osg::Matrix& inverse) +{ + double tolerance = 0.00001; + + if( _faces.empty() ) return; + + + ConvexPolyhedron cp( *this ); + + typedef std::vector< FaceDistances > FaceDistancesList; + FaceDistancesList faceDistances; + faceDistances.resize( _faces.size() ); + + double min = FLT_MAX, max = -FLT_MAX; //Hull max & min point distances + + FaceDistancesList::iterator fd = faceDistances.begin(); + // First step compute each face points distances to cutting plane + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr, ++fd ) + { + fd->itr = itr; + fd->distances.reserve( itr->vertices.size() ); + fd->on = 0; + fd->above = 0; + fd->below = 0; + + itr->plane.transformProvidingInverse(inverse); + + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr) + { + osg::Vec4d p( *vitr, 1.0 ); + p = p * matrix; + vitr->set( p[0]/p[3], p[1]/p[3], p[2]/p[3] ); + + double d = p[3]-tolerance; + + fd->points.push_back( p ); + fd->distances.push_back( d ); + if ( d>point_plane_tolerance ) ++fd->above; + else if ( d<-point_plane_tolerance ) ++fd->below; + else ++fd->on; + min = osg::minimum( min, d ); + max = osg::maximum( max, d ); + } + } + + if( max <= plane_hull_tolerance ) { // All points on or below cutting plane + _faces.clear(); + return; + } + + if( min >= -plane_hull_tolerance ) { // All points on or above cutting plane + return; + } + + typedef std::pair Edge; + typedef std::set< Edge > Edges; + Edges edges; + + for( FaceDistancesList::iterator fd = faceDistances.begin(); + fd != faceDistances.end(); + ++fd ) + { + if ( fd->below == 0 ) + { // skip face if all points on or above cutting plane ( below == 0 ) + continue; + } + + if ( /* fd->below > 0 && */ fd->above == 0 && fd->on == 0 ) + { + _faces.erase( fd->itr ); // remove face if points below or on + continue; + } + + // cut the face if some points above and below plane + // assert( fd->below > 0 && fd->above > 0 ); + + Face& face = *(fd->itr); + Points& vertices = fd->points; + Distances& distances = fd->distances; + Vertices newFaceVertices; + Vertices newVertices; + + for(unsigned int i=0; i < vertices.size(); ++i) + { + osg::Vec4d &va = vertices[i]; + osg::Vec4d &vb = vertices[(i+1)%vertices.size()]; + double &distance_a = distances[i]; + double &distance_b = distances[(i+1)%vertices.size()]; + + // Is first edge point above or on the plane? + if ( -point_plane_tolerance <= distance_a ) { + + osg::Vec3d v( vertices[i][0], vertices[i][1], vertices[i][2] ); + v /= vertices[i][3]; + + if( newVertices.empty() || v != newVertices.back() ) + newVertices.push_back( v ); + + if ( distance_a <= point_plane_tolerance ) { + if( newFaceVertices.empty() || v != newFaceVertices.back() ) + newFaceVertices.push_back( v ); + } + } + + // Does edge intersect plane ? + if ( ( distance_a < -point_plane_tolerance && distance_b > point_plane_tolerance ) || + ( distance_b < -point_plane_tolerance && distance_a > point_plane_tolerance ) ) + { + osg::Vec4d intersection; // Inserting vertex + double da = fabs( distance_a ), db = fabs( distance_b ); + + // tweaks to improve coherency of polytope after cut + if( da <= point_point_equivalence && da <= db ) { + intersection = va; + } else if( db <= point_point_equivalence && db <= da ) { + intersection = vb; + } else { + double dab4 = 0.25 * ( da + db ); + if( dab4 < da && dab4 < db ) { + intersection = (vb*distance_a - va*distance_b)/(distance_a-distance_b); + } else { + osg::Vec4d v = (vb - va)/(distance_a-distance_b); + if( da < db ) + intersection = va + v * distance_a; + else + intersection = vb + v * distance_b; + } + } + + + osg::Vec3d v( intersection[0], intersection[1], intersection[2] ); + v /= intersection[3]; + + if( newVertices.empty() || v != newVertices.back() ) + newVertices.push_back( v ); + + if( newFaceVertices.empty() || v != newFaceVertices.back() ) + newFaceVertices.push_back( v ); + } + } + + if( newVertices.size() && newVertices.front() == newVertices.back() ) + newVertices.pop_back(); + + if( newFaceVertices.size() && newFaceVertices.front() == newFaceVertices.back() ) + newFaceVertices.pop_back(); + + if( newFaceVertices.size() == 1 ) { // This is very rare but correct + WARN + << "ConvexPolyhedron::transformClip - Slicing face polygon returns " + << newFaceVertices.size() + << " points. Should be 2 (usually) or 1 (rarely)." + << std::endl; + + } else if( newFaceVertices.size() == 2 ) { + if( newFaceVertices[0] < newFaceVertices[1] ) { + edges.insert( Edge( newFaceVertices[0], newFaceVertices[1] ) ); + } else { + edges.insert( Edge( newFaceVertices[1], newFaceVertices[0] ) ); + } + } else if( newFaceVertices.size() > 2 ) { + +#if CONVEX_POLYHEDRON_WARN_ON_INCORRECT_FACE_CUT + + // This may happen if face polygon is not planar or convex. + // It may happen when face was distorted by projection transform + // or when some polygon vertices land in incorrect positions after + // some transformation by badly conditioned matrix (weak precison) + + WARN + << "ConvexPolyhedron::transformClip - Slicing face polygon returns " + << newFaceVertices.size() + << " points. Should be 2 (usually) or 1 (rarely)." + << std::endl; +#endif + +#if CONVEX_POLYHEDRON_DUMP_ON_INCORRECT_FACE_CUT + dumpGeometry( &face, NULL, &cp ); +#endif + + // Lets try to recover from this uneasy situation + // by comparing current face polygon edges cut by new plane + // with edges selected for new plane + + unsigned i0 = 0, i1 = newFaceVertices.size() - 1; + unsigned j0 = 0, j1 = newVertices.size() - 1; + + for( ; i0 < newFaceVertices.size(); i1 = i0++, j1 = j0++ ) { + while( newFaceVertices[i0] != newVertices[j0] ) j1 = j0++; + if( newFaceVertices[i1] == newVertices[j1] ) + { + if( newFaceVertices[i0] < newFaceVertices[i1] ) { + edges.insert( Edge( newFaceVertices[i0], newFaceVertices[i1] ) ); + } else { + edges.insert( Edge( newFaceVertices[i1], newFaceVertices[i0] ) ); + } + } + } + } + + if( newVertices.size() >= 3 ) { //Add faces with at least 3 points + +#if ( CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON || CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON ) + int convex = isFacePolygonConvex( face ); + face.vertices.swap( newVertices ); + if( convex && !isFacePolygonConvex( face ) ) { +#if CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON + WARN << "ConvexPolyhedron::transformClip - polygon output non convex." + << " This may lead to other issues in ConvexPolyhedron math" << std::endl; +#endif +#if CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON + dumpGeometry( &face, NULL, &cp ); +#endif + } +#else + face.vertices.swap( newVertices ); +#endif + } else //Remove face reduced to 0, 1, 2 points + _faces.erase( fd->itr ); + } + + osg::Vec3d center( 0, 0, 0 ); + unsigned count = 0; + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr ) + { + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr ) + { + center += *vitr; + count ++; + } + } + + center /= count; + + + if ( edges.size() > 1 ) //Ignore faces reduced to 0, 1, 2 points + { + Face face; + face.name = "Almost infinite plane"; + + std::deque< osg::Vec3d > vertices; + + Edges::iterator itr = edges.begin(); + vertices.push_back( itr->first ); + vertices.push_back( itr->second ); + edges.erase( itr++ ); + + for( unsigned int vertices_size = 0; + vertices_size < vertices.size(); ) + { + vertices_size = vertices.size(); + for( itr = edges.begin(); itr != edges.end(); ) + { + bool not_added = false; + + if( itr->first == vertices.back() ) + vertices.push_back( itr->second ); + else if ( itr->first == vertices.front() ) + vertices.push_front( itr->second ); + else if ( itr->second == vertices.back() ) + vertices.push_back( itr->first ); + else if ( itr->second == vertices.front() ) + vertices.push_front( itr->first ); + else + not_added = true; + + if( not_added ) + ++itr; + else + edges.erase( itr++ ); + } + } + +#if CONVEX_POLYHEDRON_WARN_ON_BAD_POLYGON + if( !edges.empty() ) { + WARN + << "ConvexPolyhedron::transformClip - Building new face polygon - " + << "Found edges not matching former polygon ends" + << std::endl; + } +#endif + + face.vertices.insert( face.vertices.begin(), + vertices.begin(), vertices.end() ); + + face.plane.set( vertices[0], vertices[1], vertices[2] ); + + if( face.plane.distance( center ) < 0.0 ) face.plane.flip(); + + _faces.push_back(face); + + // Last vertex is duplicated - remove one instance + if( face.vertices.front() == face.vertices.back() ) + face.vertices.pop_back(); + else {// If not duplicated then it may mean we have open polygon ;-( +#if CONVEX_POLYHEDRON_WARN_ON_BAD_POLYGON + WARN + << "ConvexPolyhedron::transformClip - Building new face polygon - " + << " Polygon not properly closed." + << std::endl; +#endif +#if CONVEX_POLYHEDRON_DUMP_ON_BAD_POLYGON + dumpGeometry( &_faces.back(), NULL, &cp ); +#endif + } + +#if ( CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON || CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON ) + if( !isFacePolygonConvex( face ) ) { +#if CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON + WARN << "ConvexPolyhedron::transformClip - new face polygon non convex." + << " This may lead to other issues in ConvexPolyhedron math" << std::endl; +#endif +#if CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON + ConvexPolyhedron cp; + cp.createFace() = face; + cp.dumpGeometry( ); +#endif + } +#endif + } + + // Perpective transforms and lack of precision + // occasionaly cause removal of some points + + removeDuplicateVertices( ); + + checkCoherency( true, "ConvexPolyhedron::transformClip" ); +} + +bool ConvexPolyhedron::mergeFaces + ( const Face & face0, const Face & face1, Face & face ) +{ + typedef std::pair< osg::Vec3d, osg::Vec3d > Edge; + typedef std::set Edges; + Edges edges; + + for(unsigned int i=0; i 1 ) //Ignore faces reduced to 0, 1, 2 points + { + std::deque< osg::Vec3d > vertices; + + Edges::iterator itr = edges.begin(); + vertices.push_back( itr->first ); + vertices.push_back( itr->second ); + edges.erase( itr++ ); + + for( unsigned int vertices_size = 0; + vertices_size < vertices.size(); ) + { + vertices_size = vertices.size(); + for( itr = edges.begin(); itr != edges.end(); ) + { + bool not_added = false; + + if( itr->first == vertices.back() ) + vertices.push_back( itr->second ); + else if ( itr->first == vertices.front() ) + vertices.push_front( itr->second ); + else if ( itr->second == vertices.back() ) + vertices.push_back( itr->first ); + else if ( itr->second == vertices.front() ) + vertices.push_front( itr->first ); + else + not_added = true; + + if( not_added ) + ++itr; + else + edges.erase( itr++ ); + } + } + +#if CONVEX_POLYHEDRON_WARN_ON_BAD_POLYGON + if( !edges.empty() ) { + WARN + << "ConvexPolyhedron::mergeFaces - Building new face polygon - " + << "Found edges not matching former polygon ends." + << std::endl; + } +#endif + + // Last vertex is duplicated - remove one instance + if( vertices.front() == vertices.back() ) + vertices.pop_back(); + else {// If not duplicated then it may mean we have open polygon ;-( +#if CONVEX_POLYHEDRON_WARN_ON_BAD_POLYGON + WARN + << "ConvexPolyhedron::mergeFaces - Building new face polygon - " + << " Polygon not properly closed." + << std::endl; +#endif +#if CONVEX_POLYHEDRON_DUMP_ON_BAD_POLYGON +#endif + } + +#if 1 // Resulting plane will be the same as face0 + face.plane = face0.plane; + face.vertices.insert( face.vertices.begin(), + vertices.begin(), vertices.end() ); +#else // Compute resulting plane as average of faces (not a good idea) + osg::Vec3d normal = face0.plane.getNormal() + face1.plane.getNormal(); + normal.normalize(); + + osg::Vec3d center; + for( unsigned int i = 0; i < vertices.size(); ++i ) + { + center += vertices[i]; + face.vertices.push_back( vertices[i] ); + } + center /= vertices.size(); + + face.plane.set( normal, center ); +#endif + + face.name = face0.name + " + " + face1.name; + + // No testing for concave polys + // Its possible to build concave polygon from two merged faces + // But after all coplanar faces are merged final result should be convex. + } + return intersection; +} + +void ConvexPolyhedron::mergeCoplanarFaces + ( const double & dot_tolerance, const double & delta_tolerance ) +{ + for(Faces::iterator itr0 = _faces.begin(); + itr0 != _faces.end(); + ++itr0 ) + { + double tolerance = delta_tolerance; + for( unsigned i = 0; i < itr0->vertices.size(); ++i ) { + tolerance = osg::maximum( tolerance, + fabs( itr0->plane.distance( itr0->vertices[i] ) ) ); + } + + for(Faces::iterator itr1 = _faces.begin(); + itr1 != _faces.end(); + ) + { + if( itr1 == itr0 ) { + ++itr1; + continue; + } + + bool attempt_merge = true; + for( unsigned i = 0; i < itr1->vertices.size(); ++i ) { + if( fabs( itr0->plane.distance( itr1->vertices[i] ) ) > tolerance ) + { + attempt_merge = false; + break; + } + } + + if( !attempt_merge && + 1.0 - itr0->plane.getNormal() * itr1->plane.getNormal() < dot_tolerance && + fabs( itr0->plane.ptr()[3] - itr1->plane.ptr()[3] ) < delta_tolerance ) + attempt_merge = true; + + if( attempt_merge && mergeFaces( *itr0, *itr1, *itr0 ) ) + itr1 = _faces.erase( itr1 ); + else + ++itr1; + } + } +} + +void ConvexPolyhedron::removeDuplicateVertices( void ) +{ +#if 1 + // Aggressive removal, find very close points and replace them + // with their average. Second step wil do the rest. + + typedef std::map< osg::Vec3f, osg::Vec4d > Points; + typedef std::set< osg::Vec3d > VertexSet; + + VertexSet vertexSet; + Points points; + + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr ) + { + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr ) + { + vertexSet.insert( *vitr ); + } + } + + for( VertexSet::iterator vitr = vertexSet.begin(); + vitr != vertexSet.end(); + ++vitr ) + { + points[ *vitr ] += osg::Vec4d( *vitr, 1.0 ); + } + + for( Points::iterator itr = points.begin(); + itr != points.end(); + ++itr ) + { + if( itr->second[3] > 1.0 ) + itr->second /= itr->second[3]; + } + + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr ) + { + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr ) + { + osg::Vec4d &v = points[ *vitr ]; + *vitr = osg::Vec3d( v[0], v[1], v[2] ); + } + } +#endif + + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ) + { + assert( itr->vertices.size() > 0 ); + + osg::Vec3d prev = itr->vertices.back(); + + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ) + { + if( *vitr == prev ) { + vitr = itr->vertices.erase( vitr ); + } else { + prev = *vitr; + ++vitr; + } + } + + if( itr->vertices.size() < 3 ) + itr = _faces.erase( itr ); + else + ++itr; + } + + mergeCoplanarFaces(); + +#if 1 + // Experimentally remove vertices on colinear edge chains + // effectivelyy replacing them with one edge + typedef std::map VertexCounter; + VertexCounter vertexCounter; + + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr) + { + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr ) + { + ++vertexCounter[ *vitr ]; + } + } + + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ) + { + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ) + { + if( vertexCounter[ *vitr ] == 2 ) { +#if 0 // Sanity check if we could remove this point without changing poly shape + + Vertices::iterator next = ( vitr + 1 == itr->vertices.end() ? + itr->vertices.begin() : + vitr + 1 ); + + Vertices::iterator prev = ( vitr == itr->vertices.begin() ? + itr->vertices.end() - 1 : + vitr - 1 ); + + + osg::Vec3d va = *vitr - *prev; + osg::Vec3d vb = *next - *vitr; + + double da = va.normalize(); + double db = vb.normalize(); + + if( 0.001 < da && 0.001 < db ) + { + double dot = va * vb, limit = cos( 0.01 ); + if( dot < limit ) { + WARN << "ConvexPolyhedron::removeDuplicateVertices" + << " - removed mid point connecting two non colinear edges." + << " Angle(deg): " << osg::RadiansToDegrees( acos( dot ) ) + << " Length1: " << da + << " Length2: " << db + << std::endl; + } + } else { + if( da == 0.0 || db == 0.0 ) + WARN << "ConvexPolyhedron::removeDuplicateVertices" + << " - removed degenerated mid point connecting two edges" + << std::endl; + + } +#endif + vitr = itr->vertices.erase( vitr ); + } else { + ++vitr; + } + } + + if( itr->vertices.size() < 3 ) + itr = _faces.erase( itr ); + else + ++itr; + } +#endif + + checkCoherency( false, "Leave ConvexPolyhedron::removeDuplicateVertices" ); +} + +int ConvexPolyhedron::pointsColinear + ( const osg::Vec3d & a, const osg::Vec3d & b, const osg::Vec3d & c, + const double & dot_tolerance, const double & delta_tolerance ) +{ + osg::Vec3d va = b - a; + osg::Vec3d vb = c - b; + + double da = va.normalize(); + double db = vb.normalize(); + + if( delta_tolerance >= da || delta_tolerance >= db ) + return -1; // assume collinearity if one of the edges is zero length + + if( 1.0 - fabs( va * vb ) <= dot_tolerance ) + return 1; // edge normals match collinearity condition + + return 0; // nope. not collinear +} + +int ConvexPolyhedron::isFacePolygonConvex( Face & face, bool ignoreColinearVertices ) +{ + int positive = 0, negative = 0, colinear = 0; + for( unsigned int i = 0; i < face.vertices.size(); ++i ) + { + osg::Vec3d va = face.vertices[i]; + osg::Vec3d vb = face.vertices[(i+1)%face.vertices.size()]; + osg::Vec3d vc = face.vertices[(i+2)%face.vertices.size()]; + + double dist = fabs( face.plane.distance( va ) ); + +#if ( CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA > 1 || CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON ) + if( dist > 0.0001 ) + { + WARN << "ConvexPolyhedron::isFacePolygonConvex - plane point too far from plane (" << dist <<")" << std::endl; + } +#endif + + // cast points on a plane + va -= face.plane.getNormal() * face.plane.distance( va ); + vb -= face.plane.getNormal() * face.plane.distance( vb ); + vc -= face.plane.getNormal() * face.plane.distance( vc ); + + if( pointsColinear( va, vb, vc ) ) { + colinear++; + } else { + double side =( ( vc - vb ) ^ ( vb - va ) ) * face.plane.getNormal(); + + if( side < 0 ) negative++; + if( side > 0 ) positive++; + } + } + + if( !ignoreColinearVertices && colinear > 0 ) + return 0; + + if( !negative && !positive ) + return 0; + + if( negative + colinear == face.vertices.size() ) + return -( negative + colinear ); + + if( positive + colinear == face.vertices.size() ) + return +( positive + colinear ); + + return 0; +} + +bool ConvexPolyhedron::checkCoherency + ( bool checkForNonConvexPolys, const char * errorPrefix ) +{ + bool result = true; + bool convex = true; + +#if CONVEX_POLYHEDRON_CHECK_COHERENCY + + if( !errorPrefix ) errorPrefix = "ConvexPolyhedron"; + + typedef std::pair Edge; + typedef std::map EdgeCounter; + typedef std::map VertexCounter; + + EdgeCounter edgeCounter; + VertexCounter vertexCounter; + + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr) + { + Face& face = *itr; + + if( checkForNonConvexPolys && !isFacePolygonConvex( face ) ) { + convex = false; +#if ( 1 < CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA || CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON ) + WARN << errorPrefix << + " - coherency fail - face polygon concave" << std::endl; +#endif +#if ( 1 < CONVEX_POLYHEDRON_DUMP_ON_INCOHERENT_DATA || CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON ) + dumpGeometry( &face ); +#endif + } + + Vertices& vertices = face.vertices; + for(unsigned int i=0; ifirst; + + if( e.first.isNaN() ) { + result = false; +#if ( 1 < CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA ) + WARN << errorPrefix << + " - coherency fail - Vertex is NaN." << std::endl; +#endif + } + + if( e.second.isNaN() ) { + result = false; +#if ( 1 < CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA ) + WARN << errorPrefix << + " - coherency fail - Vertex is NaN." << std::endl; +#endif + } + + if( e.first == e.second ) { + result = false; +#if ( 1 < CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA ) + WARN << errorPrefix << + " - coherency fail - Edge with identical vertices." << std::endl; +#endif + } + + if( vertexCounter[ e.first ] < 3 ) { + result = false; +#if ( 1 < CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA ) + WARN << errorPrefix << + " - coherency fail - Vertex present " << vertexCounter[ e.first ] << " times" << std::endl; +#endif + } + + if( vertexCounter[ e.second ] < 3 ) { + result = false; +#if ( 1 < CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA ) + WARN << errorPrefix << + " - coherency fail - Vertex present " << vertexCounter[ e.second ] << " times" << std::endl; +#endif + } + + if( itr->second != 2 ) { + result = false; +#if ( 1 < CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA ) + WARN << errorPrefix << + " - coherency fail - Edge present " << itr->second << " times" << std::endl; +#endif + } + } + +#if ( 1 == CONVEX_POLYHEDRON_WARN_ON_INCOHERENT_DATA ) + if( !convex ) + WARN << errorPrefix + << " - coherency fail - non convex output" << std::endl; + + if ( !result ) + WARN << errorPrefix + << " - coherency fail - incoherent output" << std::endl; +#endif + +#if ( 1 == CONVEX_POLYHEDRON_DUMP_ON_INCOHERENT_DATA ) + if( !result || !convex ) + dumpGeometry( ); +#endif + +#if ( 1 < CONVEX_POLYHEDRON_DUMP_ON_INCOHERENT_DATA ) + if( !result ) + dumpGeometry( ); +#endif + + +#endif // CONVEX_POLYHEDRON_CHECK_COHERENCY + return result && convex; +} + +osg::BoundingBox ConvexPolyhedron::computeBoundingBox( const osg::Matrix & m ) const +{ + osg::BoundingBox bb; + + if( &m != &defaultMatrix ) { + for( Faces::const_iterator itr = _faces.begin(); itr != _faces.end(); ++itr ) + for( Vertices::const_iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr ) + bb.expandBy( *vitr * m ); + } else { + for( Faces::const_iterator itr = _faces.begin(); itr != _faces.end(); ++itr ) + for( Vertices::const_iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr ) + bb.expandBy( *vitr ); + } + + return bb; +} + +void ConvexPolyhedron::cut(const osg::Polytope & polytope) +{ + const char * apc[6] = { "left", "right", "bottom", "top", "near", "far" }; + char ac[16]; + int i = 0; + + for(osg::Polytope::PlaneList::const_iterator itr = polytope.getPlaneList().begin(); + itr != polytope.getPlaneList().end(); + ++itr) + { + const char* arg; + if (i < 6) { + arg = apc[i]; + } else { + sprintf(ac, "%d", i); + arg = ac; + } + cut(*itr, std::string( arg ) ); + + i++; + } + + removeDuplicateVertices(); +} + +void ConvexPolyhedron::cut(const ConvexPolyhedron& polytope) +{ + for(Faces::const_iterator itr = polytope._faces.begin(); + itr != polytope._faces.end(); + ++itr) + { + cut(itr->plane, itr->name); + } + + removeDuplicateVertices(); +} + +void ConvexPolyhedron::cut(const osg::Plane& plane, const std::string& name) +{ + if( _faces.empty() ) return; + + ConvexPolyhedron cp( *this ); + + typedef std::vector< FaceDistances > FaceDistancesList; + FaceDistancesList faceDistances; + faceDistances.resize( _faces.size() ); + + double min = FLT_MAX, max = -FLT_MAX; //Hull max & min point distances + + FaceDistancesList::iterator fd = faceDistances.begin(); + // First step compute each face points distances to cutting plane + for( Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr, ++fd ) + { + fd->itr = itr; + fd->distances.reserve( itr->vertices.size() ); + fd->on = 0; + fd->above = 0; + fd->below = 0; + +#if 0 // Skip if cutting plane the same as one of faces + if( plane.ptr()[0] ) == itr->plane.ptr()[0] && + plane.ptr()[1] ) == itr->plane.ptr()[1] && + plane.ptr()[2] ) == itr->plane.ptr()[2] && +#else // check plane using less precise float values + if( float( plane.ptr()[0] ) == float( itr->plane.ptr()[0] ) && + float( plane.ptr()[1] ) == float( itr->plane.ptr()[1] ) && + float( plane.ptr()[2] ) == float( itr->plane.ptr()[2] ) && +#endif + plane_hull_tolerance >= fabs( float( plane.ptr()[3] )- float( itr->plane.ptr()[3] ) ) ) + return; + + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr) + { + double d = plane.distance( *vitr ); + + fd->distances.push_back( d ); + if ( d>point_plane_tolerance ) ++fd->above; + else if ( d<-point_plane_tolerance ) ++fd->below; + else ++fd->on; + min = osg::minimum( min, d ); + max = osg::maximum( max, d ); + } + } + + if( max <= plane_hull_tolerance ) { // All points on or below cutting plane + _faces.clear(); + return; + } + + if( min >= -plane_hull_tolerance ) { // All points on or above cutting plane + return; + } + + typedef std::pair Edge; + typedef std::set< Edge > Edges; + Edges edges; + + for( FaceDistancesList::iterator fd = faceDistances.begin(); + fd != faceDistances.end(); + ++fd ) + { + if ( fd->below == 0 ) + { // skip face if all points on or above cutting plane ( below == 0 ) + continue; + } + + if ( /* fd->below > 0 && */ fd->above == 0 && fd->on == 0 ) + { + _faces.erase( fd->itr ); // remove face if points below or on + continue; + } + + // cut the face if some points above and below plane + // assert( fd->below > 0 && fd->above > 0 ); + + Face& face = *(fd->itr); + Vertices& vertices = face.vertices; + Distances& distances = fd->distances; + Vertices newFaceVertices; + Vertices newVertices; + + + for(unsigned int i=0; i < vertices.size(); ++i) + { + osg::Vec3d &va = vertices[i]; + osg::Vec3d &vb = vertices[(i+1)%vertices.size()]; + double &distance_a = distances[i]; + double &distance_b = distances[(i+1)%vertices.size()]; + + // Is first edge point above or on the plane? + if ( -point_plane_tolerance <= distance_a ) { + + if( newVertices.empty() || vertices[i] != newVertices.back() ) + newVertices.push_back( vertices[i] ); + + if ( distance_a <= point_plane_tolerance ) { + if( newFaceVertices.empty() || vertices[i] != newFaceVertices.back() ) + newFaceVertices.push_back( vertices[i] ); + } + } + + // Does edge intersect plane ? + if ( ( distance_a < -point_plane_tolerance && distance_b > point_plane_tolerance ) || + ( distance_b < -point_plane_tolerance && distance_a > point_plane_tolerance ) ) + { + osg::Vec3d intersection; // Inserting vertex + double da = fabs( distance_a ), db = fabs( distance_b ); + + // tweaks to improve coherency of polytope after cut + if( da <= point_point_equivalence && da <= db ) { + intersection = va; + } else if( db <= point_point_equivalence && db <= da ) { + intersection = vb; + } else { + double dab4 = 0.25 * ( da + db ); + if( dab4 < da && dab4 < db ) { + intersection = (vb*distance_a - va*distance_b)/(distance_a-distance_b); + } else { + osg::Vec3d v = (vb - va)/(distance_a-distance_b); + if( da < db ) + intersection = va + v * distance_a; + else + intersection = vb + v * distance_b; + } + } + + if( newVertices.empty() || intersection != newVertices.back() ) + newVertices.push_back( intersection ); + + if( newFaceVertices.empty() || intersection != newFaceVertices.back() ) + newFaceVertices.push_back( intersection ); + } + } + + if( newVertices.size() && newVertices.front() == newVertices.back() ) + newVertices.pop_back(); + + if( newFaceVertices.size() && newFaceVertices.front() == newFaceVertices.back() ) + newFaceVertices.pop_back(); + + if( newFaceVertices.size() == 1 ) { // This is very rare but correct + WARN + << "ConvexPolyhedron::cut - Slicing face polygon returns " + << newFaceVertices.size() + << " points. Should be 2 (usually) or 1 (rarely)." + << std::endl; + + } else if( newFaceVertices.size() == 2 ) { + if( newFaceVertices[0] < newFaceVertices[1] ) { + edges.insert( Edge( newFaceVertices[0], newFaceVertices[1] ) ); + } else { + edges.insert( Edge( newFaceVertices[1], newFaceVertices[0] ) ); + } + } else if( newFaceVertices.size() > 2 ) { + +#if CONVEX_POLYHEDRON_WARN_ON_INCORRECT_FACE_CUT + + // This may happen if face polygon is not planar or convex. + // It may happen when face was distorted by projection transform + // or when some polygon vertices land in incorrect positions after + // some transformation by badly conditioned matrix (weak precison) + + WARN + << "ConvexPolyhedron::cut - Slicing face polygon returns " + << newFaceVertices.size() + << " points. Should be 2 (usually) or 1 (rarely)." + << std::endl; +#endif + +#if CONVEX_POLYHEDRON_DUMP_ON_INCORRECT_FACE_CUT + dumpGeometry( &face, &plane ); +#endif + + // Let try to recover from this uneasy situation + // by comparing current face polygon edges cut by new plane + // with edges selected for new plane + + unsigned i0 = 0, i1 = newFaceVertices.size() - 1; + unsigned j0 = 0, j1 = newVertices.size() - 1; + + for( ; i0 < newFaceVertices.size(); i1 = i0++, j1 = j0++ ) { + while( newFaceVertices[i0] != newVertices[j0] ) j1 = j0++; + if( newFaceVertices[i1] == newVertices[j1] ) + { + if( newFaceVertices[i0] < newFaceVertices[i1] ) { + edges.insert( Edge( newFaceVertices[i0], newFaceVertices[i1] ) ); + } else { + edges.insert( Edge( newFaceVertices[i1], newFaceVertices[i0] ) ); + } + } + } + } + + if( newVertices.size() >= 3 ) { //Add faces with at least 3 points +#if ( CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON || CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON ) + int convex = isFacePolygonConvex( face ); + vertices.swap( newVertices ); + if( convex && !isFacePolygonConvex( face ) ) { +#if CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON + WARN << "ConvexPolyhedron::cut - polygon output non convex." + << " This may lead to other issues in ConvexPolyhedron math" << std::endl; +#endif +#if CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON + dumpGeometry( &face, &plane, &cp ); +#endif + } +#else + vertices.swap( newVertices ); +#endif + } else //Remove face reduced to 0, 1, 2 points + _faces.erase( fd->itr ); + } + + if ( edges.size() > 1 ) //Ignore faces reduced to 0, 1, 2 points + { + Face face; + face.name = name; + face.plane = plane; + + std::deque< osg::Vec3d > vertices; + + Edges::iterator itr = edges.begin(); + vertices.push_back( itr->first ); + vertices.push_back( itr->second ); + edges.erase( itr++ ); + + for( unsigned int vertices_size = 0; + vertices_size < vertices.size(); ) + { + vertices_size = vertices.size(); + for( itr = edges.begin(); itr != edges.end(); ) + { + bool not_added = false; + + if( itr->first == vertices.back() ) + vertices.push_back( itr->second ); + else if ( itr->first == vertices.front() ) + vertices.push_front( itr->second ); + else if ( itr->second == vertices.back() ) + vertices.push_back( itr->first ); + else if ( itr->second == vertices.front() ) + vertices.push_front( itr->first ); + else + not_added = true; + + if( not_added ) + ++itr; + else + edges.erase( itr++ ); + } + } + +#if CONVEX_POLYHEDRON_WARN_ON_BAD_POLYGON + if( !edges.empty() ) { + WARN + << "ConvexPolyhedron::cut - Building new face polygon - " + << "Found edges not matching former polygon ends" + << std::endl; + } +#endif + + face.vertices.insert( face.vertices.begin(), + vertices.begin(), vertices.end() ); + _faces.push_back(face); + + // Last vertex is duplicated - remove one instance + if( face.vertices.front() == face.vertices.back() ) + face.vertices.pop_back(); + else {// If not duplicated then it may mean we have open polygon ;-( +#if CONVEX_POLYHEDRON_WARN_ON_BAD_POLYGON + WARN + << "ConvexPolyhedron::cut - Building new face polygon - " + << " Polygon not properly closed." + << std::endl; +#endif +#if CONVEX_POLYHEDRON_DUMP_ON_BAD_POLYGON + dumpGeometry( &_faces.back(), &plane, &cp ); +#endif + } + +#if ( CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON || CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON ) + if( !isFacePolygonConvex( face ) ) { +#if CONVEX_POLYHEDRON_DUMP_ON_CONCAVE_POLYGON + ConvexPolyhedron cp; + cp.createFace() = face; + cp.dumpGeometry( ); +#endif +#if CONVEX_POLYHEDRON_WARN_ON_CONCAVE_POLYGON + WARN << "ConvexPolyhedron::cut - new face polygon non convex." + << " This may lead to other issues in ConvexPolyhedron math" << std::endl; +#endif + } +#endif + } + +// removeDuplicateVertices( ); +} +//////////////////////////////////////////////////////////////////////////// +void ConvexPolyhedron::extrude( const osg::Vec3d & offset ) +{ + if( offset.length2() == 0 ) return; + + typedef std::pair Edge; + typedef std::vector EdgeFaces; + typedef std::map EdgeMap; + + EdgeMap edgeMap; + + // Build edge maps + for(Faces::iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr) + { + Face& face = *itr; + for(unsigned int i=0; i= 0 ) continue; + + face.plane.ptr()[3] -= dotOffset; + for(unsigned int i=0; i SilhouetteEdges; + typedef std::map< Face*, SilhouetteEdges > SilhouetteFaces; + SilhouetteFaces silhouetteFaces; + + int new_face_counter = 0; + + // Now add new faces from slhouette edges extrusion + for(EdgeMap::iterator eitr = edgeMap.begin(); + eitr != edgeMap.end(); + ++eitr) + { + const Edge& edge = eitr->first; + const EdgeFaces& edgeFaces = eitr->second; + + if ( edgeFaces.size()==1 ) + { + // WL: Execution should not reach this line. + // If you got here let me know: lewandowski@ai.com.pl + assert( 0 ); + } + else if ( edgeFaces.size()==2 ) + { +#if 0 // Use float normal computations + osg::Vec3f vf( offset ); + double dotOffset0 = osg::Vec3f( edgeFaces[0]->plane.getNormal() ) * vf; + double dotOffset1 = osg::Vec3f( edgeFaces[1]->plane.getNormal() ) * vf; +#else + double dotOffset0 = edgeFaces[0]->plane.getNormal() * offset; + double dotOffset1 = edgeFaces[1]->plane.getNormal() * offset; +#endif + //Select orthogonal faces and vertices appropriate for offseting + if( dotOffset0 == 0.0 && dotOffset1 < 0.0 || + dotOffset1 == 0.0 && dotOffset0 < 0.0 ) + { + Face * face = ( dotOffset0 == 0 ? edgeFaces[0] : edgeFaces[1] ); + silhouetteFaces[ face ].insert( edge ); + } + + if( dotOffset0 < 0.0 && dotOffset1 > 0.0 || + dotOffset1 < 0.0 && dotOffset0 > 0.0 ) + { + Face & face = createFace(); + char ac[40] = "Side plane from edge extrude "; + sprintf(ac + strlen(ac), "%d", new_face_counter++); + face.name = ac; + + // Compute face plane + face.vertices.push_back( edge.first ); + face.vertices.push_back( edge.second ); + + osg::Vec3d n = face.vertices[0] - face.vertices[1]; + n.normalize(); + n = ( n ^ offset ); + n.normalize(); + + if( n * ( edgeFaces[1]->plane.getNormal() + edgeFaces[0]->plane.getNormal() ) < 0 ) + { + n = -n; + std::swap( face.vertices[1], face.vertices[0] ); + } + + face.vertices.push_back( face.vertices[1] + offset ); + face.vertices.push_back( face.vertices[0] + offset ); + + face.plane.set( n,(face.vertices[0] + face.vertices[1] + + face.vertices[2] + face.vertices[3]) * .25 ); + } + } + else if( edgeFaces.size() > 2 ) + { + assert( 0 ); + // WL: Execution should not reach this line. + // If you got here let me know: lewandowski@ai.com.pl + } + } + + // Finally update faces which are orthogonal to our normal + for(SilhouetteFaces::iterator itr = silhouetteFaces.begin(); + itr != silhouetteFaces.end(); + ++itr) + { + SilhouetteEdges & edges = itr->second; + Vertices & vertices = itr->first->vertices; + Vertices newVertices; + + for( unsigned int i = 0; i < vertices.size(); i++ ) + { + osg::Vec3d + &va = vertices[ ( i + vertices.size() - 1 ) % vertices.size() ], + &vb = vertices[ i ], + &vc = vertices[ ( i + 1 ) % vertices.size() ]; + + Edge eab = va < vb ? Edge( va, vb ) : Edge( vb, va ); + Edge ebc = vb < vc ? Edge( vb, vc ) : Edge( vc, vb ); + + bool abFound = edges.find( eab ) != edges.end(); + bool bcFound = edges.find( ebc ) != edges.end(); + + if( abFound && bcFound ) { + newVertices.push_back( vb + offset ); + } else if( !abFound && !bcFound ) { + newVertices.push_back( vb ); + } else if( !abFound && bcFound ) { + newVertices.push_back( vb ); + newVertices.push_back( vb + offset ); + } else if( abFound && !bcFound ) { + newVertices.push_back( vb + offset ); + newVertices.push_back( vb ); + } + } + + vertices.swap( newVertices ); + } + + removeDuplicateVertices( ); + checkCoherency( true, "ConvexPolyhedron::extrude" ); +} +//////////////////////////////////////////////////////////////////////////// +void ConvexPolyhedron::translate( const osg::Vec3d & offset ) +{ + for( Faces::iterator itr = _faces.begin(); itr != _faces.end(); ++itr ) + { + itr->plane.ptr()[3] -= itr->plane.dotProductNormal( offset ); + + for( Vertices::iterator vitr = itr->vertices.begin(); + vitr != itr->vertices.end(); + ++vitr ) + { + *vitr += offset; + } + } +} +//////////////////////////////////////////////////////////////////////////// +void ConvexPolyhedron::getPolytope(osg::Polytope& polytope) const +{ + for(Faces::const_iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr) + { + polytope.add(itr->plane); + } +} +//////////////////////////////////////////////////////////////////////////// +void ConvexPolyhedron::getPoints(Vertices& vertices) const +{ + typedef std::set VerticesSet; + VerticesSet verticesSet; + for(Faces::const_iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr) + { + const Face& face = *itr; + for(Vertices::const_iterator vitr = face.vertices.begin(); + vitr != face.vertices.end(); + ++vitr) + { + verticesSet.insert(*vitr); + } + } + + for(VerticesSet::iterator sitr = verticesSet.begin(); + sitr != verticesSet.end(); + ++sitr) + { + vertices.push_back(*sitr); + } +} +//////////////////////////////////////////////////////////////////////////// +osg::Geometry* ConvexPolyhedron::buildGeometry( const osg::Vec4d& colorOutline, + const osg::Vec4d& colorInside, + osg::Geometry* geometry ) const +{ + if( !geometry ) { + geometry = new osg::Geometry; + } else { + geometry->getPrimitiveSetList( ).clear(); + } + + osg::Vec3dArray* vertices = new osg::Vec3dArray; + geometry->setVertexArray(vertices); + + osg::Vec4Array* colors = new osg::Vec4Array; + geometry->setColorArray(colors); + geometry->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE); + + for(Faces::const_iterator itr = _faces.begin(); + itr != _faces.end(); + ++itr) + { + if( colorInside[3] > 0 ) { + geometry->addPrimitiveSet( new osg::DrawArrays( GL_TRIANGLE_FAN, + vertices->size(), itr->vertices.size() ) ); + + colors->push_back( colorInside ); + } + + if( colorOutline[3] > 0 ) { + geometry->addPrimitiveSet( new osg::DrawArrays( GL_LINE_LOOP, + vertices->size(), itr->vertices.size() ) ); + + colors->push_back( colorOutline ); + } + + vertices->insert + ( vertices->end(), itr->vertices.begin(), itr->vertices.end() ); + } + + osg::StateSet* stateset = geometry->getOrCreateStateSet(); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::OFF); + + return geometry; +} +//////////////////////////////////////////////////////////////////////////// +bool ConvexPolyhedron::dumpGeometry +( const Face * face, + const osg::Plane * plane, + ConvexPolyhedron * base, + const char * filename, + const osg::Vec4d& colorOutline, + const osg::Vec4d& colorInside, + const osg::Vec4d& faceColorOutline, + const osg::Vec4d& faceColorInside, + const osg::Vec4d& planeColorOutline, + const osg::Vec4d& planeColorInside, + const osg::Vec4d& baseColorOutline, + const osg::Vec4d& baseColorInside ) const +{ + osg::Group * group = new osg::Group(); + osg::Geode * geode = new osg::Geode(); + geode->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON ); + geode->getOrCreateStateSet()->setMode( GL_CULL_FACE, osg::StateAttribute::OFF ); + geode->getOrCreateStateSet()->setMode( GL_DEPTH_TEST, osg::StateAttribute::OFF ); + + group->addChild( geode ); + + Vertices vertices; + getPoints( vertices ); + + osg::BoundingBox bb; + for( unsigned int i = 0; i < vertices.size(); i++ ) + bb.expandBy( vertices[i] ); + + ConvexPolyhedron cp( *this ), cpFace; + + for( Faces::iterator itr = cp._faces.begin(); itr != cp._faces.end(); ) + { + bool found = ( face && + itr->name == face->name && + itr->plane == face->plane && + itr->vertices == face->vertices ); +#if 1 + if( cp.isFacePolygonConvex( *itr ) < 0 ) + std::reverse( itr->vertices.begin(), itr->vertices.end() ); +#endif + + if( found ) { + cpFace.createFace() = *face; + itr = cp._faces.erase( itr ); + } else { + ++itr; + } + } + + osg::Geometry * geometry = cp.buildGeometry( colorOutline, colorInside ); + geometry->getOrCreateStateSet()->setMode( GL_CULL_FACE, osg::StateAttribute::ON ); + + geode->addDrawable( geometry ); + + if( face ) + geode->addDrawable( cpFace.buildGeometry( faceColorOutline, faceColorInside ) ); + + if( plane ) + { + ConvexPolyhedron cp; + Face & face = cp.createFace(); + face.plane = *plane; + + osg::Vec3d normal = face.plane.getNormal(); + osg::Vec3d side = fabs(normal.x()) < fabs(normal.y()) ? + osg::Vec3d(1.0, 0.0, 0.0) : + osg::Vec3d(0.0, 1.0, 0.0); + + osg::Vec3d v = normal ^ side; + v.normalize(); + v *= bb.radius(); + + osg::Vec3d u = v ^ normal; + u.normalize(); + u *= bb.radius(); + + osg::Vec3d c = bb.center(); + c -= face.plane.getNormal() * face.plane.distance( c ); + + face.vertices.push_back( c - u - v ); + face.vertices.push_back( c - u + v ); + face.vertices.push_back( c + u + v ); + face.vertices.push_back( c + u - v ); + + geode->addDrawable( cp.buildGeometry( planeColorOutline, planeColorInside ) ); + } + + if( base ) + geode->addDrawable( base->buildGeometry( baseColorOutline, baseColorInside ) ); + + return osgDB::writeNodeFile( *group, std::string( filename ) ); +} +//////////////////////////////////////////////////////////////////////////// diff --git a/src/osgShadow/DebugShadowMap.cpp b/src/osgShadow/DebugShadowMap.cpp new file mode 100644 index 000000000..1f766db79 --- /dev/null +++ b/src/osgShadow/DebugShadowMap.cpp @@ -0,0 +1,589 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace osgShadow; + + +#define VECTOR_LENGTH( v ) ( sizeof(v)/sizeof(v[0]) ) + +#define DEFAULT_DEBUG_HUD_SIZE_X 256 +#define DEFAULT_DEBUG_HUD_SIZE_Y 256 +#define DEFAULT_DEBUG_HUD_ORIGIN_X 8 +#define DEFAULT_DEBUG_HUD_ORIGIN_Y 8 + +DebugShadowMap::DebugShadowMap(): + BaseClass(), + _doDebugDraw( false ), + _hudSize( 2, 2 ), + _hudOrigin( -1, -1 ), + _viewportSize( DEFAULT_DEBUG_HUD_SIZE_X, DEFAULT_DEBUG_HUD_SIZE_Y ), + _viewportOrigin( DEFAULT_DEBUG_HUD_ORIGIN_X, DEFAULT_DEBUG_HUD_ORIGIN_Y ), + _orthoSize( 2, 2 ), + _orthoOrigin( -1, -1 ) +{ + + // Why this fancy 24 bit depth to 24 bit rainbow colors shader ? + // + // Depth values cannot be easily cast on color component because they are: + // a) 24 or 32 bit and we loose lots of precision when cast on 8 bit + // b) depth value distribution is non linear due to projection division + // when cast on componenent color there is usually very minor shade variety + // and its often difficult to notice that there is anything in the buffer + // + // Shader looks complex but it is used only for debug head-up rectangle + // so performance impact is not significant. + + _depthColorFragmentShader = new osg::Shader( osg::Shader::FRAGMENT, +#if 0 + "uniform sampler2D texture; \n" + " \n" + "void main(void) \n" + "{ \n" + " float f = texture2D( texture, vec3( gl_TexCoord[0].xy, 1.0).xy ).r; \n" + " gl_FragColor = vec4( 0.0, 1.0 - f, 0.5 - f, 0.5 ); \n" + "} \n" +#else + "uniform sampler2D texture; \n" + " \n" + "void main(void) \n" + "{ \n" + " float f = texture2D( texture, vec3( gl_TexCoord[0].xy, 1.0).xy ).r; \n" + " \n" + " f = 256.0 * f; \n" + " float fC = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fS = floor( f ) / 256.0; \n" + " \n" + " f = 256.0 * fract( f ); \n" + " float fH = floor( f ) / 256.0; \n" + " \n" + " fS *= 0.5; \n" + " fH = ( fH * 0.34 + 0.66 ) * ( 1.0 - fS ); \n" + " \n" + " vec3 rgb = vec3( ( fC > 0.5 ? ( 1.0 - fC ) : fC ), \n" + " abs( fC - 0.333333 ), \n" + " abs( fC - 0.666667 ) ); \n" + " \n" + " rgb = min( vec3( 1.0, 1.0, 1.0 ), 3.0 * rgb ); \n" + " \n" + " float fMax = max( max( rgb.r, rgb.g ), rgb.b ); \n" + " fMax = 1.0 / fMax; \n" + " \n" + " vec3 color = fMax * rgb; \n" + " \n" + " gl_FragColor = vec4( fS + fH * color, 1 ) * gl_Color; \n" + "} \n" +#endif + ); // end _depthColorFragmentShader +} + +DebugShadowMap::DebugShadowMap +(const DebugShadowMap& copy, const osg::CopyOp& copyop) : + BaseClass(copy,copyop), + _doDebugDraw( copy._doDebugDraw ), + _hudSize( copy._hudSize ), + _hudOrigin( copy._hudOrigin ), + _viewportSize( copy._viewportSize ), + _viewportOrigin( copy._viewportOrigin ), + _orthoSize( copy._viewportOrigin ), + _orthoOrigin( copy._viewportOrigin ) +{ + if( copy._depthColorFragmentShader.valid() ) + _depthColorFragmentShader = + dynamic_cast + ( copy._depthColorFragmentShader->clone(copyop) ); +} + +DebugShadowMap::~DebugShadowMap() +{ +} + +void DebugShadowMap::ViewData::cull( void ) +{ + if( getDebugDraw() && !_cameraDebugHUD.valid() ) + createDebugHUD(); + + BaseClass::ViewData::cull( ); + + cullDebugGeometry( ); +} + +bool DebugShadowMap::ViewData::DebugBoundingBox + ( const osg::BoundingBox & bb, const char * name ) +{ + bool result = false; +#if defined( _DEBUG ) || defined( DEBUG ) + if( !name ) name = ""; + + osg::BoundingBox & bb_prev = _boundingBoxMap[ std::string( name ) ]; + + result = bb.center() != bb_prev.center() || bb.radius() != bb_prev.radius(); + if( result ) + std::cout << "Box<" << name << "> (" + << ( bb._max._v[0] + bb._min._v[0] ) * 0.5 << " " + << ( bb._max._v[1] + bb._min._v[1] ) * 0.5 << " " + << ( bb._max._v[2] + bb._min._v[2] ) * 0.5 << ") [" + << ( bb._max._v[0] - bb._min._v[0] ) << " " + << ( bb._max._v[1] - bb._min._v[1] ) << " " + << ( bb._max._v[2] - bb._min._v[2] ) << "] " + << std::endl; + + bb_prev = bb; +#endif + return result; +} + +bool DebugShadowMap::ViewData::DebugPolytope +( const osg::Polytope & p, const char * name ) +{ + bool result = false; +#if defined( _DEBUG ) || defined( DEBUG ) + if( !name ) name = ""; + + osg::Polytope & p_prev = _polytopeMap[ std::string( name ) ]; + + result = ( p.getPlaneList() != p_prev.getPlaneList() ); + + if( result ) { + std::cout << "Polytope<" << name + << "> size(" << p.getPlaneList().size() << ")" + << std::endl; + + if( p.getPlaneList().size() == p_prev.getPlaneList().size() ) { + for( unsigned i = 0; i < p.getPlaneList().size(); ++i ) + { + if( p.getPlaneList()[i] != p_prev.getPlaneList()[i] ) + { + std::cout << "Plane<" << i + << "> (" + << p.getPlaneList()[i].asVec4()[0] << ", " + << p.getPlaneList()[i].asVec4()[1] << ", " + << p.getPlaneList()[i].asVec4()[2] << ", " + << p.getPlaneList()[i].asVec4()[3] << ")" + << std::endl; + } + } + } + } + + p_prev = p; +#endif + return result; +} + +bool DebugShadowMap::ViewData::DebugMatrix + ( const osg::Matrix & m, const char * name ) +{ + bool result = false; +#if defined( _DEBUG ) || defined( DEBUG ) + if( !name ) name = ""; + + osg::Matrix & m_prev = _matrixMap[ std::string( name ) ]; + + result = ( m != m_prev ); + + if( result ) + std::cout << "Matrix<" << name << "> " << std::endl + <<"[ " << m(0,0) << " " << m(0,1) << " " << m(0,2) << " " << m(0,3) << " ] " << std::endl + <<"[ " << m(1,0) << " " << m(1,1) << " " << m(1,2) << " " << m(1,3) << " ] " << std::endl + <<"[ " << m(2,0) << " " << m(2,1) << " " << m(2,2) << " " << m(2,3) << " ] " << std::endl + <<"[ " << m(3,0) << " " << m(3,1) << " " << m(3,2) << " " << m(3,3) << " ] " << std::endl; + + m_prev = m; +#endif + return result; +} + +void DebugShadowMap::ViewData::setDebugPolytope + ( const char * name, const ConvexPolyhedron & polytope, + osg::Vec4 colorOutline, osg::Vec4 colorInside ) +{ + if( !getDebugDraw() ) return; + + if( &polytope == NULL ) { // delete + PolytopeGeometry & pg = _polytopeGeometryMap[ std::string( name ) ]; + for( int i = 0; i < VECTOR_LENGTH( pg._geometry ) ; i++ ) + { + if( pg._geometry[i].valid() ) { + if( _geode[i].valid() && + _geode[i]->containsDrawable( pg._geometry[i].get() ) ) + _geode[i]->removeDrawable( pg._geometry[i].get() ); + + pg._geometry[i] = NULL; + } + } + _polytopeGeometryMap.erase( std::string( name ) ); + } else { // update + PolytopeGeometry & pg = _polytopeGeometryMap[ std::string( name ) ]; + + pg._polytope = polytope; + if( colorOutline.a() > 0 ) + pg._colorOutline = colorOutline; + if( colorInside.a() > 0 ) + pg._colorInside = colorInside; + + for( int i = 0; i < VECTOR_LENGTH( pg._geometry ) ; i++ ) + { + if( !pg._geometry[i].valid() ) { + pg._geometry[i] = new osg::Geometry; + pg._geometry[i]->setDataVariance( osg::Object::DYNAMIC ); + pg._geometry[i]->setUseDisplayList( false ); + pg._geometry[i]->setSupportsDisplayList( false ); + } + + if( _geode[i].valid() && + !_geode[i]->containsDrawable( pg._geometry[i].get() ) ) { + osg::Geode::DrawableList & dl = + const_cast< osg::Geode::DrawableList &> + ( _geode[i]->getDrawableList() ); + dl.insert( dl.begin(), pg._geometry[i].get() ); + } + } + } +} + +void DebugShadowMap::ViewData::updateDebugGeometry + ( const osg::Camera * viewCam, const osg::Camera * shadowCam ) +{ + if( !getDebugDraw() ) return; + if( _polytopeGeometryMap.empty() ) return; + + const int num = 2; // = VECTOR_LENGTH( PolytopeGeometry::_geometry ); + + const osg::Camera *camera[2] = { viewCam, shadowCam }; + + osg::Matrix + transform[ num ] = + { viewCam->getViewMatrix() * + // use near far clamped projection ( precomputed in cullDebugGeometry ) + ( viewCam == _viewCamera ? _viewProjection : viewCam->getProjectionMatrix() ), + shadowCam->getViewMatrix() * shadowCam->getProjectionMatrix() }, + inverse[ num ] = + { osg::Matrix::inverse( transform[0] ), + osg::Matrix::inverse( transform[1] ) }; + +#if 0 + ConvexPolyhedron frustum[ num ]; + for( int i = 0; i < num; i++ ) { + frustum[i].setToUnitFrustum( ); +#if 1 + frustum[i].transform( inverse[i], transform[i] ); +#else + frustum[i].transform + ( osg::Matrix::inverse( camera[i]->getProjectionMatrix() ), + camera[i]->getProjectionMatrix() ); + frustum[i].transform + ( osg::Matrix::inverse( camera[i]->getViewMatrix() ), + camera[i]->getViewMatrix() ); +#endif + }; +#else + osg::Polytope frustum[ num ]; + for( int i = 0; i < num; i++ ) { + frustum[i].setToUnitFrustum( ); + frustum[i].transformProvidingInverse( transform[i] ); + } +#endif + + transform[0] = viewCam->getViewMatrix(); + inverse[0] = viewCam->getInverseViewMatrix(); + + for( PolytopeGeometryMap::iterator itr = _polytopeGeometryMap.begin(); + itr != _polytopeGeometryMap.end(); + ++itr ) + { + PolytopeGeometry & pg = itr->second; + + for( int i = 0; i < num ; i++ ) + { + + ConvexPolyhedron cp( pg._polytope ); + cp.cut( frustum[i] ); + cp.transform( transform[i], inverse[i] ); + + pg._geometry[i] = cp.buildGeometry + ( pg._colorOutline, pg._colorInside, pg._geometry[i].get() ); + } + } +} + +void DebugShadowMap::ViewData::cullDebugGeometry( ) +{ + if( !getDebugDraw() ) return; + if( !_camera.valid() ) return; + + // View camera may use clamping projection matrix after traversal. + // Since we need to know exact matrix for drawing the frusta, + // we have to compute it here in exactly the same way as cull visitor + // will after cull traversal completes view camera subgraph. + { + _viewProjection = *_cv->getProjectionMatrix(); + _viewCamera = _cv->getRenderStage()->getCamera(); + + if( _cv->getComputeNearFarMode() ) { + + // Redo steps from CullVisitor::popProjectionMatrix() + // which clamps projection matrix when Camera & Projection + // completes traversal of their children + + // We have to do this now manually + // because we did not complete camera traversal yet but + // we need to know how this clamped projection matrix will be + + _cv->computeNearPlane(); + + double n = _cv->getCalculatedNearPlane(); + double f = _cv->getCalculatedFarPlane(); + + if( n < f ) + _cv->clampProjectionMatrix(_viewProjection, n, f ); + } + } + + updateDebugGeometry( _viewCamera, _camera.get() ); + +#if 1 // Add geometries of polytopes to main cam Render Stage + _transform[0]->accept( *_cv ); +#else + for( PolytopeGeometryMap::iterator itr = _polytopeGeometryMap.begin(); + itr != _polytopeGeometryMap.end(); + ++itr ) + { + PolytopeGeometry & pg = itr->second; + _cv->pushStateSet( _geode[0]->getStateSet() ); + _cv->addDrawableAndDepth( pg._geometry[0].get(), NULL, FLT_MAX ); + _cv->popStateSet( ); + } +#endif + + // Add geometries of polytopes to hud cam Render Stage + _cameraDebugHUD->accept( *_cv ); +} + +void DebugShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv ) +{ + BaseClass::ViewData::init( st, cv ); + + _doDebugDrawPtr = &st->_doDebugDraw; + + _hudSize = st->_hudSize; + _hudOrigin = st->_hudOrigin; + _viewportSize = st->_viewportSize; + _viewportOrigin = st->_viewportOrigin; + _orthoSize = st->_orthoSize; + _orthoOrigin = st->_orthoOrigin; + + _depthColorFragmentShader = st->_depthColorFragmentShader; + + // create placeholder geodeds for polytope geometries + // rest of their initialization will be performed during camera hud init + _geode[0] = new osg::Geode; + _geode[1] = new osg::Geode; + + _cameraDebugHUD = NULL;//Force debug HUD rebuild ( if needed ) +} + +// Callback used by debugging hud to display Shadow Map to color buffer +// Had to do it this way because OSG does not allow to use +// the same GL Texture Id with different glTexParams. +// Callback simply turns compare mode off via GL while rendering hud and +// restores it before rendering the scene with shadows. + +class DrawableDrawWithDepthShadowComparisonOffCallback: + public osg::Drawable::DrawCallback +{ +public: + DrawableDrawWithDepthShadowComparisonOffCallback( osg::Texture2D *pTex ) + : _pTexture( pTex ) + { + } + + virtual void drawImplementation + ( osg::RenderInfo & ri,const osg::Drawable* drawable ) const + { + ri.getState()->applyTextureAttribute( 0, _pTexture.get() ); + + // Turn off depth comparison mode + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE ); + + drawable->drawImplementation(ri); + + // Turn it back on + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, + GL_COMPARE_R_TO_TEXTURE_ARB ); + } + + osg::ref_ptr< osg::Texture2D > _pTexture; +}; + +void DebugShadowMap::ViewData::createDebugHUD( ) +{ + _cameraDebugHUD = new osg::Camera; + + { // Make sure default HUD layout makes sense + if( _hudSize[0] <= 0 ) _hudSize[0] = DEFAULT_DEBUG_HUD_SIZE_X; + if( _hudSize[1] <= 0 ) _hudSize[1] = DEFAULT_DEBUG_HUD_SIZE_Y; + + if( _viewportSize[0] <= 0 ) _viewportSize[0] = _hudSize[0]; + if( _viewportSize[1] <= 0 ) _viewportSize[1] = _hudSize[1]; + + if( _orthoSize[0] <= 0 ) _orthoSize[0] = _viewportSize[0]; + if( _orthoSize[1] <= 0 ) _orthoSize[1] = _viewportSize[1]; + } + + { // Initialize hud camera + osg::Camera * camera = _cameraDebugHUD.get(); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setViewMatrix(osg::Matrix::identity()); + camera->setViewport( _viewportOrigin[0], _viewportOrigin[1], + _viewportSize[0], _viewportSize[1] ); + + camera->setProjectionMatrixAsOrtho( + _orthoOrigin[0], _orthoOrigin[0] + _orthoSize[0], + _orthoOrigin[1], _orthoOrigin[1] + _orthoSize[1], + -10, 10 ); + + camera->setClearMask(GL_DEPTH_BUFFER_BIT); + camera->setRenderOrder(osg::Camera::POST_RENDER); + } + + { // Add geode and drawable with BaseClass display + // create geode to contain hud drawables + osg::Geode* geode = new osg::Geode; + _cameraDebugHUD->addChild(geode); + + // finally create and attach hud geometry + osg::Geometry* geometry = osg::createTexturedQuadGeometry + ( osg::Vec3(_hudOrigin[0],_hudOrigin[1],0), + osg::Vec3(_hudSize[0],0,0), + osg::Vec3(0,_hudSize[1],0) ); + + osg::StateSet* stateset = _cameraDebugHUD->getOrCreateStateSet(); + stateset->setTextureAttributeAndModes(0,_texture.get(),osg::StateAttribute::ON ); + stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF); +// stateset->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF); + stateset->setAttributeAndModes + ( new osg::Depth( osg::Depth::ALWAYS, 0, 1, false ) ); + stateset->setMode(GL_BLEND,osg::StateAttribute::ON); + + osg::Program* program = new osg::Program; + program->addShader( _depthColorFragmentShader.get() ); + stateset->setAttribute( program ); + stateset->addUniform( new osg::Uniform( "texture" , 0 ) ); + + geometry->setDrawCallback + ( new DrawableDrawWithDepthShadowComparisonOffCallback( _texture.get() ) ); + + geode->addDrawable( geometry ); + } + + { // Create transforms and geodes to manage polytope drawing + osg::StateSet * stateset = new osg::StateSet; + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::OFF); + stateset->setTextureMode(1, GL_TEXTURE_2D, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + stateset->setAttribute( new osg::Program() ); + stateset->setAttributeAndModes + ( new osg::Depth( osg::Depth::LEQUAL, 0, 1, false ) ); + + for( int i = 0; i < 2; i++ ) { + _geode[i]->setStateSet( stateset ); + _transform[i] = new osg::MatrixTransform; + _transform[i]->addChild( _geode[i].get() ); + _transform[i]->setMatrix( osg::Matrix::identity() ); + _transform[i]->setReferenceFrame( osg::MatrixTransform::ABSOLUTE_RF ); + } + + _transform[1]->setMatrix + ( osg::Matrix::translate( 1, 1, 0 ) * + osg::Matrix::scale( 0.5, 0.5, 1 ) * + osg::Matrix::scale( _hudSize[0], _hudSize[1], 1 ) * + osg::Matrix::translate( _hudOrigin[0], _hudOrigin[1], 0 ) ); + + _cameraDebugHUD->addChild( _transform[1].get() ); + } +} + +osg::Vec3d DebugShadowMap::ViewData::computeShadowTexelToPixelError + ( const osg::Matrix & mvpwView, + const osg::Matrix & mvpwShadow, + const osg::Vec3d & vWorld, + const osg::Vec3d & vDelta ) +{ + osg::Vec3d vS0 = mvpwShadow * vWorld; + osg::Vec3d vS1 = mvpwShadow * ( vWorld + vDelta ); + + osg::Vec3d vV0 = mvpwView * vWorld; + osg::Vec3d vV1 = mvpwView * ( vWorld + vDelta ); + + osg::Vec3d dV = vV1 - vV0; + osg::Vec3d dS = vS1 - vS0; + + return osg::Vec3( dS[0] / dV[0], dS[1] / dV[1], dS[2] / dV[2] ); +} + +void DebugShadowMap::ViewData::displayShadowTexelToPixelErrors + ( const osg::Camera* viewCamera, + const osg::Camera* shadowCamera, + const ConvexPolyhedron* hull ) +{ + osg::Matrix mvpwMain = + viewCamera->getViewMatrix() * + viewCamera->getProjectionMatrix() * + viewCamera->getViewport()->computeWindowMatrix(); + + osg::Matrix mvpwShadow = + shadowCamera->getViewMatrix() * + shadowCamera->getProjectionMatrix() * + shadowCamera->getViewport()->computeWindowMatrix(); + + osg::BoundingBox bb = + hull->computeBoundingBox( viewCamera->getViewMatrix() ); + + osg::Matrix m = viewCamera->getInverseViewMatrix(); + + osg::Vec3d vn = osg::Vec3d( 0, 0, bb._max[2] ) * m; + osg::Vec3d vf = osg::Vec3d( 0, 0, bb._min[2] ) * m; + osg::Vec3d vm = osg::Vec3d( 0, 0, ( bb._max[2] + bb._min[2] ) * 0.5 ) * m; + + osg::Vec3d vne = computeShadowTexelToPixelError( mvpwMain, mvpwShadow, vn ); + osg::Vec3d vfe = computeShadowTexelToPixelError( mvpwMain, mvpwShadow, vf ); + osg::Vec3d vme = computeShadowTexelToPixelError( mvpwMain, mvpwShadow, vm ); + + std::cout << std::setprecision( 3 ) << " " + << "ne=(" << vne[0] << "," << vne[1] << "," << vne[2] << ") " + << "fe=(" << vfe[0] << "," << vfe[1] << "," << vfe[2] << ") " + << "me=(" << vme[0] << "," << vme[1] << "," << vme[2] << ") " + << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + << std::flush; +} + diff --git a/src/osgShadow/LightSpacePerspectiveShadowMap.cpp b/src/osgShadow/LightSpacePerspectiveShadowMap.cpp new file mode 100644 index 000000000..c1eb6c10e --- /dev/null +++ b/src/osgShadow/LightSpacePerspectiveShadowMap.cpp @@ -0,0 +1,843 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + + +#include +#include +#include + +#define DIRECTIONAL_ONLY 0 +#define DIRECTIONAL_ADAPTED 1 +#define DIRECTIONAL_AND_SPOT 2 + +//#define LISPSM_ALGO DIRECTIONAL_ONLY +#define LISPSM_ALGO DIRECTIONAL_ADAPTED +//#define LISPSM_ALGO DIRECTIONAL_AND_SPOT + +#define PRINT_COMPUTED_N_OPT 0 + +using namespace osgShadow; + +//////////////////////////////////////////////////////////////////////////////// +// There are two slightly differing implemetations available on +// "Light Space Perspective Shadow Maps" page. One from 2004 and other from 2006. +// Our implementation is written in two versions based on these solutions. +//////////////////////////////////////////////////////////////////////////////// +// Original LisPSM authors 2004 implementation excerpt. Kept here for reference. +// DIRECTIONAL AND DIRECTIONAL_ADAPTED versions are based on this code. +// DIRECTIONAL_AND_SPOT version is based on later 2006 code. +//////////////////////////////////////////////////////////////////////////////// + #if 0 +//////////////////////////////////////////////////////////////////////////////// +// This code is copyright Vienna University of Technology, 2004. +// +// Please feel FREE to COPY and USE the code to include it in your own work, +// provided you include this copyright notice. +// This program 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. +// +// Authors Code: +// Daniel Scherzer (scherzer@cg.tuwien.ac.at) +// +// Authors Paper: +// Michael Wimmer (wimmer@cg.tuwien.ac.at) +// Daniel Scherzer (scherzer@cg.tuwien.ac.at) +// Werner Purgathofer +//////////////////////////////////////////////////////////////////////////////// +void calcLispSMMtx(struct VecPoint* B) { + Vector3 min, max; + Vector3 up; + Matrix4x4 lispMtx; + struct VecPoint Bcopy = VECPOINT_NULL; + double dotProd = dot(viewDir,lightDir); + double sinGamma; + + sinGamma = sqrt(1.0-dotProd*dotProd); + + copyMatrix(lispMtx,IDENTITY); + + copyVecPoint(&Bcopy,*B); + + //CHANGED + if(useBodyVec) { + Vector3 newDir; + calcNewDir(newDir,B); + calcUpVec(up,newDir,lightDir); + } + else { + calcUpVec(up,viewDir,lightDir); + } + + //temporal light View + //look from position(eyePos) + //into direction(lightDir) + //with up vector(up) + look(lightView,eyePos,lightDir,up); + + //transform the light volume points from world into light space + transformVecPoint(B,lightView); + + //calculate the cubic hull (an AABB) + //of the light space extents of the intersection body B + //and save the two extreme points min and max + calcCubicHull(min,max,B->points,B->size); + + { + //use the formulas of the paper to get n (and f) + const double factor = 1.0/sinGamma; + const double z_n = factor*nearDist; //often 1 + const double d = absDouble(max[1]-min[1]); //perspective transform depth //light space y extents + const double z_f = z_n + d*sinGamma; + const double n = (z_n+sqrt(z_f*z_n))/sinGamma; + const double f = n+d; + Vector3 pos; + + //new observer point n-1 behind eye position + //pos = eyePos-up*(n-nearDist) + linCombVector3(pos,eyePos,up,-(n-nearDist)); + + look(lightView,pos,lightDir,up); + + //one possibility for a simple perspective transformation matrix + //with the two parameters n(near) and f(far) in y direction + copyMatrix(lispMtx,IDENTITY); // a = (f+n)/(f-n); b = -2*f*n/(f-n); + lispMtx[ 5] = (f+n)/(f-n); // [ 1 0 0 0] + lispMtx[13] = -2*f*n/(f-n); // [ 0 a 0 b] + lispMtx[ 7] = 1; // [ 0 0 1 0] + lispMtx[15] = 0; // [ 0 1 0 0] + + //temporal arrangement for the transformation of the points to post-perspective space + mult(lightProjection,lispMtx,lightView); // ligthProjection = lispMtx*lightView + + //transform the light volume points from world into the distorted light space + transformVecPoint(&Bcopy,lightProjection); + + //calculate the cubic hull (an AABB) + //of the light space extents of the intersection body B + //and save the two extreme points min and max + calcCubicHull(min,max,Bcopy.points,Bcopy.size); + } + + //refit to unit cube + //this operation calculates a scale translate matrix that + //maps the two extreme points min and max into (-1,-1,-1) and (1,1,1) + scaleTranslateToFit(lightProjection,min,max); + + //together + mult(lightProjection,lightProjection,lispMtx); // ligthProjection = scaleTranslate*lispMtx +} +#endif + +#if ( LISPSM_ALGO == DIRECTIONAL_ONLY ) + + +LightSpacePerspectiveShadowMapAlgorithm::LightSpacePerspectiveShadowMapAlgorithm() +{ + lispsm = NULL; +} + +LightSpacePerspectiveShadowMapAlgorithm::~LightSpacePerspectiveShadowMapAlgorithm() +{ +} + +void LightSpacePerspectiveShadowMapAlgorithm::operator() + ( const ViewDependentShadow::ConvexPolyhedron* hullShadowedView, + const osg::Camera* cameraMain, + osg::Camera* cameraShadow ) const +{ + osg::BoundingBox bb = hullShadowedView->computeBoundingBox( cameraMain->getViewMatrix() ); + double nearDist = -bb._max[2]; + + const osg::Matrix & eyeViewToWorld = cameraMain->getInverseViewMatrix(); + + osg::Matrix lightViewToWorld = cameraShadow->getInverseViewMatrix(); + + osg::Vec3 eyePos = osg::Vec3( 0, 0, 0 ) * eyeViewToWorld; + + osg::Vec3 viewDir( osg::Matrix::transform3x3( osg::Vec3(0,0,-1), eyeViewToWorld ) ); + + osg::Vec3 lightDir( osg::Matrix::transform3x3( osg::Vec3( 0,0,-1), lightViewToWorld ) ); + osg::Vec3 up( osg::Matrix::transform3x3( osg::Vec3(0,1,0), lightViewToWorld ) ); + + osg::Matrix lightView; // compute coarse light view matrix + lightView.makeLookAt( eyePos, eyePos + lightDir, up ); + bb = hullShadowedView->computeBoundingBox( lightView ); + + const double dotProd = viewDir * lightDir; + const double sinGamma = sqrt(1.0- dotProd*dotProd); + const double factor = 1.0/sinGamma; + const double z_n = factor*nearDist; //often 1 + //use the formulas of the paper to get n (and f) + const double d = fabs( bb._max[1]-bb._min[1]); //perspective transform depth //light space y extents + const double z_f = z_n + d*sinGamma; + const double n = (z_n+sqrt(z_f*z_n))/sinGamma; + const double f = n+d; + osg::Vec3d pos = eyePos-up*(n-nearDist); + +#if PRINT_COMPUTED_N_OPT + std::cout + << " N=" << std::setw(8) << n + << " n=" << std::setw(8) << z_n + << " f=" << std::setw(8) << z_f + << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + << std::flush; +#endif + + lightView.makeLookAt( pos, pos + lightDir, up ); + + //one possibility for a simple perspective transformation matrix + //with the two parameters n(near) and f(far) in y direction + double a = (f+n)/(f-n); + double b = -2*f*n/(f-n); + + osg::Matrix lispProjection( 1, 0, 0, 0, + 0, a, 0, 1, + 0, 0,-1, 0, + 0, b, 0, 0 ); + +// lispProjection.makeIdentity( ); +#if 0 + { + osg::Matrix mvp = _camera->getViewMatrix() * + _camera->getProjectionMatrix(); + + extendScenePolytope( mvp, osg::Matrix::inverse( mvp ) ); + } +#endif + + bb = hullShadowedView->computeBoundingBox( lightView * lispProjection ); + + osg::Matrix fitToUnitFrustum; + fitToUnitFrustum.makeOrtho( bb._min[0], bb._max[0], + bb._min[1], bb._max[1], + -(bb._min[2]-1), -bb._max[2] ); + + cameraShadow->setProjectionMatrix + ( lightViewToWorld * lightView * lispProjection * fitToUnitFrustum ); + + +#if 0 // DOUBLE CHECK! + bb = computeScenePolytopeBounds + ( cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix() ); + + if( !osg::equivalent( 0.f, (bb._min - osg::Vec3(-1,-1,-1)).length2() ) || + !osg::equivalent( 0.f, (bb._max - osg::Vec3( 1, 1, 1)).length2() ) ) + { + bb = computeScenePolytopeBounds + ( cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix() ); + } +#endif +} + +#endif + + + +#if ( LISPSM_ALGO == DIRECTIONAL_ADAPTED ) + +LightSpacePerspectiveShadowMapAlgorithm::LightSpacePerspectiveShadowMapAlgorithm() +{ + lispsm = NULL; +} + +LightSpacePerspectiveShadowMapAlgorithm::~LightSpacePerspectiveShadowMapAlgorithm() +{ +} + +void LightSpacePerspectiveShadowMapAlgorithm::operator() + ( const osgShadow::ConvexPolyhedron* hullShadowedView, + const osg::Camera* cameraMain, + osg::Camera* cameraShadow ) const +{ + + // all computations are done in post projection light space + // which means we are in left handed coordinate system + osg::Matrix mvpLight = + cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix(); + + osg::Matrix m = cameraMain->getInverseViewMatrix() * mvpLight; + osg::Vec3 eye = osg::Vec3( 0, 0, 0 ) * m; + osg::Vec3 center = osg::Vec3( 0, 0, -1 ) * m; + osg::Vec3 up(0,1,0); + osg::Vec3 viewDir( center - eye ); + viewDir.normalize(); + + m.makeLookAt( eye, center, up ); + + osg::BoundingBox bb = hullShadowedView->computeBoundingBox( mvpLight * m ); + if( !bb.valid() ) + return; + + double nearDist = -bb._max[2]; + +#if 1 + // Original LiSPSM Paper suggests that algorithm should work for all light types: + // infinte directional, omnidirectional and spot types may be treated as directional + // as all computations are performed in post projection light space. + // Frankly, I have my doubts if their error analysis and methodology still works + // in non directional lights post projective space. But since I can't prove it doesn't, + // I assume it does ;-). So I made an effort to modify their original directional algo + // to work in true light post perspective space and compute all params in this space. + // And here is a snag. Although shadowed hull fits completely into light space, + // camera position may not, and after projective transform it may land outside + // light frustum and even on/or below infinity plane. I need camera pos to compute + // minimal distance to shadowed hull. If its not right rest of the computation may + // be completely off. So in the end this approach is not singulartity free. + // I guess this problem is solvable in other way but "this other + // way" looks like a topic for other scientific paper and I am definitely not that + // ambitious ;-). So for the time being I simply try to discover when this happens and + // apply workaround, I found works. This workaround may mean that adjusted projection + // may not be optimal in original LisPSM Lmax norm sense. But as I wrote above, + // I doubt they are optimal when Light is not directional, anyway. + + // Seems that most nasty case when algorithm fails is when cam pos is + // below light frustum near plane but above infinity plane - when this occurs + // shadows simply disappear. My workaround is to find this case by + // checking light postperspective transform camera z ( negative value means this ) + // and make sure min distance to shadow hull is clamped to positive value. + + if( eye[2] < 0 && nearDist <= 0 ) { + float clampedNearDist = 0.0001; + eye += viewDir * ( clampedNearDist - nearDist ); + nearDist = clampedNearDist; + } +#endif + + // Beware!!! Dirty Tricks: + // Light direction in light post proj space is actually (0,0,1) + // But since we want to pass it to std OpenGL right handed coordinate + // makeLookAt function we compensate the effects by also using right + // handed view forward vector (ie 0,0,-1) instead. + // So in the end we get left handed makeLookAt behaviour (D3D like)... + // I agree this method is bizarre. But it works so I left it as is. + // It sort of came out by itself through trial and error. + // I later understoood why it works. + + osg::Vec3 lightDir(0,0,-1); + osg::Matrix lightView; // compute coarse light view matrix + lightView.makeLookAt( eye, eye + lightDir, up ); + bb = hullShadowedView->computeBoundingBox( mvpLight * lightView ); + if( !bb.valid() ) + return; + + //use the formulas from the LiSPSM paper to get n (and f) + const double dotProd = viewDir * lightDir; + const double sinGamma = sqrt(1.0- dotProd*dotProd); + const double factor = 1.0/sinGamma; + const double z_n = factor*nearDist; + //perspective transform depth light space y extents + const double d = fabs( bb._max[1]-bb._min[1]); + const double z_f = z_n + d*sinGamma; + const double n = (z_n+sqrt(z_f*z_n))/sinGamma; + const double f = n+d; + osg::Vec3d pos = eye-up*(n-nearDist); + lightView.makeLookAt( pos, pos + lightDir, up ); + + //one possibility for a simple perspective transformation matrix + //with the two parameters n(near) and f(far) in y direction + double a = (f+n)/(f-n); + double b = -2*f*n/(f-n); + + osg::Matrix lispProjection( 1, 0, 0, 0, + 0, a, 0, 1, + 0, 0, 1, 0, + 0, b, 0, 0 ); + + cameraShadow->setProjectionMatrix + ( cameraShadow->getProjectionMatrix() * lightView * lispProjection ); +} + +#endif + +#if ( LISPSM_ALGO == DIRECTIONAL_AND_SPOT ) + + +// Adapted Modified version of LispSM authors implementation from 2006 +// Nopt formula differs from the paper. I adopted original authors class to +// use with OSG + + + +//we search the point in the LVS volume that is nearest to the camera + +static const float INFINITY = FLT_MAX; + +namespace osgShadow { + +class LispSM { +public: + typedef std::vector Vertices; + + void setProjectionMatrix( const osg::Matrix & projectionMatrix ) + { _projectionMatrix = projectionMatrix; } + + void setViewMatrix( const osg::Matrix & viewMatrix ) + { _viewMatrix = viewMatrix; } + + void setHull( const ConvexPolyhedron & hull ) + { _hull = hull; } + + const ConvexPolyhedron & getHull( ) const + { return _hull; } + + const osg::Matrix & getProjectionMatrix( void ) const + { return _projectionMatrix; } + + const osg::Matrix & getViewMatrix( void ) const + { return _viewMatrix; } + + bool getUseLiSPSM() const + { return _useLiSPSM; } + + void setUseLiSPSM( bool use ) + { _useLiSPSM = use; } + + bool getUseFormula() const + { return _useFormula; } + + void setUseFormula( bool use ) + { _useFormula = use; } + + bool getUseOldFormula() const + { return _useOldFormula; } + + void setUseOldFormula( bool use ) + { _useOldFormula = use; } + + void setN(const double& n ) + { _N = n; } + + const double getN() const + { return _N; } + + //for old LispSM formula from paper + const double getNearDist() const + { return _nearDist; } + + void setNearDist( const double & nearDist ) + { _nearDist = nearDist; } + + const double getFarDist() const + { return _farDist; } + + void setFarDist( const double & farDist ) + { _farDist = farDist; } + + const osg::Vec3d & getEyeDir() const + { return _eyeDir; } + + const osg::Vec3d & getLightDir() const + { return _lightDir; } + + void setEyeDir( const osg::Vec3d eyeDir ) + { _eyeDir = eyeDir; } + + void setLightDir( const osg::Vec3d lightDir ) + { _lightDir = lightDir; } + +protected: + + bool _useLiSPSM; + bool _useFormula; + bool _useOldFormula; + double _N; + double _nearDist; + double _farDist; + + mutable osg::Vec3d _E; + osg::Vec3d _eyeDir; + osg::Vec3d _lightDir; + + ConvexPolyhedron _hull; + + osg::Matrix _viewMatrix; + osg::Matrix _projectionMatrix; + + double getN(const osg::Matrix lightSpace, const osg::BoundingBox& B_ls) const; + + osg::Vec3d getNearCameraPointE() const; + + osg::Vec3d getZ0_ls + (const osg::Matrix& lightSpace, const osg::Vec3d& e, const double& b_lsZmax, const osg::Vec3d& eyeDir) const; + + double calcNoptGeneral + (const osg::Matrix lightSpace, const osg::BoundingBox& B_ls) const; + + double calcNoptOld + ( const double gamma_ = 999) const; + + osg::Matrix getLispSmMtx + (const osg::Matrix& lightSpace) const; + + osg::Vec3d getProjViewDir_ls + (const osg::Matrix& lightSpace) const; + + void updateLightMtx + (osg::Matrix& lightView, osg::Matrix& lightProj, const std::vector& B) const; + +public: + LispSM( ) : _useLiSPSM( true ), _useFormula( true ), _useOldFormula( false ), _N( 1 ), _nearDist( 1 ), _farDist( 10 ) { } + + virtual void updateLightMtx( osg::Matrix& lightView, osg::Matrix& lightProj ) const; +}; + +}; + +osg::Vec3d LispSM::getNearCameraPointE( ) const +{ + const osg::Matrix& eyeView = getViewMatrix(); + + ConvexPolyhedron::Vertices LVS; + _hull.getPoints( LVS ); + + //the LVS volume is always in front of the camera + //the camera points along the neg z axis. + //-> so the nearest point is the maximum + + unsigned max = 0; + for(unsigned i = 0; i < LVS.size(); i++) { + + LVS[i] = LVS[i] * eyeView; + + if( LVS[max].z() < LVS[i].z() ) { + max = i; + } + } + //transform back to world space + return LVS[max] * osg::Matrix::inverse( eyeView ); +} + +//z0 is the point that lies on the plane A parallel to the near plane through e +//and on the near plane of the C frustum (the plane z = bZmax) and on the line x = e.x +osg::Vec3d LispSM::getZ0_ls + (const osg::Matrix& lightSpace, const osg::Vec3d& e, const double& b_lsZmax, const osg::Vec3d& eyeDir) const +{ + //to calculate the parallel plane to the near plane through e we + //calculate the plane A with the three points + osg::Plane A(eyeDir,e); + //to transform plane A into lightSpace + A.transform( lightSpace ); + //transform to light space + const osg::Vec3d e_ls = e * lightSpace; + + //z_0 has the x coordinate of e, the z coord of B_lsZmax + //and the y coord of the plane A and plane (z==B_lsZmax) intersection +#if 1 + osg::Vec3d v = osg::Vec3d(e_ls.x(),0,b_lsZmax); + + // x & z are given. We compute y from equations: + // A.distance( x,y,z ) == 0 + // A.distance( x,y,z ) == A.distance( x,0,z ) + A.normal.y * y + // hence A.distance( x,0,z ) == -A.normal.y * y + + v.y() = -A.distance( v ) / A.getNormal().y(); +#else + //get the parameters of A from the plane equation n dot d = 0 + const double d = A.asVec4()[3]; + const osg::Vec3d n = A.getNormal(); + osg::Vec3d v(e_ls.x(),(-d-n.z()*b_lsZmax-n.x()*e_ls.x())/n.y(),b_lsZmax); +#endif + + return v; + +} + +double LispSM::calcNoptGeneral(const osg::Matrix lightSpace, const osg::BoundingBox& B_ls) const +{ + const osg::Matrix& eyeView = getViewMatrix(); + const osg::Matrix invLightSpace = osg::Matrix::inverse( lightSpace ); + + const osg::Vec3d z0_ls = getZ0_ls(lightSpace, _E,B_ls.zMax(),getEyeDir()); + const osg::Vec3d z1_ls = osg::Vec3d(z0_ls.x(),z0_ls.y(),B_ls.zMin()); + + //to world + const osg::Vec4d z0_ws = osg::Vec4d( z0_ls, 1 ) * invLightSpace; + const osg::Vec4d z1_ws = osg::Vec4d( z1_ls, 1 ) * invLightSpace; + + //to eye + const osg::Vec4d z0_cs = z0_ws * eyeView; + const osg::Vec4d z1_cs = z1_ws * eyeView; + + double z0 = -z0_cs.z() / z0_cs.w(); + double z1 = -z1_cs.z() / z1_cs.w(); + + if( z1 / z0 <= 1.0 ) { + + // solve camera pos singularity in light space problem brutally: + // if extreme points of B projected to Light space extend beyond + // camera frustum simply use B extents in camera frustum + + // Its not optimal selection but ceratainly better than negative N + osg::BoundingBox bb = _hull.computeBoundingBox( eyeView ); + z0 = -bb.zMax(); + if( z0 <= 0 ) + z0 = 0.1; + + z1 = -bb.zMin(); + if( z1 <= z0 ) + z1 = z0 + 0.1; + } + + const double d = osg::absolute(B_ls.zMax()-B_ls.zMin()); + + double N = d/( sqrt( z1 / z0 ) - 1.0 ); +#if PRINT_COMPUTED_N_OPT + std::cout + << " N=" << std::setw(8) << N + << " n=" << std::setw(8) << z0 + << " f=" << std::setw(8) << z1 + << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + << std::flush; +#endif + return N; +} + +double LispSM::calcNoptOld( const double gamma_ ) const +{ + const double& n = getNearDist(); + const double& f = getFarDist(); + const double d = abs(f-n); + double sinGamma(0); + if(999 == gamma_) { + double dot = getEyeDir() * getLightDir(); + sinGamma = sqrt( 1.0 - dot * dot ); + } + else { + sinGamma = sin(gamma_); + } + + double N = (n+sqrt(n*(n+d*sinGamma)))/sinGamma; +#if PRINT_COMPUTED_N_OPT + std::cout + << " N=" << std::setw(8) << N + << " n=" << std::setw(8) << n + << " f=" << std::setw(8) << f + << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" + << std::flush; +#endif + return N; +} + +double LispSM::getN(const osg::Matrix lightSpace, const osg::BoundingBox& B_ls) const +{ + if( getUseFormula()) { + if( getUseOldFormula() ) + return calcNoptOld(); + else + return calcNoptGeneral(lightSpace,B_ls); + } + else { + return getN(); + } +} +//this is the algorithm discussed in the article +osg::Matrix LispSM::getLispSmMtx( const osg::Matrix& lightSpace ) const +{ + const osg::BoundingBox B_ls = _hull.computeBoundingBox( lightSpace ); + + const double n = getN(lightSpace,B_ls); + + //get the coordinates of the near camera point in light space + const osg::Vec3d e_ls = _E * lightSpace; + //c start has the x and y coordinate of e, the z coord of B.min() + const osg::Vec3d Cstart_lp(e_ls.x(),e_ls.y(),B_ls.zMax()); + + if( n >= INFINITY ) { + //if n is inf. than we should do uniform shadow mapping + return osg::Matrix::identity(); + } + //calc C the projection center + //new projection center C, n behind the near plane of P + //we work along a negative axis so we transform +n* == -n* + const osg::Vec3d C( Cstart_lp + osg::Vec3d(0,0,1) * n ); + //construct a translation that moves to the projection center + const osg::Matrix projectionCenter = osg::Matrix::translate( -C ); + + //calc d the perspective transform depth or light space y extents + const double d = osg::absolute(B_ls.zMax()-B_ls.zMin()); + + //the lispsm perspective transformation + + //here done with a standard frustum call that maps P onto the unit cube with + //corner points [-1,-1,-1] and [1,1,1]. + //in directX you can use the same mapping and do a mapping to the directX post-perspective cube + //with corner points [-1,-1,0] and [1,1,1] as the final step after all the shadow mapping. + osg::Matrix P = osg::Matrix::frustum( -1.0,1.0,-1.0,1.0, n, n+d ); + + //invert the transform from right handed into left handed coordinate system for the ndc + //done by the openGL style frustumGL call + //so we stay in a right handed system + P = P * osg::Matrix::scale( 1.0,1.0,-1.0 ); + //return the lispsm frustum with the projection center + return projectionCenter * P; +} + +osg::Vec3d LispSM::getProjViewDir_ls(const osg::Matrix& lightSpace ) const { + //get the point in the LVS volume that is nearest to the camera + const osg::Vec3d e = _E; + //construct edge to transform into light-space + const osg::Vec3d b = e+getEyeDir(); + //transform to light-space + osg::Vec4d e_lp = osg::Vec4d( e, 1.0 ) * lightSpace; + osg::Vec4d b_lp = osg::Vec4d( b, 1.0 ) * lightSpace; + + if( e_lp[3] <= 0 ) + { + e_lp[3] = e_lp[3]; + } + + if( b_lp[3] <= 0 ) + { + osg::Vec4d v = (e_lp - b_lp)/(e_lp[3]-b_lp[3]); + + v = ( e_lp + v ) * 0.5; + + b_lp = v; + } + + osg::Vec3d projDir( osg::Vec3( b_lp[0], b_lp[1], b_lp[2] ) / b_lp[3] - + osg::Vec3( e_lp[0], e_lp[1], e_lp[2] ) / e_lp[3] ); + + projDir.normalize(); + + //project the view direction into the shadow map plane + projDir.y() = 0.0; + return projDir; +} + +void LispSM::updateLightMtx + ( osg::Matrix& lightView, osg::Matrix& lightProj ) const +{ + //calculate standard light space for spot or directional lights + //this routine returns two matrices: + //lightview contains the rotated translated frame + //lightproj contains in the case of a spot light the spot light perspective transformation + //in the case of a directional light a identity matrix + // calcLightSpace(lightView,lightProj); + + if( _hull._faces.empty() ) { + //debug() << "empty intersection body -> completely inside shadow\n";//debug output + return; + } + + _E = getNearCameraPointE(); + + lightProj = lightProj * osg::Matrix::scale( 1, 1, -1 ); + + //coordinate system change for calculations in the article + osg::Matrix switchToArticle = osg::Matrix::identity(); + switchToArticle(1,1) = 0.0; + switchToArticle(1,2) =-1.0; // y -> -z + switchToArticle(2,1) = 1.0; // z -> y + switchToArticle(2,2) = 0.0; + //switch to the lightspace used in the article + lightProj = lightProj * switchToArticle; + + osg::Matrix L = lightView * lightProj; + + osg::Vec3d projViewDir = getProjViewDir_ls(L); + + if( getUseLiSPSM() /* && projViewDir.z() < 0*/ ) { + //do Light Space Perspective shadow mapping + //rotate the lightspace so that the proj light view always points upwards + //calculate a frame matrix that uses the projViewDir[light-space] as up vector + //look(from position, into the direction of the projected direction, with unchanged up-vector) + lightProj = lightProj * + osg::Matrix::lookAt( osg::Vec3d(0,0,0), projViewDir, osg::Vec3d(0,1,0) ); + + osg::Matrix lispsm = getLispSmMtx( lightView * lightProj ); + lightProj = lightProj * lispsm; + } + + const osg::Matrix PL = lightView * lightProj; + + osg::BoundingBox bb = _hull.computeBoundingBox( PL ); + + osg::Matrix fitToUnitFrustum; + fitToUnitFrustum.makeOrtho( bb._min[0], bb._max[0], + bb._min[1], bb._max[1], + -bb._max[2], -bb._min[2] ); + + //map to unit cube + lightProj = lightProj * fitToUnitFrustum; + + //coordinate system change for calculations in the article + osg::Matrix switchToGL = osg::Matrix::identity(); + switchToGL(1,1) = 0.0; + switchToGL(1,2) = 1.0; // y -> z + switchToGL(2,1) = -1.0; // z -> -y + switchToGL(2,2) = 0.0; + + //back to open gl coordinate system y <-> z + lightProj = lightProj * switchToGL; + //transform from right handed system into left handed ndc + lightProj = lightProj * osg::Matrix::scale(1.0,1.0,-1.0); +} + +void LightSpacePerspectiveShadowMapAlgorithm::operator() + ( const ViewDependentShadow::ConvexPolyhedron* hullShadowedView, + const osg::Camera* cameraMain, + osg::Camera* cameraShadow ) const +{ + lispsm->setHull( *hullShadowedView ); + lispsm->setViewMatrix( cameraMain->getViewMatrix() ); + lispsm->setProjectionMatrix( cameraMain->getViewMatrix() ); + + lispsm->setLightDir + ( osg::Matrix::transform3x3( osg::Vec3d( 0, 0, -1 ), + osg::Matrix::inverse( cameraShadow->getViewMatrix() ) ) ); + + osg::Vec3d eyeDir = osg::Matrix::transform3x3( osg::Vec3d( 0, 0, -1 ), + osg::Matrix::inverse( cameraMain->getViewMatrix() ) ); + + osg::Matrix &proj = cameraShadow->getProjectionMatrix(); + double l,r,b,t,n,f; + if( proj.getOrtho( l,r,b,t,n,f ) ) + { + osg::Vec3d camPosInLightSpace = + osg::Vec3d( 0, 0, 0 ) * + osg::Matrix::inverse( cameraMain->getViewMatrix() ) * + cameraShadow->getViewMatrix() * + cameraShadow->getProjectionMatrix(); + } + + eyeDir.normalize(); + + lispsm->setEyeDir( eyeDir ); + + osg::BoundingBox bb = + hullShadowedView->computeBoundingBox( cameraMain->getViewMatrix() ); + + lispsm->setNearDist( -bb.zMax() ); + lispsm->setFarDist( -bb.zMin() ); + + lispsm->updateLightMtx + ( cameraShadow->getViewMatrix(), cameraShadow->getProjectionMatrix() ); +} + +LightSpacePerspectiveShadowMapAlgorithm::LightSpacePerspectiveShadowMapAlgorithm() +{ + lispsm = new LispSM; +} + +LightSpacePerspectiveShadowMapAlgorithm::~LightSpacePerspectiveShadowMapAlgorithm() +{ + delete lispsm; +} + + +#endif diff --git a/src/osgShadow/MinimalCullBoundsShadowMap.cpp b/src/osgShadow/MinimalCullBoundsShadowMap.cpp new file mode 100644 index 000000000..ee7d9d0f0 --- /dev/null +++ b/src/osgShadow/MinimalCullBoundsShadowMap.cpp @@ -0,0 +1,368 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + + +#include +#include +#include + +#define IGNORE_OBJECTS_LARGER_THAN_HEIGHT 0 + +using namespace osgShadow; + +MinimalCullBoundsShadowMap::MinimalCullBoundsShadowMap(): BaseClass() +{ +} + +MinimalCullBoundsShadowMap::MinimalCullBoundsShadowMap +(const MinimalCullBoundsShadowMap& copy, const osg::CopyOp& copyop) : + BaseClass(copy,copyop) +{ +} + +MinimalCullBoundsShadowMap::~MinimalCullBoundsShadowMap() +{ +} + +void MinimalCullBoundsShadowMap::ViewData::init + ( ThisClass *st, osgUtil::CullVisitor *cv ) +{ + BaseClass::ViewData::init( st, cv ); +} + +void MinimalCullBoundsShadowMap::ViewData::aimShadowCastingCamera + ( const osg::Light *light, + const osg::Vec4 &lightPos, + const osg::Vec3 &lightDir, + const osg::Vec3 &lightUp ) +{ + MinimalShadowMap::ViewData::aimShadowCastingCamera + ( light, lightPos, lightDir, lightUp ); + + frameShadowCastingCamera + ( _cv->getCurrentRenderBin()->getStage()->getCamera(), _camera.get() ); +} + +void MinimalCullBoundsShadowMap::ViewData::cullShadowReceivingScene( ) +{ + RenderLeafList rllOld, rllNew; + + GetRenderLeaves( _cv->getRenderStage(), rllOld ); + + MinimalShadowMap::ViewData::cullShadowReceivingScene( ); + + GetRenderLeaves( _cv->getRenderStage(), rllNew ); + + RemoveOldRenderLeaves( rllNew, rllOld ); + RemoveIgnoredRenderLeaves( rllNew ); + + osg::Matrix projectionToModelSpace = + osg::Matrix::inverse( *_modellingSpaceToWorldPtr * + *_cv->getModelViewMatrix() * *_cv->getProjectionMatrix() ); + + osg::BoundingBox bb; + if( *_cv->getProjectionMatrix() != _clampedProjection ) { + + osg::Polytope polytope; +#if 1 + polytope.setToUnitFrustum(); +#else + polytope.add( osg::Plane( 0.0,0.0,-1.0,1.0 ) ); // only far plane +#endif + polytope.transformProvidingInverse( *_modellingSpaceToWorldPtr * + *_cv->getModelViewMatrix() * _clampedProjection ); + + bb = ComputeRenderLeavesBounds( rllNew, projectionToModelSpace, polytope ); + } else { + bb = ComputeRenderLeavesBounds( rllNew, projectionToModelSpace ); + } + + cutScenePolytope( *_modellingSpaceToWorldPtr, + osg::Matrix::inverse( *_modellingSpaceToWorldPtr ), bb ); +} + +void MinimalCullBoundsShadowMap::ViewData::GetRenderLeaves + ( osgUtil::RenderBin *rb, RenderLeafList & rll ) +{ + osgUtil::RenderBin::RenderBinList& bins = rb->getRenderBinList(); + osgUtil::RenderBin::RenderBinList::const_iterator rbitr; + + // scan pre render bins + for(rbitr = bins.begin(); rbitr!=bins.end() && rbitr->first<0; ++rbitr ) + GetRenderLeaves( rbitr->second.get(), rll ); + + // scan fine grained ordering. + osgUtil::RenderBin::RenderLeafList& renderLeafList = rb->getRenderLeafList(); + osgUtil::RenderBin::RenderLeafList::const_iterator rlitr; + for( rlitr= renderLeafList.begin(); rlitr!= renderLeafList.end(); ++rlitr ) + { + rll.push_back( *rlitr ); + } + + // scan coarse grained ordering. + osgUtil::RenderBin::StateGraphList& stateGraphList = rb->getStateGraphList(); + osgUtil::RenderBin::StateGraphList::const_iterator oitr; + for( oitr= stateGraphList.begin(); oitr!= stateGraphList.end(); ++oitr ) + { + for( osgUtil::StateGraph::LeafList::const_iterator dw_itr = + (*oitr)->_leaves.begin(); dw_itr != (*oitr)->_leaves.end(); ++dw_itr) + { + rll.push_back( dw_itr->get() ); + } + } + + // scan post render bins + for(; rbitr!=bins.end(); ++rbitr ) + GetRenderLeaves( rbitr->second.get(), rll ); +} + +class CompareRenderLeavesByMatrices { +public: + bool operator()( const osgUtil::RenderLeaf *a, const osgUtil::RenderLeaf *b ) + { + if ( !a ) return false; // NULL render leaf goes last + return !b || + a->_projection < b->_projection || + a->_projection == b->_projection && a->_modelview < b->_modelview; + } +}; + +inline bool CheckAndMultiplyBoxIfWithinPolytope + ( osg::BoundingBox & bb, osg::Matrix & m, osg::Polytope &p ) +{ + if( !bb.valid() ) return false; + + osg::Vec3 o = bb._min * m, s[3]; + + for( int i = 0; i < 3; i ++ ) + s[i] = osg::Vec3( m(i,0), m(i,1), m(i,2) ) * ( bb._max[i] - bb._min[i] ); + + for( osg::Polytope::PlaneList::iterator it = p.getPlaneList().begin(); + it != p.getPlaneList().end(); + ++it ) + { + float dist = it->distance( o ), dist_min = dist, dist_max = dist; + + for( int i = 0; i < 3; i ++ ) { + dist = it->dotProductNormal( s[i] ); + if( dist < 0 ) dist_min += dist; else dist_max += dist; + } + + if( dist_max < 0 ) + return false; + } + + bb._max = bb._min = o; +#if 1 + for( int i = 0; i < 3; i ++ ) + for( int j = 0; j < 3; j ++ ) + if( s[i][j] < 0 ) bb._min[j] += s[i][j]; else bb._max[j] += s[i][j]; +#else + b.expandBy( o + s[0] ); + b.expandBy( o + s[1] ); + b.expandBy( o + s[2] ); + b.expandBy( o + s[0] + s[1] ); + b.expandBy( o + s[0] + s[2] ); + b.expandBy( o + s[1] + s[2] ); + b.expandBy( o + s[0] + s[1] + s[2] ); +#endif + +#if ( IGNORE_OBJECTS_LARGER_THAN_HEIGHT > 0 ) + if( bb._max[2] - bb._min[2] > IGNORE_OBJECTS_LARGER_THAN_HEIGHT ) // ignore huge objects + return false; +#endif + + return true; +} + +unsigned MinimalCullBoundsShadowMap::ViewData::RemoveOldRenderLeaves + ( RenderLeafList &rllNew, RenderLeafList &rllOld ) +{ + unsigned count = 0; + + std::sort( rllOld.begin(), rllOld.end() ); + RenderLeafList::iterator itNew, itOld; + for( itNew = rllNew.begin(); itNew != rllNew.end() && rllOld.size(); ++itNew ) + { + itOld = std::lower_bound( rllOld.begin(), rllOld.end(), *itNew ); + + if( itOld == rllOld.end() || *itOld != *itNew ) continue; + // found ! + rllOld.erase( itOld ); //remove it from old range to speed up search + *itNew = NULL; //its not new = invalidate it among new render leaves + count ++; + } + + return count; +} + +unsigned MinimalCullBoundsShadowMap::ViewData::RemoveIgnoredRenderLeaves + ( RenderLeafList &rll ) +{ + unsigned count = 0; + + for( RenderLeafList::iterator it = rll.begin(); it != rll.end(); ++it ) + { + if( !*it ) continue; + + const char * name = (*it)->_drawable->className(); + + // Its a dirty but quick test (not very future proof) + if( !name || name[0] != 'L' ) continue; + + if( !strcmp( name, "LightPointDrawable" ) || + !strcmp( name, "LightPointSpriteDrawable" ) ) + { + *it = NULL; //ignored = invalidate this in new render leaves list + count++; + } + } + + return count; +} + +osg::BoundingBox MinimalCullBoundsShadowMap::ViewData::ComputeRenderLeavesBounds + ( RenderLeafList &rll, osg::Matrix & projectionToWorld ) +{ + osg::BoundingBox bbResult; + + if( rll.size() == 0 ) return bbResult; + + std::sort( rll.begin(), rll.end(), CompareRenderLeavesByMatrices() ); + + osg::ref_ptr< osg::RefMatrix > modelview; + osg::ref_ptr< osg::RefMatrix > projection; + osg::Matrix viewToWorld, modelToWorld, *ptrProjection = NULL, + *ptrViewToWorld = &projectionToWorld, *ptrModelToWorld; + + osg::BoundingBox bb; + + // compute bounding boxes but skip old ones (placed at the end as NULLs) + for( RenderLeafList::iterator it = rll.begin(); ; ++it ) { + // we actually allow to pass one element behind end to flush bb queue + osgUtil::RenderLeaf *rl = ( it != rll.end() ? *it : NULL ); + + // Don't trust already computed bounds for cull generated drawables + // LightPointDrawable & LightPointSpriteDrawable are such examples + // they store wrong recorded bounds from very first pass + if( rl && rl->_modelview == NULL ) + rl->_drawable->dirtyBound(); + + // Stay as long as possible in model space to minimize matrix ops + if( rl && rl->_modelview == modelview && rl->_projection == projection ){ + bb.expandBy( rl->_drawable->getBound() ); + } else { + if( bb.valid() ) { + // Conditions to avoid matrix multiplications + if( projection.valid() ) + { + if( projection.get() != ptrProjection ) + { + ptrProjection = projection.get(); + viewToWorld = *ptrProjection * projectionToWorld; + } + ptrViewToWorld = &viewToWorld; + } else { + ptrViewToWorld = &projectionToWorld; + } + + if( modelview.valid() ) + { + modelToWorld = *modelview.get() * *ptrViewToWorld; + ptrModelToWorld = &modelToWorld; + } else { + ptrModelToWorld = ptrViewToWorld; + } + + for( int i = 0; i < 8; i++ ) + bbResult.expandBy( bb.corner( i ) * *ptrModelToWorld ); + } + if( !rl ) break; + bb = rl->_drawable->getBound(); + modelview = rl->_modelview; + projection = rl->_projection; + } + } + + rll.clear(); + + return bbResult; +} + +osg::BoundingBox MinimalCullBoundsShadowMap::ViewData::ComputeRenderLeavesBounds + ( RenderLeafList &rll, osg::Matrix & projectionToWorld, osg::Polytope & p ) +{ + osg::BoundingBox bbResult, bb; + + if( rll.size() == 0 ) return bbResult; + + std::sort( rll.begin(), rll.end(), CompareRenderLeavesByMatrices() ); + + osg::ref_ptr< osg::RefMatrix > modelview; + osg::ref_ptr< osg::RefMatrix > projection; + osg::Matrix viewToWorld, modelToWorld, *ptrProjection = NULL, + *ptrViewToWorld = &projectionToWorld, *ptrModelToWorld; + + // compute bounding boxes but skip old ones (placed at the end as NULLs) + for( RenderLeafList::iterator it = rll.begin(); it != rll.end(); ++it ) { + // we actually allow to pass one element behind end to flush bb queue + osgUtil::RenderLeaf *rl = *it; + if( !rl ) break; + + // Don't trust already computed bounds for cull generated drawables + // LightPointDrawable & LightPointSpriteDrawable are such examples + // they store wrong recorded bounds from very first pass + if( rl && rl->_modelview == NULL ) + rl->_drawable->dirtyBound(); + + bb = rl->_drawable->getBound(); + if( !bb.valid() ) continue; + + // Stay as long as possible in model space to minimize matrix ops + if( rl->_modelview != modelview || rl->_projection != projection ) { + + projection = rl->_projection; + if( projection.valid() ) + { + if( projection.get() != ptrProjection ) + { + ptrProjection = projection.get(); + viewToWorld = *ptrProjection * projectionToWorld; + } + ptrViewToWorld = &viewToWorld; + } else { + ptrViewToWorld = &projectionToWorld; + } + + modelview = rl->_modelview; + if( modelview.valid() ) + { + modelToWorld = *modelview.get() * *ptrViewToWorld; + ptrModelToWorld = &modelToWorld; + } else { + ptrModelToWorld = ptrViewToWorld; + } + } + + if( CheckAndMultiplyBoxIfWithinPolytope( bb, *ptrModelToWorld, p ) ) + bbResult.expandBy( bb ); + } + + rll.clear(); + + return bbResult; +} + diff --git a/src/osgShadow/MinimalDrawBoundsShadowMap.cpp b/src/osgShadow/MinimalDrawBoundsShadowMap.cpp new file mode 100644 index 000000000..e354f83ef --- /dev/null +++ b/src/osgShadow/MinimalDrawBoundsShadowMap.cpp @@ -0,0 +1,406 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ANALYSIS_DEPTH 1 +#define USE_FLOAT_IMAGE 1 + +using namespace osgShadow; + +MinimalDrawBoundsShadowMap::MinimalDrawBoundsShadowMap(): BaseClass() +{ +} + +MinimalDrawBoundsShadowMap::MinimalDrawBoundsShadowMap +(const MinimalDrawBoundsShadowMap& copy, const osg::CopyOp& copyop) : + BaseClass(copy,copyop) +{ +} + +MinimalDrawBoundsShadowMap::~MinimalDrawBoundsShadowMap() +{ +} + +void MinimalDrawBoundsShadowMap::ViewData::cullShadowReceivingScene( ) +{ + BaseClass::ViewData::cullShadowReceivingScene( ); + ThisClass::ViewData::cullBoundAnalysisScene( ); +} + +void MinimalDrawBoundsShadowMap::ViewData::cullBoundAnalysisScene( ) +{ + _boundAnalysisCamera->setReferenceFrame( osg::Camera::ABSOLUTE_RF ); + _boundAnalysisCamera->setViewMatrix( *_cv->getModelViewMatrix() ); + _boundAnalysisCamera->setProjectionMatrix( _clampedProjection ); + + osg::Matrix::value_type l,r,b,t,n,f; + _boundAnalysisCamera->getProjectionMatrixAsFrustum( l,r,b,t,n,f ); + + _mainCamera = _cv->getRenderStage()->getCamera(); + + extendProjection( _boundAnalysisCamera->getProjectionMatrix(), + _boundAnalysisCamera->getViewport(), osg::Vec2( 2,2 ) ); + + // record the traversal mask on entry so we can reapply it later. + unsigned int traversalMask = _cv->getTraversalMask(); + + _cv->setTraversalMask( traversalMask & + _st->getShadowedScene()->getReceivesShadowTraversalMask() ); + + // do RTT camera traversal + _boundAnalysisCamera->accept(*_cv); + + // reapply the original traversal mask + _cv->setTraversalMask( traversalMask ); +} + + +void MinimalDrawBoundsShadowMap::ViewData::createDebugHUD( ) +{ +// _hudSize[0] *= 2; + _viewportSize[0] *= 2; + _orthoSize[0] *= 2; + + MinimalShadowMap::ViewData::createDebugHUD( ); + + osg::Camera * camera = _cameraDebugHUD.get(); + + osg::Geode* geode = new osg::Geode; + camera->addChild( geode ); + + osg::Geometry* geometry = osg::createTexturedQuadGeometry + ( osg::Vec3(_hudOrigin[0]+_hudSize[0],_hudOrigin[1],0), + osg::Vec3(_hudSize[0],0,0), + osg::Vec3(0,_hudSize[1],0) ); + + geode->addDrawable(geometry); + + osg::StateSet* stateset = geometry->getOrCreateStateSet(); + stateset->setTextureAttributeAndModes + (0, _boundAnalysisTexture.get(),osg::StateAttribute::ON ); + +#if ANALYSIS_DEPTH + osg::Program* program = new osg::Program; + program->addShader( _depthColorFragmentShader.get() ); + stateset->setAttribute( program ); + stateset->addUniform( new osg::Uniform( "texture" , 0 ) ); +#else + +#endif +} + +osg::BoundingBox MinimalDrawBoundsShadowMap::ViewData::scanImage + ( const osg::Image * image, osg::Matrix m ) +{ + osg::BoundingBox bb, bbProj; + + int components = osg::Image::computeNumComponents( image->getPixelFormat() ); + + if( image->getDataType() == GL_FLOAT ) { + float scale = 255.f / 254.f; + float * pf = (float *)image->data(); + for( int y = 0; y < image->t(); y++ ) { + float fY = ( 0.5f + y ) / image->t(); + for( int x = 0; x < image->s(); x++ ) { + float fX = ( 0.5f + x ) / image->s(); + + if( pf[0] < 1.0 ) { + float fMinZ = pf[0] * scale; + bbProj.expandBy( osg::Vec3( fX, fY, fMinZ ) ); + bb.expandBy( osg::Vec3( fX, fY, fMinZ ) * m ); + + if( components > 1 ) { + float fMaxZ = scale * ( 1.f - pf[1] ); + bbProj.expandBy( osg::Vec3( fX, fY, fMaxZ ) ); + bb.expandBy( osg::Vec3( fX, fY, fMaxZ ) * m ); + } + } + + pf += components; + } + } + } else if( image->getDataType() == GL_UNSIGNED_BYTE ) { + + unsigned char * pb = (unsigned char *)image->data(); + + float scale = 1.f / 254, bias = 0.5f; + for( int y = 0; y < image->t(); y++ ) { + float fY = ( 0.5f + y ) / image->t(); + for( int x = 0; x < image->s(); x++ ) { + float fX = ( 0.5f + x ) / image->s(); + + if( pb[0] < 255 ) { + float fMinZ = scale * (pb[0] - 0.5f); + fMinZ = osg::clampTo( fMinZ, 0.f, 1.f ); + + bbProj.expandBy( osg::Vec3( fX, fY, fMinZ ) ); + bb.expandBy( osg::Vec3( fX, fY, fMinZ ) * m ); + + if( components > 1 ) { + float fMaxZ = scale * (255 - pb[1] + 0.5f); + fMaxZ = osg::clampTo( fMaxZ, 0.f, 1.f ); + + bbProj.expandBy( osg::Vec3( fX, fY, fMaxZ ) ); + bb.expandBy( osg::Vec3( fX, fY, fMaxZ ) * m ); + } + } + + pb += components; + } + } + } + + return bb; +} + +void MinimalDrawBoundsShadowMap::ViewData::performBoundAnalysis( const osg::Camera& camera ) +{ + if( !_projection.valid() ) + return; + + osg::Camera::BufferAttachmentMap & bam + = const_cast( camera ).getBufferAttachmentMap(); +#if ANALYSIS_DEPTH + osg::Camera::Attachment & attachment = bam[ osg::Camera::DEPTH_BUFFER ]; +#else + osg::Camera::Attachment & attachment = bam[ osg::Camera::COLOR_BUFFER ]; +#endif + + const osg::ref_ptr< osg::Image > image = attachment._image.get(); + if( !image.valid() ) + return; + + osg::Matrix m; + m.invert( *_modellingSpaceToWorldPtr * + camera.getViewMatrix() * + camera.getProjectionMatrix() ); + + m.preMult( osg::Matrix::scale( osg::Vec3( 2.f, 2.f, 2.f ) ) * + osg::Matrix::translate( osg::Vec3( -1.f, -1.f, -1.f ) ) ); + + osg::BoundingBox bb = scanImage( image.get(), m ); + + if( getDebugDraw() ) { + ConvexPolyhedron p; + p.setToBoundingBox( bb ); + p.transform( *_modellingSpaceToWorldPtr, + osg::Matrix::inverse( *_modellingSpaceToWorldPtr ) ); + + setDebugPolytope( "scan", p, + osg::Vec4( 0,0,0,1 ), osg::Vec4( 0,0,0,0.1 ) ); + } + + cutScenePolytope( *_modellingSpaceToWorldPtr, + osg::Matrix::inverse( *_modellingSpaceToWorldPtr ), bb ); + + frameShadowCastingCamera( _mainCamera.get(), _camera.get() ); + + _projection->set( _camera->getProjectionMatrix( ) ); + + BaseClass::ViewData::_texgen->setPlanesFromMatrix( + _camera->getProjectionMatrix() * + osg::Matrix::translate(1.0,1.0,1.0) * + osg::Matrix::scale(0.5,0.5,0.5) ); + + updateDebugGeometry( _mainCamera.get(), _camera.get() ); +} + +void MinimalDrawBoundsShadowMap::ViewData::recordShadowMapParams( ) +{ + const osgUtil::RenderStage * rs = _cv->getCurrentRenderBin()->getStage(); + + setShadowCameraProjectionMatrixPtr( _cv->getProjectionMatrix() ); + + if( !rs->getRenderBinList().empty() || rs->getBinNum() != 0 ) + { + + } +#if 0 + MinimalShadowMap::RenderLeafList rll; + + static unsigned pass = 0, c = 0; + + pass++; + + std::set< osg::ref_ptr< osg::RefMatrix > > projections; + + MinimalShadowMap::GetRenderLeaves( , rll ); + for( unsigned i =0; i < rll.size(); i++ ) { + if( rll[i]->_projection.get() != _projection.get() ) { + osg::RefMatrix * projection = rll[i]->_projection.get(); + projections.insert( rll[i]->_projection ); + c++; + } + } + + if( projections.size() > 0 ) + _projection = (*projections.begin()).get(); + + + c = 0; +#endif +} + +void MinimalDrawBoundsShadowMap::ViewData::init + ( ThisClass *st, osgUtil::CullVisitor *cv ) +{ + BaseClass::ViewData::init( st, cv ); + + _camera->setCullCallback + ( new CameraCullCallback( this, _camera->getCullCallback() ) ); + + _boundAnalysisTexture = new osg::Texture2D; + _boundAnalysisTexture->setTextureSize + ( _boundAnalysisSize[0], _boundAnalysisSize[1] ); + + _boundAnalysisImage = new osg::Image; + + +#if ANALYSIS_DEPTH + _boundAnalysisImage->allocateImage( _boundAnalysisSize[0], + _boundAnalysisSize[1], 1, + GL_DEPTH_COMPONENT, GL_FLOAT ); + + _boundAnalysisTexture->setInternalFormat(GL_DEPTH_COMPONENT); +// _boundAnalysisTexture->setShadowComparison(true); + _boundAnalysisTexture->setShadowTextureMode(osg::Texture2D::LUMINANCE); + + + _boundAnalysisImage->setInternalTextureFormat( GL_DEPTH_COMPONENT ); + _boundAnalysisTexture->setInternalFormat( GL_DEPTH_COMPONENT ); +#else + +#if USE_FLOAT_IMAGE + _boundAnalysisImage->allocateImage( _boundAnalysisSize[0], + _boundAnalysisSize[1], 1, + GL_RGBA, GL_FLOAT ); + + _boundAnalysisImage->setInternalTextureFormat( GL_RGBA16F_ARB ); + _boundAnalysisTexture->setInternalFormat(GL_RGBA16F_ARB); +#else + _boundAnalysisImage->allocateImage( _boundAnalysisSize[0], + _boundAnalysisSize[1], 1, + GL_RGBA, GL_UNSIGNED_BYTE ); + + _boundAnalysisImage->setInternalTextureFormat( GL_RGBA ); + _boundAnalysisTexture->setInternalFormat( GL_RGBA ); +#endif + +#endif + memset( _boundAnalysisImage->data(), 0, _boundAnalysisImage->getImageSizeInBytes() ); + + if( getDebugDraw() ) + _boundAnalysisTexture->setImage(0, _boundAnalysisImage.get() ); + + _boundAnalysisTexture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); + _boundAnalysisTexture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); + + // the shadow comparison should fail if object is outside the texture + _boundAnalysisTexture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); + _boundAnalysisTexture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); + + // set up the render to texture camera. + // create the camera + _boundAnalysisCamera = new osg::Camera; + _boundAnalysisCamera->setName( "AnalysisCamera" ); + + _boundAnalysisCamera->setCullCallback( new BaseClass::CameraCullCallback(st) ); +// _boundAnalysisCamera->setPreDrawCallback( _camera->getPreDrawCallback() ); + _boundAnalysisCamera->setPostDrawCallback( new CameraPostDrawCallback(this) ); + + _boundAnalysisCamera->setClearColor( osg::Vec4(1,1,1,1) ); + _boundAnalysisCamera->setClearMask(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT); + _boundAnalysisCamera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + + // set viewport + _boundAnalysisCamera->setViewport + ( 0, 0, _boundAnalysisSize[0], _boundAnalysisSize[1] ); + + // set the camera to render before the main camera. + _boundAnalysisCamera->setRenderOrder(osg::Camera::PRE_RENDER); + + // tell the camera to use OpenGL frame buffer object where supported. + _boundAnalysisCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + //_boundAnalysisCamera->setRenderTargetImplementation(osg::Camera::SEPERATE_WINDOW); + + const int OVERRIDE_ON = osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON; + const int OVERRIDE_OFF = osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF; + + osg::StateSet* stateset = _boundAnalysisCamera->getOrCreateStateSet(); + stateset->setAttributeAndModes + ( new osg::Depth( osg::Depth::LESS, 0.0, 254.f/255.f ), OVERRIDE_ON ); + +// stateset->setAttributeAndModes +// ( new osg::AlphaFunc( osg::AlphaFunc::EQUAL, 1.f ), OVERRIDE_ON ); + + stateset->setRenderBinDetails( 0, "RenderBin", + osg::StateSet::OVERRIDE_RENDERBIN_DETAILS ); + + osg::Program* program = new osg::Program; + + program->addShader( new osg::Shader( osg::Shader::FRAGMENT, + "uniform sampler2D texture; \n" + "void main(void) \n" + "{ \n" +#if ANALYSIS_DEPTH + " gl_FragColor = texture2D( texture, gl_TexCoord[0].xy ); \n" +#else + " gl_FragColor = vec4( gl_FragCoord.z, \n" + " 1.-gl_FragCoord.z, \n" + " 1., \n" + " texture2D( texture, gl_TexCoord[0].xy ).a ); \n" +#endif + "} \n" + ) ); // program->addShader Fragment + + program->addShader( new osg::Shader( osg::Shader::VERTEX, + "void main(void) \n" + "{ \n" + " gl_Position = ftransform(); \n" + " gl_TexCoord[0] = gl_MultiTexCoord0; \n" + "} \n" + ) ); // program->addShader Vertex + + stateset->setAttribute( program, OVERRIDE_ON ); + + // attach the texture and use it as the color buffer. +#if ANALYSIS_DEPTH +// _boundAnalysisCamera->attach(osg::Camera::DEPTH_BUFFER, _boundAnalysisTexture.get()); + _boundAnalysisCamera->attach(osg::Camera::DEPTH_BUFFER, _boundAnalysisImage.get()); + + stateset->setMode( GL_BLEND, OVERRIDE_OFF ); +#else +// _boundAnalysisCamera->attach(osg::Camera::COLOR_BUFFER, _boundAnalysisTexture.get()); + _boundAnalysisCamera->attach(osg::Camera::COLOR_BUFFER, _boundAnalysisImage.get()); + + stateset->setAttributeAndModes + ( new osg::BlendEquation( osg::BlendEquation::RGBA_MIN ), OVERRIDE_ON ); + + stateset->setMode( GL_DEPTH_TEST, OVERRIDE_OFF ); +#endif +} diff --git a/src/osgShadow/MinimalShadowMap.cpp b/src/osgShadow/MinimalShadowMap.cpp new file mode 100644 index 000000000..6ef6decae --- /dev/null +++ b/src/osgShadow/MinimalShadowMap.cpp @@ -0,0 +1,500 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#include +#include +#include +#include +#include + +using namespace osgShadow; + +#define PRINT_SHADOW_TEXEL_TO_PIXEL_ERROR 0 + +MinimalShadowMap::MinimalShadowMap(): + BaseClass(), + _minLightMargin( 0 ), + _maxFarPlane( FLT_MAX ), + _shadowReceivingCoarseBoundAccuracy( BOUNDING_BOX ) +{ + +} + +MinimalShadowMap::MinimalShadowMap +(const MinimalShadowMap& copy, const osg::CopyOp& copyop) : + BaseClass(copy,copyop), + _maxFarPlane( copy._maxFarPlane ), + _minLightMargin( copy._minLightMargin ), + _shadowReceivingCoarseBoundAccuracy( copy._shadowReceivingCoarseBoundAccuracy ) +{ +} + +MinimalShadowMap::~MinimalShadowMap() +{ +} + +osg::BoundingBox MinimalShadowMap::ViewData::computeShadowReceivingCoarseBounds() +{ + // Default slowest but most precise + ShadowReceivingCoarseBoundAccuracy accuracy = DEFAULT_ACCURACY; + + MinimalShadowMap * msm = dynamic_cast< MinimalShadowMap* >( _st.get() ); + if( msm ) accuracy = msm->getShadowReceivingCoarseBoundAccuracy(); + + if( accuracy == MinimalShadowMap::EMPTY_BOX ) + { + // One may skip coarse scene bounds computation if light is infinite. + // Empty box will be intersected with view frustum so in the end + // view frustum will be used as bounds approximation. + // But if light is nondirectional and bounds come out too large + // they may bring the effect of almost 180 deg perspective set + // up for shadow camera. Such projection will significantly impact + // precision of further math. + + return osg::BoundingBox(); + } + + if( accuracy == MinimalShadowMap::BOUNDING_SPHERE ) + { + // faster but less precise rough scene bound computation + // however if compute near far is active it may bring quite good result + osg::Camera * camera = _cv->getRenderStage()->getCamera(); + osg::Matrix m = camera->getViewMatrix() * _clampedProjection; + + ConvexPolyhedron frustum; + frustum.setToUnitFrustum(); + frustum.transform( osg::Matrix::inverse( m ), m ); + + osg::BoundingSphere bs =_st->getShadowedScene()->getBound(); + osg::BoundingBox bb; + bb.expandBy( bs ); + osg::Polytope box; + box.setToBoundingBox( bb ); + + frustum.cut( box ); + + // approximate sphere with octahedron. Ie first cut by box then + // additionaly cut with the same box rotated 45, 45, 45 deg. + box.transform( // rotate box around its center + osg::Matrix::translate( -bs.center() ) * + osg::Matrix::rotate( osg::PI_4, 0, 0, 1 ) * + osg::Matrix::rotate( osg::PI_4, 1, 1, 0 ) * + osg::Matrix::translate( bs.center() ) ); + frustum.cut( box ); + + return frustum.computeBoundingBox( ); + } + + if( accuracy == MinimalShadowMap::BOUNDING_BOX ) // Default + { + // more precise method but slower method + // bound visitor traversal takes lot of time for complex scenes + // (note that this adds to cull time) + + osg::ComputeBoundsVisitor cbbv(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN); + cbbv.setTraversalMask(_st->getShadowedScene()->getCastsShadowTraversalMask()); + _st->getShadowedScene()->osg::Group::traverse(cbbv); + + return cbbv.getBoundingBox(); + } + + return osg::BoundingBox(); +} + +void MinimalShadowMap::ViewData::aimShadowCastingCamera + ( const osg::Light *light, const osg::Vec4 &lightPos, + const osg::Vec3 &lightDir, const osg::Vec3 &lightUp ) +{ + osg::BoundingBox bb = computeScenePolytopeBounds(); + if( !bb.valid() ) { // empty scene or looking at the sky - substitute something + bb.expandBy( osg::BoundingSphere( _cv->getEyePoint(), 1 ) ); + } + + osg::Vec3 up = lightUp; + + if( up.length2() <= 0 ) + { + // This is extra step (not really needed but helpful in debuging) + // Compute such lightUp vector that shadow cam is intuitively aligned with eye + // We compute this vector on -ZY view plane, perpendicular to light direction + // Matrix m = ViewToWorld +#if 0 + osg::Matrix m = osg::Matrix::inverse( *cv.getModelViewMatrix() ); + osg::Vec3 camFw( -m( 2, 0 ), -m( 2, 1 ), -m( 2, 2 ) ); + camFw.normalize(); + + osg::Vec3 camUp( m( 1, 0 ), m( 1, 1 ), m( 1, 2 ) ); + camUp.normalize(); + + up = camUp * ( camFw * lightDir ) - camFw * ( camUp * lightDir ); + up.normalize(); +#else + osg::Matrix m = osg::Matrix::inverse( *_cv->getModelViewMatrix() ); + // OpenGL std cam looks along -Z axis so Cam Fw = [ 0 0 -1 0 ] * m + up.set( -m( 2, 0 ), -m( 2, 1 ), -m( 2, 2 ) ); +#endif + } + + BaseClass::ViewData::aimShadowCastingCamera + ( bb, light, lightPos, lightDir, up ); + + // Intersect scene Receiving Shadow Polytope with shadow camera frustum + // Important for cases where Scene extend beyond shadow camera frustum + // From this moment shadowed scene portion is fully contained by both + // main camera frustum and shadow camera frustum + osg::Matrix mvp = _camera->getViewMatrix() * _camera->getProjectionMatrix(); + cutScenePolytope( osg::Matrix::inverse( mvp ), mvp ); + + MinimalShadowMap::ViewData::frameShadowCastingCamera + ( _cv->getRenderStage()->getCamera(), _camera.get(), 0 ); +} + +void MinimalShadowMap::ViewData::frameShadowCastingCamera + ( const osg::Camera* cameraMain, osg::Camera* cameraShadow, int pass ) +{ + osg::Matrix mvp = + cameraShadow->getViewMatrix() * cameraShadow->getProjectionMatrix(); + + ConvexPolyhedron polytope = _sceneReceivingShadowPolytope; + std::vector points = _sceneReceivingShadowPolytopePoints; + + osg::BoundingBox bb = computeScenePolytopeBounds( mvp ); + + // projection was trimmed above, need to recompute mvp + if( bb.valid() && *_minLightMarginPtr > 0 ) { + // bb._max += osg::Vec3( 1, 1, 1 ); + // bb._min -= osg::Vec3( 1, 1, 1 ); + + osg::Matrix transform = osg::Matrix::inverse( mvp ); + + osg::Vec3d normal = osg::Matrix::transform3x3( osg::Vec3d(0,0,-1), transform ); + normal.normalize(); + _sceneReceivingShadowPolytope.extrude( normal * *_minLightMarginPtr ); + + // Zero pass does crude shadowed scene hull approximation. + // Its important to cut it to coarse light frustum properly + // at this stage. + // If not cut and polytope extends beyond shadow projection clip + // space (-1..1), it may get "twisted" by precisely adjusted shadow cam + // projection in second pass. + + if ( pass == 0 ) + { // Make sure extruded polytope does not extend beyond light frustum + osg::Polytope lightFrustum; + lightFrustum.setToUnitFrustum(); + lightFrustum.transformProvidingInverse( mvp ); + _sceneReceivingShadowPolytope.cut( lightFrustum ); + } + + _sceneReceivingShadowPolytopePoints.clear(); + _sceneReceivingShadowPolytope.getPoints + ( _sceneReceivingShadowPolytopePoints ); + + bb = computeScenePolytopeBounds( mvp ); + } + + setDebugPolytope( "extended", + _sceneReceivingShadowPolytope, osg::Vec4( 1, 0.5, 0, 1 ), osg::Vec4( 1, 0.5, 0, 0.1 ) ); + + _sceneReceivingShadowPolytope = polytope; + _sceneReceivingShadowPolytopePoints = points; + + // Warning: Trim light projection at near plane may remove shadowing + // from objects outside of view space but still casting shadows into it. + // I have not noticed this issue so I left mask at default: all bits set. + if( bb.valid() ) + trimProjection( cameraShadow->getProjectionMatrix(), bb, 1|2|4|8|16|32 ); + + ///// Debuging stuff ////////////////////////////////////////////////////////// + setDebugPolytope( "scene", _sceneReceivingShadowPolytope, osg::Vec4(0,1,0,1) ); + + +#if PRINT_SHADOW_TEXEL_TO_PIXEL_ERROR + if( pass == 1 ) + displayShadowTexelToPixelErrors + ( cameraMain, cameraShadow, &_sceneReceivingShadowPolytope ); +#endif + +} + +void MinimalShadowMap::ViewData::cullShadowReceivingScene( ) +{ + BaseClass::ViewData::cullShadowReceivingScene( ); + + _clampedProjection = *_cv->getProjectionMatrix(); + + if( _cv->getComputeNearFarMode() ) { + + // Redo steps from CullVisitor::popProjectionMatrix() + // which clamps projection matrix when Camera & Projection + // completes traversal of their children + + // We have to do this now manually + // because we did not complete camera traversal yet but + // we need to know how this clamped projection matrix will be + + _cv->computeNearPlane(); + + double n = _cv->getCalculatedNearPlane(); + double f = _cv->getCalculatedFarPlane(); + + if( n < f ) + _cv->clampProjectionMatrix(_clampedProjection, n, f ); + } + + // Aditionally clamp far plane if shadows are don't need to be cast as + // far as main projection far plane + if( 0 < *_maxFarPlanePtr ) + clampProjection( _clampedProjection, 0.f, *_maxFarPlanePtr ); + + // Give derived classes chance to initialize _sceneReceivingShadowPolytope + osg::BoundingBox bb = computeShadowReceivingCoarseBounds( ); + if( bb.valid() ) + _sceneReceivingShadowPolytope.setToBoundingBox( bb ); + else + _sceneReceivingShadowPolytope.clear(); + + // Cut initial scene using main camera frustum. + // Cutting will work correctly on empty polytope too. + // Take into consideration near far calculation and _maxFarPlane variable + + + osg::Matrix mvp = *_cv->getModelViewMatrix() * _clampedProjection; + + cutScenePolytope( osg::Matrix::inverse( mvp ), mvp ); + + setDebugPolytope + ( "frustum", _sceneReceivingShadowPolytope, osg::Vec4(1,0,1,1)); + +} + +void MinimalShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv ) +{ + BaseClass::ViewData::init( st, cv ); + + _modellingSpaceToWorldPtr = &st->_modellingSpaceToWorld; + _minLightMarginPtr = &st->_minLightMargin; + _maxFarPlanePtr = &st->_maxFarPlane; +} + +void MinimalShadowMap::ViewData::cutScenePolytope + ( const osg::Matrix & transform, + const osg::Matrix & inverse, + const osg::BoundingBox & bb ) +{ + _sceneReceivingShadowPolytopePoints.clear(); + + if( bb.valid() ) { + osg::Polytope polytope; + polytope.setToBoundingBox( bb ); + polytope.transformProvidingInverse( inverse ); + _sceneReceivingShadowPolytope.cut( polytope ); + _sceneReceivingShadowPolytope.getPoints + ( _sceneReceivingShadowPolytopePoints ); + } else + _sceneReceivingShadowPolytope.clear(); +} + +osg::BoundingBox + MinimalShadowMap::ViewData::computeScenePolytopeBounds( const osg::Matrix & m ) +{ + osg::BoundingBox bb; + + if( &m ) + for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i ) + bb.expandBy( _sceneReceivingShadowPolytopePoints[i] * m ); + else + for( unsigned i = 0; i < _sceneReceivingShadowPolytopePoints.size(); ++i ) + bb.expandBy( _sceneReceivingShadowPolytopePoints[i] ); + + return bb; +} + + + +// Utility methods for adjusting projection matrices + +void MinimalShadowMap::ViewData::trimProjection + ( osg::Matrix & projectionMatrix, osg::BoundingBox bb, unsigned int trimMask ) +{ +#if 1 + if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return; + double l = -1, r = 1, b = -1, t = 1, n = 1, f = -1; + +#if 0 + // make sure bounding box does not extend beyond unit frustum clip range + for( int i = 0; i < 3; i ++ ) { + if( bb._min[i] < -1 ) bb._min[i] = -1; + if( bb._max[i] > 1 ) bb._max[i] = 1; + } +#endif + + if( trimMask & 1 ) l = bb._min[0]; + if( trimMask & 2 ) r = bb._max[0]; + if( trimMask & 4 ) b = bb._min[1]; + if( trimMask & 8 ) t = bb._max[1]; + if( trimMask & 16 ) n = -bb._min[2]; + if( trimMask & 32 ) f = -bb._max[2]; + + projectionMatrix.postMult( osg::Matrix::ortho( l,r,b,t,n,f ) ); +#else + if( !bb.valid() || !( trimMask & (1|2|4|8|16|32) ) ) return; + double l, r, t, b, n, f; + bool ortho = projectionMatrix.getOrtho( l, r, b, t, n, f ); + if( !ortho && !projectionMatrix.getFrustum( l, r, b, t, n, f ) ) + return; // rotated or skewed or other crooked projection - give up + + // make sure bounding box does not extend beyond unit frustum clip range + for( int i = 0; i < 3; i ++ ) { + if( bb._min[i] < -1 ) bb._min[i] = -1; + if( bb._max[i] > 1 ) bb._max[i] = 1; + } + + osg::Matrix projectionToView = osg::Matrix::inverse( projectionMatrix ); + + osg::Vec3 min = + osg::Vec3( bb._min[0], bb._min[1], bb._min[2] ) * projectionToView; + + osg::Vec3 max = + osg::Vec3( bb._max[0], bb._max[1], bb._max[2] ) * projectionToView; + + if( trimMask & 16 ) { // trim near + if( !ortho ) { // recalc frustum corners on new near plane + l *= -min[2] / n; + r *= -min[2] / n; + b *= -min[2] / n; + t *= -min[2] / n; + } + n = -min[2]; + } + + if( trimMask & 32 ) // trim far + f = -max[2]; + + if( !ortho ) { + min[0] *= -n / min[2]; + min[1] *= -n / min[2]; + max[0] *= -n / max[2]; + max[1] *= -n / max[2]; + } + + if( l < r ) { // check for inverted X range + if( l < min[0] && ( trimMask & 1 ) ) l = min[0]; + if( r > max[0] && ( trimMask & 2 ) ) r = max[0]; + } else { + if( l > min[0] && ( trimMask & 1 ) ) l = min[0]; + if( r < max[0] && ( trimMask & 2 ) ) r = max[0]; + } + + if( b < t ) { // check for inverted Y range + if( b < min[1] && ( trimMask & 4 ) ) b = min[1]; + if( t > max[1] && ( trimMask & 8 ) ) t = max[1]; + } else { + if( b > min[1] && ( trimMask & 4 ) ) b = min[1]; + if( t < max[1] && ( trimMask & 8 ) ) t = max[1]; + } + + if( ortho ) + projectionMatrix.makeOrtho( l, r, b, t, n, f ); + else + projectionMatrix.makeFrustum( l, r, b, t, n, f ); +#endif +} + +void MinimalShadowMap::ViewData::clampProjection + ( osg::Matrix & projection, float new_near, float new_far ) +{ + double r, l, t, b, n, f; + bool perspective = projection.getFrustum( l, r, b, t, n, f ); + if( !perspective && !projection.getOrtho( l, r, b, t, n, f ) ) + { + // What to do here ? + osg::notify( osg::WARN ) + << "MinimalShadowMap::clampProjectionFarPlane failed - non standard matrix" + << std::endl; + + } else if( n < new_near || new_far < f ) { + + if( n < new_near && new_near < f ) { + if( perspective ) { + l *= new_near / n; + r *= new_near / n; + b *= new_near / n; + t *= new_near / n; + } + n = new_near; + } + + if( n < new_far && new_far < f ) { + f = new_far; + } + + if( perspective ) + projection.makeFrustum( l, r, b, t, n, f ); + else + projection.makeOrtho( l, r, b, t, n, f ); + } +} + +// Imagine following scenario: +// We stand in the room and look through the window. +// How should our view change if we were looking through larger window ? +// In other words how should projection be adjusted if +// window had grown by some margin ? +// Method computes such new projection which maintains perpective/world ratio + +void MinimalShadowMap::ViewData::extendProjection + ( osg::Matrix & projection, osg::Viewport * viewport, const osg::Vec2& margin ) +{ + double l,r,b,t,n,f; + + //osg::Matrix projection = camera.getProjectionMatrix(); + + bool frustum = projection.getFrustum( l,r,b,t,n,f ); + + if( !frustum && !projection.getOrtho( l,r,b,t,n,f ) ) { + osg::notify( osg::WARN ) + << " Awkward projection matrix. ComputeExtendedProjection failed" + << std::endl; + return; + } + + osg::Matrix window = viewport->computeWindowMatrix(); + + osg::Vec3 vMin( viewport->x() - margin.x(), + viewport->y() - margin.y(), + 0.0 ); + + osg::Vec3 vMax( viewport->width() + margin.x() * 2 + vMin.x(), + viewport->height() + margin.y() * 2 + vMin.y(), + 0.0 ); + + osg::Matrix inversePW = osg::Matrix::inverse( projection * window ); + + vMin = vMin * inversePW; + vMax = vMax * inversePW; + + l = vMin.x(); + r = vMax.x(); + b = vMin.y(); + t = vMax.y(); + + if( frustum ) + projection.makeFrustum( l,r,b,t,n,f ); + else + projection.makeOrtho( l,r,b,t,n,f ); +} + diff --git a/src/osgShadow/StandardShadowMap.cpp b/src/osgShadow/StandardShadowMap.cpp new file mode 100644 index 000000000..fba71959f --- /dev/null +++ b/src/osgShadow/StandardShadowMap.cpp @@ -0,0 +1,765 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace osgShadow; + +#define DISPLAY_SHADOW_TEXEL_TO_PIXEL_ERROR 0 + + +StandardShadowMap::StandardShadowMap(): + BaseClass(), + _polygonOffsetFactor( 1.1f ), + _polygonOffsetUnits( 4.0f ), + _textureSize( 1024, 1024 ), + _baseTextureUnit( 0 ), + _shadowTextureUnit( 1 ), + _baseTextureCoordIndex( 0 ), + _shadowTextureCoordIndex( 1 ) + +{ + _mainFragmentShader = new osg::Shader( osg::Shader::FRAGMENT, + " // following expressions are auto modified - do not change them: \n" + " // gl_TexCoord[0] 0 - can be subsituted with other index \n" + " \n" + "float DynamicShadow( ); \n" + " \n" + "varying vec4 colorAmbientEmissive; \n" + " \n" + "uniform sampler2D baseTexture; \n" + " \n" + "void main(void) \n" + "{ \n" + " vec4 color = texture2D( baseTexture, gl_TexCoord[0].xy ); \n" + " color *= mix( colorAmbientEmissive, gl_Color, DynamicShadow() ); \n" +#if DISPLAY_SHADOW_TEXEL_TO_PIXEL_ERROR + " color.xy = abs( dFdy( gl_TexCoord[1].xy / gl_TexCoord[1].w ) )* 1024.0; \n" + " color.z = color.y; \n" + " color.x = color.z; \n" + " color.y = color.z; \n" + " color.a = 1.0; \n" +#endif +// " float fog = clamp((gl_Fog.end - gl_FogFragCoord)*gl_Fog.scale, 0.,1.);\n" +// " color.rgb = mix( gl_Fog.color.rgb, color.rgb, fog ); \n" + " gl_FragColor = color; \n" + "} \n" ); + + + _shadowFragmentShader = new osg::Shader( osg::Shader::FRAGMENT, + " // following expressions are auto modified - do not change them: \n" + " // gl_TexCoord[1] 1 - can be subsituted with other index \n" + " \n" + "uniform sampler2DShadow shadowTexture; \n" + " \n" + "float DynamicShadow( ) \n" + "{ \n" + " return shadow2DProj( shadowTexture, gl_TexCoord[1] ).r; \n" + "} \n" ); + + + + _shadowVertexShader = new osg::Shader( osg::Shader::VERTEX, + " // following expressions are auto modified - do not change them: \n" + " // gl_TexCoord[1] 1 - can be subsituted with other index \n" + " // gl_EyePlaneS[1] 1 - can be subsituted with other index \n" + " // gl_EyePlaneT[1] 1 - can be subsituted with other index \n" + " // gl_EyePlaneR[1] 1 - can be subsituted with other index \n" + " // gl_EyePlaneQ[1] 1 - can be subsituted with other index \n" + " \n" + "void DynamicShadow( in vec4 ecPosition ) \n" + "{ \n" + " // generate coords for shadow mapping \n" + " gl_TexCoord[1].s = dot( ecPosition, gl_EyePlaneS[1] ); \n" + " gl_TexCoord[1].t = dot( ecPosition, gl_EyePlaneT[1] ); \n" + " gl_TexCoord[1].p = dot( ecPosition, gl_EyePlaneR[1] ); \n" + " gl_TexCoord[1].q = dot( ecPosition, gl_EyePlaneQ[1] ); \n" + "} \n" ); + + _mainVertexShader = new osg::Shader( osg::Shader::VERTEX, + " // following expressions are auto modified - do not change them: \n" + " // gl_TexCoord[0] 0 - can be subsituted with other index \n" + " // gl_TextureMatrix[0] 0 - can be subsituted with other index \n" + " // gl_MultiTexCoord0 0 - can be subsituted with other index \n" + " \n" + "const int NumEnabledLights = 1; \n" + " \n" + "void DynamicShadow( in vec4 ecPosition ); \n" + " \n" + "varying vec4 colorAmbientEmissive; \n" + " \n" + "void SpotLight(in int i, \n" + " in vec3 eye, \n" + " in vec3 ecPosition3, \n" + " in vec3 normal, \n" + " inout vec4 ambient, \n" + " inout vec4 diffuse, \n" + " inout vec4 specular) \n" + "{ \n" + " float nDotVP; // normal . light direction \n" + " float nDotHV; // normal . light half vector \n" + " float pf; // power factor \n" + " float spotDot; // cosine of angle between spotlight \n" + " float spotAttenuation; // spotlight attenuation factor \n" + " float attenuation; // computed attenuation factor \n" + " float d; // distance from surface to light source \n" + " vec3 VP; // direction from surface to light position \n" + " vec3 halfVector; // direction of maximum highlights \n" + " \n" + " // Compute vector from surface to light position \n" + " VP = vec3(gl_LightSource[i].position) - ecPosition3; \n" + " \n" + " // Compute distance between surface and light position \n" + " d = length(VP); \n" + " \n" + " // Normalize the vector from surface to light position \n" + " VP = normalize(VP); \n" + " \n" + " // Compute attenuation \n" + " attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + \n" + " gl_LightSource[i].linearAttenuation * d + \n" + " gl_LightSource[i].quadraticAttenuation *d*d); \n" + " \n" + " // See if point on surface is inside cone of illumination \n" + " spotDot = dot(-VP, normalize(gl_LightSource[i].spotDirection)); \n" + " \n" + " if (spotDot < gl_LightSource[i].spotCosCutoff) \n" + " spotAttenuation = 0.0; // light adds no contribution \n" + " else \n" + " spotAttenuation = pow(spotDot, gl_LightSource[i].spotExponent);\n" + " \n" + " // Combine the spotlight and distance attenuation. \n" + " attenuation *= spotAttenuation; \n" + " \n" + " halfVector = normalize(VP + eye); \n" + " \n" + " nDotVP = max(0.0, dot(normal, VP)); \n" + " nDotHV = max(0.0, dot(normal, halfVector)); \n" + " \n" + " if (nDotVP == 0.0) \n" + " pf = 0.0; \n" + " else \n" + " pf = pow(nDotHV, gl_FrontMaterial.shininess); \n" + " \n" + " ambient += gl_LightSource[i].ambient * attenuation; \n" + " diffuse += gl_LightSource[i].diffuse * nDotVP * attenuation; \n" + " specular += gl_LightSource[i].specular * pf * attenuation; \n" + "} \n" + " \n" + "void PointLight(in int i, \n" + " in vec3 eye, \n" + " in vec3 ecPosition3, \n" + " in vec3 normal, \n" + " inout vec4 ambient, \n" + " inout vec4 diffuse, \n" + " inout vec4 specular) \n" + "{ \n" + " float nDotVP; // normal . light direction \n" + " float nDotHV; // normal . light half vector \n" + " float pf; // power factor \n" + " float attenuation; // computed attenuation factor \n" + " float d; // distance from surface to light source \n" + " vec3 VP; // direction from surface to light position \n" + " vec3 halfVector; // direction of maximum highlights \n" + " \n" + " // Compute vector from surface to light position \n" + " VP = vec3(gl_LightSource[i].position) - ecPosition3; \n" + " \n" + " // Compute distance between surface and light position \n" + " d = length(VP); \n" + " \n" + " // Normalize the vector from surface to light position \n" + " VP = normalize(VP); \n" + " \n" + " // Compute attenuation \n" + " attenuation = 1.0 / (gl_LightSource[i].constantAttenuation + \n" + " gl_LightSource[i].linearAttenuation * d + \n" + " gl_LightSource[i].quadraticAttenuation * d*d);\n" + " \n" + " halfVector = normalize(VP + eye); \n" + " \n" + " nDotVP = max(0.0, dot(normal, VP)); \n" + " nDotHV = max(0.0, dot(normal, halfVector)); \n" + " \n" + " if (nDotVP == 0.0) \n" + " pf = 0.0; \n" + " else \n" + " pf = pow(nDotHV, gl_FrontMaterial.shininess); \n" + " \n" + " ambient += gl_LightSource[i].ambient * attenuation; \n" + " diffuse += gl_LightSource[i].diffuse * nDotVP * attenuation; \n" + " specular += gl_LightSource[i].specular * pf * attenuation; \n" + "} \n" + " \n" + "void DirectionalLight(in int i, \n" + " in vec3 normal, \n" + " inout vec4 ambient, \n" + " inout vec4 diffuse, \n" + " inout vec4 specular) \n" + "{ \n" + " float nDotVP; // normal . light direction \n" + " float nDotHV; // normal . light half vector \n" + " float pf; // power factor \n" + " \n" + " nDotVP = max(0.0, dot(normal, \n" + " normalize(vec3(gl_LightSource[i].position)))); \n" + " nDotHV = max(0.0, dot(normal, \n" + " vec3(gl_LightSource[i].halfVector))); \n" + " \n" + " if (nDotVP == 0.0) \n" + " pf = 0.0; \n" + " else \n" + " pf = pow(nDotHV, gl_FrontMaterial.shininess); \n" + " \n" + " ambient += gl_LightSource[i].ambient; \n" + " diffuse += gl_LightSource[i].diffuse * nDotVP; \n" + " specular += gl_LightSource[i].specular * pf; \n" + "} \n" + " \n" + "void main( ) \n" + "{ \n" + " // Transform vertex to clip space \n" + " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n" + " vec3 normal = normalize( gl_NormalMatrix * gl_Normal ); \n" + " \n" + " vec4 ecPos = gl_ModelViewMatrix * gl_Vertex; \n" + " float ecLen = length( ecPos ); \n" + " vec3 ecPosition3 = ecPos.xyz / ecPos.w; \n" + " \n" + " vec3 eye = vec3( 0.0, 0.0, 1.0 ); \n" + " //vec3 eye = -normalize(ecPosition3); \n" + " \n" + " DynamicShadow( ecPos ); \n" + " \n" + " gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; \n" + " \n" + " // Front Face lighting \n" + " \n" + " // Clear the light intensity accumulators \n" + " vec4 amb = vec4(0.0); \n" + " vec4 diff = vec4(0.0); \n" + " vec4 spec = vec4(0.0); \n" + " \n" + " // Loop through enabled lights, compute contribution from each \n" + " for (int i = 0; i < NumEnabledLights; i++) \n" + " { \n" + " if (gl_LightSource[i].position.w == 0.0) \n" + " DirectionalLight(i, normal, amb, diff, spec); \n" + " else if (gl_LightSource[i].spotCutoff == 180.0) \n" + " PointLight(i, eye, ecPosition3, normal, amb, diff, spec); \n" + " else \n" + " SpotLight(i, eye, ecPosition3, normal, amb, diff, spec); \n" + " } \n" + " \n" + " colorAmbientEmissive = gl_FrontLightModelProduct.sceneColor + \n" + " amb * gl_FrontMaterial.ambient; \n" + " \n" + " gl_FrontColor = colorAmbientEmissive + \n" + " diff * gl_FrontMaterial.diffuse; \n" + " \n" + " gl_FrontSecondaryColor = vec4(spec*gl_FrontMaterial.specular); \n" + " \n" + " gl_BackColor = gl_FrontColor; \n" + " gl_BackSecondaryColor = gl_FrontSecondaryColor; \n" + " \n" + " gl_FogFragCoord = ecLen; \n" + "} \n" ); +} + +StandardShadowMap::StandardShadowMap +(const StandardShadowMap& copy, const osg::CopyOp& copyop) : + BaseClass(copy,copyop), + _polygonOffsetFactor( copy._polygonOffsetFactor ), + _polygonOffsetUnits( copy._polygonOffsetUnits ), + _textureSize( copy._textureSize ), + _baseTextureUnit( copy._baseTextureUnit ), + _shadowTextureUnit( copy._shadowTextureUnit ) +{ + if( copy._mainVertexShader.valid() ) + _mainVertexShader = dynamic_cast + ( copy._mainVertexShader->clone(copyop) ); + + if( copy._mainFragmentShader.valid() ) + _mainFragmentShader = dynamic_cast + ( copy._mainFragmentShader->clone(copyop) ); + + if( copy._shadowVertexShader.valid() ) + _shadowVertexShader = dynamic_cast + ( copy._shadowVertexShader->clone(copyop) ); + + if( copy._shadowFragmentShader.valid() ) + _shadowFragmentShader = dynamic_cast + ( copy._shadowFragmentShader->clone(copyop) ); +} + +StandardShadowMap::~StandardShadowMap(void) +{ + +} + +void StandardShadowMap::updateTextureCoordIndices + ( unsigned int fromTextureCoordIndex, unsigned int toTextureCoordIndex ) +{ + if( fromTextureCoordIndex == toTextureCoordIndex ) return; + + const char *expressions[] = { + "gl_TexCoord[%d]", + "gl_TextureMatrix[%d]", + "gl_MultiTexCoord%d", + "gl_EyePlaneS[%d]", + "gl_EyePlaneT[%d]", + "gl_EyePlaneR[%d]", + "gl_EyePlaneQ[%d]", + }; + + for( unsigned int i = 0; + i < sizeof( expressions ) / sizeof( expressions[0] ); + i++ ) + { + char acFrom[ 32 ], acTo[32]; + + // its not elegant to mix stdio & stl strings + // but in this context I do an exception for cleaner code + + std::sprintf( acFrom, expressions[i], fromTextureCoordIndex ); + std::sprintf( acTo, expressions[i], toTextureCoordIndex ); + + std::string from( acFrom ), to( acTo ); + + searchAndReplaceShaderSource( getShadowVertexShader(), from, to ); + searchAndReplaceShaderSource( getShadowFragmentShader(), from, to ); + searchAndReplaceShaderSource( getMainVertexShader(), from, to ); + searchAndReplaceShaderSource( getMainFragmentShader(), from, to ); + } + + dirty(); +} + +void StandardShadowMap::searchAndReplaceShaderSource + ( osg::Shader* shader, std::string fromString, std::string toString ) +{ + if( !shader || fromString == toString ) return; + + const std::string & srceString = shader->getShaderSource(); + std::string destString; + + std::string::size_type fromLength = fromString.length(); + std::string::size_type srceLength = srceString.length(); + + for( std::string::size_type pos = 0; pos < srceLength; ) + { + std::string::size_type end = srceString.find( fromString, pos ); + + if( end == std::string::npos ) + end = srceLength; + + destString.append( srceString, pos, end - pos ); + + if( end == srceLength ) + break; + + destString.append( toString ); + pos = end + fromLength; + } + + shader->setShaderSource( destString ); +} + +void StandardShadowMap::ViewData::cull() +{ + // step 1: + // cull shadowed scene ie put into render bins and states into stage graphs + cullShadowReceivingScene( ); + + // step 2: + // find the light casting our shadows + osg::Vec4 lightPos; + osg::Vec3 lightDir; + osg::Vec3 lightUp( 0,0,0 ); // force computing most approprate dir + const osg::Light *light = selectLight( lightPos, lightDir ); + + if ( !light ) + return;// bail out - no shadowing needed in darkest night + + // step 3: + // compute shadow casting matrices and apply them to shadow map camera + aimShadowCastingCamera( light, lightPos, lightDir, lightUp ); + + // step 4: + // cull scene casting shadow and generate render + cullShadowCastingScene( ); + + // step 5: + // setup texgen generating shadow map coords for the shadow receiving scene + addShadowReceivingTexGen( ); + + BaseClass::ViewData::cull(); +} + +void StandardShadowMap::ViewData::init( ThisClass *st, osgUtil::CullVisitor *cv ) +{ + BaseClass::ViewData::init( st, cv ); + + _lightPtr = &st->_light; + _shadowTextureUnitPtr = &st->_shadowTextureUnit; + _baseTextureUnitPtr = &st->_baseTextureUnit; + + _texture = new osg::Texture2D; + { // Setup shadow texture + _texture->setTextureSize( st->_textureSize.x(), st->_textureSize.y()); + _texture->setInternalFormat(GL_DEPTH_COMPONENT); + _texture->setShadowComparison(true); + _texture->setShadowTextureMode(osg::Texture2D::LUMINANCE); + _texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR); + _texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR); + + // the shadow comparison should fail if object is outside the texture + _texture->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::CLAMP_TO_BORDER); + _texture->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::CLAMP_TO_BORDER); + _texture->setBorderColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); + } + + _camera = new osg::Camera; + { // Setup shadow map camera + _camera->setName( "ShadowCamera" ); +#if 0 // Absolute reference frame INHERIT_VIEWPOINT works better than this + _camera->setCullingMode + ( _camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING ); +#endif + _camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + _camera->setCullCallback(new CameraCullCallback( st )); + _camera->setClearMask(GL_DEPTH_BUFFER_BIT); + +#if 0 // Left in case of some debug testing + _camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + _camera->setClearColor( osg::Vec4(1.0f,1.0f,1.0f,1.0f) ); +#endif + _camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + _camera->setViewport(0,0, st->_textureSize.x(), st->_textureSize.y() ); + _camera->setRenderOrder(osg::Camera::PRE_RENDER); + _camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + _camera->attach(osg::Camera::DEPTH_BUFFER, _texture.get()); + } + + _texgen = new osg::TexGen; + + _stateset = new osg::StateSet; + { // Create and add fake texture for use with nodes without any texture + osg::Image * image = new osg::Image; + image->allocateImage( 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE ); + *(osg::Vec4ub*)image->data() = osg::Vec4ub( 0xFF, 0xFF, 0xFF, 0xFF ); + + osg::Texture2D* fakeTex = new osg::Texture2D( image ); + fakeTex->setWrap(osg::Texture2D::WRAP_S,osg::Texture2D::REPEAT); + fakeTex->setWrap(osg::Texture2D::WRAP_T,osg::Texture2D::REPEAT); + fakeTex->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::NEAREST); + fakeTex->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::NEAREST); + + _stateset->setTextureAttribute(st->_baseTextureUnit,fakeTex,osg::StateAttribute::ON); + _stateset->setTextureMode(st->_baseTextureUnit,GL_TEXTURE_1D,osg::StateAttribute::OFF); + _stateset->setTextureMode(st->_baseTextureUnit,GL_TEXTURE_2D,osg::StateAttribute::ON); + _stateset->setTextureMode(st->_baseTextureUnit,GL_TEXTURE_3D,osg::StateAttribute::OFF); + } + + { // Add shadow texture + _stateset->setTextureAttributeAndModes(st->_shadowTextureUnit,_texture.get(),osg::StateAttribute::ON); + _stateset->setTextureMode(st->_shadowTextureUnit,GL_TEXTURE_GEN_S,osg::StateAttribute::ON); + _stateset->setTextureMode(st->_shadowTextureUnit,GL_TEXTURE_GEN_T,osg::StateAttribute::ON); + _stateset->setTextureMode(st->_shadowTextureUnit,GL_TEXTURE_GEN_R,osg::StateAttribute::ON); + _stateset->setTextureMode(st->_shadowTextureUnit,GL_TEXTURE_GEN_Q,osg::StateAttribute::ON); + } + + { // Setup shaders used in shadow casting + osg::Program * program = new osg::Program(); + _stateset->setAttribute( program ); + + if( st->_shadowFragmentShader.valid() ) + program->addShader( st->_shadowFragmentShader.get() ); + + if( st->_mainFragmentShader.valid() ) + program->addShader( st->_mainFragmentShader.get() ); + + if( st->_shadowVertexShader.valid() ) + program->addShader( st->_shadowVertexShader.get() ); + + if( st->_mainVertexShader.valid() ) + program->addShader( st->_mainVertexShader.get() ); + + _stateset->addUniform + ( new osg::Uniform( "baseTexture", int( st->_baseTextureUnit ) ) ); + _stateset->addUniform + ( new osg::Uniform( "shadowTexture", int( st->_shadowTextureUnit ) ) ); + } + + { // Setup states used for shadow map generation + osg::StateSet * stateset = _camera->getOrCreateStateSet(); + + stateset->setAttribute( + new osg::PolygonOffset( st->_polygonOffsetFactor, st->_polygonOffsetUnits ), + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE ); + + stateset->setMode( GL_POLYGON_OFFSET_FILL, + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE ); + + // agressive optimization + stateset->setRenderBinDetails( 0, "RenderBin", + osg::StateSet::OVERRIDE_RENDERBIN_DETAILS ); + + // agressive optimization + stateset->setAttributeAndModes + ( new osg::ColorMask( false, false, false, false ), + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE ); + + // note soft (attribute only no mode override) setting. When this works ? + // 1. for objects prepared for backface culling + // because they usually also set CullFace and CullMode on in their state + // For them we override CullFace but CullMode remains set by them + // 2. For one faced, trees, and similar objects which cannot use + // backface nor front face so they usually use CullMode off set here. + // In this case we will draw them in their entirety. + + stateset->setAttribute( new osg::CullFace( osg::CullFace::FRONT ), + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE ); + + // make sure GL_CULL_FACE is off by default + // we assume that if object has cull face attribute set to back + // it will also set cull face mode ON so no need for override + stateset->setMode( GL_CULL_FACE, osg::StateAttribute::OFF ); + + // optimization attributes + osg::Program* program = new osg::Program; + stateset->setAttribute( program, osg::StateAttribute::OVERRIDE | osg::StateAttribute::ON ); + stateset->setMode + ( GL_LIGHTING, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF ); + stateset->setMode + ( GL_BLEND, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF ); + +#if 0 + stateset->setMode + ( GL_ALPHA_TEST, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF ); +#endif + +#if 0 // fixed pipeline seems faster (at least on my 7800) + program->addShader( new osg::Shader( osg::Shader::FRAGMENT, + "uniform sampler2D texture; \n" + "void main(void) \n" + "{ \n" + " gl_FragColor = texture2D( texture, gl_TexCoord[0].xy ); \n" + "} \n" + ) ); // program->addShader Fragment + + program->addShader( new osg::Shader( osg::Shader::VERTEX, + "void main(void) \n" + "{ \n" + " gl_Position = ftransform(); \n" + " gl_TexCoord[0] = gl_MultiTexCoord0; \n" + "} \n" + ) ); // program->addShader Vertex +#endif + + for( unsigned stage = 1; stage < 4; stage ++ ) + { + stateset->setTextureMode( stage, GL_TEXTURE_1D, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF ); + stateset->setTextureMode( stage, GL_TEXTURE_2D, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF ); + stateset->setTextureMode( stage, GL_TEXTURE_3D, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF ); + } + } +} + +const osg::Light* StandardShadowMap::ViewData::selectLight + ( osg::Vec4 & lightPos, osg::Vec3 & lightDir ) +{ + const osg::Light* light = 0; + + //MR testing giving a specific light + osgUtil::RenderStage * rs = _cv->getRenderStage(); + + osgUtil::PositionalStateContainer::AttrMatrixList& aml = + rs->getPositionalStateContainer()->getAttrMatrixList(); + + osg::RefMatrix* matrix; + + for(osgUtil::PositionalStateContainer::AttrMatrixList::iterator itr = aml.begin(); + itr != aml.end(); + ++itr) + { + const osg::Light* found = dynamic_cast(itr->first.get()); + if( found ) + { + if( _lightPtr->valid() && _lightPtr->get() != found ) + continue; // continue search for the right one + + light = found; + matrix = itr->second.get(); + } + } + + if( light ) { // transform light to world space + + osg::Matrix localToWorld = osg::Matrix::inverse( *_cv->getModelViewMatrix() ); + if( matrix ) localToWorld.preMult( *matrix ); + + lightPos = light->getPosition(); + + if( lightPos[3] == 0 ) + lightDir.set( -lightPos[0], -lightPos[1], -lightPos[2] ); + else + lightDir = light->getDirection(); + + lightPos = lightPos * localToWorld; + lightDir = osg::Matrix::transform3x3( lightDir, localToWorld ); + lightDir.normalize(); + } + + return light; +} + +void StandardShadowMap::ViewData::aimShadowCastingCamera( const osg::Light *light, + const osg::Vec4 &lightPos, + const osg::Vec3 &lightDir, + const osg::Vec3 &lightUp + /* by default = osg::Vec3( 0, 1 0 )*/ ) +{ +#if 0 // less precise but faster + osg::BoundingSphere bs =_st->getShadowedScene()->getBound(); +#else + // get the bounds of the model. + osg::ComputeBoundsVisitor cbbv(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN); + cbbv.setTraversalMask(_st->getShadowedScene()->getCastsShadowTraversalMask()); + _st->getShadowedScene()->osg::Group::traverse(cbbv); + osg::BoundingSphere bs( cbbv.getBoundingBox() ); +#endif + + aimShadowCastingCamera + ( bs, light, lightPos, lightDir, lightUp ); +} + +void StandardShadowMap::ViewData::aimShadowCastingCamera( + const osg::BoundingSphere &bs, + const osg::Light *light, + const osg::Vec4 &lightPos, + const osg::Vec3 &lightDir, + const osg::Vec3 &lightUpVector + /* by default = osg::Vec3( 0, 1 0 )*/ ) +{ + osg::Matrix & view = _camera->getViewMatrix(); + osg::Matrix & projection = _camera->getProjectionMatrix(); + + osg::Vec3 up = lightUpVector; + if( up.length2() <= 0 ) up.set( 0,1,0 ); + + if( light->getSpotCutoff() < 180.0f) // spotlight, no need for bounding box + { + osg::Vec3 position(lightPos.x(), lightPos.y(), lightPos.z()); + float spotAngle = light->getSpotCutoff(); + + projection.makePerspective( spotAngle, 1.0, 0.1, 1000.0); + view.makeLookAt(position,position+lightDir,up); + } + else + { + if (lightPos[3]!=0.0) // point light + { + osg::Vec3 position(lightPos.x(), lightPos.y(), lightPos.z()); + + float centerDistance = (position-bs.center()).length(); + + float znear = centerDistance-bs.radius(); + float zfar = centerDistance+bs.radius(); + float zNearRatio = 0.001f; + if (znearpushStateSet( _stateset.get() ); + + _st->getShadowedScene()->osg::Group::traverse( *_cv ); + + _cv->popStateSet(); +} + +void StandardShadowMap::ViewData::cullShadowCastingScene( ) +{ + // record the traversal mask on entry so we can reapply it later. + unsigned int traversalMask = _cv->getTraversalMask(); + + _cv->setTraversalMask( traversalMask & + _st->getShadowedScene()->getCastsShadowTraversalMask() ); + + // do RTT camera traversal + _camera->accept(*_cv); + + // reapply the original traversal mask + _cv->setTraversalMask( traversalMask ); +} + +void StandardShadowMap::ViewData::addShadowReceivingTexGen( ) +{ + _texgen->setMode(osg::TexGen::EYE_LINEAR); + + // compute the matrix which takes a vertex from view coords into tex coords + _texgen->setPlanesFromMatrix( + _camera->getProjectionMatrix() * + osg::Matrix::translate(1.0,1.0,1.0) * + osg::Matrix::scale(0.5f,0.5f,0.5f) ); + + osg::RefMatrix * refMatrix = new osg::RefMatrix + ( _camera->getInverseViewMatrix() * *_cv->getModelViewMatrix() ); + + _cv->getRenderStage()->getPositionalStateContainer()-> + addPositionedTextureAttribute + ( *_shadowTextureUnitPtr, refMatrix, _texgen.get() ); +} + diff --git a/src/osgShadow/ViewDependentShadowTechnique.cpp b/src/osgShadow/ViewDependentShadowTechnique.cpp new file mode 100644 index 000000000..9862b3ba7 --- /dev/null +++ b/src/osgShadow/ViewDependentShadowTechnique.cpp @@ -0,0 +1,127 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library 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 + * OpenSceneGraph Public License for more details. + * + * ViewDependentShadow codes Copyright (C) 2008 Wojciech Lewandowski + * Thanks to to my company http://www.ai.com.pl for allowing me free this work. +*/ + +#include +#include + +using namespace osgShadow; + + +ViewDependentShadowTechnique::ViewDependentShadowTechnique() +{ + dirty(); +} + +ViewDependentShadowTechnique::ViewDependentShadowTechnique + (const ViewDependentShadowTechnique& copy, const osg::CopyOp& copyop): + ShadowTechnique(copy,copyop) +{ + dirty(); +} + +ViewDependentShadowTechnique::~ViewDependentShadowTechnique(void) +{ + +} + +void ViewDependentShadowTechnique::traverse(osg::NodeVisitor& nv) +{ + osgShadow::ShadowTechnique::traverse(nv); +} + +void ViewDependentShadowTechnique::dirty() +{ + OpenThreads::ScopedLock lock(_viewDataMapMutex); + + osgShadow::ShadowTechnique::_dirty = true; + + for( ViewDataMap::iterator mitr = _viewDataMap.begin(); + mitr != _viewDataMap.end(); + ++mitr ) + { + mitr->second->dirty( true ); + } +} + +void ViewDependentShadowTechnique::init() +{ + //osgShadow::ShadowTechnique::init( ); + osgShadow::ShadowTechnique::_dirty = false; +} + +void ViewDependentShadowTechnique::update(osg::NodeVisitor& nv) +{ + //osgShadow::ShadowTechnique::update( nv ); + osgShadow::ShadowTechnique::_shadowedScene->osg::Group::traverse(nv); +} + +void ViewDependentShadowTechnique::cull(osgUtil::CullVisitor& cv) +{ + //osgShadow::ShadowTechnique::cull( cv ); + + ViewData * vd = getViewDependentData( &cv ); + + if ( !vd || vd->_dirty || vd->_cv != &cv || vd->_st != this ) { + vd = initViewDependentData( &cv, vd ); + setViewDependentData( &cv, vd ); + } + + if( vd ) { + OpenThreads::ScopedLock lock(vd->_mutex); + vd->cull(); + } else { + osgShadow::ShadowTechnique::_shadowedScene->osg::Group::traverse(cv); + } +} + +void ViewDependentShadowTechnique::cleanSceneGraph() +{ + //osgShadow::ShadowTechnique::cleanSceneGraph( ); +} + +ViewDependentShadowTechnique::ViewData * +ViewDependentShadowTechnique::getViewDependentData( osgUtil::CullVisitor * cv ) +{ + OpenThreads::ScopedLock lock(_viewDataMapMutex); + return _viewDataMap[ cv ].get(); +} + +void ViewDependentShadowTechnique::setViewDependentData + ( osgUtil::CullVisitor * cv, ViewData * data ) +{ + OpenThreads::ScopedLock lock(_viewDataMapMutex); + _viewDataMap[ cv ] = data; +} + +void ViewDependentShadowTechnique::ViewData::dirty( bool flag ) +{ + OpenThreads::ScopedLock lock(_mutex); + _dirty = flag; +} + +void ViewDependentShadowTechnique::ViewData::init + ( ViewDependentShadowTechnique *st, osgUtil::CullVisitor * cv ) +{ + _cv = cv; + _st = st; + dirty( false ); +} + +void ViewDependentShadowTechnique::ViewData::cull( void ) +{ + +} +