// 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/zone.h" #include "nel/3d/landscape.h" #include "nel/3d/zone_symmetrisation.h" #include "nel/misc/common.h" #include "nel/misc/hierarchical_timer.h" using namespace NLMISC; using namespace std; // define it only for debug bind. //#define NL3D_DEBUG_DONT_BIND_PATCH namespace NL3D { // *************************************************************************** // *************************************************************************** // CPatchInfo // *************************************************************************** // *************************************************************************** // *************************************************************************** void CPatchInfo::setCornerSmoothFlag(uint corner, bool smooth) { nlassert(corner<=3); uint mask= 1< &patchs, const std::vector &borderVertices, uint32 numVertices) { CZoneInfo zinfo; zinfo.ZoneId= zoneId; zinfo.Patchs= patchs; zinfo.BorderVertices= borderVertices; build(zinfo, numVertices); } // *************************************************************************** void CZone::build(const CZoneInfo &zoneInfo, uint32 numVertices) { sint i,j; nlassert(!Compiled); // Ref inupt uint16 zoneId= zoneInfo.ZoneId; const std::vector &patchs= zoneInfo.Patchs; const std::vector &borderVertices= zoneInfo.BorderVertices; ZoneId= zoneId; BorderVertices= borderVertices; // Compute the bbox and the bias/scale. //===================================== CAABBox bb; if(patchs.size()) bb.setCenter(patchs[0].Patch.Vertices[0]); bb.setHalfSize(CVector::Null); for(j=0;j<(sint)patchs.size();j++) { const CBezierPatch &p= patchs[j].Patch; for(i=0;i<4;i++) bb.extend(p.Vertices[i]); for(i=0;i<8;i++) bb.extend(p.Tangents[i]); for(i=0;i<4;i++) bb.extend(p.Interiors[i]); } // Compute BBox, and Patch Scale Bias, according to Noise. computeBBScaleBias(bb); // Compute/compress Patchs. //========================= Patchs.resize(patchs.size()); PatchConnects.resize(patchs.size()); sint maxVertex=-1; for(j=0;j<(sint)patchs.size();j++) { const CPatchInfo &pi= patchs[j]; const CBezierPatch &p= pi.Patch; CPatch &pa= Patchs[j]; CPatchConnect &pc= PatchConnects[j]; // Smoothing flags pa.Flags&=~NL_PATCH_SMOOTH_FLAG_MASK; pa.Flags|=NL_PATCH_SMOOTH_FLAG_MASK&(pi.Flags< plRemap; _PointLightArray.build(zoneInfo.PointLights, plRemap); // Check TileLightInfluences integrity, and remap PointLight Indices. for(j=0;j<(sint)patchs.size();j++) { CPatch &pa= Patchs[j]; for(uint k= 0; k &patchs, std::vector &borderVertices) { CZoneInfo zinfo; retrieve(zinfo); patchs= zinfo.Patchs; borderVertices= zinfo.BorderVertices; } // *************************************************************************** void CZone::retrieve(CZoneInfo &zoneInfo) { sint i,j; // Ref on input. std::vector &patchs= zoneInfo.Patchs; std::vector &borderVertices= zoneInfo.BorderVertices; // Copy zoneId. zoneInfo.ZoneId= getZoneId(); // uncompress Patchs. //========================= patchs.resize(Patchs.size()); for(j=0;j<(sint)patchs.size();j++) { CPatchInfo &pi= patchs[j]; CBezierPatch &p= pi.Patch; CPatch &pa= Patchs[j]; CPatchConnect &pc= PatchConnects[j]; // Smoothing flags pi.Flags= (pa.Flags&NL_PATCH_SMOOTH_FLAG_MASK)>>NL_PATCH_SMOOTH_FLAG_SHIFT; // Noise Data // copy noise rotation. pi.NoiseRotation= pa.NoiseRotation; // copy all noise smoothing info. for(i=0;i<4;i++) { pi.setCornerSmoothFlag(i, pa.getCornerSmoothFlag(i)); } // re-Build the uncompressed bezier patch. for(i=0;i<4;i++) pa.Vertices[i].unpack(p.Vertices[i], PatchBias, PatchScale); for(i=0;i<8;i++) pa.Tangents[i].unpack(p.Tangents[i], PatchBias, PatchScale); for(i=0;i<4;i++) pa.Interiors[i].unpack(p.Interiors[i], PatchBias, PatchScale); pi.Tiles= pa.Tiles; pi.TileColors= pa.TileColors; pi.TileLightInfluences= pa.TileLightInfluences; pi.Lumels.resize ((pa.OrderS*4)*(pa.OrderT*4)); pi.Flags=(pa.Flags&NL_PATCH_SMOOTH_FLAG_MASK)>>NL_PATCH_SMOOTH_FLAG_SHIFT; // Unpack the lumel map pa.unpackShadowMap (&pi.Lumels[0]); // from the patchConnect. pi.OrderS= pa.OrderS; pi.OrderT= pa.OrderT; pi.ErrorSize= pc.ErrorSize; for(i=0;i<4;i++) { pi.BaseVertices[i]= pc.BaseVertices[i]; } for(i=0;i<4;i++) pi.BindEdges[i]= pc.BindEdges[i]; } // retrieve bordervertices. //========================= borderVertices= BorderVertices; // retrieve PointLights. //========================= zoneInfo.PointLights= _PointLightArray.getPointLights(); } // *************************************************************************** void CZone::build(const CZone &zone) { nlassert(!Compiled); ZoneId= zone.ZoneId; BorderVertices= zone.BorderVertices; // Compute the bbox and the bias/scale. //===================================== ZoneBB= zone.ZoneBB; PatchScale= zone.PatchScale; PatchBias= zone.PatchBias; // Compute/compress Patchs. //========================= Patchs= zone.Patchs; PatchConnects= zone.PatchConnects; // Init the Clip Arrays _PatchRenderClipped.resize(Patchs.size()); _PatchOldRenderClipped.resize(Patchs.size()); _PatchRenderClipped.setAll(); _PatchOldRenderClipped.setAll(); // copy pointLights. //========================= _PointLightArray= zone._PointLightArray; NumVertices= zone.NumVertices; } // *************************************************************************** void CBorderVertex::serial(NLMISC::IStream &f) { /* *********************************************** * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance * It can be loaded/called through CAsyncFileManager for instance * ***********************************************/ (void)f.serialVersion(0); f.xmlSerial (CurrentVertex, "CURRENT_VERTEX"); f.xmlSerial (NeighborZoneId, "NEIGHTBOR_ZONE_ID"); f.xmlSerial (NeighborVertex, "NEIGHTBOR_VERTEX"); } void CZone::CPatchConnect::serial(NLMISC::IStream &f) { /* *********************************************** * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance * It can be loaded/called through CAsyncFileManager for instance * ***********************************************/ uint ver= f.serialVersion(1); if (ver<1) f.serial(OldOrderS, OldOrderT, ErrorSize); else f.serial(ErrorSize); f.xmlSerial (BaseVertices[0], BaseVertices[1], BaseVertices[2], BaseVertices[3], "BASE_VERTICES"); f.xmlSerial (BindEdges[0], BindEdges[1], BindEdges[2], BindEdges[3], "BIND_EDGES"); } void CPatchInfo::CBindInfo::serial(NLMISC::IStream &f) { /* *********************************************** * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance * It can be loaded/called through CAsyncFileManager for instance * ***********************************************/ (void)f.serialVersion(0); f.xmlSerial(NPatchs, "NPATCH"); nlassert ( (NPatchs==0) | (NPatchs==1) | (NPatchs==2) | (NPatchs==4) | (NPatchs==5) ); f.xmlSerial (ZoneId, "ZONE_ID"); f.xmlSerial (Next[0], Next[1], Next[2], Next[3], "NEXT_PATCH"); f.xmlSerial (Edge[0], Edge[1], Edge[2], Edge[3], "NEXT_EDGE"); } // *************************************************************************** void CZone::serial(NLMISC::IStream &f) { /* *********************************************** * WARNING: This Class/Method must be thread-safe (ctor/dtor/serial): no static access for instance * It can be loaded/called through CAsyncFileManager for instance * ***********************************************/ /* Version 4: - PointLights Version 3: - Lumels compression version 2. Version 2: - Lumels. Version 1: - Tile color. Version 0: - base verison. */ uint ver= f.serialVersion(4); // No more compatibility before version 3 if (ver<3) { throw EOlderStream(f); } f.serialCheck((uint32)'ENOZ'); f.xmlSerial (ZoneId, "ZONE_ID"); f.xmlSerial (ZoneBB, "BB"); f.xmlSerial (PatchBias, "PATCH_BIAS"); f.xmlSerial (PatchScale, "PATCH_SCALE"); f.xmlSerial (NumVertices, "NUM_VERTICES"); f.xmlPush ("BORDER_VERTICES"); f.serialCont(BorderVertices); f.xmlPop (); f.xmlPush ("PATCHES"); f.serialCont(Patchs); f.xmlPop (); f.xmlPush ("PATCH_CONNECTS"); f.serialCont(PatchConnects); f.xmlPop (); if (ver>=4) { f.xmlPush ("POINT_LIGHTS"); f.serial(_PointLightArray); f.xmlPop (); } // If read, must create and init Patch Clipped state to true (clipped even if not compiled) if(f.isReading()) { _PatchRenderClipped.resize(Patchs.size()); _PatchOldRenderClipped.resize(Patchs.size()); _PatchRenderClipped.setAll(); _PatchOldRenderClipped.setAll(); } // If read and version 0, must init default TileColors of patchs. //=============================================================== // if(f.isReading() && ver<2) ... // Deprecated, because ver<3 not supported } // *************************************************************************** void CZone::compile(CLandscape *landscape, TZoneMap &loadedZones) { sint i,j; TZoneMap neighborZones; //nlinfo("Compile Zone: %d \n", (sint32)getZoneId()); // Can't compile if compiled. nlassert(!Compiled); Landscape= landscape; // Attach this to loadedZones. //============================ nlassert(loadedZones.find(ZoneId)==loadedZones.end()); loadedZones[ZoneId]= this; // Create/link the base vertices according to present neigbor zones. //============================ BaseVertices.clear(); BaseVertices.resize(NumVertices); // First try to link vertices to other. for(i=0;i<(sint)BorderVertices.size();i++) { sint cur= BorderVertices[i].CurrentVertex; sint vertto= BorderVertices[i].NeighborVertex; sint zoneto= BorderVertices[i].NeighborZoneId; nlassert(curgetBaseVertex(vertto); } } // Else, create unbounded vertices. for(i=0;i<(sint)BaseVertices.size();i++) { if(BaseVertices[i]==NULL) { BaseVertices[i]= new CTessBaseVertex; } } // compile() the patchs. //====================== for(j=0;j<(sint)Patchs.size();j++) { CPatch &pa= Patchs[j]; CPatchConnect &pc= PatchConnects[j]; CTessVertex *baseVertices[4]; baseVertices[0]= &(BaseVertices[pc.BaseVertices[0]]->Vert); baseVertices[1]= &(BaseVertices[pc.BaseVertices[1]]->Vert); baseVertices[2]= &(BaseVertices[pc.BaseVertices[2]]->Vert); baseVertices[3]= &(BaseVertices[pc.BaseVertices[3]]->Vert); pa.compile(this, j, pa.OrderS, pa.OrderT, baseVertices, pc.ErrorSize); }; // compile() the Clip information for the patchs. //====================== _PatchBSpheres.resize(Patchs.size()); for(j=0;j<(sint)Patchs.size();j++) { CPatch &pa= Patchs[j]; // Buil the BSPhere of the patch. CAABBox bb= pa.buildBBox(); _PatchBSpheres[j].Center= bb.getCenter(); _PatchBSpheres[j].Radius= bb.getRadius(); } // bind() the patchs. (after all compiled). //=================== for(j=0;j<(sint)Patchs.size();j++) { CPatch &pa= Patchs[j]; CPatchConnect &pc= PatchConnects[j]; // bind the patch. This is the original bind, not a rebind. bindPatch(loadedZones, pa, pc, false); } // rebindBorder() on neighbor zones. //================================== ItZoneMap zoneIt; // Traverse the neighborood. for(zoneIt= neighborZones.begin(); zoneIt!=neighborZones.end(); zoneIt++) { (*zoneIt).second->rebindBorder(loadedZones); } // End!! Compiled= true; } // *************************************************************************** void CZone::release(TZoneMap &loadedZones) { sint i,j; if(!Compiled) return; // detach this zone to loadedZones. //================================= nlassert(loadedZones.find(ZoneId)!=loadedZones.end()); loadedZones.erase(ZoneId); // It doesn't server to unbindPatch(), since patch is not binded to neigbors. // unbind() the patchs. //===================== for(j=0;j<(sint)Patchs.size();j++) { CPatch &pa= Patchs[j]; unbindPatch(pa); } // rebindBorder() on neighbor zones. //================================== // Build the nieghborood. TZoneMap neighborZones; for(i=0;i<(sint)BorderVertices.size();i++) { sint cur= BorderVertices[i].CurrentVertex; sint zoneto= BorderVertices[i].NeighborZoneId; nlassert(currebindBorder(loadedZones); } // release() the patchs. //====================== // unbind() need compiled neigbor patchs, so do the release after all unbind (so after rebindBorder() too...). for(j=0;j<(sint)Patchs.size();j++) { CPatch &pa= Patchs[j]; pa.release(); } // destroy/unlink the base vertices (internal..), according to present neigbor zones. //================================= // Just release the smartptrs (easy!!). Do it after patchs released... BaseVertices.clear(); // End!! Compiled= false; Landscape= NULL; ClipResult= ClipOut; } // *************************************************************************** // *************************************************************************** // Private part. // *************************************************************************** // *************************************************************************** // *************************************************************************** void CZone::rebindBorder(TZoneMap &loadedZones) { sint j; // rebind patchs which are on border. for(j=0;j<(sint)Patchs.size();j++) { CPatch &pa= Patchs[j]; CPatchConnect &pc= PatchConnects[j]; if(patchOnBorder(pc)) { // rebind the patch. This is a rebind. bindPatch(loadedZones, pa, pc, true); } } } // *************************************************************************** CPatch *CZone::getZonePatch(TZoneMap &loadedZones, sint zoneId, sint patch) { #ifdef NL3D_DEBUG_DONT_BIND_PATCH return NULL; #endif if(loadedZones.find(zoneId)==loadedZones.end()) return NULL; else return (loadedZones[zoneId])->getPatch(patch); } // *************************************************************************** void CZone::buildBindInfo(uint patchId, uint edge, CZone *neighborZone, CPatch::CBindInfo &paBind) { nlassert(patchId < Patchs.size()); nlassert(neighborZone); CPatchConnect &pc= PatchConnects[patchId]; // Get the bind info of this patch to his neighbor on "edge". CPatchInfo::CBindInfo &pcBind= pc.BindEdges[edge]; nlassert(pcBind.NPatchs==0 || pcBind.NPatchs==1 || pcBind.NPatchs==2 || pcBind.NPatchs==4 || pcBind.NPatchs==5); // copy zone ptr. paBind.Zone= neighborZone; // Special case of a small patch connected to a bigger. if(pcBind.NPatchs==5) { paBind.NPatchs= 1; paBind.Next[0]= neighborZone->getPatch(pcBind.Next[0]); paBind.Edge[0]= pcBind.Edge[0]; // Get the twin bindInfo of pcBind. const CPatchInfo::CBindInfo &pcBindNeighbor= neighborZone->getPatchConnect(pcBind.Next[0])->BindEdges[pcBind.Edge[0]]; // must have a multiple bind. nlassert(pcBindNeighbor.NPatchs == 2 || pcBindNeighbor.NPatchs == 4); // number of bind is stored on the twin bindInfo. paBind.MultipleBindNum= pcBindNeighbor.NPatchs; // Search our patchId on neighbor; paBind.MultipleBindId= 255; for(sint i=0; igetPatch(pcBind.Next[i]); paBind.Edge[i]= pcBind.Edge[i]; } } } // *************************************************************************** void CZone::bindPatch(TZoneMap &loadedZones, CPatch &pa, CPatchConnect &pc, bool rebind) { CPatch::CBindInfo edges[4]; // Fill all edges. for(sint i=0;i<4;i++) { CPatchInfo::CBindInfo &pcBind= pc.BindEdges[i]; CPatch::CBindInfo &paBind= edges[i]; nlassert(pcBind.NPatchs==0 || pcBind.NPatchs==1 || pcBind.NPatchs==2 || pcBind.NPatchs==4 || pcBind.NPatchs==5); paBind.NPatchs= pcBind.NPatchs; // Find the zone. TZoneMap::iterator itZoneMap; // If no neighbor, or if zone neighbor not loaded. if( paBind.NPatchs==0 || (itZoneMap=loadedZones.find(pcBind.ZoneId)) == loadedZones.end() ) paBind.Zone= NULL; else paBind.Zone= itZoneMap->second; // Special case of a small patch connected to a bigger. if(paBind.NPatchs==5) { paBind.Edge[0]= pcBind.Edge[0]; paBind.Next[0]= CZone::getZonePatch(loadedZones, pcBind.ZoneId, pcBind.Next[0]); // If not loaded, don't bind to this edge. if(!paBind.Next[0]) paBind.NPatchs=0; else { // pa.bind() will do the job. // Leave it flagged with NPatchs==5. continue; } } // Bind 1/1 and 1/2,1/4 if(paBind.NPatchs>=1) { paBind.Edge[0]= pcBind.Edge[0]; paBind.Next[0]= CZone::getZonePatch(loadedZones, pcBind.ZoneId, pcBind.Next[0]); // If not loaded, don't bind to this edge. if(!paBind.Next[0]) paBind.NPatchs=0; } if(paBind.NPatchs>=2) { paBind.Edge[1]= pcBind.Edge[1]; paBind.Next[1]= CZone::getZonePatch(loadedZones, pcBind.ZoneId, pcBind.Next[1]); // If not loaded, don't bind to this edge. if(!paBind.Next[1]) paBind.NPatchs=0; } if(paBind.NPatchs>=4) { paBind.Edge[2]= pcBind.Edge[2]; paBind.Edge[3]= pcBind.Edge[3]; paBind.Next[2]= CZone::getZonePatch(loadedZones, pcBind.ZoneId, pcBind.Next[2]); paBind.Next[3]= CZone::getZonePatch(loadedZones, pcBind.ZoneId, pcBind.Next[3]); // If not loaded, don't bind to this edge. if(!paBind.Next[2] || !paBind.Next[3]) paBind.NPatchs=0; } } // First, unbind. pa.unbind(); // Then bind. pa.bind(edges, rebind); } // *************************************************************************** void CZone::unbindPatch(CPatch &pa) { /* Remind: the old version with CPatch::unbindFrom*() doesn't work because of CZone::release(). This function first erase the zone from loadedZones... Not matter here. We use CPatch::unbind() which should do all the good job correctly (unbind pa from ohters , and unbind others from pa at same time). */ pa.unbind(); } // *************************************************************************** bool CZone::patchOnBorder(const CPatchConnect &pc) const { // If only one of neighbor patch is not of this zone, we are on a border. // Test all edges. for(sint i=0;i<4;i++) { const CPatchInfo::CBindInfo &pcBind= pc.BindEdges[i]; nlassert(pcBind.NPatchs==0 || pcBind.NPatchs==1 || pcBind.NPatchs==2 || pcBind.NPatchs==4 || pcBind.NPatchs==5); if(pcBind.NPatchs>=1) { if(pcBind.ZoneId != ZoneId) return true; } } return false; } // *************************************************************************** // *************************************************************************** // Render part. // *************************************************************************** // *************************************************************************** // *************************************************************************** const CBSphere &CZone::getPatchBSphere(uint patch) const { static CBSphere dummySphere; if(patch<_PatchBSpheres.size()) return _PatchBSpheres[patch]; else return dummySphere; } // *************************************************************************** void CZone::clip(const std::vector &pyramid) { H_AUTO( NLMISC_ClipZone ); nlassert(Compiled); // bkup old ClipResult. NB: by default, it is ClipOut (no VB created). sint oldClipResult= ClipResult; // Pyramid with only the planes that clip the zone static std::vector patchPyramid(10); static std::vector patchPyramidIndex(10); patchPyramidIndex.clear(); // Compute ClipResult. //------------------- ClipResult= ClipIn; for(sint i=0;i<(sint)pyramid.size();i++) { // If entirely out. if(!ZoneBB.clipBack(pyramid[i])) { ClipResult= ClipOut; // If out of only one plane, out of all. break; } // If partially IN (ie not entirely out, and not entirely IN) else if(ZoneBB.clipFront(pyramid[i])) { // Force ClipResult to be ClipSide, and not ClipIn. ClipResult=ClipSide; // Append the plane index to list to test patchPyramidIndex.push_back(i); } } // Easy Clip :) if(Patchs.empty()) { ClipResult= ClipOut; // don't need to go below... return; } // Clip By Patch Pass. //-------------------- if(ClipResult==ClipOut) { H_AUTO( NLMISC_ClipZone_Out ); // Set All RenderClip flags to true. _PatchRenderClipped.setAll(); } else if(ClipResult==ClipIn) { H_AUTO( NLMISC_ClipZone_In ); // Set All RenderClip flags to false. _PatchRenderClipped.clearAll(); } else { H_AUTO( NLMISC_ClipZone_Side ); // Copy only the pyramid planes of interest patchPyramid.resize(patchPyramidIndex.size()); uint i; for(i=0;i(&_PatchOldRenderClipped.getVector()[0]); const uint32 *newRenderClip= &_PatchRenderClipped.getVector()[0]; uint numPatchs= Patchs.size(); // Then, we must test by patch. for(uint i=0;i0;maxNumBits--, mask<<=1, i++) { // same as: if(_PatchOldRenderClipped[i] != _PatchRenderClipped[i]) if( (oldWord^newWord)&mask ) { // set the flag. *oldRenderClip&= ~mask; *oldRenderClip|= newWord&mask; // update clip patch Patchs[i].updateClipPatchVB( (newWord&mask)!=0 ); } } } } } // *************************************************************************** void CZone::clipPatchs(const std::vector &pyramid) { // Init all to Not clipped _PatchRenderClipped.clearAll(); for(uint j=0;j<_PatchBSpheres.size();j++) { CBSphere &bSphere= _PatchBSpheres[j]; for(sint i=0;i<(sint)pyramid.size();i++) { // If entirely out. if(!bSphere.clipBack(pyramid[i])) { _PatchRenderClipped.set(j, true); break; } } } } // *************************************************************************** // DebugYoyo. // Code for Debug test Only.. Do not erase it, may be used later :) /* static void cleanTess(CTessFace *face) { if(!face->isLeaf()) { cleanTess(face->SonLeft); cleanTess(face->SonRight); } // If has father, clean it. if(face->Father) { CTessFace *face1=face->Father; CTessFace *face2=face->Father->FBase; face1->FLeft= face1->SonLeft->FBase; face1->FRight= face1->SonRight->FBase; if(face2!=NULL) { face2->FLeft= face2->SonLeft->FBase; face2->FRight= face2->SonRight->FBase; } } } static void testTess(CTessFace *face) { if(!face->isLeaf()) { testTess(face->SonLeft); testTess(face->SonRight); } // Test validity. nlassert(!face->FBase || face->FBase->Patch!=(CPatch*)0xdddddddd); nlassert(!face->FLeft || face->FLeft->Patch!=(CPatch*)0xdddddddd); nlassert(!face->FRight || face->FRight->Patch!=(CPatch*)0xdddddddd); } static void checkTess() { // This test should be inserted at begin of CZone::refine(). // And it needs hacking public/private. CPatch *pPatch; sint n; pPatch= &(*Patchs.begin()); for(n=(sint)Patchs.size();n>0;n--, pPatch++) { cleanTess(pPatch->Son0); cleanTess(pPatch->Son1); } pPatch= &(*Patchs.begin()); for(n=(sint)Patchs.size();n>0;n--, pPatch++) { testTess(pPatch->Son0); testTess(pPatch->Son1); } } */ // *************************************************************************** void CZone::excludePatchFromRefineAll(uint patch, bool exclude) { nlassert(Compiled); nlassert(patch=Patchs.size()) return; Patchs[patch].ExcludeFromRefineAll= exclude; } // *************************************************************************** void CZone::refineAll() { nlassert(Compiled); if(Patchs.size()==0) return; // DO NOT do a forceNoRenderClip(), to avoid big allocation of Near/Far VB vertices in driver. // DO NOT modify ClipResult, to avoid big allocation of Near/Far VB vertices in driver. // refine ALL patchs (even those which may be invisible). CPatch *pPatch= &(*Patchs.begin()); sint n; for(n=(sint)Patchs.size();n>0;n--, pPatch++) { // For Pacs construction: may exclude some patch from refineAll (for speed improvement). if(!pPatch->ExcludeFromRefineAll) pPatch->refineAll(); } } // *************************************************************************** void CZone::averageTesselationVertices() { nlassert(Compiled); if(Patchs.size()==0) return; // averageTesselationVertices of ALL patchs. CPatch *pPatch= &(*Patchs.begin()); for(sint n=(sint)Patchs.size();n>0;n--, pPatch++) { pPatch->averageTesselationVertices(); } } // *************************************************************************** void CZone::preRender() { nlassert(Compiled); // Must be 2^X-1. static const uint updateFarRefineFreq= 15; // Take the renderDate here. uint curDateMod= CLandscapeGlobals::CurrentRenderDate & updateFarRefineFreq; // If no patchs, do nothing. if(Patchs.empty()) return; /* If patchs invisible, must still update their Far Textures, else, there may be slowdown when we turn the head. */ // If all the zone is invisible. if(ClipResult==ClipOut) { // No patchs are visible, but maybe update the far textures. if( curDateMod==(ZoneId & updateFarRefineFreq) ) { // updateTextureFarOnly for all patchs. for(uint i=0;i0) pPatch= &(*Patchs.begin()); for(sint n=(sint)Patchs.size();n>0;n--, pPatch++) { pPatch->forceMergeAtTileLevel(); } } // *************************************************************************** // *************************************************************************** // Misc part. // *************************************************************************** // *************************************************************************** // *************************************************************************** void CZone::changePatchTextureAndColor (sint numPatch, const std::vector *tiles, const std::vector *colors) { nlassert(numPatch>=0); nlassert(numPatchsize() ); Patchs[numPatch].Tiles = *tiles; } // Update the patch colors. if (colors) { nlassert( Patchs[numPatch].TileColors.size() == colors->size() ); Patchs[numPatch].TileColors = *colors; } if (Compiled) { // If the patch is visible, then we must LockBuffers, because new VertexVB may be created. if(!_PatchRenderClipped[numPatch]) Landscape->updateGlobalsAndLockBuffers(CVector::Null); // Recompute UVs for new setup of Tiles. Patchs[numPatch].deleteTileUvs(); Patchs[numPatch].recreateTileUvs(); // unlockBuffers() if necessary. if(!_PatchRenderClipped[numPatch]) { Landscape->unlockBuffers(); // This patch is visible, and TileFaces have been deleted / added. // So must update TessBlock. Landscape->updateTessBlocksFaceVector(); } } } // *************************************************************************** void CZone::refreshTesselationGeometry(sint numPatch) { nlassert(numPatch>=0); nlassert(numPatch_RenderMustRefillVB= true; Patchs[numPatch].refreshTesselationGeometry(); } // *************************************************************************** const std::vector &CZone::getPatchTexture(sint numPatch) const { nlassert(numPatch>=0); nlassert(numPatch &CZone::getPatchColor(sint numPatch) const { nlassert(numPatch>=0); nlassert(numPatch= 0.0f); // factor must not be negative as its a multiplier if (monochrome) { for (uint32 i = 0; i < Patchs.size(); ++i) { vector &rTC = Patchs[i].TileColors; for (uint32 j = 0; j < rTC.size(); ++j) { float fR = (rTC[j].Color565 & 31) / 32.0f; float fG = ((rTC[j].Color565 >> 5) & 63) / 64.0f; float fB = ((rTC[j].Color565 >> 11) & 31) / 32.0f; fR = 0.28f * fR + 0.59f * fG + 0.13f * fB; nlassert(fR < 0.99f); fR *= factor; if (fR > 0.99f) fR = 0.99f; // Avoid reaching 1 uint16 nR = (uint16)(fR * 32.0f); uint16 nG = (uint16)(fR * 64.0f); uint16 nB = (uint16)(fR * 32.0f); rTC[j].Color565 = nR + (nG << 5) + (nB << 11); } } } else { if (factor != 1.0f) { for (uint32 i = 0; i < Patchs.size(); ++i) { vector &rTC = Patchs[i].TileColors; for (uint32 j = 0; j < rTC.size(); ++j) { float fR = (rTC[j].Color565 & 31) / 32.0f; float fG = ((rTC[j].Color565 >> 5) & 63) / 64.0f; float fB = ((rTC[j].Color565 >> 11) & 31) / 32.0f; fR *= factor; fG *= factor; fB *= factor; if (fR > 0.99f) fR = 0.99f; if (fG > 0.99f) fG = 0.99f; if (fB > 0.99f) fB = 0.99f; uint16 nR = (uint16)(fR * 32.0f); uint16 nG = (uint16)(fG * 64.0f); uint16 nB = (uint16)(fB * 32.0f); rTC[j].Color565 = nR + (nG << 5) + (nB << 11); } } } } } // *************************************************************************** void CZone::debugBinds(FILE *f) { fprintf(f, "*****************************\n"); fprintf(f, "ZoneId: %d. NPatchs:%zu\n", ZoneId, PatchConnects.size()); sint i; for(i=0;i<(sint)PatchConnects.size();i++) { CPatchConnect &pc= PatchConnects[i]; fprintf(f, "patch%d:\n", i); for(sint j=0;j<4;j++) { CPatchInfo::CBindInfo &bd= pc.BindEdges[j]; fprintf(f, " edge%d: Zone:%d. NPatchs:%d. ", j, bd.ZoneId, bd.NPatchs); for(sint k=0;k (zone %d) vertex %d\n",BorderVertices[i].CurrentVertex, BorderVertices[i].NeighborZoneId, BorderVertices[i].NeighborVertex); } } // *************************************************************************** void CZone::applyHeightField(const CLandscape &landScape) { sint i,j; vector patchs; // no patch, do nothing. if(Patchs.size()==0) return; // 0. Unpack patchs to Bezier Patchs. //=================================== patchs.resize(Patchs.size()); for(j=0;j<(sint)patchs.size();j++) { CBezierPatch &p= patchs[j]; CPatch &pa= Patchs[j]; // re-Build the uncompressed bezier patch. for(i=0;i<4;i++) pa.Vertices[i].unpack(p.Vertices[i], PatchBias, PatchScale); for(i=0;i<8;i++) pa.Tangents[i].unpack(p.Tangents[i], PatchBias, PatchScale); for(i=0;i<4;i++) pa.Interiors[i].unpack(p.Interiors[i], PatchBias, PatchScale); } // 1. apply heightfield on bezier patchs. //=================================== for(j=0;j<(sint)patchs.size();j++) { CBezierPatch &p= patchs[j]; // apply delta. for(i=0;i<4;i++) p.Vertices[i]+= landScape.getHeightFieldDeltaZ(p.Vertices[i].x, p.Vertices[i].y); for(i=0;i<8;i++) p.Tangents[i]+= landScape.getHeightFieldDeltaZ(p.Tangents[i].x, p.Tangents[i].y); for(i=0;i<4;i++) p.Interiors[i]+= landScape.getHeightFieldDeltaZ(p.Interiors[i].x, p.Interiors[i].y); } // 2. Re-compute Patch Scale/Bias, and Zone BBox. //=================================== CAABBox bb; bb.setCenter(patchs[0].Vertices[0]); bb.setHalfSize(CVector::Null); for(j=0;j<(sint)patchs.size();j++) { // extend bbox. const CBezierPatch &p= patchs[j]; for(i=0;i<4;i++) bb.extend(p.Vertices[i]); for(i=0;i<8;i++) bb.extend(p.Tangents[i]); for(i=0;i<4;i++) bb.extend(p.Interiors[i]); } // Compute BBox, and Patch Scale Bias, according to Noise. computeBBScaleBias(bb); // 3. Re-pack patchs. //=================================== for(j=0;j<(sint)patchs.size();j++) { CBezierPatch &p= patchs[j]; CPatch &pa= Patchs[j]; // Build the packed patch. for(i=0;i<4;i++) pa.Vertices[i].pack(p.Vertices[i], PatchBias, PatchScale); for(i=0;i<8;i++) pa.Tangents[i].pack(p.Tangents[i], PatchBias, PatchScale); for(i=0;i<4;i++) pa.Interiors[i].pack(p.Interiors[i], PatchBias, PatchScale); } } // *************************************************************************** void CZone::setupColorsFromTileFlags(const NLMISC::CRGBA colors[4]) { for (uint k = 0; k < Patchs.size(); ++k) { Patchs[k].setupColorsFromTileFlags(colors); } } // *************************************************************************** void CZone::copyTilesFlags(sint destPatchId, const CPatch *srcPatch) { CPatch *destPatch = getPatch(destPatchId); destPatch->copyTileFlagsFromPatch(srcPatch); } // *************************************************************************** bool CPatchInfo::getNeighborTile (uint patchId, uint edge, sint position, uint &patchOut, sint &sOut, sint &tOut, const vector &patchInfos) const { nlassert (edge<4); // S or T ? uint length = (edge&1) ? OrderS : OrderT; nlassert ((uint)position= bank.getTileSetCount())) { nlwarning("tile %d has an unknown tileSet (%d)",tile, tileSet); return false; } // Is it an oriented tile ? if (bank.getTileSet (tileSet)->getOriented()) { // New rotation value rotate = 0; } // Ok return true; } return false; } else return true; } // *************************************************************************** bool CPatchInfo::transformTile (const CTileBank &bank, uint &tile, uint &tileRotation, bool symmetry, uint rotate, bool goofy) { // Tile exist ? if ( (rotate!=0) || symmetry ) { if (tile < (uint)bank.getTileCount()) { // Get xref int tileSet; int number; CTileBank::TTileType type; // Get tile xref bank.getTileXRef ((int)tile, tileSet, number, type); // Transition ? if (type == CTileBank::transition) { // Rotation for transition uint transRotate = rotate; // Number should be ok nlassert (number>=0); nlassert (numbergetOrientedBorder (CTileSet::left, CTileSet::getEdgeType ((CTileSet::TTransition)number, CTileSet::left)), pTileSet->getOrientedBorder (CTileSet::bottom, CTileSet::getEdgeType ((CTileSet::TTransition)number, CTileSet::bottom)), pTileSet->getOrientedBorder (CTileSet::right, CTileSet::getEdgeType ((CTileSet::TTransition)number, CTileSet::right)), pTileSet->getOrientedBorder (CTileSet::top, CTileSet::getEdgeType ((CTileSet::TTransition)number, CTileSet::top)) }; // Symmetry ? if (symmetry) { if ( (tileRotation & 1) ^ goofy ) { CTileSet::TFlagBorder tmp = oriented[1]; oriented[1] = CTileSet::getInvertBorder (oriented[3]); oriented[3] = CTileSet::getInvertBorder (tmp); oriented[2] = CTileSet::getInvertBorder (oriented[2]); oriented[0] = CTileSet::getInvertBorder (oriented[0]); } else { CTileSet::TFlagBorder tmp = oriented[0]; oriented[0] = CTileSet::getInvertBorder (oriented[2]); oriented[2] = CTileSet::getInvertBorder (tmp); oriented[1] = CTileSet::getInvertBorder (oriented[1]); oriented[3] = CTileSet::getInvertBorder (oriented[3]); } } // Rotation CTileSet::TFlagBorder edges[4]; edges[0] = pTileSet->getOrientedBorder (CTileSet::left, oriented[(0 + transRotate )&3]); edges[1] = pTileSet->getOrientedBorder (CTileSet::bottom, oriented[(1 + transRotate )&3]); edges[2] = pTileSet->getOrientedBorder (CTileSet::right, oriented[(2 + transRotate )&3]); edges[3] = pTileSet->getOrientedBorder (CTileSet::top, oriented[(3 + transRotate )&3]); // Get the good tile number CTileSet::TTransition transition = pTileSet->getTransitionTile (edges[3], edges[1], edges[0], edges[2]); nlassert ((CTileSet::TTransition)transition != CTileSet::notfound); tile = (uint)(pTileSet->getTransition (transition)->getTile ()); } // Transform rotation: invert rotation tileRotation += rotate; // If goofy, add +2 if (goofy && symmetry) tileRotation += 2; // Mask the rotation tileRotation &= 3; } else return false; } // Ok return true; } // *************************************************************************** void CPatchInfo::transform256Case (const CTileBank &bank, uint8 &case256, uint tileRotation, bool symmetry, uint rotate, bool goofy) { // Tile exist ? if ( (rotate!=0) || symmetry ) { // Symmetry ? if (symmetry) { // Take the symmetry uint symArray[4] = {3, 2, 1, 0}; case256 = symArray[case256]; if (goofy && ((tileRotation & 1) ==0)) case256 += 2; if ((!goofy) && (tileRotation & 1)) case256 += 2; } // Rotation ? case256 -= rotate; case256 &= 3; } } // *************************************************************************** bool CPatchInfo::transform (std::vector &patchInfo, NL3D::CZoneSymmetrisation &zoneSymmetry, const NL3D::CTileBank &bank, bool symmetry, uint rotate, float snapCell, float weldThreshold, const NLMISC::CMatrix &toOriginalSpace) { uint patchCount = patchInfo.size (); uint i; // --- Export tile info Symmetry of the bind info. // --- Parse each patch and each edge // For each patches NL3D::CZoneSymmetrisation::CError error; // Build the structure if (!zoneSymmetry.build (patchInfo, snapCell, weldThreshold, bank, error, toOriginalSpace)) { return false; } // Symmetry ? if (symmetry) { for(i=0 ; i1) && (bindEdge.NPatchs!=5) ) { for (next=0; next<(uint)bindEdge.NPatchs/2; next++) { swap (bindEdge.Next[bindEdge.NPatchs - next - 1], bindEdge.Next[next]); swap (bindEdge.Edge[bindEdge.NPatchs - next - 1], bindEdge.Edge[next]); } } // Look if we are binded on a reversed edge uint bindCount = (bindEdge.NPatchs==5) ? 1 : bindEdge.NPatchs; for (next=0; next tiles = pi.Tiles; int u,v; for (v=0; v