// 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/landscape.h" #include "nel/3d/landscape_model.h" #include "nel/misc/bsphere.h" #include "nel/3d/texture_file.h" #include "nel/3d/texture_far.h" #include "nel/3d/landscape_profile.h" #include "nel/3d/height_map.h" #include "nel/3d/tile_noise_map.h" #include "nel/3d/vegetable_manager.h" #include "nel/3d/vegetable.h" #include "nel/3d/landscape_vegetable_block.h" #include "nel/misc/fast_floor.h" #include "nel/3d/tile_vegetable_desc.h" #include "nel/3d/texture_dlm.h" #include "nel/3d/patchdlm_context.h" #include "nel/misc/hierarchical_timer.h" #include "nel/3d/scene.h" #include "nel/3d/vertex_program.h" using namespace NLMISC; using namespace std; namespace NL3D { // *************************************************************************** /* Target is 20K faces in frustum. So 80K faces at same time So 160K elements (bin tree). A good BlockSize (in my opinion) is EstimatedMaxSize / 10, to have less memory leak as possible, and to make not so many system allocation. NL3D_TESSRDR_ALLOC_BLOCKSIZE is 2 times less, because elements are in Far zone or in Near zone only (approx same size...) */ #define NL3D_TESS_ALLOC_BLOCKSIZE 16000 #define NL3D_TESSRDR_ALLOC_BLOCKSIZE 8000 // *************************************************************************** // This value is important for the precision of the priority list #define NL3D_REFINE_PLIST_DIST_STEP 0.0625 /* This value is important, because faces will be inserted at maximum at this entry in the priority list. If not so big (eg 50 meters), a big bunch of faces may be inserted in this entry, which may cause slow down sometimes, when all this bunch comes to 0 in the priority list. To avoid such a thing, see CTessFacePriorityList::init(), and use of NL3D_REFINE_PLIST_DIST_MAX_MOD. Here, distMax= 2048*0.0625= 128 */ #define NL3D_REFINE_PLIST_NUM_ENTRIES 2048 #define NL3D_REFINE_PLIST_DIST_MAX_MOD 0.7f // For the Split priority list only, numbers of quadrants. MergeList has 0 quadrants. #define NL3D_REFINE_PLIST_SPLIT_NUMQUADRANT 16 /* OverHead size of one RollingTable of priority list is 8 * (NL3D_REFINE_PLIST_NUM_ENTRIES) So here, it is "only" 16K. Since we have 2 Priority list and 16 quadrants for the split one, the total overhead is 18*12.8= 288K */ // *************************************************************************** // Size (in cases) of the quadgrid. must be e power of 2. const uint CLandscape::_PatchQuadGridSize= 128; // Size of a case of the quadgrid. const float CLandscape::_PatchQuadGridEltSize= 16; // *************************************************************************** // Bitmap Cross class CTextureCross : public ITexture { public: /** * Generate the texture * \author Stephane Coutelas * \date 2000 */ virtual void doGenerate(bool /* async */) { // Resize resize (16, 16); // Cross static const uint32 cross[16*16]= { // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, }; // Null memcpy (&_Data[0][0], cross, 16*16*4); } // Dummy serial... virtual void serial(NLMISC::IStream &/* f */) throw(NLMISC::EStream) {nlstop;} NLMISC_DECLARE_CLASS(CTextureCross); }; // *************************************************************************** const char *EBadBind::what() const throw() { sint numErr= 0; const sint NErrByLines= 4; _Output= "Landscape Bind Error in (3DSMax indices!! (+1) ): "; std::list::const_iterator it; for(it= BindErrors.begin();it!=BindErrors.end(); it++, numErr++) { char tmp[256]; sint x= it->ZoneId & 255; sint y= it->ZoneId >> 8; sprintf(tmp, "zone%3d_%c%c.patch%3d; ", y+1, (char)('A'+(x/26)), (char)('A'+(x%26)), it->PatchId+1); if( (numErr%NErrByLines) == 0) _Output+= "\n"; _Output+= tmp; } return _Output.c_str(); } // *************************************************************************** // Init BlockAllcoator with standard BlockMemory. CLandscape::CLandscape() : TessFaceAllocator(NL3D_TESS_ALLOC_BLOCKSIZE), TessVertexAllocator(NL3D_TESS_ALLOC_BLOCKSIZE), TessNearVertexAllocator(NL3D_TESSRDR_ALLOC_BLOCKSIZE), TessFarVertexAllocator(NL3D_TESSRDR_ALLOC_BLOCKSIZE), TileMaterialAllocator(NL3D_TESSRDR_ALLOC_BLOCKSIZE), TileFaceAllocator(NL3D_TESSRDR_ALLOC_BLOCKSIZE), _Far0VB(CLandscapeVBAllocator::Far0, "LandscapeFar0VB"), _Far1VB(CLandscapeVBAllocator::Far1, "LandscapeFar1VB"), _TileVB(CLandscapeVBAllocator::Tile, "LandscapeTileVB") { OwnerModel = NULL; // Init the Tile Infos with Max TileId TileInfos.resize(NL3D::NbTilesMax, NULL); // Far texture not initialized till initTileBanks is not called _FarInitialized=false; // Init far lighting with White/black setupStaticLight (CRGBA(255,255,255), CRGBA(0,0,0), 1.f); // Default material for pointLights _PointLightDiffuseMaterial= CRGBA::White; _FarTransition= 10; // 10 meters. _TileDistNear=100.f; _Threshold= 0.001f; _RefineMode=true; _TileMaxSubdivision= 0; _NFreeLightMaps= 0; // By default Automatic light comes from up. _AutomaticLighting = false; _AutomaticLightDir= -CVector::K; // By default, noise is enabled. _NoiseEnabled= true; // By default, we compute Geomorph and Alpha in software. _VertexShaderOk= false; _VPThresholdChange= false; _RenderMustRefillVB= false; // priority list. _MustRefineAllAtNextRefine= true; _SplitPriorityList.init(NL3D_REFINE_PLIST_DIST_STEP, NL3D_REFINE_PLIST_NUM_ENTRIES, NL3D_REFINE_PLIST_DIST_MAX_MOD, NL3D_REFINE_PLIST_SPLIT_NUMQUADRANT); // See updateRefine* Doc in tesselation.cpp for why the merge list do not need quadrants. _MergePriorityList.init(NL3D_REFINE_PLIST_DIST_STEP, NL3D_REFINE_PLIST_NUM_ENTRIES, NL3D_REFINE_PLIST_DIST_MAX_MOD, 0); // just for getTesselatedPos to work properly. _OldRefineCenter= CVector::Null; // create / Init the vegetable manager. _VegetableManager= new CVegetableManager(NL3D_LANDSCAPE_VEGETABLE_MAX_AGP_VERTEX_UNLIT, NL3D_LANDSCAPE_VEGETABLE_MAX_AGP_VERTEX_LIGHTED); // Init vegetable setup. _VegetableManagerEnabled= false; _DriverOkForVegetable= false; // default global vegetable color, used for dynamic lighting only (arbitrary). _DLMGlobalVegetableColor.set(180, 180, 180); _PZBModelPosition= CVector::Null; // Default: no updateLighting. _ULFrequency= 0; _ULPrecTimeInit= false; // Default: no textureFar created. _ULTotalFarPixels= 0; _ULFarPixelsToUpdate= 0; _ULRootTextureFar= NULL; // Default: no patch created _ULTotalNearPixels= 0; _ULNearPixelsToUpdate= 0; _ULRootNearPatch= NULL; _ULNearCurrentTessBlockId= 0; // Dynamic Lighting. _TextureDLM= new CTextureDLM(NL3D_LANDSCAPE_DLM_WIDTH, NL3D_LANDSCAPE_DLM_HEIGHT); _PatchDLMContextList= new CPatchDLMContextList; _DLMMaxAttEnd= 30.f; CLandscapeGlobals::PassTriArray.setFormat(NL_LANDSCAPE_INDEX_FORMAT); // Alloc some global space for tri rendering. if( CLandscapeGlobals::PassTriArray.getNumIndexes() < 1000 ) CLandscapeGlobals::PassTriArray.setNumIndexes( 1000 ); // set volatile index buffer to avoid stalls CLandscapeGlobals::PassTriArray.setPreferredMemory(CIndexBuffer::RAMVolatile, false); _LockCount = 0; _TextureTileCategory= new ITexture::CTextureCategory("LANDSCAPE TILES"); _TextureFarCategory= new ITexture::CTextureCategory("LANDSCAPE FAR"); _TextureNearCategory= new ITexture::CTextureCategory("LANDSCAPE LIGHTMAP NEAR"); } // *************************************************************************** CLandscape::~CLandscape() { clear(); // release the VegetableManager. delete _VegetableManager; _VegetableManager= NULL; // Dynamic Lighting. // smartPtr delete _TextureDLM= NULL; delete _PatchDLMContextList; _PatchDLMContextList= NULL; } // *************************************************************************** void CLandscape::init() { // Fill Far mat. // Must init his BlendFunction here!!! becaus it switch between blend on/off during rendering. FarMaterial.initUnlit(); FarMaterial.setSrcBlend(CMaterial::srcalpha); FarMaterial.setDstBlend(CMaterial::invsrcalpha); // FarMaterial: pass trhough Alpha from diffuse. FarMaterial.texEnvOpAlpha(0, CMaterial::Replace); FarMaterial.texEnvArg0Alpha(0, CMaterial::Diffuse, CMaterial::SrcAlpha); FarMaterial.texEnvOpAlpha(1, CMaterial::Replace); FarMaterial.texEnvArg0Alpha(1, CMaterial::Diffuse, CMaterial::SrcAlpha); // FarMaterial: Add RGB from static lightmap and dynamic lightmap FarMaterial.texEnvOpRGB(0, CMaterial::Replace); FarMaterial.texEnvArg0RGB(0, CMaterial::Texture, CMaterial::SrcColor); FarMaterial.texEnvOpRGB(1, CMaterial::Add); FarMaterial.texEnvArg0RGB(1, CMaterial::Texture, CMaterial::SrcColor); FarMaterial.texEnvArg1RGB(1, CMaterial::Previous, CMaterial::SrcColor); // Init material for tile. TileMaterial.initUnlit(); // init quadGrid. _PatchQuadGrid.create(_PatchQuadGridSize, _PatchQuadGridEltSize); } // *************************************************************************** void CLandscape::setThreshold (float thre) { thre= max(thre, 0.f); if(thre != _Threshold) { _Threshold= thre; _VPThresholdChange= true; // force refine all at next refine _MustRefineAllAtNextRefine= true; } } // *************************************************************************** void CLandscape::setTileNear (float tileNear) { tileNear= max(tileNear, _FarTransition); if(tileNear!=_TileDistNear) { _TileDistNear= tileNear; resetRenderFarAndDeleteVBFV(); // force refine all at next refine _MustRefineAllAtNextRefine= true; } } // *************************************************************************** void CLandscape::setTileMaxSubdivision (uint tileDiv) { nlassert(tileDiv<=4); if(tileDiv!=_TileMaxSubdivision) { _TileMaxSubdivision= tileDiv; // Force at Tile==0. Nex refine will split correctly. forceMergeAtTileLevel(); } } // *************************************************************************** uint CLandscape::getTileMaxSubdivision () { return _TileMaxSubdivision; } // *************************************************************************** void CLandscape::resetRenderFarAndDeleteVBFV() { // For all patch of all zones. for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { ((*it).second)->resetRenderFarAndDeleteVBFV(); } } // *************************************************************************** void CLandscape::forceMergeAtTileLevel() { // For all patch of all zones. for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { ((*it).second)->forceMergeAtTileLevel(); } } // *************************************************************************** bool CLandscape::addZone(const CZone &newZone) { // -1. Update globals updateGlobalsAndLockBuffers (CVector::Null); // NB: adding a zone may add vertices in VB in visible patchs (because of binds)=> buffers are locked. uint16 zoneId= newZone.getZoneId(); if(Zones.find(zoneId)!=Zones.end()) { unlockBuffers(); return false; } CZone *zone= new CZone; // copy zone. zone->build(newZone); // Affect the current lighting of pointLight to the zone. if (OwnerModel) { CScene *scene = OwnerModel->getOwnerScene(); zone->_PointLightArray.initAnimatedLightIndex(*scene); zone->_PointLightArray.setPointLightFactor(*scene); } // apply the landscape heightField, modifying BBoxes. zone->applyHeightField(*this); // compile the zone for this landscape. zone->compile(this, Zones); // For test of _PatchQuadGrid erase. CAABBox zoneBBForErase= zone->getZoneBB().getAABBox(); // Avoid precision problems by enlarging a little bbox size of zone for erase zoneBBForErase.setHalfSize( zoneBBForErase.getHalfSize() * 1.1f); // add patchs of this zone to the quadgrid. for(sint i= 0; igetNumPatchs(); i++) { const CPatch *pa= ((const CZone*)zone)->getPatch(i); CPatchIdentEx paId; paId.ZoneId= zoneId; paId.PatchId= uint16(i); paId.Patch= pa; CAABBox bb= pa->buildBBox(); _PatchQuadGrid.insert(bb.getMin(), bb.getMax(), paId); // NB: the bbox of zone is used to remove patch. Hence it is VERY important that zoneBBox includes // all patchs bbox (else some patchs entries may not be deleted in removeZone()). nlassert(zoneBBForErase.include(bb)); } // Must realase VB Buffers unlockBuffers(); // Because bind may add faces in other (visible) zones because of enforced split, we must check // and update any FaceVector. updateTessBlocksFaceVector(); return true; } // *************************************************************************** bool CLandscape::removeZone(uint16 zoneId) { // -1. Update globals updateGlobalsAndLockBuffers (CVector::Null); // NB: remove a zone may change vertices in VB in visible patchs => buffers are locked. // find the zone. if(Zones.find(zoneId)==Zones.end()) { unlockBuffers(); return false; } CZone *zone= Zones[zoneId]; // delete patchs from this zone to the quadgrid. // use the quadgrid itself to find where patch are. do this using bbox of zone. CAABBox zoneBBForErase= zone->getZoneBB().getAABBox(); // Avoid precision problems by enlarging a little bbox size of zone for erase zoneBBForErase.setHalfSize( zoneBBForErase.getHalfSize() * 1.1f); // select iterators in the area of this zone. _PatchQuadGrid.clearSelection(); _PatchQuadGrid.select(zoneBBForErase.getMin(), zoneBBForErase.getMax()); // for each patch, remove it if from deleted zone. CQuadGrid::CIterator it; sint nPatchRemoved= 0; for(it= _PatchQuadGrid.begin(); it!= _PatchQuadGrid.end();) { // if the patch belong to the zone to remove if( (*it).ZoneId== zone->getZoneId() ) { // remove from the quadgrid. it= _PatchQuadGrid.erase(it); nPatchRemoved++; } else it++; } // verify we have removed all patch in the quadGrid for this zone nlassert(nPatchRemoved==zone->getNumPatchs()); // remove the zone. zone->release(Zones); delete zone; // Must realase VB Buffers unlockBuffers(); // because of forceMerge() at unbind, removeZone() can cause change in faces in other (visible) zones. updateTessBlocksFaceVector(); return true; } // *************************************************************************** void CLandscape::getZoneList(std::vector &zoneIds) const { zoneIds.clear(); zoneIds.reserve(Zones.size()); std::map::const_iterator it; for(it= Zones.begin();it!=Zones.end();it++) { zoneIds.push_back((*it).first); } } // *************************************************************************** void CLandscape::buildZoneName(sint zoneId, std::string &zoneName) { char tmp[256]; sint x= zoneId & 255; sint y= zoneId >> 8; sprintf(tmp, "%d_%c%c", y+1, (char)('A'+(x/26)), (char)('A'+(x%26))); zoneName= tmp; } // *************************************************************************** void CLandscape::clear() { // Build the list of zoneId. vector zoneIds; getZoneList(zoneIds); // Remove each zone one by one. sint i; for(i=0;i<(sint)zoneIds.size();i++) { nlverify(removeZone(zoneIds[i])); } // ensure the quadgrid is empty. _PatchQuadGrid.clear(); releaseAllTiles(); // If not done, delete all VBhards allocated. _Far0VB.clear(); _Far1VB.clear(); _TileVB.clear(); // Reset All Far Texture and unlink _ULRootTextureFar ciruclarList. ItSPRenderPassVector itFar= _TextureFars.begin(); // unitl set is empty while( itFar != _TextureFars.end() ) { // erase with link update. clearFarRenderPass(*itFar); itFar++; } // delete all _TextureFars.clear(); // reset driver. _Driver= NULL; } // *************************************************************************** void CLandscape::setDriver(IDriver *drv) { nlassert(drv); if(_Driver != drv) { _Driver= drv; // Does the driver support VertexShader??? // only if VP supported by GPU. _VertexShaderOk= (_Driver->isVertexProgramSupported() && !_Driver->isVertexProgramEmulated()); // Does the driver has sufficient requirements for Vegetable??? // only if VP supported by GPU, and Only if max vertices allowed. _DriverOkForVegetable= _VertexShaderOk && (_Driver->getMaxVerticesByVertexBufferHard()>=(uint)NL3D_LANDSCAPE_VEGETABLE_MAX_AGP_VERTEX_MAX); } } // *************************************************************************** void CLandscape::clip(const CVector &refineCenter, const std::vector &pyramid) { // -1. Update globals updateGlobalsAndLockBuffers (refineCenter); // NB: clip may add/remove vertices in VB in visible patchs => buffers are locked. for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { (*it).second->clip(pyramid); } // Must realase VB Buffers unlockBuffers(); // clip() should not cause change in faces in visible patchs. // It should not happens, but check for security. nlassert(_TessBlockModificationRoot.getNextToModify()==NULL); updateTessBlocksFaceVector(); } // *************************************************************************** void CLandscape::refine(const CVector &refineCenter) { NL3D_PROFILE_LAND_SET(ProfNRefineFaces, 0); NL3D_PROFILE_LAND_SET(ProfNRefineComputeFaces, 0); NL3D_PROFILE_LAND_SET(ProfNRefineLeaves, 0); NL3D_PROFILE_LAND_SET(ProfNSplits, 0); NL3D_PROFILE_LAND_SET(ProfNMerges, 0); NL3D_PROFILE_LAND_SET(ProfNRefineInTileTransition, 0); NL3D_PROFILE_LAND_SET(ProfNRefineWithLowDistance, 0); NL3D_PROFILE_LAND_SET(ProfNSplitsPass, 0); if(!_RefineMode) return; // Update the priority list. // ========================== CTessFacePListNode rootSplitTessFaceToUpdate; CTessFacePListNode rootMergeTessFaceToUpdate; if( _MustRefineAllAtNextRefine ) { // ok, first pass done, setup OldRefineCetner _MustRefineAllAtNextRefine= false; _OldRefineCenter= refineCenter; // then shift all faces _SplitPriorityList.shiftAll(rootSplitTessFaceToUpdate); _MergePriorityList.shiftAll(rootMergeTessFaceToUpdate); } else { // else, compute delta between positions CVector diff= refineCenter - _OldRefineCenter; _OldRefineCenter= refineCenter; // and shift according to distance of deplacement. _SplitPriorityList.shift(diff, rootSplitTessFaceToUpdate); _MergePriorityList.shift(diff, rootMergeTessFaceToUpdate); } // Refine Faces which may need it. // ========================== // Update globals updateGlobalsAndLockBuffers (refineCenter); // NB: refine may change vertices in VB in visible patchs => buffers are locked. // Increment the update date. CLandscapeGlobals::CurrentDate++; // Because CTessFacePriorityList::insert use it. NLMISC::OptFastFloorBegin(); /* While there is still face in list, update them NB: updateRefine() always insert the face in _***PriorityList, so face is removed from root***TessFaceToUpdate list. NB: it is possible ( with enforced merge() ) that faces dissapears from root***TessFaceToUpdate list before they are traversed here. It is why we must use a Circular list system, and not an array of elements. Basically. TessFaces are ALWAYS in a list, either in one of the entry list in _***PriorityList, or in root***TessFaceToUpdate list. It is newTessFace() and deleteTessFace() which insert/remove the nodes in the list. */ // Update the Merge priority list. while( rootMergeTessFaceToUpdate.nextInPList() != &rootMergeTessFaceToUpdate ) { // Get the face. CTessFace *face= static_cast(rootMergeTessFaceToUpdate.nextInPList()); // update the refine of this face. This may lead in deletion (merge) of other faces which are still in // root***TessFaceToUpdate, but it's work. face->updateRefineMerge(); } // Update the Split priority list. do { NL3D_PROFILE_LAND_ADD(ProfNSplitsPass, 1); // Append the new leaves, to the list of triangles to update rootSplitTessFaceToUpdate.appendPList(_RootNewLeaves); // While triangle to test for split exists while( rootSplitTessFaceToUpdate.nextInPList() != &rootSplitTessFaceToUpdate ) { // Get the face. CTessFace *face= static_cast(rootSplitTessFaceToUpdate.nextInPList()); // update the refine of this face. face->updateRefineSplit(); } } // do it until we are sure no more split is needed, ie no more faces are created while( _RootNewLeaves.nextInPList() != &_RootNewLeaves ); // Because CTessFacePriorityList::insert use it. NLMISC::OptFastFloorEnd(); // Before unlockBuffers, test for vegetable IG creation. { H_AUTO( NL3D_Vegetable_Update ); // Because CLandscapeVegetableBlock::update() use OptFastFloor.. NLMISC::OptFastFloorBegin(); // For each vegetableBlock, test IG creation CLandscapeVegetableBlock *vegetBlock= _VegetableBlockList.begin(); for(;vegetBlock!=NULL; vegetBlock= (CLandscapeVegetableBlock*)vegetBlock->Next) { vegetBlock->update(refineCenter, _VegetableManager); } // update lighting for vegetables _VegetableManager->updateLighting(); // Stop fastFloor optim. NLMISC::OptFastFloorEnd(); } // Must realase VB Buffers unlockBuffers(); // refine() may cause change in faces in visible patchs. updateTessBlocksFaceVector(); } // *************************************************************************** void CLandscape::refineAll(const CVector &refineCenter) { // -1. Update globals updateGlobalsAndLockBuffers (refineCenter); // NB: refineAll may change vertices in VB in visible patchs => buffers are locked. // Increment the update date. CLandscapeGlobals::CurrentDate++; for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { (*it).second->refineAll(); } // Must realase VB Buffers unlockBuffers(); // refineAll() may cause change in faces in visible patchs. updateTessBlocksFaceVector(); } // *************************************************************************** void CLandscape::excludePatchFromRefineAll(sint zoneId, uint patch, bool exclude) { ItZoneMap it= Zones.find(uint16(zoneId)); if(it!=Zones.end()) { it->second->excludePatchFromRefineAll(patch, exclude); } } // *************************************************************************** void CLandscape::averageTesselationVertices() { // -1. Update globals updateGlobalsAndLockBuffers (CVector::Null); // NB: averageTesselationVertices may change vertices in VB in visible patchs => buffers are locked. // Increment the update date. CLandscapeGlobals::CurrentDate++; for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { (*it).second->averageTesselationVertices(); } // Must realase VB Buffers unlockBuffers(); // averageTesselationVertices() should not cause change in faces in any patchs. // It should not happens, but check for security. nlassert(_TessBlockModificationRoot.getNextToModify()==NULL); updateTessBlocksFaceVector(); } // *************************************************************************** void CLandscape::updateGlobalsAndLockBuffers (const CVector &refineCenter) { // Setup CLandscapeGlobals static members... // Far limits. CLandscapeGlobals::FarTransition= _FarTransition; // Tile subdivsion part. CLandscapeGlobals::TileMaxSubdivision= _TileMaxSubdivision; CLandscapeGlobals::TileDistNear = _TileDistNear; CLandscapeGlobals::TileDistFar = CLandscapeGlobals::TileDistNear+20; CLandscapeGlobals::TileDistNearSqr = sqr(CLandscapeGlobals::TileDistNear); CLandscapeGlobals::TileDistFarSqr = sqr(CLandscapeGlobals::TileDistFar); CLandscapeGlobals::OOTileDistDeltaSqr = 1.0f / (CLandscapeGlobals::TileDistFarSqr - CLandscapeGlobals::TileDistNearSqr); // Tile Pixel size part. // \todo yoyo: choose according to wanted tile pixel size. CLandscapeGlobals::TilePixelSize= 128.0f; CLandscapeGlobals::TilePixelBias128= 0.5f/CLandscapeGlobals::TilePixelSize; CLandscapeGlobals::TilePixelScale128= 1-1/CLandscapeGlobals::TilePixelSize; CLandscapeGlobals::TilePixelBias256= 0.5f/(CLandscapeGlobals::TilePixelSize*2); CLandscapeGlobals::TilePixelScale256= 1-1/(CLandscapeGlobals::TilePixelSize*2); // RefineThreshold. CLandscapeGlobals::RefineThreshold= _Threshold; if (_Threshold == 0.0f) CLandscapeGlobals::OORefineThreshold = FLT_MAX; else CLandscapeGlobals::OORefineThreshold = 1.0f / CLandscapeGlobals::RefineThreshold; // Refine Center*. CLandscapeGlobals::RefineCenter= refineCenter; CLandscapeGlobals::TileFarSphere.Center= CLandscapeGlobals::RefineCenter; CLandscapeGlobals::TileFarSphere.Radius= CLandscapeGlobals::TileDistFar; CLandscapeGlobals::TileNearSphere.Center= CLandscapeGlobals::RefineCenter; CLandscapeGlobals::TileNearSphere.Radius= CLandscapeGlobals::TileDistNear; // PZBModelPosition CLandscapeGlobals::PZBModelPosition= _PZBModelPosition; // VB Allocators. CLandscapeGlobals::CurrentFar0VBAllocator= &_Far0VB; CLandscapeGlobals::CurrentFar1VBAllocator= &_Far1VB; CLandscapeGlobals::CurrentTileVBAllocator= &_TileVB; if(_Driver) lockBuffers (); } // *************************************************************************** void CLandscape::lockBuffers () { // Already locked if ((_LockCount++) == 0) { // Must check driver, and create VB infos,locking buffers. if(_Driver) { _Far0VB.updateDriver(_Driver); _Far1VB.updateDriver(_Driver); _TileVB.updateDriver(_Driver); // must do the same for _VegetableManager. if(_DriverOkForVegetable) _VegetableManager->updateDriver(_Driver); } _Far0VB.lockBuffer(CLandscapeGlobals::CurrentFar0VBInfo); _Far1VB.lockBuffer(CLandscapeGlobals::CurrentFar1VBInfo); _TileVB.lockBuffer(CLandscapeGlobals::CurrentTileVBInfo); // lock buffer of the vegetable manager. _VegetableManager->lockBuffers(); // VertexProgrma mode??? CLandscapeGlobals::VertexProgramEnabled= _VertexShaderOk; } } // *************************************************************************** void CLandscape::unlockBuffers(bool force) { // Already locked if ((_LockCount == 1) || force) { _Far0VB.unlockBuffer(); _Far1VB.unlockBuffer(); _TileVB.unlockBuffer(); // unlock buffer of the vegetable manager. _VegetableManager->unlockBuffers(); _LockCount = 0; } if (_LockCount > 0) _LockCount--; } // *************************************************************************** void CLandscape::updateTessBlocksFaceVector() { // while some tessBlock to update remains. CTessBlock *tb; while( (tb=_TessBlockModificationRoot.getNextToModify()) !=NULL ) { // Get the patch which owns this TessBlock. CPatch *patch= tb->getPatch(); // If this patch is visible, recreate faceVector for his tessBlock. patch->recreateTessBlockFaceVector(*tb); // remove from list. tb->removeFromModifyList(); } } // *************************************************************************** static inline void initPassTriArray(CPatchRdrPass &/* pass */, uint32 numIndex) { //uint numIndices= pass.getMaxRenderedFaces()*3; // realloc if necessary // We use /*if( CLandscapeGlobals::PassTriArray.getNumIndexes() < numIndices ) CLandscapeGlobals::PassTriArray.setNumIndexes( numIndices );*/ CLandscapeGlobals::PassTriArray.setFormat(NL_LANDSCAPE_INDEX_FORMAT); CLandscapeGlobals::PassTriArray.setNumIndexes(numIndex); // reset ptr. nlassert (!CLandscapeGlobals::PassTriArray.isLocked()); CLandscapeGlobals::PassTriArray.lock (CLandscapeGlobals::PassTriArrayIBA); NL3D_LandscapeGlobals_PassTriCurPtr= CLandscapeGlobals::PassTriArrayIBA.getPtr(); NL3D_LandscapeGlobals_PassTriFormat = CLandscapeGlobals::PassTriArrayIBA.getFormat(); } // *************************************************************************** static #ifndef NL_DEBUG inline #endif void drawPassTriArray(CMaterial &mat) { nlassert (CLandscapeGlobals::PassTriArray.isLocked()); CLandscapeGlobals::PassTriArrayIBA.unlock(); if(NL3D_LandscapeGlobals_PassNTri>0) { //mat.setZFunc(CMaterial::ZFunc::greaterequal); CLandscapeGlobals::PatchCurrentDriver->setupMaterial(mat); CLandscapeGlobals::PatchCurrentDriver->activeIndexBuffer(CLandscapeGlobals::PassTriArray); CLandscapeGlobals::PatchCurrentDriver->renderSimpleTriangles(0, NL3D_LandscapeGlobals_PassNTri); NL3D_LandscapeGlobals_PassNTri= 0; } } // *************************************************************************** static inline uint32 countNumWantedIndex(CRdrTileId *tileToRdr, uint rdrPass) { uint32 numIndex = 0; while(tileToRdr) { if(tileToRdr->TileMaterial->Pass[rdrPass].PatchRdrPass) { // renderSimpleTriangles() with the material setuped. numIndex += *(tileToRdr->TileMaterial->TileFaceVectors[rdrPass]); } tileToRdr= (CRdrTileId*)tileToRdr->getNext(); } return 3 * numIndex; } // *************************************************************************** static inline uint32 countNumWantedIndexFar0(CPatch *patch) { uint32 numTri = 0; while(patch) { // renderSimpleTriangles() with the material setuped. numTri += patch->countNumTriFar0(); patch = patch->getNextFar0ToRdr(); } return 3 * numTri; } // *************************************************************************** static inline uint32 countNumWantedIndexFar1(CPatch *patch) { uint32 numTri = 0; while(patch) { numTri += patch->countNumTriFar1(); patch = patch ->getNextFar1ToRdr(); } return 3 * numTri; } // *************************************************************************** void CLandscape::render(const CVector &refineCenter, const CVector &frontVector, const CPlane pyramid[NL3D_TESSBLOCK_NUM_CLIP_PLANE], bool doTileAddPass) { IDriver *driver= _Driver; nlassert(driver); // values in Stencil Buffer which match with landscape are replace by 128 // rest of Stencil is replace by 0. CScene *scene = NULL; if (OwnerModel) { scene = OwnerModel->getOwnerScene(); if(scene->getLandscapePolyDrawingCallback()) { scene->getLandscapePolyDrawingCallback()->beginPolyDrawing(); } } // Increment the update date for preRender. CLandscapeGlobals::CurrentRenderDate++; ItZoneMap it; sint i; ItTileRdrPassSet itTile; ItSPRenderPassVector itFar; // Yoyo: profile. NL3D_PROFILE_LAND_SET(ProfNRdrFar0, 0); NL3D_PROFILE_LAND_SET(ProfNRdrFar1, 0); NL3D_PROFILE_LAND_SET(ProfNPatchRdrFar0, 0); NL3D_PROFILE_LAND_SET(ProfNPatchRdrFar1, 0); for(i=0;i buffers are locked. // -1. clear all PatchRenderPass renderList //=================== H_BEFORE( NL3D_Landscape_Render_Clear ); // Fars. for(itFar= _TextureFars.begin(); itFar!= _TextureFars.end(); itFar++) { CPatchRdrPass &pass= **itFar; // clear list. pass.clearAllRenderList(); } // Tiles. for(itTile= TileRdrPassSet.begin(); itTile!= TileRdrPassSet.end(); itTile++) { CPatchRdrPass &pass= const_cast(*itTile); // clear list. pass.clearAllRenderList(); } // Lightmaps. for(sint lightRdrPass=0; lightRdrPass<(sint)_TextureNears.size(); lightRdrPass++) { CPatchRdrPass &pass= *_TextureNears[lightRdrPass]; // clear list. pass.clearAllRenderList(); } H_AFTER( NL3D_Landscape_Render_Clear ); // 0. preRender pass. //=================== H_BEFORE( NL3D_Landscape_Render_PreRender ); // change Far0 / Far1. // Clip TessBlocks against pyramid and Far Limit. for(i=0; ipreRender(); } H_AFTER( NL3D_Landscape_Render_PreRender ); H_BEFORE( NL3D_Landscape_Render_Refill ); // Check if the vertex buffers must be refilled _Far0VB.checkVertexBuffersResident(); _Far1VB.checkVertexBuffersResident(); _TileVB.checkVertexBuffersResident(); // Reallocation Mgt. If any of the VB is reallocated, we must refill it entirely. // NB: all VBs are refilled entirely. It is not optimal (maybe 3* too slow), but reallocation are supposed // to be very rare. if( _Far0VB.reallocationOccurs() || _Far1VB.reallocationOccurs() || _TileVB.reallocationOccurs() ) _RenderMustRefillVB= true; // VertexProgram dependency on RefineThreshold Management. If VertexShader, and if the refineThreshold has // changed since the last time, we must refill All the VB, because data are out of date. if( _VertexShaderOk && _VPThresholdChange ) { _VPThresholdChange= false; _RenderMustRefillVB= true; } // If we must refill the VB (for any reason). if(_RenderMustRefillVB ) { // Ok, ok, we refill All the VB with good data. _RenderMustRefillVB= false; // First reset the flag, so fillVB() will effectively fill the VB. _Far0VB.resetReallocation(); _Far1VB.resetReallocation(); _TileVB.resetReallocation(); // Then recompute good VBInfo (those in CurrentVBInfo are false!!). // Do it by unlocking then re-locking Buffers. unlockBuffers(true); lockBuffers(); // Finally, fill the VB for all patchs visible. for(it= Zones.begin();it!=Zones.end();it++) { if((*it).second->ClipResult==CZone::ClipOut) continue; for(sint i=0;i<(*it).second->getNumPatchs(); i++) { CPatch *patch= (*it).second->getPatch(i); patch->fillVBIfVisible(); } } } H_AFTER( NL3D_Landscape_Render_Refill ); H_BEFORE( NL3D_Landscape_Render_SoftGeomorph ); // If software GeoMorph / Alpha Transition (no VertexShader), do it now. if(!_VertexShaderOk) { // For all patch visible, compute geomoprh and alpha in software. for(it= Zones.begin();it!=Zones.end();it++) { if((*it).second->ClipResult==CZone::ClipOut) continue; for(sint i=0;i<(*it).second->getNumPatchs(); i++) { CPatch *patch= (*it).second->getPatch(i); // If visible, compute Geomorph And Alpha patch->computeSoftwareGeomorphAndAlpha(); } } /* Optim note: here, lot of vertices are 1/ geomorphed twice (vertices on edges of patchs) 2/ vertices are geomorphed, but not used (because o the Tessblock clip), because lot of vertices used by faces in small TessBlocks are still in MasterBlock. Some tries have been made to solve this, but result are even worse (2 times or more), because: 1/ - does not really matter edges of patchs (and corner) because the majority is in interior of patch. - in this case, we need to reset all the flags which is very costly (reparse all zones...) . 2/ Except for the old CTessBlockEdge management which not solve all the thing, the other solution is to test all faces not clipped (on a per TessBlock basis), to compute only vertices needed. But in this cases, result are worse, maybe because there is 6 times more tests, and with bad BTB cache. */ } H_AFTER( NL3D_Landscape_Render_SoftGeomorph ); // Must realase VB Buffers Now!! The VBuffers are now OK! // NB: no parallelism is made between 3dCard and Fill of vertices. // We Suppose Fill of vertices is rare, and so do not need to be parallelized. unlockBuffers(true); // If VertexShader enabled, setup VertexProgram Constants. if(_VertexShaderOk) { // c[0..3] take the ModelViewProjection Matrix. driver->setConstantMatrix(0, IDriver::ModelViewProjection, IDriver::Identity); // c[4] take useful constants. driver->setConstant(4, 0, 1, 0.5f, 0); // c[5] take RefineCenter driver->setConstant(5, refineCenter); // c[6] take info for Geomorph trnasition to TileNear. driver->setConstant(6, CLandscapeGlobals::TileDistFarSqr, CLandscapeGlobals::OOTileDistDeltaSqr, 0, 0); // c[10] take the fog vector. driver->setConstantFog(10); // c[12] take the current landscape Center / delta Pos to apply driver->setConstant(12, _PZBModelPosition); } // 1. TileRender pass. //==================== // Yoyo: profile NL3D_PROFILE_LAND_SET(ProfNTileSetupMaterial, driver->profileSetupedMaterials() ); H_BEFORE( NL3D_Landscape_Render_Tile ); // First, update Dynamic Lighting for Near, ie multiply Dynamic Lightmap with UserColor, and upload to texture. // ================== CPatchDLMContext *dlmCtxPtr= _PatchDLMContextList->begin(); while(dlmCtxPtr!=NULL) { // do it only if the patch has some Near stuff to render, and if it is visible if(dlmCtxPtr->getPatch()->getFar0() == 0 && !dlmCtxPtr->getPatch()->isRenderClipped() ) { // upload lightmap into textureDLM, modulating before with patch TileColor. // NB: no-op if both src and dst are already full black. dlmCtxPtr->compileLighting(CPatchDLMContext::ModulateTileColor); } // next dlmCtxPtr= (CPatchDLMContext*)dlmCtxPtr->Next; } // Active VB. // ================== // Active the good VB, and maybe activate the VertexProgram Nb 0. _TileVB.activate(0); // Render. // ================== // Before any render call. Set the global driver used to render. CLandscapeGlobals::PatchCurrentDriver= driver; // bkup the original fog color CRGBA fogColor= driver->getFogColor(); // Render Order. Must "invert", since initial order is NOT the render order. This is done because the lightmap pass // DO NOT have to do any renderTile(), since it is computed in RGB0 pass. nlassert(NL3D_MAX_TILE_PASS==5); static sint RenderOrder[NL3D_MAX_TILE_PASS]= {NL3D_TILE_PASS_RGB0, NL3D_TILE_PASS_RGB1, NL3D_TILE_PASS_RGB2, NL3D_TILE_PASS_LIGHTMAP, NL3D_TILE_PASS_ADD}; // For ALL pass.. for(i=0; isetupFog(driver->getFogStart(), driver->getFogEnd(), fogColor); // RGB0 pass. ItTileRdrPassSet itTile; for(itTile= TileRdrPassSet.begin(); itTile!= TileRdrPassSet.end(); itTile++) { // Get a ref on the render pass. Const cast work because we only modify attribut from CPatchRdrPass // that don't affect the operator< of this class CPatchRdrPass &pass= const_cast(*itTile); // Enlarge PassTriArray as needed CRdrTileId *tileToRdr= pass.getRdrTileRoot(passOrder); uint32 numWantedIndex = countNumWantedIndex(tileToRdr, NL3D_TILE_PASS_RGB0); if (numWantedIndex) { initPassTriArray(pass, numWantedIndex); // Setup Diffuse texture of the tile. TileMaterial.setTexture(0, pass.TextureDiffuse); // Add triangles to array while(tileToRdr) { // renderSimpleTriangles() with the material setuped. tileToRdr->TileMaterial->renderTilePassRGB0(); tileToRdr= (CRdrTileId*)tileToRdr->getNext(); } // Render triangles. drawPassTriArray(TileMaterial); } } } else if(passOrder==NL3D_TILE_PASS_LIGHTMAP) { // for Lightmap pass (modulate blend), setup a White fogColor. This is not // mathematically correct but works fine driver->setupFog(driver->getFogStart(), driver->getFogEnd(), CRGBA::White); // Lightmap Pass. /* \todo yoyo: TODO_CLOUD: setup stage2, and setup texcoord generation. COMPLEX because of interaction with Dynamic LightMap */ // Setup the Dynamic Lightmap into stage 0. TileMaterial.setTexture(0, _TextureDLM); // if vertex shader not used. if(!_VertexShaderOk) { // special setup such that stage0 takes Uv2. driver->mapTextureStageToUV(0, 2); } // else VertexProgram map itself the good vertex Attribute to UV0. // Render All the lightmaps. for(sint lightRdrPass=0; lightRdrPass<(sint)_TextureNears.size(); lightRdrPass++) { CPatchRdrPass &pass= *_TextureNears[lightRdrPass]; // Enlarge PassTriArray as needed CRdrTileId *tileToRdr= pass.getRdrTileRoot(passOrder); uint32 numWantedIndex = countNumWantedIndex(tileToRdr, NL3D_TILE_PASS_RGB0); if (numWantedIndex) { initPassTriArray(pass, numWantedIndex); // Setup Lightmap into stage1. Because we share UV with pass RGB0. So we use UV1. // Also, now stage0 is used for DynamicLightmap TileMaterial.setTexture(1, pass.TextureDiffuse); // Add triangles to array while(tileToRdr) { // renderSimpleTriangles() with the material setuped. tileToRdr->TileMaterial->renderTilePassLightmap(); tileToRdr= (CRdrTileId*)tileToRdr->getNext(); } // Render triangles. drawPassTriArray(TileMaterial); } } // if vertex shader not used. if(!_VertexShaderOk) { // Reset special stage/UV setup to normal behavior driver->mapTextureStageToUV(0, 0); } } else { // RGB1, RGB2, and ADD pass. // for ADD pass (additive blend), setup a Black fogColor if(passOrder==NL3D_TILE_PASS_ADD) driver->setupFog(driver->getFogStart(), driver->getFogEnd(), CRGBA::Black); // else setup the standard color (std blend) else driver->setupFog(driver->getFogStart(), driver->getFogEnd(), fogColor); // Render Base, Transitions or Additives. ItTileRdrPassSet itTile; for(itTile= TileRdrPassSet.begin(); itTile!= TileRdrPassSet.end(); itTile++) { // Get a ref on the render pass. Const cast work because we only modify attribut from CPatchRdrPass // that don't affect the operator< of this class CPatchRdrPass &pass= const_cast(*itTile); // Enlarge PassTriArray as needed CRdrTileId *tileToRdr= pass.getRdrTileRoot(passOrder); uint32 numWantedIndex = countNumWantedIndex(tileToRdr, passOrder); if (numWantedIndex) { initPassTriArray(pass, numWantedIndex); // Add triangles to array while(tileToRdr) { // renderSimpleTriangles() with the material setuped. tileToRdr->TileMaterial->renderTile(passOrder); tileToRdr= (CRdrTileId*)tileToRdr->getNext(); } // Pass not empty ? if(NL3D_LandscapeGlobals_PassNTri>0) { // Setup material. // Setup Diffuse texture of the tile. TileMaterial.setTexture(0, pass.TextureDiffuse); // If transition tile, must enable the alpha for this pass. // NB: Additive pass may have pass.TextureAlpha==NULL TileMaterial.setTexture(1, pass.TextureAlpha); } // Render triangles. drawPassTriArray(TileMaterial); } } } } // restore old fog color driver->setupFog(driver->getFogStart(), driver->getFogEnd(), fogColor); // Yoyo: profile NL3D_PROFILE_LAND_SET(ProfNTileSetupMaterial, driver->profileSetupedMaterials()-ProfNTileSetupMaterial ); H_AFTER( NL3D_Landscape_Render_Tile ); // 2. Far0Render pass. //==================== // Yoyo: profile NL3D_PROFILE_LAND_SET(ProfNFar0SetupMaterial, driver->profileSetupedMaterials() ); H_BEFORE( NL3D_Landscape_Render_DLM ); // First, update Dynamic Lighting for Far, ie multiply Dynamic Lightmap with TextureFar, and upload to texture. // ================== dlmCtxPtr= _PatchDLMContextList->begin(); while(dlmCtxPtr!=NULL) { // do it only if the patch has some Far stuff to render, and if it is visible if( (dlmCtxPtr->getPatch()->getFar0()>0 || dlmCtxPtr->getPatch()->getFar1()>0) && !dlmCtxPtr->getPatch()->isRenderClipped() ) { // upload lightmap into textureDLM, modulating before with patch TextureFar. // NB: no-op if both src and dst are already full black. dlmCtxPtr->compileLighting(CPatchDLMContext::ModulateTextureFar); } // next dlmCtxPtr= (CPatchDLMContext*)dlmCtxPtr->Next; } H_AFTER( NL3D_Landscape_Render_DLM ); H_BEFORE( NL3D_Landscape_Render_Far0 ); // Active VB. // ================== // Active the good VB, and maybe activate the std VertexProgram. _Far0VB.activate(0); // Render. // ================== // Setup common material. FarMaterial.setBlend(false); // set the DLM texture. FarMaterial.setTexture(1, _TextureDLM); // Render All material RdrPass0. itFar=_TextureFars.begin(); while (itFar!=_TextureFars.end()) { CPatchRdrPass &pass= **itFar; // Enlarge PassTriArray as needed CPatch *patchToRdr= pass.getRdrPatchFar0(); if (patchToRdr) { uint32 numWantedIndex = countNumWantedIndexFar0(patchToRdr); if (numWantedIndex) { initPassTriArray(pass, numWantedIndex); // Setup the material. FarMaterial.setTexture(0, pass.TextureDiffuse); // If the texture need to be updated, do it now. if(pass.TextureDiffuse && pass.TextureDiffuse->touched()) driver->setupTexture(*pass.TextureDiffuse); // Add triangles to array while(patchToRdr) { // renderSimpleTriangles() with the material setuped. patchToRdr->renderFar0(); patchToRdr= patchToRdr->getNextFar0ToRdr(); } // Render triangles. drawPassTriArray(FarMaterial); } } // Next render pass itFar++; } // Yoyo: profile NL3D_PROFILE_LAND_SET(ProfNFar0SetupMaterial, driver->profileSetupedMaterials()-ProfNFar0SetupMaterial ); H_AFTER( NL3D_Landscape_Render_Far0 ); // 3. Far1Render pass. //==================== // Yoyo: profile NL3D_PROFILE_LAND_SET(ProfNFar1SetupMaterial, driver->profileSetupedMaterials() ); H_BEFORE( NL3D_Landscape_Render_Far1 ); // Active VB. // ================== // Active the good VB, and maybe activate the std VertexProgram. _Far1VB.activate(0); // Render // ================== // Setup common material. FarMaterial.setBlend(true); // set the DLM texture. FarMaterial.setTexture(1, _TextureDLM); // Render All material RdrPass1. itFar=_TextureFars.begin(); while (itFar!=_TextureFars.end()) { CPatchRdrPass &pass= **itFar; // Enlarge PassTriArray as needed CPatch *patchToRdr= pass.getRdrPatchFar1(); if (patchToRdr) { //uint32 numWantedIndex = countNumWantedIndexFar1(patchToRdr); uint32 numWantedIndex = countNumWantedIndexFar1(patchToRdr); if (numWantedIndex) { initPassTriArray(pass, numWantedIndex); // Setup the material. FarMaterial.setTexture(0, pass.TextureDiffuse); // If the texture need to be updated, do it now. if(pass.TextureDiffuse && pass.TextureDiffuse->touched()) driver->setupTexture(*pass.TextureDiffuse); // Add triangles to array while(patchToRdr) { // renderSimpleTriangles() with the material setuped. patchToRdr->renderFar1(); patchToRdr= patchToRdr->getNextFar1ToRdr(); } // Render triangles. drawPassTriArray(FarMaterial); } } // Next render pass itFar++; } // Yoyo: profile NL3D_PROFILE_LAND_SET(ProfNFar1SetupMaterial, driver->profileSetupedMaterials()-ProfNFar1SetupMaterial ); H_AFTER( NL3D_Landscape_Render_Far1 ); // 4. "Release" texture materials. //================================ FarMaterial.setTexture(0, NULL); FarMaterial.setTexture(1, NULL); FarMaterial.setTexture(2, NULL); FarMaterial.setTexture(3, NULL); TileMaterial.setTexture(0, NULL); TileMaterial.setTexture(1, NULL); TileMaterial.setTexture(2, NULL); TileMaterial.setTexture(3, NULL); // To ensure no use but in render().. CLandscapeGlobals::PatchCurrentDriver= NULL; // Desactive the vertex program (if anyone) if(_VertexShaderOk) driver->activeVertexProgram (NULL); // 5. Vegetable Management. //================================ if(scene && scene->getLandscapePolyDrawingCallback()) { scene->getLandscapePolyDrawingCallback()->endPolyDrawing(); } // First, update Dynamic Lighting for Vegetable, ie just copy. // ================== if(isVegetableActive()) { /* Actually we modulate the DLM with an arbitrary constant for this reason: Color of vegetable (ie their material) are NOT modulated with DLM. Doing this without using PixelShader / additional UVs seems to be impossible. And add new UVs (+700K in AGP) just for this is not worth the effort. We prefer using a constant to simulate the "global vegetable color", which is a big trick. Additionally, the vegetable take the diffuse lighting of landscape, which is false because it replaces the diffuse lighting it should have (ie with his own Normal and his own "global vegetable color") We can't do anything for "correct normal vegetable", but it is possible to replace landscape material with vegetable material, by dividing _DLMGlobalVegetableColor by LandscapeDiffuseMaterial. This is a very approximate result because of CRGBA clamp, but it is acceptable. */ CRGBA vegetDLMCte; // the constant is _DLMGlobalVegetableColor / PointLightDiffuseMaterial uint v; v= (_DLMGlobalVegetableColor.R*256) / (_PointLightDiffuseMaterial.R+1); vegetDLMCte.R= (uint8)min(v, 255U); v= (_DLMGlobalVegetableColor.G*256) / (_PointLightDiffuseMaterial.G+1); vegetDLMCte.G= (uint8)min(v, 255U); v= (_DLMGlobalVegetableColor.B*256) / (_PointLightDiffuseMaterial.B+1); vegetDLMCte.B= (uint8)min(v, 255U); // Parse all patch which have some vegetables dlmCtxPtr= _PatchDLMContextList->begin(); while(dlmCtxPtr!=NULL) { // do it only if the patch has some vegetable stuff to render, and if it is visible // NB: we may have some vegetable stuff to render if the patch has some TileMaterial created. if(dlmCtxPtr->getPatch()->getTileMaterialRefCount()>0 && !dlmCtxPtr->getPatch()->isRenderClipped() ) { // NB: no-op if both src and dst are already full black. dlmCtxPtr->compileLighting(CPatchDLMContext::ModulateConstant, vegetDLMCte); } // next dlmCtxPtr= (CPatchDLMContext*)dlmCtxPtr->Next; } } // profile. _VegetableManager->resetNumVegetableFaceRendered(); // render all vegetables, only if driver support VertexProgram. // ================== if(isVegetableActive()) { // Use same plane as TessBlock for faster clipping. vector vegetablePyramid; vegetablePyramid.resize(NL3D_TESSBLOCK_NUM_CLIP_PLANE); for(i=0;irender(refineCenter, frontVector, vegetablePyramid, _TextureDLM, driver); } } // *************************************************************************** // *************************************************************************** // Tile mgt. // *************************************************************************** // *************************************************************************** // *************************************************************************** ITexture *CLandscape::findTileTexture(const std::string &textName, bool clamp) { ITexture *text; text= TileTextureMap[textName]; // If just inserted, RefPtr is NULL!! :) // This test too if the RefPtr is NULL... (tile released) // The object is not owned by this map. It will be own by the multiple RdrPass via CSmartPtr. // They will destroy it when no more points to them. if(!text) { TileTextureMap[textName]= text= new CTextureFile(textName); text->setWrapS(clamp?ITexture::Clamp:ITexture::Repeat); text->setWrapT(clamp?ITexture::Clamp:ITexture::Repeat); text->setUploadFormat(ITexture::DXTC5); text->setTextureCategory(_TextureTileCategory); } return text; } // *************************************************************************** CPatchRdrPass *CLandscape::findTileRdrPass(const CPatchRdrPass &pass) { ItTileRdrPassSet it; // If already here, find it, else insert. it= (TileRdrPassSet.insert(pass)).first; return const_cast(&(*it)); } // *************************************************************************** void CLandscape::loadTile(uint16 tileId) { CTile *tile; CTileInfo *tileInfo; string textName; // Retrieve or create texture. // =========================== // Tile Must exist. // nlassert(tileId==0xFFFF || tileIdgetRelativeFileName(CTile::additive); else textName= ""; // If no additive for this tile, rdrpass is NULL. if(textName=="") tileInfo->AdditiveRdrPass= NULL; else { // Fill rdrpass. CPatchRdrPass pass; // Avoid using Clamp for diffuse, because of recent NVidia GL drivers Bugs in 77.72 pass.TextureDiffuse= findTileTexture(TileBank.getAbsPath()+textName, false); // We may have an alpha part for additive. textName= tile->getRelativeFileName (CTile::alpha); if(textName!="") // Must Use clamp for alpha (although NVidia drivers are buggy), because the texture doesn't tile at all pass.TextureAlpha= findTileTexture(TileBank.getAbsPath()+textName, true); // Fill tileInfo. tileInfo->AdditiveRdrPass= findTileRdrPass(pass); // Fill UV Info. // NB: for now, One Tile== One Texture, so UVScaleBias is simple. tileInfo->AdditiveUvScaleBias.x= 0; tileInfo->AdditiveUvScaleBias.y= 0; tileInfo->AdditiveUvScaleBias.z= 1; } // Fill diffuse part. // ======================= // Fill rdrpass. CPatchRdrPass pass; // The diffuse part for a tile is inevitable. if(tile) { textName= tile->getRelativeFileName(CTile::diffuse); if(textName!="") // Avoid using Clamp for diffuse, because of recent NVidia GL drivers Bugs in 77.72 pass.TextureDiffuse= findTileTexture(TileBank.getAbsPath()+textName, false); else { pass.TextureDiffuse= new CTextureCross; nldebug("Missing Tile diffuse texname: %d", tileId); } } else pass.TextureDiffuse= new CTextureCross; if(tile) { textName= tile->getRelativeFileName (CTile::alpha); if(textName!="") // Must Use clamp for alpha (although NVidia drivers are buggy), because the texture doesn't tile at all pass.TextureAlpha= findTileTexture(TileBank.getAbsPath()+textName, true); } // Fill tileInfo. tileInfo->DiffuseRdrPass= findTileRdrPass(pass); // Fill UV Info. // NB: for now, One Tile== One Texture, so UVScaleBias is simple. tileInfo->DiffuseUvScaleBias.x= 0; tileInfo->DiffuseUvScaleBias.y= 0; tileInfo->DiffuseUvScaleBias.z= 1; tileInfo->AlphaUvScaleBias.x= 0; tileInfo->AlphaUvScaleBias.y= 0; tileInfo->AlphaUvScaleBias.z= 1; // Retrieve the good rot alpha decal. if(tile) tileInfo->RotAlpha= tile->getRotAlpha(); else tileInfo->RotAlpha= 0; // Increment RefCount of RenderPart. // ================================= if(tileInfo->AdditiveRdrPass) tileInfo->AdditiveRdrPass->RefCount++; if(tileInfo->DiffuseRdrPass) tileInfo->DiffuseRdrPass->RefCount++; } // *************************************************************************** void CLandscape::releaseTile(uint16 tileId) { CTileInfo *tileInfo; tileInfo= TileInfos[tileId]; nlassert(tileInfo!=NULL); // "Release" the rdr pass. if(tileInfo->AdditiveRdrPass) tileInfo->AdditiveRdrPass->RefCount--; if(tileInfo->DiffuseRdrPass) tileInfo->DiffuseRdrPass->RefCount--; delete tileInfo; TileInfos[tileId]= NULL; } // *************************************************************************** CPatchRdrPass *CLandscape::getTileRenderPass(uint16 tileId, bool additiveRdrPass) { CTileInfo *tile= TileInfos[tileId]; // If not here, create it. //======================== if(tile==NULL) { // Force loading of tile. loadTile(tileId); tile= TileInfos[tileId]; nlassert(tile!=NULL); } // Retrieve. //======================== if(additiveRdrPass) { // NB: additive pass is not lighted by the lightmap, so there is no lighted version of this rednerpass. return tile->AdditiveRdrPass; } else { return tile->DiffuseRdrPass; } } // *************************************************************************** void CLandscape::getTileUvScaleBiasRot(uint16 tileId, CTile::TBitmap bitmapType, CVector &uvScaleBias, uint8 &rotAlpha) { CTileInfo *tile= TileInfos[tileId]; // tile should not be NULL. // Because load of tiles are always done in getTileRenderPass(), and this insertion always succeed. nlassert(tile); rotAlpha= 0; switch(bitmapType) { case CTile::diffuse: uvScaleBias= tile->DiffuseUvScaleBias; break; case CTile::additive: uvScaleBias= tile->AdditiveUvScaleBias; break; case CTile::alpha: uvScaleBias= tile->AlphaUvScaleBias; rotAlpha= tile->RotAlpha; break; default: break; } } // *************************************************************************** NLMISC::CSmartPtr CLandscape::getTileTexture(uint16 tileId, CTile::TBitmap bitmapType, CVector &uvScaleBias) { CPatchRdrPass *pass; if(bitmapType== CTile::additive) pass= getTileRenderPass(tileId, true); else pass= getTileRenderPass(tileId, false); if(!pass) return NULL; uint8 dummy; getTileUvScaleBiasRot(tileId, bitmapType, uvScaleBias, dummy); // return the wanted texture. if(bitmapType==CTile::diffuse || bitmapType==CTile::additive) return pass->TextureDiffuse; else return pass->TextureAlpha; } // *************************************************************************** CTileElement *CLandscape::getTileElement(const CPatchIdent &patchId, const CUV &uv) { // \todo yoyo: TODO_ZONEID: change ZoneId in 32 bits... std::map::const_iterator it= Zones.find((uint16)patchId.ZoneId); if(it!=Zones.end()) { sint N= (*it).second->getNumPatchs(); // patch must exist in the zone. nlassert(patchId.PatchId((*it).second)->getPatch(patchId.PatchId); return pa->getTileElement (uv); } else // Return not found return NULL; } // *************************************************************************** void CLandscape::flushTiles(IDriver *drv, uint32 tileStart, uint32 nbTiles) { nlassert(nbTiles<=65536); nlassert(tileStart+nbTiles<=65536); // Load tile rdrpass, force setup the texture. for(sint tileId= tileStart; tileId<(sint)(tileStart+nbTiles); tileId++) { CTileInfo *tile= TileInfos[tileId]; if(tile==NULL) { loadTile(uint16(tileId)); CTileInfo *tile= TileInfos[tileId]; nlassert(tile); if(tile->DiffuseRdrPass) { const CPatchRdrPass &pass= *tile->DiffuseRdrPass; // If present and not already setuped... if(pass.TextureDiffuse && !pass.TextureDiffuse->setupedIntoDriver()) drv->setupTexture(*pass.TextureDiffuse); // If present and not already setuped... if(pass.TextureAlpha && !pass.TextureAlpha->setupedIntoDriver()) drv->setupTexture(*pass.TextureAlpha); } if(tile->AdditiveRdrPass) { const CPatchRdrPass &pass= *tile->AdditiveRdrPass; // If present and not already setuped... if(pass.TextureDiffuse && !pass.TextureDiffuse->setupedIntoDriver()) drv->setupTexture(*pass.TextureDiffuse); // If present and not already setuped... if(pass.TextureAlpha && !pass.TextureAlpha->setupedIntoDriver()) drv->setupTexture(*pass.TextureAlpha); } } } } // *************************************************************************** void CLandscape::releaseTiles(uint32 tileStart, uint32 nbTiles) { nlassert(nbTiles<=65536); nlassert(tileStart+nbTiles<=65536); // release tiles. for(sint tileId= tileStart; tileId<(sint)(tileStart+nbTiles); tileId++) { CTileInfo *tile= TileInfos[tileId]; if(tile!=NULL) { releaseTile(uint16(tileId)); } } // For all rdrpass, release one that are no more referenced. ItTileRdrPassSet it; for(it= TileRdrPassSet.begin(); it!=TileRdrPassSet.end();) { // If no more tile access the rdrpass, delete it. if((*it).RefCount==0) { ItTileRdrPassSet itDel=it++; TileRdrPassSet.erase(itDel); } else it++; } // Textures are automaticly deleted by smartptr, but not their entry int the map (TileTextureMap). // => doesn't matter since findTileTexture() manages this case. // And the memory overhead is not a problem (we talk about pointers). } // *************************************************************************** uint CLandscape::getTileLightMap(CRGBA map[NL_TILE_LIGHTMAP_SIZE*NL_TILE_LIGHTMAP_SIZE], CPatchRdrPass *&lightmapRdrPass) { sint textNum; uint lightMapId; /* NB: TextureNear are a grow only Array... TextureNear are never deleted. Why? : 2/ Unused near texture may be uncahced by opengl (and maybe by windows, to disk). (old reason, no longer valid, since lightmaps are unlinked from tiles. 1/ There is an important issue with releasing texture nears: tiles may acces them (see getTileRenderPass()) ) */ // 0. Alloc Near Texture if necessary. //==================================== if(_NFreeLightMaps==0) { CTextureNear *text= new CTextureNear(TextureNearSize); TSPRenderPass newPass= new CPatchRdrPass; text->setTextureCategory(_TextureNearCategory); newPass->TextureDiffuse= text; _TextureNears.push_back(newPass); _NFreeLightMaps+= text->getNbAvailableTiles(); } // 1. Search the first texture which has a free tile. //================================================== CTextureNear *nearText= NULL; CPatchRdrPass *nearRdrPass= NULL; for(textNum=0;textNum<(sint)_TextureNears.size();textNum++) { nearRdrPass= _TextureNears[textNum]; nearText= (CTextureNear*)(ITexture*)nearRdrPass->TextureDiffuse; if(nearText->getNbAvailableTiles()!=0) break; } nlassert(textNum<(sint)_TextureNears.size()); // A empty space has been found. _NFreeLightMaps--; // 2. Fill the texture with the data, and updaterect. //=================================================== lightMapId= nearText->getTileAndFillRect(map); // Compute the Id. lightMapId= textNum*NbTileLightMapByTexture + lightMapId; // 3. updateLighting //=================================================== // Increment number of pixels to update for near. _ULTotalNearPixels+= NL_TILE_LIGHTMAP_SIZE*NL_TILE_LIGHTMAP_SIZE; // Result: lightmapRdrPass= nearRdrPass; return lightMapId; } // *************************************************************************** void CLandscape::getTileLightMapUvInfo(uint tileLightMapId, CVector &uvScaleBias) { uint id, s,t; // Scale. static const float scale10= (float)NL_TILE_LIGHTMAP_SIZE/TextureNearSize; static const float scale4= 4.f/TextureNearSize; static const float scale1= 1.f/TextureNearSize; // The size of a minilightmap, mapped onto the polygon, is still 4 pixels. uvScaleBias.z= scale4; // Get the id local in the texture. id= tileLightMapId%NbTileLightMapByTexture; // Commpute UVBias. // Get the coordinate of the tile, in tile number. s= id%NbTileLightMapByLine; t= id/NbTileLightMapByLine; // But the real size of a minilightmap is 10 pixels, and we must reach the pixel 1,1. uvScaleBias.x= s*scale10 + scale1; uvScaleBias.y= t*scale10 + scale1; } // *************************************************************************** void CLandscape::releaseTileLightMap(uint tileLightMapId) { uint id, textNum; // Get the id local in the texture. textNum= tileLightMapId / NbTileLightMapByTexture; id= tileLightMapId % NbTileLightMapByTexture; nlassert(textNum<_TextureNears.size()); // Release the tile in this texture. CPatchRdrPass *nearRdrPass= _TextureNears[textNum]; CTextureNear *nearText= (CTextureNear*)(ITexture*)nearRdrPass->TextureDiffuse; nearText->releaseTile(id); _NFreeLightMaps++; // updateLighting // Decrement number of pixels to update for near. _ULTotalNearPixels-= NL_TILE_LIGHTMAP_SIZE*NL_TILE_LIGHTMAP_SIZE; } // *************************************************************************** void CLandscape::refillTileLightMap(uint tileLightMapId, CRGBA map[NL_TILE_LIGHTMAP_SIZE*NL_TILE_LIGHTMAP_SIZE]) { uint id, textNum; // Get the id local in the texture. textNum= tileLightMapId / NbTileLightMapByTexture; id= tileLightMapId % NbTileLightMapByTexture; nlassert(textNum<_TextureNears.size()); // get a ptr on the texture. CPatchRdrPass *nearRdrPass= _TextureNears[textNum]; CTextureNear *nearText= (CTextureNear*)(ITexture*)nearRdrPass->TextureDiffuse; // refill this tile nearText->refillRect(id, map); } // *************************************************************************** // *************************************************************************** // Far. // *************************************************************************** // *************************************************************************** // *************************************************************************** CPatchRdrPass* CLandscape::getFarRenderPass(CPatch* pPatch, uint farIndex, float& farUScale, float& farVScale, float& farUBias, float& farVBias, bool& bRot) { // Check args nlassert (farIndex>0); // Get size of the far texture uint width=(pPatch->getOrderS ()*NL_NUM_PIXELS_ON_FAR_TILE_EDGE)>>(farIndex-1); uint height=(pPatch->getOrderT ()*NL_NUM_PIXELS_ON_FAR_TILE_EDGE)>>(farIndex-1); // For updateLighting: increment total of pixels to update. _ULTotalFarPixels+= width*height; // Try to allocate in every textures uint i; sint bestRdrPass= -1; sint bestSplit= INT_MAX; for(i=0;i<_TextureFars.size();i++) { CTextureFar *pTextureFar=(CTextureFar*)(&*(_TextureFars[i]->TextureDiffuse)); sint splitRes= pTextureFar->tryAllocatePatch(pPatch, farIndex); // if found with no split, ok, stop! if(splitRes==0) { bestRdrPass= i; break; } // else if found, but with split else if(splitRes > 0) { // Then must take the small split possible along all the texture fars. if (splitRessetTextureCategory(_TextureFarCategory); // Append this textureFar to the list of TextureFar to updateLighting. if(_ULRootTextureFar==NULL) _ULRootTextureFar= pTextureFar; else pTextureFar->linkBeforeUL(_ULRootTextureFar); // Set the bank pTextureFar->_Bank=&TileFarBank; // Set as diffuse texture for this renderpass pass->TextureDiffuse=pTextureFar; // Add the render pass to the list _TextureFars.push_back(pass); } // Ok, add the patch to the best render pass in the _TextureFars TSPRenderPass pass= _TextureFars[bestRdrPass]; // Get a pointer on the diffuse far texture CTextureFar *pTextureFar=(CTextureFar*)(&*(pass->TextureDiffuse)); // Allocate really in it (must success since tryAllocate() has succeed) pTextureFar->allocatePatch(pPatch, farIndex, farUScale, farVScale, farUBias, farVBias, bRot); // Return the renderpass return pass; } // *************************************************************************** void CLandscape::freeFarRenderPass (CPatch* pPatch, CPatchRdrPass* pass, uint farIndex) { // Get size of the far texture uint width=(pPatch->getOrderS ()*NL_NUM_PIXELS_ON_FAR_TILE_EDGE)>>(farIndex-1); uint height=(pPatch->getOrderT ()*NL_NUM_PIXELS_ON_FAR_TILE_EDGE)>>(farIndex-1); // For updateLighting: decrement total of pixels to update. _ULTotalFarPixels-= width*height; nlassert(_ULTotalFarPixels>=0); // Get a pointer on the diffuse far texture CTextureFar *pTextureFar=(CTextureFar*)(&*(pass->TextureDiffuse)); // Remove from the patch from the texture if empty pTextureFar->removePatch (pPatch, farIndex); } // *************************************************************************** void CLandscape::clearFarRenderPass (CPatchRdrPass* pass) { // Before deleting, must remove TextureFar from UpdateLighting list. // Get a pointer on the diffuse far texture CTextureFar *pTextureFar=(CTextureFar*)(&*(pass->TextureDiffuse)); // If I delete the textureFar which is the current root if(_ULRootTextureFar==pTextureFar) { // switch to next _ULRootTextureFar= pTextureFar->getNextUL(); // if still the same, it means that the circular list is now empty if(_ULRootTextureFar==pTextureFar) _ULRootTextureFar= NULL; } // unlink the texture from list pTextureFar->unlinkUL(); } // *************************************************************************** // *************************************************************************** // Misc. // *************************************************************************** // *************************************************************************** // *************************************************************************** CZone* CLandscape::getZone (sint zoneId) { TZoneMap::iterator it; it= Zones.find(uint16(zoneId)); if (it!=Zones.end()) return (*it).second; else return NULL; } // *************************************************************************** const CZone* CLandscape::getZone (sint zoneId) const { TZoneMap::const_iterator it; it= Zones.find(uint16(zoneId)); if (it!=Zones.end()) return (*it).second; else return NULL; } // *************************************************************************** void CLandscape::checkZoneBinds(CZone &curZone, EBadBind &bindError) { for(sint i=0;igetPatchConnect(bd.Next[0])); const CPatchInfo::CBindInfo &bo= po.BindEdges[bd.Edge[0]]; if(bo.NPatchs!=1 || bo.Next[0]!=i || bo.Edge[0]!=j) bindError.BindErrors.push_back( EBadBind::CBindError(curZone.getZoneId(), i)); } } } } } // *************************************************************************** void CLandscape::checkBinds() throw(EBadBind) { EBadBind bindError; for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { CZone &curZone= *(*it).second; checkZoneBinds(curZone, bindError); } if(!bindError.BindErrors.empty()) throw bindError; } // *************************************************************************** void CLandscape::checkBinds(uint16 zoneId) throw(EBadBind) { EBadBind bindError; ItZoneMap it= Zones.find(zoneId); if(it!= Zones.end()) { CZone &curZone= *(*it).second; checkZoneBinds(curZone, bindError); if(!bindError.BindErrors.empty()) throw bindError; } } // *************************************************************************** void CLandscape::addTrianglesInBBox(const CPatchIdentEx &paIdEx, const CAABBox &bbox, std::vector &triangles, uint8 tileTessLevel) const { // No clear here, just add triangles to the array. const CPatch *pa= paIdEx.Patch; CPatchIdent paId; paId.ZoneId= paIdEx.ZoneId; paId.PatchId= paIdEx.PatchId; pa->addTrianglesInBBox(paId, bbox, triangles, tileTessLevel); } // *************************************************************************** void CLandscape::buildTrianglesInBBox(const CAABBox &bbox, std::vector &triangles, uint8 tileTessLevel) { // clear selection. triangles.clear(); // search path of interest. _PatchQuadGrid.clearSelection(); _PatchQuadGrid.select(bbox.getMin(), bbox.getMax()); CQuadGrid::CIterator it; // for each patch, add triangles to the array. for(it= _PatchQuadGrid.begin(); it!= _PatchQuadGrid.end(); it++) { addTrianglesInBBox((*it), bbox, triangles, tileTessLevel); } } // *************************************************************************** void CLandscape::addPatchBlocksInBBox(const CPatchIdentEx &paIdEx, const CAABBox &bbox, std::vector &paBlockIds) { // No clear here, just add blocks to the array. const CPatch *pa= paIdEx.Patch; CPatchIdent paId; paId.ZoneId= paIdEx.ZoneId; paId.PatchId= paIdEx.PatchId; pa->addPatchBlocksInBBox(paId, bbox, paBlockIds); } // *************************************************************************** void CLandscape::buildPatchBlocksInBBox(const CAABBox &bbox, std::vector &paBlockIds) { // clear selection. paBlockIds.clear(); // search path of interest. _PatchQuadGrid.clearSelection(); _PatchQuadGrid.select(bbox.getMin(), bbox.getMax()); CQuadGrid::CIterator it; // for each patch, add blocks to the array. for(it= _PatchQuadGrid.begin(); it!= _PatchQuadGrid.end(); it++) { addPatchBlocksInBBox((*it), bbox, paBlockIds); } } // *************************************************************************** void CLandscape::fillPatchQuadBlock(CPatchQuadBlock &quadBlock) const { sint zoneId= quadBlock.PatchBlockId.PatchId.ZoneId; sint patchId= quadBlock.PatchBlockId.PatchId.PatchId; std::map::const_iterator it= Zones.find(uint16(zoneId)); if(it!=Zones.end()) { sint N= (*it).second->getNumPatchs(); // patch must exist in the zone. nlassert(patchId>=0); nlassert(patchId((*it).second)->getPatch(patchId); pa->fillPatchQuadBlock(quadBlock); } } // *************************************************************************** void CLandscape::buildCollideFaces(const CAABBoxExt &bbox, vector &faces, bool faceClip) { CBSphere bsWanted(bbox.getCenter(), bbox.getRadius()); faces.clear(); // For all zones. for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { const CAABBoxExt &bb= (*it).second->getZoneBB(); CBSphere bs(bb.getCenter(), bb.getRadius()); // If zone intersect the wanted area. //=================================== if(bs.intersect(bsWanted)) { // Then trace all patch. sint N= (*it).second->getNumPatchs(); for(sint i=0;i((*it).second)->getPatch(i); // If patch in wanted area.... //============================ if(bsWanted.intersect((*it).second->getPatchBSphere(i))) { // 0. Build the faces. //==================== sint ordS= pa->getOrderS(); sint ordT= pa->getOrderT(); sint x,y,j; vector tmpFaces; tmpFaces.reserve(ordS*ordT); float OOS= 1.0f/ordS; float OOT= 1.0f/ordT; for(y=0;ycomputeVertex(x*OOS, y*OOT); f.V1= pa->computeVertex(x*OOS, (y+1)*OOT); f.V2= pa->computeVertex((x+1)*OOS, (y+1)*OOT); tmpFaces.push_back(f); f.V0= pa->computeVertex(x*OOS, y*OOT); f.V1= pa->computeVertex((x+1)*OOS, (y+1)*OOT); f.V2= pa->computeVertex((x+1)*OOS, y*OOT); tmpFaces.push_back(f); } } // 1. Clip the faces. //=================== if(faceClip) { // Insert only faces which are In the area. for(j=0;j<(sint)tmpFaces.size();j++) { CTriangle &f= tmpFaces[j]; if(bbox.intersect(f.V0, f.V1, f.V2)) { faces.push_back(f); } } } else { // Else insert ALL. faces.insert(faces.end(), tmpFaces.begin(), tmpFaces.end()); } } } } } } // *************************************************************************** void CLandscape::buildCollideFaces(sint zoneId, sint patch, std::vector &faces) { faces.clear(); ItZoneMap it= Zones.find(uint16(zoneId)); if(it!=Zones.end()) { // Then trace all patch. sint N= (*it).second->getNumPatchs(); nlassert(patch>=0); nlassert(patch((*it).second)->getPatch(patch); // Build the faces. //================= sint ordS= pa->getOrderS(); sint ordT= pa->getOrderT(); sint x,y; float OOS= 1.0f/ordS; float OOT= 1.0f/ordT; for(y=0;ycomputeVertex(x*OOS, y*OOT); f.V1= pa->computeVertex(x*OOS, (y+1)*OOT); f.V2= pa->computeVertex((x+1)*OOS, (y+1)*OOT); faces.push_back(f); f.V0= pa->computeVertex(x*OOS, y*OOT); f.V1= pa->computeVertex((x+1)*OOS, (y+1)*OOT); f.V2= pa->computeVertex((x+1)*OOS, y*OOT); faces.push_back(f); } } } } // *************************************************************************** CVector CLandscape::getTesselatedPos(const CPatchIdent &patchId, const CUV &uv) const { // First, must update globals, for CTessFace::computeTesselatedPos() to work properly. // VertexProgrma mode??? CLandscapeGlobals::VertexProgramEnabled= _VertexShaderOk; // If VertexProgram enabled if( CLandscapeGlobals::VertexProgramEnabled ) { /* because VertexProgram enabled, CTessVertex::Pos (geomorphed Pos) are not computed each frame Hence, CTessFace::computeTesselatedPos() will call CTessVertex::computeGeomPos() to have correct CTessVertex::Pos. ThereFore we must setup globals so CTessVertex::computeGeomPos() works properly. */ // see copy in updateGlobalsAndLockBuffers(). NB: Just copy what needed here!!!! // Tile subdivsion part. CLandscapeGlobals::TileDistNear = _TileDistNear; CLandscapeGlobals::TileDistFar = CLandscapeGlobals::TileDistNear+20; CLandscapeGlobals::TileDistNearSqr = sqr(CLandscapeGlobals::TileDistNear); CLandscapeGlobals::TileDistFarSqr = sqr(CLandscapeGlobals::TileDistFar); CLandscapeGlobals::OOTileDistDeltaSqr = 1.0f / (CLandscapeGlobals::TileDistFarSqr - CLandscapeGlobals::TileDistNearSqr); // RefineThreshold. CLandscapeGlobals::RefineThreshold= _Threshold; CLandscapeGlobals::OORefineThreshold= 1.0f / CLandscapeGlobals::RefineThreshold; // Refine Center*. // NB: setup the last setuped refineCenter. CLandscapeGlobals::RefineCenter= _OldRefineCenter; } // \todo yoyo: TODO_ZONEID: change ZoneId in 32 bits... std::map::const_iterator it= Zones.find((uint16)patchId.ZoneId); if(it!=Zones.end()) { sint N= (*it).second->getNumPatchs(); // patch must exist in the zone. nlassert(patchId.PatchId((*it).second)->getPatch(patchId.PatchId); return pa->getTesselatedPos(uv); } else return CVector::Null; } #define NL_TILE_FAR_SIZE_ORDER0 (NL_NUM_PIXELS_ON_FAR_TILE_EDGE*NL_NUM_PIXELS_ON_FAR_TILE_EDGE) #define NL_TILE_FAR_SIZE_ORDER1 ((NL_NUM_PIXELS_ON_FAR_TILE_EDGE>>1)*(NL_NUM_PIXELS_ON_FAR_TILE_EDGE>>1)) #define NL_TILE_FAR_SIZE_ORDER2 ((NL_NUM_PIXELS_ON_FAR_TILE_EDGE>>2)*(NL_NUM_PIXELS_ON_FAR_TILE_EDGE>>2)) // *************************************************************************** // internal use bool CLandscape::eraseTileFarIfNotGood (uint tileNumber, uint sizeOrder0, uint sizeOrder1, uint sizeOrder2) { // The same tiles ? bool bSame=true; // It is the same tile ? if (TileFarBank.getTile (tileNumber)->isFill (CTileFarBank::diffuse)) { // Good diffuse size ? if ( (TileFarBank.getTile (tileNumber)->getSize (CTileFarBank::diffuse, CTileFarBank::order0) != sizeOrder0) || (TileFarBank.getTile (tileNumber)->getSize (CTileFarBank::diffuse, CTileFarBank::order1) != sizeOrder1) || (TileFarBank.getTile (tileNumber)->getSize (CTileFarBank::diffuse, CTileFarBank::order2) != sizeOrder2) ) { TileFarBank.getTile (tileNumber)->erasePixels (CTileFarBank::diffuse); bSame=false; } } // It is the same tile ? if (TileFarBank.getTile (tileNumber)->isFill (CTileFarBank::additive)) { // Good additive size ? if ( (TileFarBank.getTile (tileNumber)->getSize (CTileFarBank::additive, CTileFarBank::order0) != sizeOrder0) || (TileFarBank.getTile (tileNumber)->getSize (CTileFarBank::additive, CTileFarBank::order1) != sizeOrder1) || (TileFarBank.getTile (tileNumber)->getSize (CTileFarBank::additive, CTileFarBank::order2) != sizeOrder2) ) { TileFarBank.getTile (tileNumber)->erasePixels (CTileFarBank::additive); bSame=false; } } // Return true if the tiles seem to be the sames return bSame; } // *************************************************************************** bool CLandscape::initTileBanks () { // *** Check the two banks are OK _FarInitialized=false; // Compatibility check bool bCompatibility=true; // Same number of tiles if (TileBank.getTileCount()==TileFarBank.getNumTile()) { // Same tileSet for (int tileSet=0; tileSetgetNumTile128(); tile++) { // tile number uint tileNumber=TileBank.getTileSet(tileSet)->getTile128(tile); // erase the tiles if not good bCompatibility&=eraseTileFarIfNotGood (tileNumber, NL_TILE_FAR_SIZE_ORDER0, NL_TILE_FAR_SIZE_ORDER1, NL_TILE_FAR_SIZE_ORDER2); } // Same tile256 for (tile=0; tilegetNumTile256(); tile++) { // tile number uint tileNumber=TileBank.getTileSet(tileSet)->getTile256(tile); // erase the tiles if not good bCompatibility&=eraseTileFarIfNotGood (tileNumber, NL_TILE_FAR_SIZE_ORDER0<<2, NL_TILE_FAR_SIZE_ORDER1<<2, NL_TILE_FAR_SIZE_ORDER2<<2); } // Same transition for (tile=0; tilegetTransition(tile)->getTile(); // erase the tiles if not good bCompatibility&=eraseTileFarIfNotGood (tileNumber, NL_TILE_FAR_SIZE_ORDER0, NL_TILE_FAR_SIZE_ORDER1, NL_TILE_FAR_SIZE_ORDER2); } } // Far actived! _FarInitialized=true; } // Register / Load the vegetables. TileBank.initTileVegetableDescs(_VegetableManager); return bCompatibility; } // *************************************************************************** void CLandscape::setupStaticLight (const CRGBA &diffuse, const CRGBA &ambiant, float multiply) { sint nMultiply=(sint)(256.f*multiply); for (int i=0; i<256; i++) { sint max=0; sint r=(((nMultiply*diffuse.R*i)>>8)+ambiant.R*(256-i))>>8; if (r>max) max=r; sint g=(((nMultiply*diffuse.G*i)>>8)+ambiant.G*(256-i))>>8; if (g>max) max=g; sint b=(((nMultiply*diffuse.B*i)>>8)+ambiant.B*(256-i))>>8; if (b>max) max=b; // Not << 8 but << 7 because the _LightValue color table represent a ramp from 0 to 512 r <<= 7; g <<= 7; b <<= 7; max=std::max(max, 256); r/=max; g/=max; b/=max; clamp (r, 0, 255); clamp (g, 0, 255); clamp (b, 0, 255); _LightValue[i].R=uint8(r); _LightValue[i].G=uint8(g); _LightValue[i].B=uint8(b); _LightValue[i].A=255; } } // *************************************************************************** void CLandscape::enableAutomaticLighting(bool enable) { _AutomaticLighting= enable; } // *************************************************************************** void CLandscape::setupAutomaticLightDir(const CVector &lightDir) { _AutomaticLightDir= lightDir; _AutomaticLightDir.normalize(); } // *************************************************************************** CVector CLandscape::getHeightFieldDeltaZ(float x, float y) const { if(_HeightField.ZPatchs.size()==0) return CVector::Null; // map to _HeightField coordinates. x-= _HeightField.OriginX; y-= _HeightField.OriginY; x*= _HeightField.OOSizeX; y*= _HeightField.OOSizeY; // get patch on the grid. sint ix, iy; ix= (sint)floor(x); iy= (sint)floor(y); // out of the grid? if( ix<0 || ix>=(sint)_HeightField.Width || iy<0 || iy>=(sint)_HeightField.Height) return CVector::Null; // get patch. const CBezierPatchZ &paz= _HeightField.ZPatchs[iy*_HeightField.Width + ix]; // compute result. CVector ret=CVector::Null; ret.x= 0; ret.y= 0; ret.z= paz.eval(x-ix, y-iy); return ret; } // *************************************************************************** void CLandscape::CBezierPatchZ::makeInteriors() { float &a = Vertices[0]; float &b = Vertices[1]; float &c = Vertices[2]; float &d = Vertices[3]; Interiors[0] = Tangents[7] + Tangents[0] - a; Interiors[1] = Tangents[1] + Tangents[2] - b; Interiors[2] = Tangents[3] + Tangents[4] - c; Interiors[3] = Tangents[5] + Tangents[6] - d; } // *************************************************************************** float CLandscape::CBezierPatchZ::eval(float ps, float pt) const { float p; float ps2 = ps * ps; float ps1 = 1.0f - ps; float ps12 = ps1 * ps1; float s0 = ps12 * ps1; float s1 = 3.0f * ps * ps12; float s2 = 3.0f * ps2 * ps1; float s3 = ps2 * ps; float pt2 = pt * pt; float pt1 = 1.0f - pt; float pt12 = pt1 * pt1; float t0 = pt12 * pt1; float t1 = 3.0f * pt * pt12; float t2 = 3.0f * pt2 * pt1; float t3 = pt2 * pt; p = Vertices[0] * s0 * t0 + Tangents[7] * s1 * t0 + Tangents[6] * s2 * t0 + Vertices[3] * s3 * t0; p+= Tangents[0] * s0 * t1 + Interiors[0]* s1 * t1 + Interiors[3]* s2 * t1 + Tangents[5] * s3 * t1; p+= Tangents[1] * s0 * t2 + Interiors[1]* s1 * t2 + Interiors[2]* s2 * t2 + Tangents[4] * s3 * t2; p+= Vertices[1] * s0 * t3 + Tangents[2] * s1 * t3 + Tangents[3] * s2 * t3 + Vertices[2] * s3 * t3; return p; } // *************************************************************************** void CLandscape::setHeightField(const CHeightMap &hf) { if(hf.getWidth()<2) return; if(hf.getHeight()<2) return; // Fill basics. _HeightField.OriginX= hf.OriginX; _HeightField.OriginY= hf.OriginY; _HeightField.SizeX= hf.SizeX; _HeightField.SizeY= hf.SizeY; _HeightField.OOSizeX= 1/hf.SizeX; _HeightField.OOSizeY= 1/hf.SizeY; uint w= hf.getWidth()-1; uint h= hf.getHeight()-1; _HeightField.Width= w; _HeightField.Height= h; _HeightField.ZPatchs.resize(w * h); // compute patchs sint x,y; // compute vertices / tangents on each patch for(y=0;y<(sint)h;y++) { for(x=0;x<(sint)w;x++) { CBezierPatchZ &paz= _HeightField.ZPatchs[y*w+x]; // vertices. paz.Vertices[0]= hf.getZ(x, y); paz.Vertices[1]= hf.getZ(x, y+1); paz.Vertices[2]= hf.getZ(x+1, y+1); paz.Vertices[3]= hf.getZ(x+1, y); } } // compute tangents for(y=0;y<(sint)h;y++) { for(x=0;x<(sint)w;x++) { CBezierPatchZ &paz= _HeightField.ZPatchs[y*w+x]; sint tg; // For each tangent, what vertex (relative to x,y) we must take. struct CDeltaPos { sint ox,oy; sint dx1,dy1; sint dx2,dy2; }; static CDeltaPos deltas[8]= { {0,0, 0,1, 0,-1} , {0,1, 0,0, 0,2} , {0,1, 1,1, -1,1} , {1,1, 0,1, 2,1} , {1,1, 1,0, 1,2} , {1,0, 1,1, 1,-1} , {1,0, 0,0, 2,0} , {0,0, 1,0, -1,0} , }; // compute each tangent of this patch. for(tg=0; tg<8;tg++) { sint x0,y0; sint x1,y1; sint x2,y2; x0= x+deltas[tg].ox; y0= y+deltas[tg].oy; x1= x+deltas[tg].dx1; y1= y+deltas[tg].dy1; x2= x+deltas[tg].dx2; y2= y+deltas[tg].dy2; // borders case. if(x2<0 || x2>=(sint)hf.getWidth() || y2<0 || y2>=(sint)hf.getHeight()) { float v,dv; // base of tangents. v= hf.getZ(x0,y0); // target tangents. dv= hf.getZ(x1,y1) - v; // result of tangent. paz.Tangents[tg]= v+dv/3; } // middle case. else { float v,dv; // base of tangents. v= hf.getZ(x0,y0); // target tangents. dv= hf.getZ(x1,y1) - v; // add mirror target tangent. dv+= -(hf.getZ(x2,y2) - v); dv/=2; // result of tangent. paz.Tangents[tg]= v+dv/3; } } } } // compute interiors. for(y=0;y<(sint)h;y++) { for(x=0;x<(sint)w;x++) { CBezierPatchZ &paz= _HeightField.ZPatchs[y*w+x]; paz.makeInteriors(); } } } // *************************************************************************** void CLandscape::getTessellationLeaves(std::vector &leaves) const { leaves.clear(); std::map::const_iterator it; for(it= Zones.begin();it!=Zones.end();it++) { // Then trace all patch. sint N= (*it).second->getNumPatchs(); for(sint i=0;i((*it).second)->getPatch(i); pa->appendTessellationLeaves(leaves); } } } // *************************************************************************** void CLandscape::setPZBModelPosition(const CVector &pos) { _PZBModelPosition= pos; } // *************************************************************************** class CTextureFarLevelInfo { public: uint NumUsedPatchs; uint NumTextures; CTextureFarLevelInfo() { NumUsedPatchs= 0; NumTextures= 0; } }; void CLandscape::profileRender() { // TODO yoyo: new Far mgt profile /* std::map levelFarMap; nlinfo("***** Landscape TextureFar Profile. NumTextureFar= %d", _TextureFars.size()); // Profile Texture Allocate ItSPRenderPassVector itFar; uint totalMemUsage= 0; for(itFar= _TextureFars.begin(); itFar!= _TextureFars.end(); itFar++) { CPatchRdrPass &pass= **itFar; CTextureFar *textureFar= safe_cast((ITexture*)pass.TextureDiffuse); // Info uint memUsage= textureFar->getPatchWidth()*textureFar->getPatchHeight()*NL_NUM_FAR_PATCHES_BY_TEXTURE*2; nlinfo(" * Patch Texture Size: (%d,%d) => :%d bytes for %d patchs", textureFar->getPatchWidth(), textureFar->getPatchHeight(), memUsage, NL_NUM_FAR_PATCHES_BY_TEXTURE); totalMemUsage+= memUsage; // Profile Texture Far Allocated nlinfo(" * NumberOf Patch in the texture:%d", textureFar->getPatchCount()); // Profile currently used Patchs uint numRdrPatch= 0; CPatch *pa= pass.getRdrPatchFar0(); while(pa) { numRdrPatch++; pa= pa->getNextFar0ToRdr(); } pa= pass.getRdrPatchFar1(); while(pa) { numRdrPatch++; pa= pa->getNextFar1ToRdr(); } nlinfo(" * NumberOf Patch in frustum for this texture (Far0+Far1):%d", numRdrPatch); // regroup by level CVector2f sizeLevel; sizeLevel.x= (float)textureFar->getPatchWidth(); sizeLevel.y= (float)textureFar->getPatchHeight(); levelFarMap[sizeLevel].NumUsedPatchs+= textureFar->getPatchCount(); levelFarMap[sizeLevel].NumTextures++; } nlinfo("***** Landscape TextureFar Level Profile. TotalVideoMemory= %d", totalMemUsage); std::map::iterator itLevelFar= levelFarMap.begin(); while(itLevelFar!=levelFarMap.end()) { nlinfo(" * Level PatchSize: (%d, %d). Total NumberOf Patch: %d. Use Percentage: %d %%", (uint)itLevelFar->first.x, (uint)itLevelFar->first.y, itLevelFar->second.NumUsedPatchs, 100*itLevelFar->second.NumUsedPatchs/(itLevelFar->second.NumTextures*NL_NUM_FAR_PATCHES_BY_TEXTURE) ); itLevelFar++; } */ } // *************************************************************************** // *************************************************************************** // Allocators. // *************************************************************************** // *************************************************************************** // *************************************************************************** CTessFace *CLandscape::newTessFace() { // allcoate the face. CTessFace *face= TessFaceAllocator.allocate(); // for refine() mgt, append the face to the list of newLeaves, so they will be tested in refine() face->linkInPList(_RootNewLeaves); return face; } // *************************************************************************** CTessVertex *CLandscape::newTessVertex() { return TessVertexAllocator.allocate(); } // *************************************************************************** CTessNearVertex *CLandscape::newTessNearVertex() { return TessNearVertexAllocator.allocate(); } // *************************************************************************** CTessFarVertex *CLandscape::newTessFarVertex() { return TessFarVertexAllocator.allocate(); } // *************************************************************************** CTileMaterial *CLandscape::newTileMaterial() { return TileMaterialAllocator.allocate(); } // *************************************************************************** CTileFace *CLandscape::newTileFace() { return TileFaceAllocator.allocate(); } // *************************************************************************** void CLandscape::deleteTessFace(CTessFace *f) { // for refine() mgt, must remove from refine priority list, or from the temp rootTessFaceToUpdate list. f->unlinkInPList(); TessFaceAllocator.free(f); } // *************************************************************************** void CLandscape::deleteTessVertex(CTessVertex *v) { TessVertexAllocator.free(v); } // *************************************************************************** void CLandscape::deleteTessNearVertex(CTessNearVertex *v) { TessNearVertexAllocator.free(v); } // *************************************************************************** void CLandscape::deleteTessFarVertex(CTessFarVertex *v) { TessFarVertexAllocator.free(v); } // *************************************************************************** void CLandscape::deleteTileMaterial(CTileMaterial *tm) { TileMaterialAllocator.free(tm); } // *************************************************************************** void CLandscape::deleteTileFace(CTileFace *tf) { TileFaceAllocator.free(tf); } // *************************************************************************** // *************************************************************************** // Noise // *************************************************************************** // *************************************************************************** // *************************************************************************** void CLandscape::setNoiseMode(bool enable) { _NoiseEnabled= enable; } // *************************************************************************** bool CLandscape::getNoiseMode() const { return _NoiseEnabled; } // *************************************************************************** // *************************************************************************** // Micro vegetation // *************************************************************************** // *************************************************************************** // *************************************************************************** void CLandscape::enableVegetable(bool enable) { _VegetableManagerEnabled= enable; // if false, delete all Vegetable IGs. if(!_VegetableManagerEnabled) { // Landscape always create ClipBlokcs, but IGs/addInstances() are created only if isVegetableActive(). // For all zones. for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { // for all patch. sint N= (*it).second->getNumPatchs(); for(sint i=0;igetPatch(i); pa->deleteAllVegetableIgs(); } } } // if true else { // reload all Shapes (actually load only new shapes) TileBank.initTileVegetableDescs(_VegetableManager); // And recreate vegetable igs. // Landscape always create ClipBlokcs, but IGs/addInstances() are created only if isVegetableActive(). // For all zones. for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { // for all patch. sint N= (*it).second->getNumPatchs(); for(sint i=0;igetPatch(i); pa->recreateAllVegetableIgs(); } } } } // *************************************************************************** bool CLandscape::isVegetableActive() const { return _VegetableManagerEnabled && _DriverOkForVegetable; } // *************************************************************************** void CLandscape::loadVegetableTexture(const string &textureFileName) { // load the texture in the manager _VegetableManager->loadTexture(textureFileName); } // *************************************************************************** void CLandscape::setupVegetableLighting(const CRGBA &ambient, const CRGBA &diffuse, const CVector &directionalLight) { // set the directional light to the manager _VegetableManager->setDirectionalLight(ambient, diffuse, directionalLight); } // *************************************************************************** void CLandscape::setVegetableWind(const CVector &windDir, float windFreq, float windPower, float windBendMin) { // setup vegetable manager _VegetableManager->setWind(windDir, windFreq, windPower, windBendMin); } // *************************************************************************** void CLandscape::setVegetableTime(double time) { // setup vegetable manager _VegetableManager->setTime(time); } // *************************************************************************** void CLandscape::setVegetableUpdateLightingTime(double time) { // setup vegetable manager _VegetableManager->setUpdateLightingTime(time); } // *************************************************************************** uint CLandscape::getNumVegetableFaceRendered() const { return _VegetableManager->getNumVegetableFaceRendered(); } // *************************************************************************** const CTileVegetableDesc &CLandscape::getTileVegetableDesc(uint16 tileId) { return TileBank.getTileVegetableDesc(tileId); } // *************************************************************************** void CLandscape::createVegetableBlendLayersModels(CScene *scene) { _VegetableManager->createVegetableBlendLayersModels(scene); } // *************************************************************************** void CLandscape::setVegetableUpdateLightingFrequency(float freq) { _VegetableManager->setUpdateLightingFrequency(freq); } // *************************************************************************** void CLandscape::setupColorsFromTileFlags(const NLMISC::CRGBA colors[4]) { for (TZoneMap::iterator it = Zones.begin(); it != Zones.end(); ++it) { it->second->setupColorsFromTileFlags(colors); } } // *************************************************************************** void CLandscape::setVegetableDensity(float density) { // if the density is really different from what actually setuped if(density!=_VegetableManager->getGlobalDensity()) { _VegetableManager->setGlobalDensity(density); // must recreate all vegetables IGs for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { // for all patch. sint N= (*it).second->getNumPatchs(); for(sint i=0;igetPatch(i); pa->deleteAllVegetableIgs(); // then recreate vegetable Igs of this patch pa->recreateAllVegetableIgs(); } } } } // *************************************************************************** float CLandscape::getVegetableDensity() const { return _VegetableManager->getGlobalDensity(); } // *************************************************************************** // *************************************************************************** // Lightmap Get interface. // *************************************************************************** // *************************************************************************** // *************************************************************************** uint8 CLandscape::getLumel(const CPatchIdent &patchId, const CUV &uv) const { // \todo yoyo: TODO_ZONEID: change ZoneId in 32 bits... std::map::const_iterator it= Zones.find((uint16)patchId.ZoneId); if(it!=Zones.end()) { sint N= (*it).second->getNumPatchs(); // patch must exist in the zone. nlassert(patchId.PatchId((*it).second)->getPatch(patchId.PatchId); return pa->getLumel(uv); } else // Return full sun contribution as default return 255; } // *************************************************************************** void CLandscape::appendTileLightInfluences(const CPatchIdent &patchId, const CUV &uv, std::vector &pointLightList) const { // \todo yoyo: TODO_ZONEID: change ZoneId in 32 bits... std::map::const_iterator it= Zones.find((uint16)patchId.ZoneId); if(it!=Zones.end()) { sint N= (*it).second->getNumPatchs(); // patch must exist in the zone. nlassert(patchId.PatchId((*it).second)->getPatch(patchId.PatchId); pa->appendTileLightInfluences(uv, pointLightList); } } // *************************************************************************** // *************************************************************************** // Lighting // *************************************************************************** // *************************************************************************** // *************************************************************************** void CLandscape::removeAllPointLights() { for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { // for all patch. sint N= (*it).second->getNumPatchs(); for(sint i=0;igetPatch(i); pa->resetTileLightInfluences(); } // Remove all PointLights. (*it).second->_PointLightArray.clear(); } } // *************************************************************************** void CLandscape::setPointLightFactor(const CScene &scene) { // Affect currently added zones. for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { (*it).second->_PointLightArray.setPointLightFactor(scene); } } // *************************************************************************** void CLandscape::updateLighting(double time) { _ULTime= time; // first time in this method?? if(!_ULPrecTimeInit) { _ULPrecTimeInit= true; _ULPrecTime= _ULTime; } // compute delta time from last update. float dt= float(_ULTime - _ULPrecTime); _ULPrecTime= _ULTime; // If not disabled if(_ULFrequency) { // Do it for near and far in 2 distinct ways. updateLightingTextureFar(dt * _ULFrequency); updateLightingTextureNear(dt * _ULFrequency); } } // *************************************************************************** void CLandscape::updateLightingAll() { // Do it for near and far in 2 distinct ways. //================ updateLightingTextureFar(1); updateLightingTextureNear(1); // update lighting for vegetables //================ // Must lock buffers for update Lighting of vegetables. updateGlobalsAndLockBuffers (CVector::Null); // Because updateLighting() may use OptFastFloor.. NLMISC::OptFastFloorBegin(); // update ALL lighting for vegetables _VegetableManager->updateLightingAll(); // Stop fastFloor optim. NLMISC::OptFastFloorEnd(); // Must realase VB Buffers unlockBuffers(); } // *************************************************************************** void CLandscape::setUpdateLightingFrequency(float freq) { freq= max(freq, 0.f); _ULFrequency= freq; } // *************************************************************************** void CLandscape::linkPatchToNearUL(CPatch *patch) { // Append this patch to the list of patch to updateLighting. if(_ULRootNearPatch==NULL) _ULRootNearPatch= patch; else patch->linkBeforeNearUL(_ULRootNearPatch); } // *************************************************************************** void CLandscape::unlinkPatchFromNearUL(CPatch *patch) { // If I unlink the patch which is the current root if(_ULRootNearPatch==patch) { // switch to next _ULRootNearPatch= patch->getNextNearUL(); // if still the same, it means that the circular list is now empty if(_ULRootNearPatch==patch) _ULRootNearPatch= NULL; // reset tessBlock counter. _ULNearCurrentTessBlockId= 0; } // unlink the patch from list patch->unlinkNearUL(); } // *************************************************************************** void CLandscape::updateLightingTextureFar(float ratio) { // compute number of pixels to update. _ULFarPixelsToUpdate+= ratio * _ULTotalFarPixels; // maximize, so at max, it computes all patchs, just one time. _ULFarPixelsToUpdate= min(_ULFarPixelsToUpdate, (float)_ULTotalFarPixels); // Test Profile Yoyo /*extern bool YOYO_LandULTest; if(YOYO_LandULTest) { nlinfo("YOYO_UL Far: %dK, %dK", (sint)_ULFarPixelsToUpdate/1024, (sint)_ULTotalFarPixels/1024); }*/ // while there is still some pixels to update. while(_ULFarPixelsToUpdate > 0 && _ULRootTextureFar) { // update patch (if not null) in the textureFar. _ULFarPixelsToUpdate-= _ULRootTextureFar->touchPatchULAndNext(); // last patch in the texture?? if( _ULRootTextureFar->endPatchULTouch() ) { // yes, go to next texture. _ULRootTextureFar= _ULRootTextureFar->getNextUL(); // reset to 0th patch. _ULRootTextureFar->startPatchULTouch(); } } // Now, _ULFarPixelsToUpdate should be <=0. (most of the time < 0) } // *************************************************************************** void CLandscape::updateLightingTextureNear(float ratio) { // compute number of pixels to update. _ULNearPixelsToUpdate+= ratio * _ULTotalNearPixels; // maximize, so at max, it computes all patchs, just one time. _ULNearPixelsToUpdate= min(_ULNearPixelsToUpdate, (float)_ULTotalNearPixels); // while there is still some pixels to update. while(_ULNearPixelsToUpdate > 0 && _ULRootNearPatch) { // update tessBlock (if lightmap exist for this tessBlock) in the patch. _ULNearPixelsToUpdate-= _ULRootNearPatch->updateTessBlockLighting(_ULNearCurrentTessBlockId); // Next tessBlock to process. _ULNearCurrentTessBlockId++; // last tessBlock in the patch?? if(_ULNearCurrentTessBlockId>=_ULRootNearPatch->getNumNearTessBlocks()) { // yes, go to next patch. _ULRootNearPatch= _ULRootNearPatch->getNextNearUL(); // reset to 0th tessBlock. _ULNearCurrentTessBlockId=0; } } } // *************************************************************************** void CLandscape::computeDynamicLighting(const std::vector &pls) { uint i; // Update globals, and lock buffers //==================== updateGlobalsAndLockBuffers (CVector::Null); // NB: averageTesselationVertices may change vertices in VB in visible patchs => buffers are locked. // Run all DLM Context create, to init Lighting process. //=============== CPatchDLMContext *ctxPtr= _PatchDLMContextList->begin(); while(ctxPtr!=NULL) { // init lighting process, do differential from last computeDynamicLighting() ctxPtr->getPatch()->beginDLMLighting(); // next ctxPtr= (CPatchDLMContext*)ctxPtr->Next; } // compile all pointLights //=============== static vector dlmPls; dlmPls.resize(pls.size()); for(i=0;i::CIterator it; // for each patch, light it with the light. for(it= _PatchQuadGrid.begin(); it!= _PatchQuadGrid.end(); it++) { // get the patch const CPatch *pa= (*it).Patch; // More precise clipping: if( pa->getZone()->getPatchBSphere(pa->getPatchId()).intersect( pl.BSphere ) ) { // Ok, light the patch with this spotLight const_cast(pa)->processDLMLight(pl); } } } // Run all DLM Context create, to end Lighting process. //=============== ctxPtr= _PatchDLMContextList->begin(); while(ctxPtr!=NULL) { // get enxt now, because the DLM itself may be deleted in endDLMLighting() CPatchDLMContext *next= (CPatchDLMContext*)ctxPtr->Next; // delete the DLM if no more needed (near don't use nor pointLights) ctxPtr->getPatch()->endDLMLighting(); // next ctxPtr= next; } // Must realase VB Buffers //==================== unlockBuffers(); } // *************************************************************************** void CLandscape::setDynamicLightingMaxAttEnd(float maxAttEnd) { maxAttEnd= max(maxAttEnd, 1.f); _DLMMaxAttEnd= maxAttEnd; } // *************************************************************************** uint CLandscape::getDynamicLightingMemoryLoad() const { uint mem= 0; // First, set size of global texture overhead. mem= NL3D_LANDSCAPE_DLM_WIDTH * NL3D_LANDSCAPE_DLM_HEIGHT * sizeof(CRGBA); // Then, for each patchContext created CPatchDLMContext *ctxPtr= _PatchDLMContextList->begin(); while(ctxPtr!=NULL) { // add its memory load. mem+= ctxPtr->getMemorySize(); // next ctxPtr= (CPatchDLMContext*)ctxPtr->Next; } return mem; } // *************************************************************************** void CLandscape::setDLMGlobalVegetableColor(CRGBA gvc) { _DLMGlobalVegetableColor= gvc; } // *************************************************************************** void CLandscape::setPointLightDiffuseMaterial(CRGBA diffuse) { _PointLightDiffuseMaterial= diffuse; } // *************************************************************************** void CLandscape::initAnimatedLightIndex(const CScene &scene) { // Affect currently added zones. for(ItZoneMap it= Zones.begin();it!=Zones.end();it++) { (*it).second->_PointLightArray.initAnimatedLightIndex(scene); } } // *************************************************************************** void CLandscape::releaseAllTiles() { nlassert(Zones.empty()); releaseTiles (0, (uint32)TileInfos.size()); } // *************************************************************************** // *************************************************************************** // Dynamic ShadowMaping // *************************************************************************** // *************************************************************************** // *************************************************************************** void CLandscape::appendToShadowPolyReceiver(CTessFace *face) { CTriangle tri; tri.V0= face->VBase->EndPos; tri.V1= face->VLeft->EndPos; tri.V2= face->VRight->EndPos; // Add and store id for remove face->ShadowMapTriId= _ShadowPolyReceiver.addTriangle(tri); } // *************************************************************************** void CLandscape::removeFromShadowPolyReceiver(CTessFace *face) { if(face->ShadowMapTriId!=-1) { _ShadowPolyReceiver.removeTriangle(face->ShadowMapTriId); // set NULL Id. face->ShadowMapTriId=-1; } } // *************************************************************************** void CLandscape::receiveShadowMap(IDriver *drv, CShadowMap *shadowMap, const CVector &casterPos, const CMaterial &shadowMat, const CVector &pzb) { /* substract the PZB from all coordinates. Must add a small zbias because The rendered Triangles may be computed with VertexProgram, but _ShadowPolyReceiver does not. => there is a small float difference at the end Even if same vertex is produced in theory, VertexProgram may cause 2 precision problems: 1/ On NVidia, even with a simple matrix mul VP, the precision result is not the same 2/ Our Landscape VP is not a simple matrix mul. Lot of vertex mul/add are done fpr geomorphs */ CMaterial &sm= const_cast(shadowMat); float oldZBias= sm.getZBias(); sm.setZBias(-0.02f); _ShadowPolyReceiver.render(drv, sm, shadowMap, casterPos, -pzb); sm.setZBias(oldZBias); } // *************************************************************************** void CLandscape::setZFunc(CMaterial::ZFunc val) { TileMaterial.setZFunc(val); FarMaterial.setZFunc(val); } // *************************************************************************** void CLandscape::invalidateAllTiles() { updateGlobalsAndLockBuffers(CVector::Null); for(TZoneMap::iterator it = Zones.begin(); it != Zones.end(); ++it) { if (it->second->Compiled) { for(uint k = 0; k < it->second->Patchs.size(); ++k) { it->second->Patchs[k].deleteTileUvs(); it->second->Patchs[k].recreateTileUvs(); } } } unlockBuffers(); updateTessBlocksFaceVector(); } // *************************************************************************** float CLandscape::getCameraCollision(const CVector &start, const CVector &end, float radius, bool cone) { return _ShadowPolyReceiver.getCameraCollision(start, end, cone?CShadowPolyReceiver::CameraColCone:CShadowPolyReceiver::CameraColCylinder, radius); } // *************************************************************************** float CLandscape::getRayCollision(const CVector &start, const CVector &end) { return _ShadowPolyReceiver.getCameraCollision(start, end, CShadowPolyReceiver::CameraColSimpleRay, 0.f); } // *************************************************************************** bool CLandscape::isTileCallback(ULandscapeTileCallback *cb) const { return std::find(_TileCallbacks.begin(), _TileCallbacks.end(), cb) != _TileCallbacks.end(); } // *************************************************************************** void CLandscape::addTileCallback(ULandscapeTileCallback *cb) { nlassert(cb); nlassert(!isTileCallback(cb)); // callback added twice _TileCallbacks.push_back(cb); } // *************************************************************************** void CLandscape::removeTileCallback(ULandscapeTileCallback *cb) { nlassert(cb); std::vector::iterator it = std::find(_TileCallbacks.begin(), _TileCallbacks.end(), cb); nlassert(it != _TileCallbacks.end()); _TileCallbacks.erase(it); } } // NL3D