]> git.mxchange.org Git - simgear.git/commitdiff
Harald JOHNSEN:
authorehofman <ehofman>
Sun, 26 Jun 2005 17:16:45 +0000 (17:16 +0000)
committerehofman <ehofman>
Sun, 26 Jun 2005 17:16:45 +0000 (17:16 +0000)
Changes
=======

New volumetric shadows for FlightGear.

There is now two new checkboxes in the rendering dialog to enable/disable shadows
for the user aircraft and for static scenery objects (ie those defined in the .stg files).
AI and random objects are not handled for the moment.

known bugs
==========
- ghost objects

simgear/environment/visual_enviro.hxx
simgear/scene/model/Makefile.am
simgear/scene/model/shadowvolume.cxx [new file with mode: 0644]
simgear/scene/model/shadowvolume.hxx [new file with mode: 0644]

index e3740b6c7ebb208775748b22a380e2c837952c15..946c169caaa05ade89bbeb3ca3354be970f31eee 100644 (file)
@@ -24,7 +24,9 @@
 
 #include <simgear/compiler.h>
 #include STL_STRING
+#include <vector>
 
+SG_USING_STD(vector);
 SG_USING_STD(string);
 
 class SGLightning;
@@ -217,6 +219,8 @@ public:
        void getFOV( float &w, float &h );
 
        list_of_SGWxRadarEcho *get_radar_echo(void);
+
+       sgMat4 *get_transform(void) { return &transform; }
 };
 
 extern SGEnviro sgEnviro;
index e12c582967f9b3df60caad3e6a6defc6288257df..d05744c1eafa7a532184d0f601a2f4dbccc1aec9 100644 (file)
@@ -12,7 +12,8 @@ include_HEADERS = \
        modellib.hxx \
        personality.hxx \
        placement.hxx \
-       placementtrans.hxx
+       placementtrans.hxx \
+       shadowvolume.hxx
 
 libsgmodel_a_SOURCES = \
        animation.cxx \
@@ -22,6 +23,7 @@ libsgmodel_a_SOURCES = \
        modellib.cxx \
        personality.cxx \
        placement.cxx \
-       placementtrans.cxx
+       placementtrans.cxx \
+       shadowvolume.cxx
 
 INCLUDES = -I$(top_srcdir)
