// Copyright (C) 2002-2012 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_SHADOW_VOLUME_SCENENODE_ #include "CShadowVolumeSceneNode.h" #include "ISceneManager.h" #include "IMesh.h" #include "IVideoDriver.h" #include "ICameraSceneNode.h" #include "SViewFrustum.h" #include "SLight.h" #include "os.h" namespace irr { namespace scene { //! constructor CShadowVolumeSceneNode::CShadowVolumeSceneNode(const IMesh* shadowMesh, ISceneNode* parent, ISceneManager* mgr, s32 id, bool zfailmethod, f32 infinity) : IShadowVolumeSceneNode(parent, mgr, id), AdjacencyDirtyFlag(true), ShadowMesh(0), IndexCount(0), VertexCount(0), ShadowVolumesUsed(0), Infinity(infinity), UseZFailMethod(zfailmethod), Optimization(ESV_SILHOUETTE_BY_POS) { #ifdef _DEBUG setDebugName("CShadowVolumeSceneNode"); #endif setShadowMesh(shadowMesh); setAutomaticCulling(scene::EAC_OFF); } //! destructor CShadowVolumeSceneNode::~CShadowVolumeSceneNode() { if (ShadowMesh) ShadowMesh->drop(); } void CShadowVolumeSceneNode::createShadowVolume(const core::vector3df& light, bool isDirectional) { SShadowVolume* svp = 0; core::aabbox3d<f32>* bb = 0; // builds the shadow volume and adds it to the shadow volume list. if (ShadowVolumes.size() > ShadowVolumesUsed) { // get the next unused buffer svp = &ShadowVolumes[ShadowVolumesUsed]; svp->set_used(0); bb = &ShadowBBox[ShadowVolumesUsed]; } else { ShadowVolumes.push_back(SShadowVolume()); svp = &ShadowVolumes.getLast(); ShadowBBox.push_back(core::aabbox3d<f32>()); bb = &ShadowBBox.getLast(); } svp->reallocate(IndexCount*5); ++ShadowVolumesUsed; // We use triangle lists Edges.set_used(IndexCount*2); u32 numEdges = 0; numEdges=createEdgesAndCaps(light, isDirectional, svp, bb); // for all edges add the near->far quads core::vector3df lightDir1(light*Infinity); core::vector3df lightDir2(light*Infinity); for (u32 i=0; i<numEdges; ++i) { const core::vector3df &v1 = Vertices[Edges[2*i+0]]; const core::vector3df &v2 = Vertices[Edges[2*i+1]]; if ( !isDirectional ) { lightDir1 = (v1 - light).normalize()*Infinity; lightDir2 = (v2 - light).normalize()*Infinity; } const core::vector3df v3(v1+lightDir1); const core::vector3df v4(v2+lightDir2); // Add a quad (two triangles) to the vertex list #ifdef _DEBUG if (svp->size() >= svp->allocated_size()-5) os::Printer::log("Allocation too small.", ELL_DEBUG); #endif svp->push_back(v1); svp->push_back(v2); svp->push_back(v3); svp->push_back(v2); svp->push_back(v4); svp->push_back(v3); } } // TODO. // Not sure what's going on. Either FaceData should mean the opposite and true should mean facing away from light // or I'm missing something else. Anyway - when not setting this then Shadows will look wrong on Burnings driver // while they seem to look OK on first view either way on other drivers. Only tested with z-fail so far. // Maybe errors only show up close to near/far plane on other drivers as otherwise the stencil-buffer-count // is probably ending up with same value anyway #define IRR_USE_REVERSE_EXTRUDED u32 CShadowVolumeSceneNode::createEdgesAndCaps(const core::vector3df& light, bool isDirectional, SShadowVolume* svp, core::aabbox3d<f32>* bb) { u32 numEdges=0; const u32 faceCount = IndexCount / 3; if(faceCount >= 1) bb->reset(Vertices[Indices[0]]); else bb->reset(0,0,0); // Check every face if it is front or back facing the light. core::vector3df lightDir0(light); core::vector3df lightDir1(light); core::vector3df lightDir2(light); for (u32 i=0; i<faceCount; ++i) { const core::vector3df v0 = Vertices[Indices[3*i+0]]; const core::vector3df v1 = Vertices[Indices[3*i+1]]; const core::vector3df v2 = Vertices[Indices[3*i+2]]; if ( !isDirectional ) { lightDir0 = (v0-light).normalize(); } #ifdef IRR_USE_REVERSE_EXTRUDED FaceData[i]=core::triangle3df(v2,v1,v0).isFrontFacing(lightDir0); // actually the back-facing polygons #else FaceData[i]=core::triangle3df(v0,v1,v2).isFrontFacing(lightDir0); #endif #if 0 // Useful for internal debugging & testing. Show all the faces in the light. if ( FaceData[i] ) { video::SMaterial m; m.Lighting = false; SceneManager->getVideoDriver()->setMaterial(m); #ifdef IRR_USE_REVERSE_EXTRUDED SceneManager->getVideoDriver()->draw3DTriangle(core::triangle3df(v0+lightDir0,v1+lightDir0,v2+lightDir0), irr::video::SColor(255,255, 0, 0)); #else SceneManager->getVideoDriver()->draw3DTriangle(core::triangle3df(v0-lightDir0,v1-lightDir0,v2-lightDir0), irr::video::SColor(255,255, 0, 0)); #endif } #endif if (UseZFailMethod && FaceData[i]) { #ifdef _DEBUG if (svp->size() >= svp->allocated_size()-5) os::Printer::log("Allocation too small.", ELL_DEBUG); #endif // add front cap from light-facing faces svp->push_back(v2); svp->push_back(v1); svp->push_back(v0); // add back cap if ( !isDirectional ) { lightDir1 = (v1-light).normalize(); lightDir2 = (v2-light).normalize(); } const core::vector3df i0 = v0+lightDir0*Infinity; const core::vector3df i1 = v1+lightDir1*Infinity; const core::vector3df i2 = v2+lightDir2*Infinity; svp->push_back(i0); svp->push_back(i1); svp->push_back(i2); bb->addInternalPoint(i0); bb->addInternalPoint(i1); bb->addInternalPoint(i2); } } // Create edges for (u32 i=0; i<faceCount; ++i) { // check all front facing faces if (FaceData[i] == true) { const u16 wFace0 = Indices[3*i+0]; const u16 wFace1 = Indices[3*i+1]; const u16 wFace2 = Indices[3*i+2]; if ( Optimization == ESV_NONE ) { // add edge v0-v1 Edges[2*numEdges+0] = wFace0; Edges[2*numEdges+1] = wFace1; ++numEdges; // add edge v1-v2 Edges[2*numEdges+0] = wFace1; Edges[2*numEdges+1] = wFace2; ++numEdges; // add edge v2-v0 Edges[2*numEdges+0] = wFace2; Edges[2*numEdges+1] = wFace0; ++numEdges; } else { const u16 adj0 = Adjacency[3*i+0]; const u16 adj1 = Adjacency[3*i+1]; const u16 adj2 = Adjacency[3*i+2]; // add edges if face is adjacent to back-facing face // or if no adjacent face was found if (adj0 == i || FaceData[adj0] == false) { // add edge v0-v1 Edges[2*numEdges+0] = wFace0; Edges[2*numEdges+1] = wFace1; ++numEdges; } if (adj1 == i || FaceData[adj1] == false) { // add edge v1-v2 Edges[2*numEdges+0] = wFace1; Edges[2*numEdges+1] = wFace2; ++numEdges; } if (adj2 == i || FaceData[adj2] == false) { // add edge v2-v0 Edges[2*numEdges+0] = wFace2; Edges[2*numEdges+1] = wFace0; ++numEdges; } } } } return numEdges; } void CShadowVolumeSceneNode::setShadowMesh(const IMesh* mesh) { if (ShadowMesh == mesh) return; if (ShadowMesh) ShadowMesh->drop(); ShadowMesh = mesh; if (ShadowMesh) { ShadowMesh->grab(); Box = ShadowMesh->getBoundingBox(); } } void CShadowVolumeSceneNode::updateShadowVolumes() { const u32 oldIndexCount = IndexCount; const u32 oldVertexCount = VertexCount; VertexCount = 0; IndexCount = 0; ShadowVolumesUsed = 0; const IMesh* const mesh = ShadowMesh; if (!mesh) return; // create as much shadow volumes as there are lights but // do not ignore the max light settings. const u32 lightCount = SceneManager->getVideoDriver()->getDynamicLightCount(); if (!lightCount) return; // calculate total amount of vertices and indices u32 i; u32 totalVertices = 0; u32 totalIndices = 0; const u32 bufcnt = mesh->getMeshBufferCount(); for (i=0; i<bufcnt; ++i) { const IMeshBuffer* buf = mesh->getMeshBuffer(i); if ( buf->getIndexType() == video::EIT_16BIT && buf->getPrimitiveType() == scene::EPT_TRIANGLES ) { totalIndices += buf->getIndexCount(); totalVertices += buf->getVertexCount(); } else { os::Printer::log("ShadowVolumeSceneNode only supports meshbuffers with 16 bit indices and triangles", ELL_WARNING); return; } } if ( totalIndices != (u32)(u16)totalIndices) { // We could switch to 32-bit indices, not much work and just bit of extra memory (< 192k) per shadow volume. // If anyone ever complains and really needs that just switch it. But huge shadows are usually a bad idea as they will be slow. os::Printer::log("ShadowVolumeSceneNode does not yet support shadowvolumes which need more than 16 bit indices", ELL_WARNING); return; } // allocate memory if necessary Vertices.set_used(totalVertices); Indices.set_used(totalIndices); FaceData.set_used(totalIndices / 3); // copy mesh // (could speed this up for static meshes by adding some user flag to prevents copying) for (i=0; i<bufcnt; ++i) { const IMeshBuffer* buf = mesh->getMeshBuffer(i); const u16* idxp = buf->getIndices(); const u16* idxpend = idxp + buf->getIndexCount(); for (; idxp!=idxpend; ++idxp) Indices[IndexCount++] = *idxp + VertexCount; const u32 vtxcnt = buf->getVertexCount(); for (u32 j=0; j<vtxcnt; ++j) Vertices[VertexCount++] = buf->getPosition(j); } // recalculate adjacency if necessary if (oldVertexCount != VertexCount || oldIndexCount != IndexCount || AdjacencyDirtyFlag) calculateAdjacency(); core::matrix4 matInv(Parent->getAbsoluteTransformation()); matInv.makeInverse(); core::matrix4 matTransp(Parent->getAbsoluteTransformation(), core::matrix4::EM4CONST_TRANSPOSED); const core::vector3df parentpos = Parent->getAbsolutePosition(); for (i=0; i<lightCount; ++i) { const video::SLight& dl = SceneManager->getVideoDriver()->getDynamicLight(i); if ( dl.Type == video::ELT_DIRECTIONAL ) { core::vector3df ldir(dl.Direction); matTransp.transformVect(ldir); createShadowVolume(ldir, true); } else { core::vector3df lpos(dl.Position); if (dl.CastShadows && fabs((lpos - parentpos).getLengthSQ()) <= (dl.Radius*dl.Radius*4.0f)) { matInv.transformVect(lpos); createShadowVolume(lpos, false); } } } } void CShadowVolumeSceneNode::setOptimization(ESHADOWVOLUME_OPTIMIZATION optimization) { if ( Optimization != optimization ) { Optimization = optimization; AdjacencyDirtyFlag = true; } } //! pre render method void CShadowVolumeSceneNode::OnRegisterSceneNode() { if (IsVisible) { SceneManager->registerNodeForRendering(this, scene::ESNRP_SHADOW); ISceneNode::OnRegisterSceneNode(); } } //! renders the node. void CShadowVolumeSceneNode::render() { video::IVideoDriver* driver = SceneManager->getVideoDriver(); if (!ShadowVolumesUsed || !driver) return; driver->setTransform(video::ETS_WORLD, Parent->getAbsoluteTransformation()); bool checkFarPlaneClipping = UseZFailMethod && !driver->queryFeature(video::EVDF_DEPTH_CLAMP); // get camera frustum converted to local coordinates when we have to check for far plane clipping SViewFrustum frust; if ( checkFarPlaneClipping ) { const irr::scene::ICameraSceneNode* camera = SceneManager->getActiveCamera(); if ( camera ) { frust = *camera->getViewFrustum(); core::matrix4 invTrans(Parent->getAbsoluteTransformation(), core::matrix4::EM4CONST_INVERSE); frust.transform(invTrans); } else checkFarPlaneClipping = false; } for (u32 i=0; i<ShadowVolumesUsed; ++i) { bool drawShadow = true; if (checkFarPlaneClipping) { // Disable shadows drawing, when back cap is behind of ZFar plane. // TODO: Using infinite projection matrices instead is said to work better // as then we wouldn't fail when the shadow clip the far plane. // I couldn't get it working (and neither anyone before me it seems). // Anyone who can figure it out is welcome to provide a patch. core::vector3df edges[8]; ShadowBBox[i].getEdges(edges); for(int j = 0; j < 8; ++j) { if (frust.planes[scene::SViewFrustum::VF_FAR_PLANE].classifyPointRelation(edges[j]) == core::ISREL3D_FRONT) { drawShadow = false; break; } } } if(drawShadow) driver->drawStencilShadowVolume(ShadowVolumes[i], UseZFailMethod, DebugDataVisible); else { // TODO: For some reason (not yet further investigated), Direct3D needs a call to drawStencilShadowVolume // even if we have nothing to draw here to set the renderstate into a StencilShadowMode. // If that's not done it has effect on further render calls. core::array<core::vector3df> triangles; driver->drawStencilShadowVolume(triangles, UseZFailMethod, DebugDataVisible); } } } //! returns the axis aligned bounding box of this node const core::aabbox3d<f32>& CShadowVolumeSceneNode::getBoundingBox() const { return Box; } //! Generates adjacency information based on mesh indices. void CShadowVolumeSceneNode::calculateAdjacency() { AdjacencyDirtyFlag = false; if ( Optimization == ESV_NONE ) { Adjacency.clear(); } else if ( Optimization == ESV_SILHOUETTE_BY_POS ) { Adjacency.set_used(IndexCount); // go through all faces and fetch their three neighbours for (u32 f=0; f<IndexCount; f+=3) { for (u32 edge = 0; edge<3; ++edge) { const core::vector3df& v1 = Vertices[Indices[f+edge]]; const core::vector3df& v2 = Vertices[Indices[f+((edge+1)%3)]]; // now we search an_O_ther _F_ace with these two // vertices, which is not the current face. u32 of; for (of=0; of<IndexCount; of+=3) { // only other faces if (of != f) { bool cnt1 = false; bool cnt2 = false; for (s32 e=0; e<3; ++e) { if (v1.equals(Vertices[Indices[of+e]])) cnt1=true; if (v2.equals(Vertices[Indices[of+e]])) cnt2=true; } // one match for each vertex, i.e. edge is the same if (cnt1 && cnt2) break; } } // no adjacent edges -> store face number, else store adjacent face if (of >= IndexCount) Adjacency[f + edge] = f/3; else Adjacency[f + edge] = of/3; } } } } } // end namespace scene } // end namespace irr #endif // _IRR_COMPILE_WITH_SHADOW_VOLUME_SCENENODE_