mirror of
https://github.com/minetest/irrlicht.git
synced 2024-12-27 00:17:30 +01:00
8310a3fbad
git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@6000 dfc29bdd-3216-0410-991c-e03cc46cb475
532 lines
14 KiB
C++
532 lines
14 KiB
C++
// 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_
|