From 87e8f06522a89823e0e7add627ec68abce996bd8 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Mon, 6 Oct 2008 08:53:25 +0000 Subject: [PATCH] Ran dos2unix on new shadow implemenations --- src/osgShadow/ConvexPolyhedron.cpp | 3752 ++++++++--------- src/osgShadow/DebugShadowMap.cpp | 1178 +++--- .../LightSpacePerspectiveShadowMap.cpp | 1686 ++++---- src/osgShadow/MinimalCullBoundsShadowMap.cpp | 736 ++-- src/osgShadow/MinimalDrawBoundsShadowMap.cpp | 812 ++-- src/osgShadow/MinimalShadowMap.cpp | 1000 ++--- src/osgShadow/StandardShadowMap.cpp | 1530 +++---- .../ViewDependentShadowTechnique.cpp | 254 +- 8 files changed, 5474 insertions(+), 5474 deletions(-) diff --git a/src/osgShadow/ConvexPolyhedron.cpp b/src/osgShadow/ConvexPolyhedron.cpp index 436861376..461ade689 100644 --- a/src/osgShadow/ConvexPolyhedron.cpp +++ b/src/osgShadow/ConvexPolyhedron.cpp @@ -1,1876 +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 ) ); -} -//////////////////////////////////////////////////////////////////////////// +/* -*-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 index 1f766db79..e0774452a 100644 --- a/src/osgShadow/DebugShadowMap.cpp +++ b/src/osgShadow/DebugShadowMap.cpp @@ -1,589 +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; -} - +/* -*-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 index c1eb6c10e..e049df1e3 100644 --- a/src/osgShadow/LightSpacePerspectiveShadowMap.cpp +++ b/src/osgShadow/LightSpacePerspectiveShadowMap.cpp @@ -1,843 +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 +/* -*-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 index ee7d9d0f0..2560aba85 100644 --- a/src/osgShadow/MinimalCullBoundsShadowMap.cpp +++ b/src/osgShadow/MinimalCullBoundsShadowMap.cpp @@ -1,368 +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; -} - +/* -*-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 index e354f83ef..e628be636 100644 --- a/src/osgShadow/MinimalDrawBoundsShadowMap.cpp +++ b/src/osgShadow/MinimalDrawBoundsShadowMap.cpp @@ -1,406 +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 -} +/* -*-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 index 6ef6decae..1d6eb57f2 100644 --- a/src/osgShadow/MinimalShadowMap.cpp +++ b/src/osgShadow/MinimalShadowMap.cpp @@ -1,500 +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 ); -} - +/* -*-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 index fba71959f..0f3c0696f 100644 --- a/src/osgShadow/StandardShadowMap.cpp +++ b/src/osgShadow/StandardShadowMap.cpp @@ -1,765 +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() ); -} - +/* -*-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 index 9862b3ba7..f4420bd6f 100644 --- a/src/osgShadow/ViewDependentShadowTechnique.cpp +++ b/src/osgShadow/ViewDependentShadowTechnique.cpp @@ -1,127 +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 ) -{ - -} - +/* -*-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 ) +{ + +} +