// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/misc/hierarchical_timer.h" #include "nel/misc/polygon.h" #include "nel/3d/shadow_poly_receiver.h" #include "nel/3d/shadow_map.h" #include "nel/3d/driver.h" #include "nel/3d/camera_col.h" using namespace std; using namespace NLMISC; namespace NL3D { // *************************************************************************** CShadowPolyReceiver::CShadowPolyReceiver(uint quadGridSize, float quadGridCellSize) { _Vertices.reserve(64); _FreeVertices.reserve(64); _FreeTriangles.reserve(64); _Triangles.reserve(64); _TriangleGrid.create(quadGridSize, quadGridCellSize); _VB.setVertexFormat(CVertexBuffer::PositionFlag); _VB.setName("CShadowPolyReceiver"); // lock volatile, to avoid cpu stall when rendering multiple shadows in the same polyReceiver _VB.setPreferredMemory(CVertexBuffer::RAMVolatile, false); _RenderTriangles.setPreferredMemory(CIndexBuffer::RAMVolatile, false); _RenderTriangles.setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT); NL_SET_IB_NAME(_RenderTriangles, "CShadowPolyReceiver"); } // *************************************************************************** uint CShadowPolyReceiver::addTriangle(const NLMISC::CTriangle &tri) { uint id; // Look for a free triangle entry. if(_FreeTriangles.empty()) { _Triangles.push_back(TTriangleGrid::CIterator()); id= _Triangles.size()-1; // enlarge render size. _RenderTriangles.setNumIndexes(_Triangles.size() * 3); } else { id= _FreeTriangles.back(); _FreeTriangles.pop_back(); } // Allocate vertices, reusing same ones. CTriangleId triId; CVector v[3]; v[0]= tri.V0; v[1]= tri.V1; v[2]= tri.V2; for(uint i=0;i<3;i++) { // Find the vertex in the map. TVertexMap::iterator it; it= _VertexMap.find(v[i]); // if not found, allocate it if(it==_VertexMap.end()) { triId.Vertex[i]= allocateVertex(v[i]); } // else get its id else { triId.Vertex[i]= it->second; } // increment the reference of this vertex incVertexRefCount(triId.Vertex[i]); } // Insert the triangle in the quadGrid. CAABBox bb; bb.setCenter(tri.V0); bb.extend(tri.V1); bb.extend(tri.V2); // insert in QuadGrid and store iterator for future remove _Triangles[id]= _TriangleGrid.insert(bb.getMin(), bb.getMax(), triId); return id; } // *************************************************************************** void CShadowPolyReceiver::removeTriangle(uint id) { nlassert(id<_Triangles.size()); // Must not be NULL iterator. nlassert(_Triangles[id]!=_TriangleGrid.end()); // Release Vertices const CTriangleId &triId= *_Triangles[id]; releaseVertex(triId.Vertex[0]); releaseVertex(triId.Vertex[1]); releaseVertex(triId.Vertex[2]); // Delete Triangle. _TriangleGrid.erase(_Triangles[id]); _Triangles[id]= _TriangleGrid.end(); // Append to free list. _FreeTriangles.push_back(id); } // *************************************************************************** uint CShadowPolyReceiver::allocateVertex(const CVector &v) { uint id; // if not valid double, will crash cause map crash when float are not valid nlassert(isValidDouble(v.x) && isValidDouble(v.y) && isValidDouble(v.z)); // Look for a free vertex entry. if(_FreeVertices.empty()) { // Add the vertex, and init refCount to 0. _Vertices.push_back(v); id= _Vertices.size()-1; // Resize the VBuffer at max possible _VB.setNumVertices(_Vertices.size()); } else { id= _FreeVertices.back(); _FreeVertices.pop_back(); // init entry _Vertices[id]= v; _Vertices[id].RefCount= 0; } // insert in the map (should not be here) _VertexMap.insert( make_pair(v, id) ); return id; } // *************************************************************************** void CShadowPolyReceiver::releaseVertex(uint id) { nlassert(id<_Vertices.size()); // dec ref nlassert(_Vertices[id].RefCount>0); _Vertices[id].RefCount--; // no more used? if(_Vertices[id].RefCount==0) { // Free it. _FreeVertices.push_back(id); // Remove it from map. TVertexMap::iterator it= _VertexMap.find(_Vertices[id]); if (it!=_VertexMap.end()) _VertexMap.erase(it); else nlwarning("vertex %u doesn't exist in _VertexMap, this should not happen", id); } } // *************************************************************************** void CShadowPolyReceiver::incVertexRefCount(uint id) { nlassert(id<_Vertices.size()); nlassert(_Vertices[id].RefCount < NL3D_SPR_MAX_REF_COUNT); _Vertices[id].RefCount++; } // *************************************************************************** inline void CShadowPolyReceiver::renderSelection(IDriver *drv, CMaterial &shadowMat, const CShadowMap *shadowMap, const CVector &casterPos, const CVector &vertDelta) { uint i, j; // For all triangles, reset vertices flags. TTriangleGrid::CIterator it; for(it=_TriangleGrid.begin();it!=_TriangleGrid.end();it++) { CTriangleId &triId= *it; for(i=0;i<3;i++) { _Vertices[triId.Vertex[i]].Flags= 0; _Vertices[triId.Vertex[i]].VBIdx= -1; } } // Compute the world Clip Volume static std::vector worldClipPlanes; CMatrix worldMat; // set -casterPos, because to transform a plane, we must do plane * M-1 worldMat.setPos(-casterPos); // Allow max bits of planes clip. worldClipPlanes.resize(min((uint)shadowMap->LocalClipPlanes.size(), (uint)NL3D_SPR_NUM_CLIP_PLANE)); // Transform into world for(i=0;iLocalClipPlanes[i] * worldMat; } uint currentTriIdx= 0; { // Volatile: must resize before lock _VB.setNumVertices(_Vertices.size()); _RenderTriangles.setNumIndexes(_Triangles.size() * 3); // lock volatile, to avoid cpu stall CVertexBufferReadWrite vba; _VB.lock(vba); CIndexBufferReadWrite iba; _RenderTriangles.lock (iba); TIndexType *triPtr = (TIndexType *) iba.getPtr(); // For All triangles, clip them. uint currentVbIdx= 0; for(it=_TriangleGrid.begin();it!=_TriangleGrid.end();it++) { CTriangleId &triId= *it; uint triFlag= NL3D_SPR_NUM_CLIP_PLANE_MASK; // for all vertices, clip them for(i=0;i<3;i++) { uint vid= triId.Vertex[i]; uint vertexFlags= _Vertices[vid].Flags; // if this vertex is still not computed if(!vertexFlags) { // For all planes of the Clip Volume, clip this vertex. for(j=0;j 0; vertexFlags|= ((uint)out)<activeVertexBuffer(_VB); drv->activeIndexBuffer(_RenderTriangles); drv->renderTriangles(shadowMat, 0, currentTriIdx/3); // TestYoyo. Show in Red triangles selected /*static CMaterial tam; tam.initUnlit(); tam.setColor(CRGBA(255,0,0,128)); tam.setZFunc(CMaterial::always); tam.setZWrite(false); tam.setBlend(true); tam.setBlendFunc(CMaterial::srcalpha, CMaterial::invsrcalpha); tam.setDoubleSided(true); drv->renderTriangles(tam, &_RenderTriangles[0], currentTriIdx/3);*/ } // *************************************************************************** void CShadowPolyReceiver::computeClippedTrisWithPolyClip(const CShadowMap *shadowMap, const CVector &casterPos, const CVector &vertDelta, const NLMISC::CPolygon2D &poly, std::vector &destTris, bool colorUpfacingVertices) { nlctassert(sizeof(CRGBAVertex) == 12 + 4); // ensure padding works as expected destTris.clear(); selectPolygon(poly); if (_TriangleGrid.begin() == _TriangleGrid.end()) return; uint i, j; static std::vector vertexNormals; // normal for each vertex static std::vector vertexNormalsUndefined; // normal for each vertex static std::vector visibleTris; // triangles that passed the clip vertexNormals.resize(_Vertices.size()); vertexNormalsUndefined.resize(_Vertices.size(), 1); visibleTris.clear(); // For all triangles, reset vertices flags. TTriangleGrid::CIterator it; for(it=_TriangleGrid.begin();it!=_TriangleGrid.end();it++) { CTriangleId &triId= *it; for(i=0;i<3;i++) { _Vertices[triId.Vertex[i]].Flags= 0; } } // Compute the world Clip Volume static std::vector worldClipPlanes; CMatrix worldMat; // set -casterPos, because to transform a plane, we must do plane * M-1 worldMat.setPos(-casterPos); // Allow max bits of planes clip. worldClipPlanes.resize(min((uint)shadowMap->LocalClipPlanes.size(), (uint)NL3D_SPR_NUM_CLIP_PLANE)); // Transform into world for(i=0;iLocalClipPlanes[i] * worldMat; } static NLMISC::CPolygon clippedTri; CVector triNormal; // For All triangles, clip them. for(it=_TriangleGrid.begin();it!=_TriangleGrid.end();it++) { CTriangleId &triId= *it; uint triFlag= NL3D_SPR_NUM_CLIP_PLANE_MASK; CVectorId *vid[3] = { &_Vertices[triId.Vertex[0]], &_Vertices[triId.Vertex[1]], &_Vertices[triId.Vertex[2]] }; // for all vertices, clip them for(i=0;i<3;i++) { // if this vertex is still not computed if(!vid[i]->Flags) { // For all planes of the Clip Volume, clip this vertex. for(j=0;j 0; vid[i]->Flags |= ((uint)out)<Flags |= NL3D_SPR_NUM_CLIP_PLANE_SHIFT; } vertexNormalsUndefined[triId.Vertex[i]] = 1; // invalidate normal for next pass // And all vertex bits. triFlag&= vid[i]->Flags; } // if triangle not clipped, do finer clip then add resulting triangles if( (triFlag & NL3D_SPR_NUM_CLIP_PLANE_MASK)==0 ) { visibleTris.push_back(&triId); } } uint numVisibleTris = visibleTris.size(); // compute normals if needed if (colorUpfacingVertices) { for (uint triIndex = 0; triIndex < numVisibleTris; ++triIndex) { CTriangleId &triId= *visibleTris[triIndex]; CVectorId *vid[3] = { &_Vertices[triId.Vertex[0]], &_Vertices[triId.Vertex[1]], &_Vertices[triId.Vertex[2]] }; // compute normal for this tri triNormal = ((*vid[1] - *vid[0]) ^ (*vid[2] - *vid[0])).normed(); // for all vertices, clip them for(i=0;i<3;i++) { sint vertIndex = triId.Vertex[i]; if (vertexNormalsUndefined[vertIndex]) { vertexNormals[vertIndex] = triNormal; vertexNormalsUndefined[vertIndex] = 0; } else { vertexNormals[vertIndex] += triNormal; } } } } if (colorUpfacingVertices) { for (uint triIndex = 0; triIndex < numVisibleTris; ++triIndex) { CTriangleId &triId= *visibleTris[triIndex]; // use CPlane 'uv cliping', store 'color' in 'U' static std::vector corner0; static std::vector corner1; static std::vector uv0; static std::vector uv1; uv0.resize(3 + worldClipPlanes.size()); uv1.resize(3 + worldClipPlanes.size()); corner0.resize(3 + worldClipPlanes.size()); corner1.resize(3 + worldClipPlanes.size()); // corner0[0] = _Vertices[triId.Vertex[0]]; corner0[1] = _Vertices[triId.Vertex[1]]; corner0[2] = _Vertices[triId.Vertex[2]]; // uv0[0].set(vertexNormals[triId.Vertex[0]].z >= 0.f ? 1.f : 0.f, 0.f); uv0[1].set(vertexNormals[triId.Vertex[1]].z >= 0.f ? 1.f : 0.f, 0.f); uv0[2].set(vertexNormals[triId.Vertex[2]].z >= 0.f ? 1.f : 0.f, 0.f); // sint numVerts = 3; // for (uint k = 0; k < worldClipPlanes.size(); ++k) { numVerts = worldClipPlanes[k].clipPolygonBack(&corner0[0], &uv0[0], &corner1[0], &uv1[0], numVerts); nlassert(numVerts <= (sint) corner1.size()); if (numVerts == 0) break; uv0.swap(uv1); corner0.swap(corner1); } for (sint k = 0; k < numVerts - 2; ++k) { uint8 alpha[3] = { (uint8) (255.f * uv0[0].U), (uint8) (255.f * uv0[k + 1].U), (uint8) (255.f * uv0[k + 2].U) }; if (alpha[0] != 0 || alpha[1] != 0 || alpha[2] != 0) { destTris.push_back(CRGBAVertex(corner0[0] + vertDelta, CRGBA(255, 255, 255, alpha[0]))); destTris.push_back(CRGBAVertex(corner0[k + 1] + vertDelta, CRGBA(255, 255, 255, alpha[1]))); destTris.push_back(CRGBAVertex(corner0[k + 2] + vertDelta, CRGBA(255, 255, 255, alpha[2]))); } } } } else { for (uint triIndex = 0; triIndex < numVisibleTris; ++triIndex) { CTriangleId &triId= *visibleTris[triIndex]; clippedTri.Vertices.resize(3); clippedTri.Vertices[0] = _Vertices[triId.Vertex[0]]; clippedTri.Vertices[1] = _Vertices[triId.Vertex[1]]; clippedTri.Vertices[2] = _Vertices[triId.Vertex[2]]; clippedTri.clip(worldClipPlanes); if (clippedTri.Vertices.size() >= 3) { for(uint k = 0; k < clippedTri.Vertices.size() - 2; ++k) { destTris.push_back(CRGBAVertex(clippedTri.Vertices[0] + vertDelta, CRGBA::White)); destTris.push_back(CRGBAVertex(clippedTri.Vertices[k + 1] + vertDelta, CRGBA::White)); destTris.push_back(CRGBAVertex(clippedTri.Vertices[k + 2] + vertDelta, CRGBA::White)); } } } } } // *************************************************************************** void CShadowPolyReceiver::render(IDriver *drv, CMaterial &shadowMat, const CShadowMap *shadowMap, const CVector &casterPos, const CVector &vertDelta) { // **** Fill Triangles that are hit by the Caster // First select with quadGrid CAABBox worldBB; worldBB= shadowMap->LocalBoundingBox; worldBB.setCenter(worldBB.getCenter() + casterPos); _TriangleGrid.select(worldBB.getMin(), worldBB.getMax()); if (_TriangleGrid.begin() == _TriangleGrid.end()) return; renderSelection(drv, shadowMat, shadowMap, casterPos, vertDelta); } // *************************************************************************** void CShadowPolyReceiver::selectPolygon(const NLMISC::CPolygon2D &poly) { static TTriangleGrid::TSelectionShape selectionShape; _TriangleGrid.buildSelectionShape(selectionShape, poly); _TriangleGrid.select(selectionShape); } // *************************************************************************** void CShadowPolyReceiver::renderWithPolyClip(IDriver *drv, CMaterial &shadowMat, const CShadowMap *shadowMap, const CVector &casterPos, const CVector &vertDelta, const NLMISC::CPolygon2D &poly) { selectPolygon(poly); renderSelection(drv, shadowMat, shadowMap, casterPos, vertDelta); } // *************************************************************************** float CShadowPolyReceiver::getCameraCollision(const CVector &start, const CVector &end, TCameraColTest testType, float radius) { // **** build the camera collision info CCameraCol camCol; if(testType==CameraColSimpleRay) camCol.buildRay(start, end); else camCol.build(start, end, radius, testType==CameraColCone); // select with quadGrid if(testType==CameraColSimpleRay) { _TriangleGrid.selectRay(start, end); } else { _TriangleGrid.select(camCol.getBBox().getMin(), camCol.getBBox().getMax()); } // **** For all triangles, test if intersect the camera collision TTriangleGrid::CIterator it; float sqrMinDist= FLT_MAX; for(it=_TriangleGrid.begin();it!=_TriangleGrid.end();it++) { CTriangleId &triId= *it; camCol.minimizeDistanceAgainstTri( _Vertices[triId.Vertex[0]], _Vertices[triId.Vertex[1]], _Vertices[triId.Vertex[2]], sqrMinDist); } // **** return the collision found, between [0,1] if(sqrMinDist == FLT_MAX) return 1; else { float f= 1; float d= camCol.getRayLen(); if(d>0) { f= sqrtf(sqrMinDist) / d; f= min(f, 1.f); } return f; } } } // NL3D