diff --git a/simgear/scene/model/shadowvolume.cxx b/simgear/scene/model/shadowvolume.cxx
new file mode 100644 (file)
index 0000000..de106bc
--- /dev/null
@@ -0,0 +1,900 @@
+// Shadow volume class
+//
+// Written by Harald JOHNSEN, started June 2005.
+//
+// Copyright (C) 2005  Harald JOHNSEN - hjohnsen@evc.net
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// 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.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+//
+//
+
+#include <plib/sg.h>
+#include <plib/ssg.h>
+#include <simgear/props/props.hxx>
+#include <simgear/debug/logstream.hxx>
+#include SG_GLU_H
+
+#include "shadowvolume.hxx"
+
+/*
+ geometry and edge list
+ - traverse object graph until leaf to get geometry
+ - what about transform and selection ?
+       - use sub objects rather then objects
+       - get local transform and selection
+               - range anim : ssgRangeSelector ( min, max ) => ssgSelector ( true/false )
+                       => selection [0..n], kids [0..n]
+               - select/timed anim : ssgSelector ( true/false )
+                       => isSelected( nkid )
+               - spin/rotate/trans/scale/... : ssgTransform ( matrix )
+                       => getNetTransform
+ - on new object :
+       - for one object branch
+               - for each leaf
+                       - save geometry
+                       - save address in cache
+ - when rendering object
+       - for each leaf
+               - getNetTransform + object global rotation (ac) => transform for light position
+               - go up in tree and check isSelected( self )
+ - generate connectivity each time the geometry change
+       => using local light so connectivity never changes
+ - generate active edge list when :
+       - light moves
+       - subpart moves (animation code)
+       - ac rotate
+               => cache rotation matrix and generate edge list only if something change
+               => even if it changes, no need to do that every frame
+
+ - static objects have static edge list if light does not move
+
+ shadowing a scene
+ - render full scene as normal
+ - render occluder in stencil buffer with their shadow volumes
+ - apply stencil to framebuffer (darkens shadowed parts)
+
+*/
+
+// TODO
+//     - shadow for objects
+//             * aircraft
+//             * tile objects (from .stg)
+//             - ai objects
+//             - random objects => tie shadow geometry to lib objects and reuse them
+//     - zfail if camera inside shadow
+//     - queue geometry work if lot of objects
+//     * add a render property on/off (for aircraft, for scene objects, for ai)
+//     * add a render property in rendering dialog
+//     * filter : halo, light, shadow, disc, disk, flame, (exhaust), noshadow
+// BUG
+//     - ghost objects ?
+//     - no shadow : check static 737/747
+// why has the ggb an alpha animation = 0.01 ?
+
+static int statSilhouette=0;
+static int statGeom=0;
+static int statObj=0;
+
+static SGShadowVolume *states;
+
+
+SGShadowVolume::ShadowCaster::ShadowCaster( int _num_tri, ssgBranch * _geometry_leaf ) : 
+       geometry_leaf ( _geometry_leaf ),
+       scenery_object ( 0 ),
+       first_select ( 0 ),
+       frameNumber ( 0 ),
+       numTriangles ( 0 ),
+       indices ( 0 ),
+       vertices ( 0 ),
+       planeEquations ( 0 ),
+       isFacingLight ( 0 ),
+       neighbourIndices ( 0 ),
+       isSilhouetteEdge ( 0 )
+{
+       int num_tri = _num_tri;
+       numTriangles = num_tri;
+       indices = new int[num_tri * 3];
+       vertices = new sgVec3[num_tri * 3];
+       planeEquations = new sgVec4[num_tri];
+       neighbourIndices = new int[num_tri * 3];
+       isSilhouetteEdge = new bool[num_tri * 3];
+       isFacingLight = new bool[num_tri];
+       sgSetVec3(last_lightpos, 0.0, 0.0, 0.0);
+       statGeom ++;
+
+       ssgBranch *branch = (ssgBranch *) _geometry_leaf;
+       while( branch && branch->getNumParents() > 0 ) {
+               if( !first_select && branch->isA(ssgTypeSelector())) {
+                       first_select = branch;
+                       break;
+               }
+               branch = branch->getParent(0);
+       }
+}
+
+void SGShadowVolume::ShadowCaster::addLeaf( int & tri_idx, ssgLeaf *geometry_leaf ) {
+       int num_tri = geometry_leaf->getNumTriangles();
+       for(int i = 0; i < num_tri ; i ++ ) {
+               short v1, v2, v3;
+               sgVec3 a, b, c;
+               geometry_leaf->getTriangle( i, &v1, &v2, &v3 );
+               sgCopyVec3(a, geometry_leaf->getVertex(v1));
+               sgCopyVec3(b, geometry_leaf->getVertex(v2));
+               sgCopyVec3(c, geometry_leaf->getVertex(v3));
+               addTri( tri_idx++, a, b, c);
+       }
+}
+
+SGShadowVolume::ShadowCaster::~ShadowCaster() {
+       delete [] indices ;
+       delete [] vertices ;
+       delete [] planeEquations ;
+       delete [] isFacingLight ;
+       delete [] neighbourIndices ;
+       delete [] isSilhouetteEdge ;
+}
+
+
+void SGShadowVolume::ShadowCaster::addTri(int p, sgVec3 a, sgVec3 b, sgVec3 c) {
+       sgVec4 tri_plane;
+       assert( p >= 0 && p < numTriangles );
+
+       sgMakePlane ( tri_plane, a, b, c );
+       sgCopyVec4(planeEquations[p], tri_plane);
+       sgCopyVec3(vertices[p*3], a);
+       sgCopyVec3(vertices[p*3+1], b);
+       sgCopyVec3(vertices[p*3+2], c);
+       indices[p*3] = p*3;
+       indices[p*3+1] = p*3+1;
+       indices[p*3+2] = p*3+2;
+}
+
+bool SGShadowVolume::ShadowCaster::sameVertex(int edge1, int edge2) {
+//     const float epsilon = 0.01;     // 1cm
+//     const float epsilon = 0.0;      // 1cm
+//return false;
+       if( edge1 == edge2)
+               return true;
+       sgVec3 delta_v;
+       sgSubVec3( delta_v, vertices[edge1], vertices[edge2]);
+       if( delta_v[SG_X] != 0.0)       return false;
+       if( delta_v[SG_Y] != 0.0)       return false;
+       if( delta_v[SG_Z] != 0.0)       return false;
+       return true;
+}
+
+
+//Calculate neighbour faces for each edge
+// caution, this is O(n2)
+void SGShadowVolume::ShadowCaster::SetConnectivity(void)
+{
+       int edgeCount = 0;
+
+       //set the neighbour indices to be -1
+       for(int ii=0; ii<numTriangles*3; ++ii)
+               neighbourIndices[ii]=-1;
+
+       //loop through triangles
+       for(int i=0; i<numTriangles-1; ++i)
+       {
+               //loop through edges on the first triangle
+               for(int edgeI=0; edgeI<3; ++edgeI)
+               {
+                       //continue if this edge already has a neighbour set
+                       if(neighbourIndices[ i*3+edgeI ]!=-1)
+                               continue;
+
+                       //get the vertex indices on each edge
+                       int edgeI1=indices[i*3+edgeI];
+                       int edgeI2=indices[i*3+(edgeI+1)%3];
+
+                       //loop through triangles with greater indices than this one
+                       for(int j=i+1; j<numTriangles; ++j)
+                       {
+                               //loop through edges on triangle j
+                               for(int edgeJ=0; edgeJ<3; ++edgeJ)
+                               {
+                                       //get the vertex indices on each edge
+                                       int edgeJ1=indices[j*3+edgeJ];
+                                       int edgeJ2=indices[j*3+(edgeJ == 2 ? 0 : edgeJ+1)];
+
+                                       //if these are the same (possibly reversed order), these faces are neighbours
+                                       //no, we only use reverse order because same order means that
+                                       //the triangle is wrongly oriented and that will cause shadow artifact
+                                       if(     sameVertex(edgeI1, edgeJ1) && sameVertex(edgeI2, edgeJ2) ) {
+                                                       // can happens with 'bad' geometry
+//                                                     printf("flipped triangle detected...check your model\n");
+                                               continue;
+                                       }
+                                       if(     false && sameVertex(edgeI1, edgeJ1) && sameVertex(edgeI2, edgeJ2)
+                                               || sameVertex(edgeI1, edgeJ2) && sameVertex(edgeI2, edgeJ1) )
+                                       {
+                                               int edgeI3=indices[i*3+(edgeI+2)%3];
+                                               int edgeJ3=indices[j*3+(edgeJ+2)%3];
+                                               if(     sameVertex(edgeI3, edgeJ3) ) {
+                                                       // can happens with 'bad' geometry
+//                                                     printf("duplicated tri...check your model\n");
+                                                       // exit loop
+                                                       break;
+                                               }
+                                               //continue if this edge already has a neighbour set
+                                               // can happens with 'bad' geometry
+                                               if(neighbourIndices[ j*3+edgeJ ]!=-1) {
+//                                                     printf("bad edge detected\n");
+                                                       continue;
+                                               }
+                                               neighbourIndices[i*3+edgeI]=j;
+                                               neighbourIndices[j*3+edgeJ]=i;
+                                               edgeCount ++;
+                                               // exit loop
+                                               j = numTriangles;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+       }
+//     printf("submodel has %d triangles and %d shared edges\n", numTriangles, edgeCount);
+}
+
+//calculate silhouette edges
+void SGShadowVolume::ShadowCaster::CalculateSilhouetteEdges(sgVec3 lightPosition)
+{
+       //Calculate which faces face the light
+       for(int i=0; i<numTriangles; ++i)
+       {
+               if( sgDistToPlaneVec3 ( planeEquations[i], lightPosition ) > 0.0 )
+                       isFacingLight[i]=true;
+               else
+                       isFacingLight[i]=false;
+       }
+
+       //loop through faces
+
+       for(int t=0; t < numTriangles; t++) {
+               int v = t * 3;
+               //if this face is not facing the light, not a silhouette edge
+               if(!isFacingLight[t])
+               {
+                       isSilhouetteEdge[v+0]=false;
+                       isSilhouetteEdge[v+1]=false;
+                       isSilhouetteEdge[v+2]=false;
+                       continue;
+               }
+               //loop through edges
+               for(int i = v ; i < v+3 ; i++) {
+                       //this face is facing the light
+                       //if the neighbouring face is not facing the light, or there is no neighbouring face,
+                       //then this is a silhouette edge
+                       if(neighbourIndices[i]==-1 || !isFacingLight[neighbourIndices[i]])
+                               isSilhouetteEdge[i]=true;
+                       else 
+                               isSilhouetteEdge[i]=false;
+               }
+       }
+}
+
+// TODO: everyhting here is constant, store the vertex in a cache buffer and call drawelements
+void SGShadowVolume::ShadowCaster::DrawInfiniteShadowVolume(sgVec3 lightPosition, bool drawCaps)
+{
+       glColor4f(1.0, 1.0, 0.0, 0.5);
+       //TODO: no need for a quad here
+//     glBegin(GL_QUADS);
+       glBegin(GL_TRIANGLES);
+       {
+               for(int i=0; i<numTriangles; ++i)
+               {
+                       //if this face does not face the light, continue
+                       if(!isFacingLight[i])
+                               continue;
+
+                       int v = i*3;
+                       //Loop through edges on this face
+                       for(int j=0; j<3; ++j)
+                       {
+                               //Draw the shadow volume "edge" if this is a silhouette edge
+                               if(isSilhouetteEdge[v+j])
+                               {
+                                       sgVec3 vertex1, vertex2;
+                                       sgCopyVec3(vertex1, vertices[indices[v+j]]);
+                                       sgCopyVec3(vertex2, vertices[indices[v+(j == 2 ? 0 : j+1)]]);
+
+                                       glVertex3fv(vertex2);
+                                       glVertex3fv(vertex1);
+                                       // w == 0 for infinite shadow
+#if 0
+                                       glVertex4f(     vertex1[SG_X]-lightPosition[SG_X],
+                                                               vertex1[SG_Y]-lightPosition[SG_Y],
+                                                               vertex1[SG_Z]-lightPosition[SG_Z], 0.0f);
+//                                     glVertex4f(     vertex2[SG_X]-lightPosition[SG_X],
+//                                                             vertex2[SG_Y]-lightPosition[SG_Y],
+//                                                             vertex2[SG_Z]-lightPosition[SG_Z], 0.0f);
+#else
+                                       glVertex3f(     vertex1[SG_X]-lightPosition[SG_X],
+                                                               vertex1[SG_Y]-lightPosition[SG_Y],
+                                                               vertex1[SG_Z]-lightPosition[SG_Z]);
+//                                     glVertex3f(     vertex2[SG_X]-lightPosition[SG_X],
+//                                                             vertex2[SG_Y]-lightPosition[SG_Y],
+//                                                             vertex2[SG_Z]-lightPosition[SG_Z]);
+#endif
+                               }
+                       }
+               }
+       }
+       glEnd();
+
+       //Draw caps if required
+       if(drawCaps)
+       {
+               glBegin(GL_TRIANGLES);
+               {
+                       for(int i=0; i<numTriangles; ++i)
+                       {
+                               int v = i*3;
+                               for(int j=0; j<3; ++j)
+                               {
+                                       sgVec3 vertex;
+                                       sgCopyVec3(vertex, vertices[indices[v+j]]);
+
+                                       if(isFacingLight[i])
+                                               glVertex3fv(vertex);
+                                       else
+                                               glVertex4f(     vertex[SG_X]-lightPosition[SG_X],
+                                                                       vertex[SG_Y]-lightPosition[SG_Y],
+                                                                       vertex[SG_Z]-lightPosition[SG_Z], 0.0f);
+                               }
+                       }
+               }
+               glEnd();
+       }
+}
+
+
+void SGShadowVolume::ShadowCaster::getNetTransform ( ssgBranch * branch, sgMat4 xform )
+{
+       // one less matmult...
+       bool first = true;
+//     sgMakeIdentMat4 ( xform ) ;
+       while( branch && branch != lib_object ) {
+               if( branch->isA(ssgTypeTransform()) ) {
+                       sgMat4 transform;
+                       if( first ) {
+                               ((ssgTransform *) branch)->getTransform( xform );
+                               first = false;
+                       } else {
+                               ((ssgTransform *) branch)->getTransform(transform);
+                               sgPostMultMat4 ( xform, transform ) ;
+                       }
+               }
+               branch = branch->getParent( 0 );
+       }
+       if( first )
+               sgMakeIdentMat4 ( xform ) ;
+}
+
+// check the value of <select> and <range> animation
+// wich have been computed during the rendering
+bool SGShadowVolume::ShadowCaster::isSelected (  ssgBranch * branch ) {
+       while( branch && branch != lib_object) {
+               if( branch->isA(ssgTypeSelector()) )
+                       if( !((ssgSelector *) branch)->isSelected(0) )
+                               return false;
+               branch = branch->getParent(0);
+       }
+       return true;
+}
+/*
+       trans ----- personality ---- shared object
+       perso1 *-----
+                    \
+                     *--------* shared object
+                    /
+       perso2 *-----
+*/
+void SGShadowVolume::ShadowCaster::computeShadows(sgMat4 rotation, sgMat4 rotation_translation) {
+  
+       // check the select and range ssgSelector node
+       // object can't cast shadow if it is not visible
+
+       if( first_select && ! isSelected( first_select) )
+               return;
+
+               sgMat4 transform ;
+               sgMat4 invTransform;
+               // get the transformations : this comes from animation code for example
+               // or static transf
+               getNetTransform( (ssgBranch *) geometry_leaf, transform );
+               sgMat4 transf;
+               sgCopyMat4( transf, transform );
+               sgPostMultMat4( transf, rotation_translation );
+               sgPostMultMat4( transform, rotation );
+//             sgInvertMat4( invTransform, transform ); 
+               sgTransposeNegateMat4 ( invTransform, transform ); 
+
+               glLoadMatrixf ( (float *) states->CameraViewM ) ;
+               glMultMatrixf( (float *) transf );
+
+               sgVec3 lightPos;
+               sgCopyVec3( lightPos, states->sunPos );
+               sgXformPnt3( lightPos, invTransform );
+
+               sgVec3 lightPosNorm;
+               sgNormaliseVec3( lightPosNorm, lightPos );
+               float deltaPos = sgAbs(lightPosNorm[0] - last_lightpos[0]) + 
+                               sgAbs(lightPosNorm[1] - last_lightpos[1]) +
+                               sgAbs(lightPosNorm[2] - last_lightpos[2]);
+               // if the geometry has rotated/moved enought then
+               // we need to recompute the silhouette
+               // but this computation does not need to be done each frame
+               // -6 fps
+               if( (deltaPos > 0.0) && ( states->frameNumber - frameNumber > 0)) {
+                       CalculateSilhouetteEdges( lightPos );
+                       sgCopyVec3( last_lightpos, lightPosNorm );
+                       frameNumber = states->frameNumber ;
+                       statSilhouette ++;
+               }
+
+       if( states->shadowsDebug_enabled )
+       {
+               glStencilFunc(GL_ALWAYS, 0, ~0);
+               glDisable( GL_CULL_FACE  );
+               glDisable( GL_DEPTH_TEST );
+               glDisable(GL_STENCIL_TEST);
+               glColorMask(1, 1, 1, 1);
+               glColor4f(0.0, 0.0, 1.0, 0.8);
+               glBegin(GL_LINES);
+               for(int i=0; i<numTriangles; ++i)
+               {
+                       if(!isFacingLight[i])
+                               continue;
+                       int v = i*3;
+                       //Loop through edges on this face
+                       sgVec3 vertex1, vertex2, vertex3;
+                       sgCopyVec3(vertex1, vertices[indices[v+0]]);
+                       sgCopyVec3(vertex2, vertices[indices[v+1]]);
+                       sgCopyVec3(vertex3, vertices[indices[v+2]]);
+
+                       if(isSilhouetteEdge[i*3+0]) {
+                               glVertex3fv(vertex2);
+                               glVertex3fv(vertex1);
+                       }
+                       if(isSilhouetteEdge[i*3+1]) {
+                               glVertex3fv(vertex2);
+                               glVertex3fv(vertex3);
+                       }
+                       if(isSilhouetteEdge[i*3+2]) {
+                               glVertex3fv(vertex3);
+                               glVertex3fv(vertex1);
+                       }
+               }
+                       glEnd();
+               glColorMask(0, 0, 0, 0);
+               glEnable( GL_CULL_FACE  );
+               glEnable( GL_DEPTH_TEST );
+               glEnable(GL_STENCIL_TEST);
+       }
+
+       // -11 fps
+
+               // TODO:compute intersection with near clip plane...
+               bool needZFail=false;
+
+               // GL_INCR_WRAP_EXT, GL_DECR_WRAP_EXT
+
+               if(needZFail)
+               {
+                       //Increment stencil buffer for back face depth fail
+                       glStencilFunc(GL_ALWAYS, 0, ~0);
+                       glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
+                       glCullFace(GL_FRONT);
+
+                       DrawInfiniteShadowVolume( lightPos, true);
+
+                       //Decrement stencil buffer for front face depth fail
+                       glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
+                       glCullFace(GL_BACK);
+
+                       DrawInfiniteShadowVolume( lightPos, true);
+               }
+               else    //using zpass
+               {
+                       //Increment stencil buffer for front face depth pass
+                       glStencilFunc(GL_ALWAYS, 0, ~0);
+                       glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+                       glCullFace(GL_BACK);
+
+                       DrawInfiniteShadowVolume( lightPos, false);
+
+                       //Decrement stencil buffer for back face depth pass
+                       glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
+                       glCullFace(GL_FRONT);
+
+                       DrawInfiniteShadowVolume( lightPos, false);
+               }
+       // -15 fps
+}
+
+
+void SGShadowVolume::SceneryObject::computeShadows(void) {
+
+       bool intersect = true;
+       // compute intersection with view frustum
+       // use pending_object (pointer on obj transform) & tile transform
+       // to get position
+       sgMat4 position, CamXpos;
+       sgFrustum *f = ssgGetFrustum();
+       pending_object->getParent(0)->getNetTransform( position );
+       sgCopyMat4 ( CamXpos, states->CameraViewM ) ;
+       sgPreMultMat4 ( CamXpos, position ) ;
+
+       sgSphere tmp = *(pending_object->getBSphere()) ;
+       if ( tmp.isEmpty () )
+               intersect = false;
+       else {
+               // 7000
+               float max_dist = 5000.0f;
+               tmp . orthoXform ( CamXpos ) ;
+               // cull if too far
+               if ( -tmp.center[2] - tmp.radius > max_dist )
+                       intersect = false;
+               else if( tmp.center[2] == 0.0 )
+                       intersect = true;
+               // cull if too small on screen
+               else if ( tmp.radius / sgAbs(tmp.center[2]) < 1.0 / 40.0 )
+                       intersect = false;
+               else
+                       intersect = SSG_OUTSIDE != (ssgCullResult) f -> contains ( &tmp );
+       }
+
+       if( intersect ) {
+               if( !scenery_object ) {
+                       find_trans();
+                       if( scenery_object )
+                               traverseTree( pending_object );
+                       else
+                               return;
+               }
+               sgMat4 rotation, rotation_translation;
+               scenery_object->getNetTransform ( rotation_translation );
+               // split placement offset into rotation and offset
+               // rotation from model inside world
+               // we are not interested in translation since the light is very far away
+               // without translation we reduce the frequency of updates of the silouhette
+               sgCopyMat4( rotation, rotation_translation );
+               sgSetVec4( rotation[3], 0, 0, 0, 1);
+
+               ShadowCaster_list::iterator iShadowCaster;
+               for(iShadowCaster = parts.begin() ; iShadowCaster != parts.end() ; iShadowCaster ++ ) {
+                       (*iShadowCaster)->computeShadows(rotation, rotation_translation);
+               }
+       }
+}
+
+/*
+       1) starting at the root of a scene object we follow all branch to find vertex leaf.
+       each vertex leaf will create a shadow sub object so that the shadow casted by this
+       geometry can be correctly rendered according to select or transform nodes.
+       2) models are not optimized for shadows because the geometry is cut by
+               - select / transform nodes
+               - material differences
+       since we are not interested in the geometry material we try to merge same level
+       leafs. this can divide the number of shadow sub models by a lot (reducing the cpu
+       overhead for matrix computation) and at the end this will reduce the number of
+       silouhette edge by a lot too.
+*/
+static bool filterName(const char *leaf_name) {
+       if( ! leaf_name )
+               return true;
+       char lname[20];
+       int l = 0;
+           char *buff;
+       for( buff = lname; *leaf_name && l < (sizeof( lname )-1); ++buff, l++ )
+            *buff = tolower(*leaf_name++);
+       *buff = 0;
+       if( strstr(lname, "shadow") || strstr(lname, "light") || strstr(lname, "disk") ||
+               strstr(lname, "disk") || strstr(lname, "flame") || strstr(lname, "halo"))
+               return false;
+       return true;
+}
+void SGShadowVolume::SceneryObject::traverseTree(ssgBranch *branch) {
+       int num_tri = 0;
+       int num_leaf = 0;
+       for(int i = 0 ; i < branch->getNumKids() ; i++) {
+               ssgEntity *this_kid = branch->getKid( i );
+               if( this_kid->isAKindOf(ssgTypeLeaf()) ) {
+                       if( filterName( this_kid->getName()) ) {
+                               num_tri += ((ssgLeaf *) this_kid)->getNumTriangles();
+                               num_leaf ++;
+                       }
+               } else
+                       traverseTree( (ssgBranch *) this_kid );
+       }
+       if( num_tri > 0) {
+               int tri_idx = 0;
+               ShadowCaster *new_part = new ShadowCaster( num_tri, branch);
+               new_part->scenery_object = scenery_object;
+               new_part->lib_object = lib_object;
+               for(int i = 0 ; i < branch->getNumKids() ; i++) {
+                       ssgEntity *this_kid = branch->getKid( i );
+                       if( this_kid->isAKindOf(ssgTypeLeaf()) ) {
+                               if( filterName( this_kid->getName()) )
+                                       new_part->addLeaf( tri_idx, (ssgLeaf *) this_kid );
+                       }
+               }
+               new_part->SetConnectivity();
+               parts.push_back( new_part );
+       }
+}
+
+void SGShadowVolume::SceneryObject::find_trans(void) {
+       ssgBranch *branch = pending_object;
+       ssgBranch *prev_branch = pending_object;
+       // check the existence of the root node
+       while( branch && branch->getNumParents() > 0 ) {
+               prev_branch = branch;
+               branch = branch->getParent(0);
+       }
+       // check if we are connected to the scene graph
+       if( !branch->isA(ssgTypeRoot() ) )
+               return;
+       scenery_object = pending_object;
+}
+
+SGShadowVolume::SceneryObject::SceneryObject(ssgBranch *_scenery_object, OccluderType _occluder_type) :
+       pending_object ( _scenery_object ),
+       occluder_type ( _occluder_type ),
+       scenery_object ( 0 )
+{
+       // queue objects, don't do all the work in the first frames because of
+       // massive cpu power needed
+       statObj++;
+       if( occluder_type == SGShadowVolume::occluderTypeAircraft )
+               lib_object = _scenery_object;
+       else
+               lib_object = (ssgBranch *) ((ssgBranch *)_scenery_object->getKid(0))->getKid(0);
+#if 0
+       find_trans();
+       if( scenery_object )
+               traverseTree( pending_object );
+#endif
+}
+
+SGShadowVolume::SceneryObject::~SceneryObject()
+{
+//     parts.erase();
+}
+
+void SGShadowVolume::computeShadows(void) {
+
+       // intensity = ambient + lights
+       // lights = sun_const * (sun_angle . normal)
+       double dot_light = cos(sun_angle);
+       // do nothing if sun is under horizon
+       if( dot_light < 0.2 )
+               return;
+
+       //Draw shadow volumes
+       glPushAttrib(GL_ALL_ATTRIB_BITS);
+       glClear(GL_STENCIL_BUFFER_BIT);
+
+       glDisable( GL_LIGHTING );
+       glDisable( GL_FOG );
+       glDisable(GL_ALPHA);
+       glDisable(GL_BLEND);
+       glEnable( GL_CULL_FACE  );
+       glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+
+       glColorMask(0, 0, 0, 0);
+       glShadeModel(GL_FLAT);
+
+       glDepthMask( false );
+       glEnable( GL_DEPTH_TEST );
+       glDepthFunc(GL_LESS);
+       glEnable(GL_STENCIL_TEST);
+
+       SceneryObject_map::iterator iSceneryObject;
+       // compute shadows for each objects
+       for(iSceneryObject = sceneryObjects.begin() ; iSceneryObject != sceneryObjects.end(); iSceneryObject++ ) {
+               SceneryObject *an_occluder = iSceneryObject->second;
+               if( shadowsTO_enabled && (an_occluder->occluder_type == occluderTypeTileObject) ||
+                       shadowsAI_enabled && (an_occluder->occluder_type == occluderTypeAI ) ||
+                       shadowsAC_enabled && (an_occluder->occluder_type == occluderTypeAircraft ) )
+                               an_occluder->computeShadows();
+       }
+
+       // now the stencil contains 0 for scenery in light and != 0 for parts in shadow
+       // draw a quad covering the screen, the stencil will be the mask
+       // we darken the shadowed parts
+       glStencilFunc(GL_NOTEQUAL, 0, ~0);
+       glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+       glEnable(GL_STENCIL_TEST);
+       glColorMask(1, 1, 1, 1);
+
+       glMatrixMode   ( GL_PROJECTION ) ;
+       glPushMatrix   () ;
+       glLoadIdentity () ;
+       glOrtho        ( -100, 100, -100, 100, -1, 1 ) ;
+       glMatrixMode   ( GL_MODELVIEW ) ;
+       glPushMatrix   () ;
+       glLoadIdentity () ;
+
+       glDisable(GL_DEPTH_TEST);
+       glDisable(GL_CULL_FACE);
+//     glBindTexture(GL_TEXTURE_2D, 0);
+       glPolygonMode(GL_FRONT, GL_FILL);
+       glEnable(GL_ALPHA);
+       glAlphaFunc(GL_GREATER, 0.0f);
+       glEnable(GL_BLEND);
+       glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
+       glColor4f(0.0, 0.0, 0.0, sgLerp(0.1, 0.3, dot_light) );
+       // fixed value is better, the previous line is surely wrong
+       glColor4f(0.0, 0.0, 0.0, 0.3 );
+//     glColor4f(1.0, 0.0, 0.0, 0.5);
+       glRectf(-100,-100,100,100);
+
+       glMatrixMode   ( GL_PROJECTION ) ;
+       glPopMatrix    () ;
+       glMatrixMode   ( GL_MODELVIEW ) ;
+       glPopMatrix    () ;
+
+       glDisable(GL_STENCIL_TEST);
+       glPopAttrib();
+}
+
+SGShadowVolume::SGShadowVolume() : 
+       init_done( false ),
+       shadows_enabled( false ),
+       frameNumber( 0 )
+{
+       states = this;
+}
+
+SGShadowVolume::~SGShadowVolume() {
+//     sceneryObjects.erase();
+}
+
+void SGShadowVolume::init(SGPropertyNode *sim_rendering_options) {
+       init_done = true;
+       shadows_enabled = true;
+       sim_rendering = sim_rendering_options;
+}
+
+void SGShadowVolume::startOfFrame(void) {
+}
+void SGShadowVolume::deleteOccluderFromTile(ssgBranch *tile) {
+       SceneryObject_map::iterator iSceneryObject, iPrevious;
+       iPrevious = sceneryObjects.begin();
+       for(iSceneryObject = sceneryObjects.begin() ; iSceneryObject != sceneryObjects.end(); iSceneryObject++ ) {
+               if( iSceneryObject->second->tile == tile ) {
+                       delete iSceneryObject->second;
+                       sceneryObjects.erase( iSceneryObject );
+                       iSceneryObject = iPrevious;
+               }
+               iPrevious = iSceneryObject;
+       }
+}
+
+void SGShadowVolume::deleteOccluder(ssgBranch *occluder) {
+       ssgBranch *branch = occluder;
+       // skip first node and go to first transform (placement)
+       while( occluder && !occluder->isA(ssgTypeTransform()))
+               occluder = (ssgBranch *) occluder->getKid(0);
+
+       // check if we allready know this object
+       SceneryObject_map::iterator iSceneryObject = sceneryObjects.find( occluder );
+       if( iSceneryObject != sceneryObjects.end() ) {
+               delete iSceneryObject->second;
+               sceneryObjects.erase( occluder );
+       }
+}
+
+void SGShadowVolume::addOccluder(ssgBranch *occluder, OccluderType occluder_type, ssgBranch *tile) {
+
+       ssgBranch *branch = occluder;
+
+       // skip first node and go to first transform (placement)
+       while( occluder && !occluder->isA(ssgTypeTransform()))
+               occluder = (ssgBranch *) occluder->getKid(0);
+
+       // check if we allready know this object
+       SceneryObject_map::iterator iSceneryObject = sceneryObjects.find( occluder );
+       if( iSceneryObject == sceneryObjects.end() ) {
+//             printf("adding model %x %x\n", occluder, tile);
+               SceneryObject *entry = new SceneryObject( occluder, occluder_type );
+               entry->tile = tile;
+               sceneryObjects[ occluder ] = entry;
+       }
+
+}
+
+void SGShadowVolume::setupShadows( double lon, double lat,
+               double gst, double SunRightAscension, double SunDeclination, double sunAngle) {
+
+       shadowsAC_enabled = sim_rendering->getBoolValue("shadows-ac", false);
+       shadowsAI_enabled = sim_rendering->getBoolValue("shadows-ai", false);
+       shadowsTO_enabled = sim_rendering->getBoolValue("shadows-to", false);
+       shadowsDebug_enabled = sim_rendering->getBoolValue("shadows-debug", false);
+//     shadows_enabled   = sim_rendering->getBoolValue("shadows", false);
+       shadows_enabled = shadowsAC_enabled || shadowsAI_enabled || shadowsTO_enabled;
+
+       if( ! shadows_enabled )
+               return;
+
+       sgMat4 view_angle;
+       sun_angle = sunAngle;
+       {
+               sgMat4 LON, LAT;
+               sgVec3 axis;
+
+               sgSetVec3( axis, 0.0, 0.0, 1.0 );
+               sgMakeRotMat4( LON, lon, axis );
+
+               sgSetVec3( axis, 0.0, 1.0, 0.0 );
+               sgMakeRotMat4( LAT, 90.0 - lat, axis );
+
+               sgMat4 TRANSFORM;
+
+               sgMakeIdentMat4 ( TRANSFORM );
+               sgPreMultMat4( TRANSFORM, LON );
+               sgPreMultMat4( TRANSFORM, LAT );
+
+               sgCoord pos;
+               sgSetCoord( &pos, TRANSFORM );
+
+               sgMakeCoordMat4( view_angle, &pos );
+       }
+       {
+       sgMat4 GST, RA, DEC;
+       sgVec3 axis;
+
+
+       sgSetVec3( axis, 0.0, 0.0, -1.0 );
+       sgMakeRotMat4( GST, (gst) * 15.0, axis );
+
+       sgSetVec3( axis, 0.0, 0.0, 1.0 );
+       sgMakeRotMat4( RA, (SunRightAscension * SGD_RADIANS_TO_DEGREES) - 90.0, axis );
+
+       sgSetVec3( axis, 1.0, 0.0, 0.0 );
+       sgMakeRotMat4( DEC, SunDeclination * SGD_RADIANS_TO_DEGREES, axis );
+
+       sgInvertMat4( invViewAngle, view_angle); 
+
+       sgMat4 TRANSFORM;
+       sgMakeIdentMat4( TRANSFORM );
+       sgPreMultMat4( TRANSFORM, GST );
+       sgPreMultMat4( TRANSFORM, RA );
+       sgPreMultMat4( TRANSFORM, DEC );
+//     sgSetVec3( sunPos, 0.0, 99000.0, 0.0);
+       sgSetVec3( sunPos, 0.0, 9900000.0, 0.0);
+       sgXformPnt3( sunPos, TRANSFORM );
+       }
+
+       ssgGetModelviewMatrix( CameraViewM );
+
+}
+
+void SGShadowVolume::endOfFrame(void) {
+       if( ! shadows_enabled )
+               return;
+       glBindTexture(GL_TEXTURE_2D, 0);
+       glBindTexture(GL_TEXTURE_1D, 0);
+
+       glMatrixMode(GL_MODELVIEW);
+
+       computeShadows();
+
+       frameNumber ++;
+}
+
diff --git a/simgear/scene/model/shadowvolume.hxx b/simgear/scene/model/shadowvolume.hxx
new file mode 100644 (file)
index 0000000..102da84
--- /dev/null
@@ -0,0 +1,142 @@
+// Shadow volume class
+//
+// Written by Harald JOHNSEN, started June 2005.
+//
+// Copyright (C) 2005  Harald JOHNSEN - hjohnsen@evc.net
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// 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.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+//
+//
+
+#ifndef _SHADOWVOLUME_HXX
+#define _SHADOWVOLUME_HXX
+
+
+#include <plib/sg.h>
+#include <vector>
+#include <map>
+
+SG_USING_STD(vector);
+SG_USING_STD(map);
+
+class ssgBranch;
+class ssgLeaf;
+class SGPropertyNode;
+
+/**
+ * A class wich add shadows in a postprocess stage.
+ */
+class SGShadowVolume {
+
+public:
+       SGShadowVolume();
+       ~SGShadowVolume();
+
+       typedef enum {
+               occluderTypeAircraft,
+               occluderTypeAI,
+               occluderTypeTileObject
+       } OccluderType;
+
+       void init(SGPropertyNode *sim_rendering_options);
+       void startOfFrame(void);
+       void addOccluder(ssgBranch *occluder, OccluderType occluder_type, ssgBranch *tile=0);
+       void deleteOccluder(ssgBranch *occluder);
+       void deleteOccluderFromTile(ssgBranch *tile);
+       void setupShadows(double lon, double lat,
+               double gst, double SunRightAscension, double SunDeclination, double sunAngle );
+       void endOfFrame(void);
+
+
+private:
+
+       class ShadowCaster
+       {
+       public:
+               ssgBranch *geometry_leaf;
+               ssgBranch *scenery_object;
+               ssgBranch *lib_object;
+               ssgBranch *first_select;
+               sgVec3 last_lightpos;
+               int frameNumber;
+
+               int *indices;
+               int numTriangles;
+               sgVec3 * vertices;
+
+               // plane equation of each face
+               sgVec4 * planeEquations;
+
+               bool * isFacingLight;
+
+               int * neighbourIndices;
+
+               bool * isSilhouetteEdge;
+
+               ShadowCaster( int _num_tri, ssgBranch * _geometry_leaf );
+               ~ShadowCaster();
+               void addLeaf( int & tri_idx, ssgLeaf *_geometry_leaf );
+               void SetConnectivity();
+               void CalculateSilhouetteEdges(sgVec3 lightPosition);
+               void DrawInfiniteShadowVolume(sgVec3 lightPosition, bool drawCaps);
+               void computeShadows(sgMat4 rotation, sgMat4 rotation_translation);
+               void getNetTransform ( ssgBranch * branch, sgMat4 xform );
+               bool isSelected (  ssgBranch * branch );
+
+               bool sameVertex(int edge1, int edge2);
+               void addTri(int p, sgVec3 a, sgVec3 b, sgVec3 c);
+       };
+       typedef vector<ShadowCaster *> ShadowCaster_list;
+
+       class SceneryObject {
+       public:
+               SceneryObject(ssgBranch *_scenery_object, OccluderType _occluder_type);
+               ~SceneryObject();
+               void computeShadows(void);
+               void traverseTree(ssgBranch *branch);
+               void find_trans(void);
+               ssgBranch *scenery_object;
+               ssgBranch *lib_object;
+               ssgBranch *pending_object;
+               ssgBranch *tile;
+               ShadowCaster_list parts;
+               OccluderType occluder_type;
+       };
+       typedef map<ssgBranch *, SceneryObject *> SceneryObject_map;
+
+
+private:
+       void update_light_view(void);
+       void computeShadows(void);
+
+       bool    init_done;
+       bool    shadows_enabled;
+       bool    shadowsAC_enabled, shadowsAI_enabled, shadowsTO_enabled, shadowsDebug_enabled;
+       SGPropertyNode *sim_rendering;
+
+       sgVec3 sunPos;
+       int frameNumber;
+       sgMat4 CameraViewM;
+       sgMat4 invViewAngle;
+       double  sun_angle;
+       SceneryObject_map sceneryObjects;
+       /** this sphere contains the visible scene and is used to cull shadow casters */
+       sgSphere frustumSphere;
+       /** this sphere contains the near clip plane and is used to check the need of a zfail */
+       sgSphere nearClipSphere;
+
+};
+
+#endif // _SHADOWVOLUME_HXX