// 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/3d/visual_collision_manager.h" #include "nel/3d/visual_collision_entity.h" #include "nel/3d/landscape.h" #include "nel/3d/camera_col.h" #include "nel/3d/shadow_map.h" #include "nel/3d/light.h" #include "nel/misc/common.h" using namespace std; using namespace NLMISC; namespace NL3D { // *************************************************************************** // Those blocks size are computed to be approximatively one block for 10 entities. const uint TileDescNodeAllocatorBlockSize= 40000; const uint PatchQuadBlockAllocatorBlockSize= 160; // For Mesh QuadGrid const uint MeshColQuadGridSize= 64; const float MeshColQuadGridEltSize= 20; // *************************************************************************** CVisualCollisionManager::CVisualCollisionManager() : _TileDescNodeAllocator(TileDescNodeAllocatorBlockSize), _PatchQuadBlockAllocator(PatchQuadBlockAllocatorBlockSize) { _Landscape= NULL; // Default. setSunContributionPower(0.5f, 0.5f); // init the mesh quadGrid _MeshQuadGrid.create(MeshColQuadGridSize, MeshColQuadGridEltSize); // valid id start at 1 _MeshIdPool= 1; _PlayerInside= false; _ShadowIndexBuffer.setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT); NL_SET_IB_NAME(_ShadowIndexBuffer, "CVisualCollisionManager"); } // *************************************************************************** CVisualCollisionManager::~CVisualCollisionManager() { _Landscape= NULL; } // *************************************************************************** void CVisualCollisionManager::setLandscape(CLandscape *landscape) { _Landscape= landscape; } // *************************************************************************** CVisualCollisionEntity *CVisualCollisionManager::createEntity() { return new CVisualCollisionEntity(this); } // *************************************************************************** void CVisualCollisionManager::deleteEntity(CVisualCollisionEntity *entity) { delete entity; } // *************************************************************************** CVisualTileDescNode *CVisualCollisionManager::newVisualTileDescNode() { return _TileDescNodeAllocator.allocate(); } // *************************************************************************** void CVisualCollisionManager::deleteVisualTileDescNode(CVisualTileDescNode *ptr) { _TileDescNodeAllocator.free(ptr); } // *************************************************************************** CPatchQuadBlock *CVisualCollisionManager::newPatchQuadBlock() { return _PatchQuadBlockAllocator.allocate(); } // *************************************************************************** void CVisualCollisionManager::deletePatchQuadBlock(CPatchQuadBlock *ptr) { _PatchQuadBlockAllocator.free(ptr); } // *************************************************************************** void CVisualCollisionManager::setSunContributionPower(float power, float maxThreshold) { NLMISC::clamp(power, 0.f, 1.f); for(uint i=0; i<256; i++) { float f= i/255.f; f = powf(f/maxThreshold, power); sint uf= (sint)floor(255*f); NLMISC::clamp(uf, 0, 255); _SunContributionLUT[i]= uf; } } // *************************************************************************** // *************************************************************************** // Camera collision // *************************************************************************** // *************************************************************************** // *************************************************************************** void CVisualCollisionManager::setPlayerInside(bool state) { _PlayerInside= state; } // *************************************************************************** float CVisualCollisionManager::getCameraCollision(const CVector &start, const CVector &end, float radius, bool cone) { float minCol= 1; // try col with landscape if(_Landscape) { minCol= _Landscape->getCameraCollision(start, end, radius, cone); } // try col with meshes CCameraCol camCol; camCol.build(start, end, radius, cone); _MeshQuadGrid.select(camCol.getBBox().getMin(), camCol.getBBox().getMax()); // try to intersect with any instance meshs CQuadGrid::CIterator it; for(it= _MeshQuadGrid.begin();it!=_MeshQuadGrid.end();it++) { // Skip this mesh according to special flag (IBBR problem) if((*it)->AvoidCollisionWhenPlayerInside && _PlayerInside) continue; if((*it)->AvoidCollisionWhenPlayerOutside && !_PlayerInside) continue; // collide float meshCol= (*it)->getCameraCollision(camCol); // Keep only yhe smallest value minCol= min(minCol, meshCol); } return minCol; } // *************************************************************************** bool CVisualCollisionManager::getRayCollision(const NLMISC::CVector &start, const NLMISC::CVector &end, bool landscapeOnly) { // try col with landscape if(_Landscape) { if( _Landscape->getRayCollision(start, end) < 1.f) return true; } // try col with meshes, if wanted if(!landscapeOnly) { CCameraCol camCol; camCol.buildRay(start, end); _MeshQuadGrid.select(camCol.getBBox().getMin(), camCol.getBBox().getMax()); // try to intersect with any instance meshs CQuadGrid::CIterator it; for(it= _MeshQuadGrid.begin();it!=_MeshQuadGrid.end();it++) { // Skip this mesh according to special flag (IBBR problem) if((*it)->AvoidCollisionWhenPlayerInside && _PlayerInside) continue; if((*it)->AvoidCollisionWhenPlayerOutside && !_PlayerInside) continue; // collide float meshCol= (*it)->getCameraCollision(camCol); if(meshCol<1.f) return true; } } return false; } // *************************************************************************** float CVisualCollisionManager::CMeshInstanceCol::getCameraCollision(CCameraCol &camCol) { // if mesh still present (else it s may be an error....) if(Mesh) { // first test if intersect with the bboxes if(!camCol.getBBox().intersect(WorldBBox)) return 1; // get the collision with the mesh return Mesh->getCameraCollision(WorldMatrix, camCol); } else // no collision return 1; } // *************************************************************************** uint CVisualCollisionManager::addMeshInstanceCollision(CVisualCollisionMesh *mesh, const CMatrix &instanceMatrix, bool avoidCollisionWhenInside, bool avoidCollisionWhenOutside) { if(!mesh) return 0; // allocate a new id uint32 id= _MeshIdPool++; // insert in map CMeshInstanceCol &meshInst= _Meshs[id]; // Build the col mesh instance meshInst.Mesh= mesh; meshInst.WorldMatrix= instanceMatrix; meshInst.WorldBBox= mesh->computeWorldBBox(instanceMatrix); meshInst.AvoidCollisionWhenPlayerInside= avoidCollisionWhenInside; meshInst.AvoidCollisionWhenPlayerOutside= avoidCollisionWhenOutside; meshInst.ID = id; // insert in quadGrid meshInst.QuadGridIt= _MeshQuadGrid.insert(meshInst.WorldBBox.getMin(), meshInst.WorldBBox.getMax(), &meshInst); return id; } // *************************************************************************** void CVisualCollisionManager::removeMeshCollision(uint id) { // find in map TMeshColMap::iterator it= _Meshs.find(id); if(it!=_Meshs.end()) { // remove from the quadgrid _MeshQuadGrid.erase(it->second.QuadGridIt); // remove from the map _Meshs.erase(it); } } // *************************************************************************** void CVisualCollisionManager::receiveShadowMap(IDriver *drv, CShadowMap *shadowMap, const CVector &casterPos, CMaterial &shadowMat, CShadowMapProjector &smp) { // Build a shadow context CVisualCollisionMesh::CShadowContext shadowContext(shadowMat, _ShadowIndexBuffer, smp); shadowContext.Driver= drv; shadowContext.ShadowMap= shadowMap; shadowContext.CasterPos= casterPos; shadowContext.ShadowWorldBB= shadowMap->LocalBoundingBox; shadowContext.ShadowWorldBB.setCenter(shadowContext.ShadowWorldBB.getCenter() + casterPos); // bkup shadowColor CRGBA shadowColor= shadowContext.ShadowMaterial.getColor(); // try to intersect with any instance meshs in quadgrid _MeshQuadGrid.select(shadowContext.ShadowWorldBB.getMin(), shadowContext.ShadowWorldBB.getMax()); CQuadGrid::CIterator it; bool lightSetupFaked= false; for(it= _MeshQuadGrid.begin();it!=_MeshQuadGrid.end();it++) { /* NB: this is a collision flag, but the problem is exactly the same for shadows: If the player is outside, then an "outside entity" can cast shadow only on "outside mesh" */ // Skip this mesh according to special flag (IBBR problem) if((*it)->AvoidCollisionWhenPlayerInside && _PlayerInside) continue; if((*it)->AvoidCollisionWhenPlayerOutside && !_PlayerInside) continue; /* Avoid BackFace Shadowing on CVisualCollisionMesh. To do this simply and smoothly, use a trick: Use a FakeLight: a directional light in reverse direction of the shadow direction The material is now a lighted material, with Emissive= ShadowColor so we get correct dark color, for frontfaces agst shadow direction (which are actually backfaces agst FakeLight) Diffuse= White so shadow is off, for pure backFace agst shadow direction (which are actually frontFaces agst FakeLight) NB: CShadowMapManager::renderProject() has called CRenderTrav::resetLighting() and enableLight(0, true) so we get clean setup here */ // need to do it only one time per shadowMap reception if(!lightSetupFaked) { lightSetupFaked= true; // Build the light direction as the opposite to shadow direction (in LocalProjectionMatrix.getJ()) CLight fakeLight; fakeLight.setupDirectional(CRGBA::Black, CRGBA::White, CRGBA::Black, -shadowMap->LocalProjectionMatrix.getJ()); drv->setLight(0, fakeLight); // setup the material shadowContext.ShadowMaterial.setLighting(true, shadowColor, CRGBA::Black, CRGBA::White, CRGBA::Black); } // cast shadow (*it)->receiveShadowMap(shadowContext); } // if the material has been faked, reset if(lightSetupFaked) { shadowContext.ShadowMaterial.setLighting(false); shadowContext.ShadowMaterial.setColor(shadowColor); } } // *************************************************************************** void CVisualCollisionManager::CMeshInstanceCol::receiveShadowMap(const CVisualCollisionMesh::CShadowContext &shadowContext) { // if mesh still present (else it s may be an error....) if(Mesh) { // first test if intersect with the bboxes if(!shadowContext.ShadowWorldBB.intersect(WorldBBox)) return; // get the collision with the mesh Mesh->receiveShadowMap(WorldMatrix, shadowContext); } } // *************************************************************************** void CVisualCollisionManager::getMeshs(const NLMISC::CAABBox &aabbox, std::vector &dest) { _MeshQuadGrid.select(aabbox.getMin(), aabbox.getMax()); dest.clear(); CQuadGrid::CIterator it = _MeshQuadGrid.begin(); CQuadGrid::CIterator endIt = _MeshQuadGrid.end(); while (it != endIt) { if ((*it)->WorldBBox.intersect(aabbox)) { CMeshInstanceColInfo infos; infos.Mesh = (*it)->Mesh; infos.ID = (*it)->ID; infos.WorldBBox = &((*it)->WorldBBox); infos.WorldMatrix = &((*it)->WorldMatrix); dest.push_back(infos); } ++ it; } } } // NL3D