// 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/patch.h" #include "nel/3d/tessellation.h" #include "nel/3d/bezier_patch.h" #include "nel/3d/zone.h" #include "nel/3d/landscape.h" #include "nel/misc/vector.h" #include "nel/misc/common.h" #include "nel/3d/patchuv_locator.h" #include "nel/3d/vegetable_manager.h" #include "nel/misc/fast_floor.h" #include "nel/3d/light_influence_interpolator.h" #include "nel/3d/patchdlm_context.h" #include "nel/misc/hierarchical_timer.h" #include "nel/3d/u_landscape.h" using namespace std; using namespace NLMISC; // Define this to remove user color (debug) // #define NEL_FORCE_NO_USER_COLOR namespace NL3D { // *************************************************************************** CBezierPatch CPatch::CachePatch; const CPatch *CPatch::LastPatch= NULL; uint32 CPatch::_Version=7; // *************************************************************************** CPatch::CPatch() { Zone= NULL; OrderS=0; OrderT=0; Son0=NULL; Son1=NULL; TessBlockRefCount=0; NumRenderableFaces= 0; // for Pacs process. By default, false. ExcludeFromRefineAll= false; // Init Passes. // DO NOT FILL Patch here, because of operator= problem. do it in compile(). // By default, RdrPasses are NULL. // To force computation of texture info on next preRender(). Far0= -1; Far1= -1; // Default: not binded. _BindZoneNeighbor[0]= NULL; _BindZoneNeighbor[1]= NULL; _BindZoneNeighbor[2]= NULL; _BindZoneNeighbor[3]= NULL; NoiseRotation= 0; // No smooth by default. _CornerSmoothFlag= 0; // MasterBlock never clipped. MasterBlock.resetClip(); // Init UL circular list to NULL (not compiled) _ULNearPrec= NULL; _ULNearNext= NULL; // Dynamic LightMap _DLMContext= NULL; _DLMContextRefCount= 0; // Render Passes _PatchRdrPassFar0= NULL; _NextRdrFar0= NULL; _PatchRdrPassFar1= NULL; _NextRdrFar1= NULL; } // *************************************************************************** CPatch::~CPatch() { release(); } // *************************************************************************** void CPatch::release() { if(Zone) { // First, delete the VB if the zone was removed while the patch is visible. if(!isRenderClipped()) { // release VertexBuffer. deleteVBAndFaceVector(); // Flag RenderClipped in Zone Zone->_PatchRenderClipped.set(PatchId, true); } // THIS PATCH MSUT BE UNBOUND FIRST!!!!! nlassert(Son0 && Son1); nlassert(Son0->isLeaf() && Son1->isLeaf()); nlassert(Son0->FLeft == NULL); nlassert(Son0->FRight == NULL); nlassert(Son1->FLeft == NULL); nlassert(Son1->FRight == NULL); // Free renderPass of landscape, and maybe force computation of texture info on next preRender(). // Must do it here, before deletion of Zone, OrderS/T etc... resetRenderFar(); getLandscape()->deleteTessFace(Son0); getLandscape()->deleteTessFace(Son1); // Vertices are smartptr/deleted in zone. } // Flag the fact that this patch can't be rendered. OrderS=0; OrderT=0; Son0=NULL; Son1=NULL; clearTessBlocks(); resetMasterBlock(); // Flag RenderClipped in Zone if(Zone) { Zone->_PatchRenderClipped.set( PatchId, true); Zone->_PatchOldRenderClipped.set( PatchId, true); } // the pathc is uncompiled. must do it after clearTessBlocks(), because may use it // for vegetable manager, and for updateLighting Zone= NULL; // uncompile: reset UpdateLighting circular list to NULL. if(_ULNearPrec!= NULL) { // verify the patch is correctly unlinked from any ciruclar list. nlassert(_ULNearPrec==this && _ULNearNext==this); } _ULNearPrec= NULL; _ULNearNext= NULL; // DynamciLightMap: release the _DLMContext if still exist. if(_DLMContext) delete _DLMContext; // reset _DLMContext= NULL; _DLMContextRefCount= 0; } // *************************************************************************** CBezierPatch *CPatch::unpackIntoCache() const { if(LastPatch!=this) { unpack(CachePatch); LastPatch=this; } return &CachePatch; } // *************************************************************************** void CPatch::unpack(CBezierPatch &p) const { sint i; const CVector &bias= Zone->getPatchBias(); float scale= Zone->getPatchScale(); for(i=0;i<4;i++) Vertices[i].unpack(p.Vertices[i], bias, scale); for(i=0;i<8;i++) Tangents[i].unpack(p.Tangents[i], bias, scale); for(i=0;i<4;i++) Interiors[i].unpack(p.Interiors[i], bias, scale); } // *************************************************************************** void CPatch::computeDefaultErrorSize() { CBezierPatch &p= *unpackIntoCache(); CVector &v0= p.Vertices[0]; CVector &v1= p.Vertices[1]; CVector &v2= p.Vertices[2]; // \todo yoyo: TODO_NOISE: modulate this value with tangents (roundiness of patch), and with the displacement map. ErrorSize= ((v1 - v0)^(v2 - v0)).norm(); } // *************************************************************************** void CPatch::buildBBoxFromBezierPatch(const CBezierPatch &p, CAABBox &ret) const { // Because of the structure of CAABBox, extend() is not fast enough for us. first compute bmin, bmax, // then compute the bbox. CVector bmin= p.Vertices[0]; CVector bmax= bmin; sint i; for(i=0;i<4;i++) { bmin.minof(bmin, p.Vertices[i]); bmax.maxof(bmax, p.Vertices[i]); } for(i=0;i<8;i++) { bmin.minof(bmin, p.Tangents[i]); bmax.maxof(bmax, p.Tangents[i]); } for(i=0;i<4;i++) { bmin.minof(bmin, p.Interiors[i]); bmax.maxof(bmax, p.Interiors[i]); } // Modulate with the maximum displacement map (useful for patch clipping). static CVector vectorNoiseMax(NL3D_NOISE_MAX, NL3D_NOISE_MAX, NL3D_NOISE_MAX); bmin-= vectorNoiseMax; bmax+= vectorNoiseMax; // NB: this is not very optimal, since the BBox may be very too big. eg: patch 16mx16m => bbox 18mx18m. // But remind that tessblocks do not use this BBox, and are computed with the real geometry. ret.setMinMax(bmin, bmax); } // *************************************************************************** CAABBox CPatch::buildBBox() const { CBezierPatch &p= *unpackIntoCache(); // Compute Bounding Box. (easiest way...) CAABBox ret; buildBBoxFromBezierPatch(p, ret); return ret; } // *************************************************************************** void CPatch::addTrianglesInBBox(CPatchIdent paId, const CAABBox &bbox, std::vector &triangles, uint8 tileTessLevel) const { CBezierPatch &bpatch= *unpackIntoCache(); // call with the whole root patch. addTrianglesInBBoxRecurs(paId, bbox, triangles, tileTessLevel, bpatch, 0, OrderS, 0, OrderT); } // *************************************************************************** void CPatch::addTrianglesInBBoxRecurs(CPatchIdent paId, const CAABBox &bbox, std::vector &triangles, uint8 tessLevel, const CBezierPatch &pa, uint8 s0, uint8 s1, uint8 t0, uint8 t1) const { uint8 lenS=s1-s0, lenT=t1-t0; nlassert(lenS>0); nlassert(lenT>0); // compute and compare bbox of the subdivision patch against request bbox. //======================== // NB: this compute includes possible noise. CAABBox paBBox; buildBBoxFromBezierPatch(pa, paBBox); // if do not intersect, stop here. if( !paBBox.intersect(bbox) ) return; // else if at tile level, then just computeTriangles. //======================== else if( lenS==1 && lenT==1 ) { addTileTrianglesInBBox(paId, bbox, triangles, tessLevel, s0, t0); } // else subdiv and reccurs. //======================== else { // Subdivide along the bigger side. if(lenS>lenT) { // subdivide. CBezierPatch left, right; pa.subdivideS(left, right); uint8 sMiddle= (uint8)( ((uint)s0+(uint)s1) /2 ); // recurs left. addTrianglesInBBoxRecurs(paId, bbox, triangles, tessLevel, left, s0, sMiddle, t0, t1); // recurs right. addTrianglesInBBoxRecurs(paId, bbox, triangles, tessLevel, right, sMiddle, s1, t0, t1); } else { // subdivide. CBezierPatch top, bottom; pa.subdivideT(top, bottom); uint8 tMiddle= (uint8)( ((uint)t0+(uint)t1) /2 ); // recurs top. addTrianglesInBBoxRecurs(paId, bbox, triangles, tessLevel, top, s0, s1, t0, tMiddle); // recurs bottom. addTrianglesInBBoxRecurs(paId, bbox, triangles, tessLevel, bottom, s0, s1, tMiddle, t1); } } } // *************************************************************************** void CPatch::addTileTrianglesInBBox(CPatchIdent paId, const CAABBox &bbox, std::vector &triangles, uint8 tessLevel, uint8 s0, uint8 t0) const { nlassert(s0 &paBlockIds) const { CBezierPatch &bpatch= *unpackIntoCache(); // call with the whole root patch. addPatchBlocksInBBoxRecurs(paId, bbox, paBlockIds, bpatch, 0, OrderS, 0, OrderT); } // *************************************************************************** void CPatch::addPatchBlocksInBBoxRecurs(CPatchIdent paId, const CAABBox &bbox, std::vector &paBlockIds, const CBezierPatch &pa, uint8 s0, uint8 s1, uint8 t0, uint8 t1) const { uint8 lenS=s1-s0, lenT=t1-t0; nlassert(lenS>0); nlassert(lenT>0); // compute and compare bbox of the subdivision patch against request bbox. //======================== // NB: this compute includes possible noise. CAABBox paBBox; buildBBoxFromBezierPatch(pa, paBBox); // if do not intersect, stop here. if( !paBBox.intersect(bbox) ) return; // else if at CPatchQuadBlock tile level, then just add this Id. //======================== else if( lenS<=NL_PATCH_BLOCK_MAX_QUAD && lenT<=NL_PATCH_BLOCK_MAX_QUAD ) { // Add this PatchBlock desctiptor to the list. CPatchBlockIdent pbId; // Fill struct from this and result of recursion. pbId.PatchId= paId; pbId.OrderS= OrderS; pbId.OrderT= OrderT; pbId.S0= s0; pbId.S1= s1; pbId.T0= t0; pbId.T1= t1; // Add to list. paBlockIds.push_back(pbId); } // else subdiv and reccurs. //======================== else { // Subdivide along the bigger side. if(lenS>lenT) { // subdivide. CBezierPatch left, right; pa.subdivideS(left, right); uint8 sMiddle= (uint8)( ((uint)s0+(uint)s1) /2 ); // recurs left. addPatchBlocksInBBoxRecurs(paId, bbox, paBlockIds, left, s0, sMiddle, t0, t1); // recurs right. addPatchBlocksInBBoxRecurs(paId, bbox, paBlockIds, right, sMiddle, s1, t0, t1); } else { // subdivide. CBezierPatch top, bottom; pa.subdivideT(top, bottom); uint8 tMiddle= (uint8)( ((uint)t0+(uint)t1) /2 ); // recurs top. addPatchBlocksInBBoxRecurs(paId, bbox, paBlockIds, top, s0, s1, t0, tMiddle); // recurs bottom. addPatchBlocksInBBoxRecurs(paId, bbox, paBlockIds, bottom, s0, s1, tMiddle, t1); } } } // *************************************************************************** CVector CPatch::getTesselatedPos(CUV uv) const { // clamp values. clamp(uv.U, 0, 1); clamp(uv.V, 0, 1); // recurs down the 2 sons. CVector ret= CVector::Null; Son0->getTesselatedPos(uv, true, ret); Son1->getTesselatedPos(uv, true, ret); return ret; } // *************************************************************************** bool CPatch::isRenderClipped() const { if(Zone) return Zone->isPatchRenderClipped(PatchId); else return true; } // *************************************************************************** // *************************************************************************** // RENDER LIST. // *************************************************************************** // *************************************************************************** // *************************************************************************** void CPatch::addRefTessBlocks() { uint i; TessBlockRefCount++; if(TessBlocks.size()==0) { // Allocate the tessblocks. //========== nlassert(NL3D_TESSBLOCK_TILESIZE==4); // A tessblock is 2*2 tiles. sint os= OrderS>>1; sint ot= OrderT>>1; nlassert(os>0); nlassert(ot>0); TessBlocks.resize(os*ot); // init all tessBlocks with the Patch ptr. for(i=0; i_VegetableManager; // Create ClipBlocks. uint nTbPerCb= NL3D_PATCH_VEGETABLE_NUM_TESSBLOCK_PER_CLIPBLOCK; uint wCB= (os + nTbPerCb-1) >> NL3D_PATCH_VEGETABLE_NUM_TESSBLOCK_PER_CLIPBLOCK_SHIFT; uint hCB= (ot + nTbPerCb-1) >> NL3D_PATCH_VEGETABLE_NUM_TESSBLOCK_PER_CLIPBLOCK_SHIFT; VegetableClipBlocks.resize(wCB * hCB); // allocate ClipBlocks for(i=0; icreateClipBlock(); } // updateLighting management. //========== // append patch for Near updateLighting, since TessBlock lightmap may/will exist. getLandscape()->linkPatchToNearUL(this); } } // *************************************************************************** void CPatch::decRefTessBlocks() { TessBlockRefCount--; // If no loinger need the tessblocks, delete them. if(TessBlockRefCount==0) clearTessBlocks(); nlassert(TessBlockRefCount>=0); } // *************************************************************************** void CPatch::clearTessBlocks() { uint i; // Vegetable management //========== // if compiled. if(Zone) { CVegetableManager *vegetableManager= getLandscape()->_VegetableManager; // delete still existing vegetable Igs. deleteAllVegetableIgs(); // delete ClipBlocks. for(i=0; ideleteClipBlock(VegetableClipBlocks[i]); } contReset(VegetableClipBlocks); } // updateLighting management. //========== if(Zone) { // remove patch from Near updateLighting, since no more TessBlock lightmap can exist. getLandscape()->unlinkPatchFromNearUL(this); } // Delete TessBlocks //========== TessBlockRefCount=0; contReset(TessBlocks); } // *************************************************************************** void CPatch::resetMasterBlock() { // We should be not visible so FaceVector no more exist. nlassert(isRenderClipped()); MasterBlock.FarVertexList.clear(); MasterBlock.FarFaceList.clear(); NumRenderableFaces= 0; // no tiles should be here!! nlassert(MasterBlock.NearVertexList.size()==0); } // *************************************************************************** uint CPatch::getNumTessBlock(CTessFace *face) { // To which tessBlocks link the face? // compute an approx middle of the face. CParamCoord edgeMid(face->PVLeft, face->PVRight); CParamCoord middle(edgeMid, face->PVBase); // Coordinate of the tessblock (2*2 a tile!! so the >>1). uint ts= ((uint)middle.S * (uint)(OrderS>>1)) / 0x8000; uint tt= ((uint)middle.T * (uint)(OrderT>>1)) / 0x8000; uint numtb= tt*(uint)(OrderS>>1) + ts; return numtb; } // *************************************************************************** void CPatch::getNumTessBlock(CParamCoord pc, TFarVertType &type, uint &numtb) { uint tboS= (uint)(OrderS>>1); uint tboT= (uint)(OrderT>>1); // Coordinate of the tessblock (2*2 a tile!! so the >>1). uint ts= ((uint)pc.S * tboS) / 0x8000; uint tt= ((uint)pc.T * tboT) / 0x8000; numtb= tt*tboS + ts; bool edgeS= (ts*0x8000) == ((uint)pc.S * tboS); bool edgeT= (tt*0x8000) == ((uint)pc.T * tboT); // Does this vertex lies on a corner of a TessBlock? if(edgeS && edgeT) type= FVMasterBlock; // Does this vertex lies on a edge of a TessBlock? else if(edgeS || edgeT) type= FVTessBlockEdge; // Else it lies exclusively IN a TessBlock. else type= FVTessBlock; } // *************************************************************************** void CPatch::extendTessBlockWithEndPos(CTessFace *face) { if(face->Level>=TessBlockLimitLevel) { // get the tessBlock of the face. uint numtb= getNumTessBlock(face); // Must enlarge the BSphere of the tesblock!! TessBlocks[numtb].extendSphereFirst(face->VBase->EndPos); TessBlocks[numtb].extendSphereAdd(face->VLeft->EndPos); TessBlocks[numtb].extendSphereAdd(face->VRight->EndPos); TessBlocks[numtb].extendSphereCompile(); } } // *************************************************************************** void CPatch::dirtTessBlockFaceVector(CTessBlock &tb) { // If patch is visible, block's faceVector should exist, but are no more valid. if(!isRenderClipped()) { // If this tessBlock not already notified to modification. if(!tb.isInModifyList()) { // Then append, and delete all FaceVector. // NB: delete FaceVector now, because the TessBlock himself may disapear soon. tb.appendToModifyListAndDeleteFaceVector(getLandscape()->_TessBlockModificationRoot, getLandscape()->_FaceVectorManager); } } } // *************************************************************************** void CPatch::appendFaceToRenderList(CTessFace *face) { // Update the number of renderable Faces NumRenderableFaces++; // Update Gnal render. //==================== if(face->LevelVBase->EndPos); TessBlocks[numtb].extendSphereAdd(face->VLeft->EndPos); TessBlocks[numtb].extendSphereAdd(face->VRight->EndPos); // I think this should be done too on StartPos, for geomorph (rare??...) problems. // \todo yoyo: is this necessary??? TessBlocks[numtb].extendSphereAdd(face->VBase->StartPos); TessBlocks[numtb].extendSphereAdd(face->VLeft->StartPos); TessBlocks[numtb].extendSphereAdd(face->VRight->StartPos); TessBlocks[numtb].extendSphereCompile(); // Update Tile render (no need to do it if face not at least at tessblock level). appendFaceToTileRenderList(face); } } // *************************************************************************** void CPatch::removeFaceFromRenderList(CTessFace *face) { // Update the number of renderable Faces NumRenderableFaces--; nlassert(NumRenderableFaces>=0); // Update Gnal render. //==================== if(face->LevelTileMaterial) { // For all valid faces, update their links. // Do not do this for lightmap, since it use same face from RGB0 pass. for(sint i=0;iTileMaterial->Pass[i].PatchRdrPass; // If tile i enabled. if(tilePass) { // a face should have created for this pass. nlassert(face->TileFaces[i]); face->TileMaterial->TileFaceList[i].append(face->TileFaces[i]); } } // The facelist is modified, so we must update the faceVector, if visible. uint numtb= getNumTessBlock(face); dirtTessBlockFaceVector(TessBlocks[numtb]); // Shadow: append it to the Shadow Triangles. getLandscape()->appendToShadowPolyReceiver(face); } } // *************************************************************************** void CPatch::removeFaceFromTileRenderList(CTessFace *face) { if(face->TileMaterial) { // For all valid faces, update their links. // Do not do this for lightmap, since it use same face from RGB0 pass. for(sint i=0;iTileMaterial->Pass[i].PatchRdrPass; // If tile i enabled. if(tilePass) { // a face should have created for this pass. nlassert(face->TileFaces[i]); face->TileMaterial->TileFaceList[i].remove(face->TileFaces[i]); } } // The facelist is modified, so we must update the faceVector, if visible. uint numtb= getNumTessBlock(face); dirtTessBlockFaceVector(TessBlocks[numtb]); // Shadow: remove it from the Shadow Triangles. getLandscape()->removeFromShadowPolyReceiver(face); } } // *************************************************************************** void CPatch::computeTbTm(uint &numtb, uint &numtm, uint ts, uint tt) { sint is= ts&1; sint it= tt&1; ts>>=1; tt>>=1; numtb= tt*(uint)(OrderS>>1) + ts; numtm= it*2+is; } // *************************************************************************** void CPatch::appendTileMaterialToRenderList(CTileMaterial *tm) { nlassert(tm); // Alloc if necessary the TessBlocks. addRefTessBlocks(); uint numtb, numtm; computeTbTm(numtb, numtm, tm->TileS, tm->TileT); TessBlocks[numtb].RdrTileRoot[numtm]= tm; TessBlocks[numtb].FaceTileMaterialRefCount++; TessBlocks[numtb].TileMaterialRefCount++; // The master block contains the whole patch TileMaterialRefCount MasterBlock.TileMaterialRefCount++; // DynamicLighting. When in near, must compute the context, to have good UVs. //========== // inc ref to the context, creating it if needed. addRefDLMContext(); // NB: do it before creating the vegetableBlock, because vegetables use DLM Uvs. // if was no tiles before in this tessBlock, create a Vegetable block. //========== // one Tile <=> was 0 before if( TessBlocks[numtb].TileMaterialRefCount == 1 && getLandscape()->isVegetableActive() ) { createVegetableBlock(numtb, tm->TileS, tm->TileT); } const std::vector &tc = getLandscape()->getTileCallbacks(); if (!tc.empty()) { CBezierPatch *bpatch= unpackIntoCache(); CTileAddedInfo tai; // tai.Corners[0] = bpatch->eval((float) tm->TileS / OrderS, (float) tm->TileT / OrderT); tai.Corners[1] = bpatch->eval((tm-> TileS + 1.f) / OrderS, (float) tm->TileT / OrderT); tai.Corners[2] = bpatch->eval( (tm-> TileS + 1.f) / OrderS, (tm->TileT + 1.f) / OrderT); tai.Corners[3] = bpatch->eval( (float) tm-> TileS / OrderS, (tm->TileT + 1.f) / OrderT); tai.Center = bpatch->eval( (tm-> TileS + 0.5f) / OrderS, (tm->TileT + 0.5f) / OrderT); // tai.Normal = bpatch->evalNormal( (tm-> TileS + 0.5f) / OrderS, (tm->TileT + 0.5f) / OrderT); tai.TileID = (uint64) tm; // pointer to tile material serves as a unique identifier // for(std::vector::const_iterator it = tc.begin(); it != tc.end(); ++it) { (*it)->tileAdded(tai); } } } // *************************************************************************** void CPatch::removeTileMaterialFromRenderList(CTileMaterial *tm) { nlassert(tm); uint numtb, numtm; computeTbTm(numtb, numtm, tm->TileS, tm->TileT); TessBlocks[numtb].RdrTileRoot[numtm]= NULL; TessBlocks[numtb].FaceTileMaterialRefCount--; TessBlocks[numtb].TileMaterialRefCount--; // The master block contains the whole patch TileMaterialRefCount MasterBlock.TileMaterialRefCount--; // if no more tiles in this tessBlock, delete the vegetable Block. //========== // if no more tiles in this tessBlock if( TessBlocks[numtb].TileMaterialRefCount==0 ) { // release the vegetableBlock (if any) releaseVegetableBlock(numtb); } // Destroy if necessary the TessBlocks. decRefTessBlocks(); // DynamicLighting. When in near, must compute the context, to have good UVs. //========== // dec ref the context, deleting it if needed. decRefDLMContext(); const std::vector &tc = getLandscape()->getTileCallbacks(); if (!tc.empty()) { // for(std::vector::const_iterator it = tc.begin(); it != tc.end(); ++it) { (*it)->tileRemoved((uint64) tm); // pointer to tile material serves as a unique identifier } } } // *************************************************************************** void CPatch::appendFarVertexToRenderList(CTessFarVertex *fv) { TFarVertType type; uint numtb; getNumTessBlock(fv->PCoord, type, numtb); if(type==FVMasterBlock || type==FVTessBlockEdge) { fv->OwnerBlock= &MasterBlock; MasterBlock.FarVertexList.append(fv); } else { // Alloc if necessary the TessBlocks. addRefTessBlocks(); fv->OwnerBlock= &TessBlocks[numtb]; TessBlocks[numtb].FarVertexList.append(fv); } } // *************************************************************************** void CPatch::removeFarVertexFromRenderList(CTessFarVertex *fv) { TFarVertType type; uint numtb; getNumTessBlock(fv->PCoord, type, numtb); if(type==FVMasterBlock || type==FVTessBlockEdge) { MasterBlock.FarVertexList.remove(fv); fv->OwnerBlock= NULL; } else { TessBlocks[numtb].FarVertexList.remove(fv); fv->OwnerBlock= NULL; // Destroy if necessary the TessBlocks. decRefTessBlocks(); } } // *************************************************************************** void CPatch::appendNearVertexToRenderList(CTileMaterial *tileMat, CTessNearVertex *nv) { nlassert(tileMat); // Alloc if necessary the TessBlocks. addRefTessBlocks(); uint numtb, numtm; computeTbTm(numtb, numtm, tileMat->TileS, tileMat->TileT); nv->OwnerBlock= &TessBlocks[numtb]; TessBlocks[numtb].NearVertexList.append(nv); } // *************************************************************************** void CPatch::removeNearVertexFromRenderList(CTileMaterial *tileMat, CTessNearVertex *nv) { nlassert(tileMat); uint numtb, numtm; computeTbTm(numtb, numtm, tileMat->TileS, tileMat->TileT); TessBlocks[numtb].NearVertexList.remove(nv); nv->OwnerBlock= NULL; // Destroy if necessary the TessBlocks. decRefTessBlocks(); } // *************************************************************************** // *************************************************************************** // BASIC BUILD. // *************************************************************************** // *************************************************************************** // *************************************************************************** void CPatch::makeRoots() { CTessVertex *a= BaseVertices[0]; CTessVertex *b= BaseVertices[1]; CTessVertex *c= BaseVertices[2]; CTessVertex *d= BaseVertices[3]; // Set positions. a->Pos= a->StartPos= a->EndPos= computeVertex(0,0); b->Pos= b->StartPos= b->EndPos= computeVertex(0,1); c->Pos= c->StartPos= c->EndPos= computeVertex(1,1); d->Pos= d->StartPos= d->EndPos= computeVertex(1,0); // Init Far vetices. CTessFarVertex *fa= &BaseFarVertices[0]; CTessFarVertex *fb= &BaseFarVertices[1]; CTessFarVertex *fc= &BaseFarVertices[2]; CTessFarVertex *fd= &BaseFarVertices[3]; fa->Src= a; fa->PCoord.setST(0,0); fb->Src= b; fb->PCoord.setST(0,1); fc->Src= c; fc->PCoord.setST(1,1); fd->Src= d; fd->PCoord.setST(1,0); // We don't have to fill the Far vertices VB here, because this patch is still not visible. // NB: we can't because we don't have any driver here. nlassert(isRenderClipped()==true); // Make Roots. /* Tesselation layout. For Square Face, and if OrderS>=OrderT. A-------D |\ Son1 | | \ | | \ | | Son0 \| B-------C For rectangles whith OrderT>OrderS. It is VERY IMPORTANT, for splitRectangular() reasons. A-------D | Son0 /| | / | | / | |/ Son1 | B-------C */ nlassert(Son0==NULL); nlassert(Son1==NULL); Son0= getLandscape()->newTessFace(); Son1= getLandscape()->newTessFace(); // Son0. Son0->Patch= this; Son0->Level= 0; if(OrderS>=OrderT) { Son0->VBase= b; Son0->VLeft= c; Son0->VRight= a; Son0->FVBase= fb; Son0->FVLeft= fc; Son0->FVRight= fa; Son0->PVBase.setST(0, 1); Son0->PVLeft.setST(1, 1); Son0->PVRight.setST(0, 0); } else { Son0->VBase= a; Son0->VLeft= b; Son0->VRight= d; Son0->FVBase= fa; Son0->FVLeft= fb; Son0->FVRight= fd; Son0->PVBase.setST(0, 0); Son0->PVLeft.setST(0, 1); Son0->PVRight.setST(1, 0); } Son0->FBase= Son1; Son0->FLeft= NULL; Son0->FRight= NULL; // No tile info. Son0->Size= ErrorSize/2; Son0->computeSplitPoint(); // Son1. Son1->Patch= this; Son1->Level= 0; if(OrderS>=OrderT) { Son1->VBase= d; Son1->VLeft= a; Son1->VRight= c; Son1->FVBase= fd; Son1->FVLeft= fa; Son1->FVRight= fc; Son1->PVBase.setST(1, 0); Son1->PVLeft.setST(0, 0); Son1->PVRight.setST(1, 1); } else { Son1->VBase= c; Son1->VLeft= d; Son1->VRight= b; Son1->FVBase= fc; Son1->FVLeft= fd; Son1->FVRight= fb; Son1->PVBase.setST(1, 1); Son1->PVLeft.setST(1, 0); Son1->PVRight.setST(0, 1); } Son1->FBase= Son0; Son1->FLeft= NULL; Son1->FRight= NULL; // No tile info. Son1->Size= ErrorSize/2; Son1->computeSplitPoint(); // Prepare the render list... clearTessBlocks(); resetMasterBlock(); appendFarVertexToRenderList(fa); appendFarVertexToRenderList(fb); appendFarVertexToRenderList(fc); appendFarVertexToRenderList(fd); appendFaceToRenderList(Son0); appendFaceToRenderList(Son1); // Useful for geomorph: Init 2 root faces MaxNearLimit, and MaxFaceSize // NB: since no geomorph is made on endpoints (StartPos==EndPos) of patchs, this is not useful. // but it is important to ensure the VP or software geomorph won't crash with bad float values. // Init MaxFaceSize. Son0->VBase->MaxFaceSize= 1; Son0->VLeft->MaxFaceSize= 1; Son0->VRight->MaxFaceSize= 1; Son1->VBase->MaxFaceSize= 1; Son1->VLeft->MaxFaceSize= 1; Son1->VRight->MaxFaceSize= 1; // Init MaxNearLimit. Son0->VBase->MaxNearLimit= 1; Son0->VLeft->MaxNearLimit= 1; Son0->VRight->MaxNearLimit= 1; Son1->VBase->MaxNearLimit= 1; Son1->VLeft->MaxNearLimit= 1; Son1->VRight->MaxNearLimit= 1; } // *************************************************************************** void CPatch::compile(CZone *z, uint patchId, uint8 orderS, uint8 orderT, CTessVertex *baseVertices[4], float errorSize) { nlassert(z); Zone= z; // For updateLighting, get the correct pointer now. // Init UL circular list to me _ULNearPrec= this; _ULNearNext= this; // Once the patch is inserted and compiled in a zone, it is ready to be rendered. // init the MasterBlock. MasterBlock.init(this); // only 65536 patch per zone allowed. nlassert(patchId<0x10000); PatchId= (uint16)patchId; if(errorSize==0) computeDefaultErrorSize(); else ErrorSize= errorSize; nlassert(orderS==2 || orderS==4 || orderS==8 || orderS==16); nlassert(orderT==2 || orderT==4 || orderT==8 || orderT==16); nlassert (OrderS==orderS); nlassert (OrderT==orderT); // Compile additional infos. sint ps= getPowerOf2(orderS) , pt= getPowerOf2(orderT); sint pmin= min(ps,pt); sint pmax= max(ps,pt); // Rectangular patch OK. // Work, since patch 1xX are illegal. => The TileLimitLevel is at least 2 level distant from the time where // the rectangular patch is said "un-rectangular-ed" (tesselation looks like square). Hence, there is no problem // with rectangular UV geomorph (well don't bother me, make a draw :) ). TileLimitLevel= pmin*2 + pmax-pmin; // A TessBlock is a 2*2 tile. This simple formula works because patch 1xX are illegal. TessBlockLimitLevel= TileLimitLevel-2; // This tell us when the tess face is "un-rectangular-ed" (to say a square). Before, it is a "rectangular" face, // which has a strange fxxxxxg split. // If patch is square, then SquareLimitLevel=0 (ok!!). SquareLimitLevel= pmax-pmin; // Bind vertices, to zone base vertices. BaseVertices[0]= baseVertices[0]; BaseVertices[1]= baseVertices[1]; BaseVertices[2]= baseVertices[2]; BaseVertices[3]= baseVertices[3]; // build Sons. makeRoots(); } // *************************************************************************** CVector CPatch::computeVertex(float s, float t) const { // \todo yoyo: TODO_UVCORRECT: use UV correction. if(getLandscape()->getNoiseMode()) { // compute displacement map to disturb result. CVector displace; computeNoise(s,t, displace); // return patch(s,t) + dispalcement result. // unpack. Do it after computeNoise(), because this last may change the cache. CBezierPatch *patch= unpackIntoCache(); return patch->eval(s,t) + displace; } else { // unpack and return patch(s,t). CBezierPatch *patch= unpackIntoCache(); return patch->eval(s,t); } } // *************************************************************************** CVector CPatch::computeContinousVertex(float s, float t) const { // must be compiled nlassert(Zone); // Test is on a edge/corner of the patch sint edgeIdS= -1; sint edgeIdT= -1; if(s==0) edgeIdS= 0; else if(s==1) edgeIdS= 2; if(t==0) edgeIdT= 3; else if(t==1) edgeIdT= 1; // test if on a corner if(edgeIdS>=0 && edgeIdT>=0) { // return the baseVertex according to edge falgs if(edgeIdS==0 && edgeIdT==3) return BaseVertices[0]->EndPos; if(edgeIdS==0 && edgeIdT==1) return BaseVertices[1]->EndPos; if(edgeIdS==2 && edgeIdT==1) return BaseVertices[2]->EndPos; if(edgeIdS==2 && edgeIdT==3) return BaseVertices[3]->EndPos; nlstop; // Error, but Avoid warning return CVector::Null; } // test if on an edge else if(edgeIdS>=0 || edgeIdT>=0) { // Yes, must compute myslef CVector vertexOnMe= computeVertex(s,t); // Then, must compute my neighbor. sint edgeId; if(edgeIdS>=0) edgeId= edgeIdS; else edgeId= edgeIdT; // Get my neighbor, if any. CBindInfo bindInfo; getBindNeighbor(edgeId, bindInfo); // Fast reject: if no neighbor on the edge, just return my pos. if(!bindInfo.Zone) { return vertexOnMe; } // else must get vertex pos of my neighbor, and average. else { // use a CPatchUVLocator to get UV in neighbor patch CPatchUVLocator uvLocator; uvLocator.build(this, edgeId, bindInfo); // UVlocator use OrderS/OrderT coordinate system. CVector2f stTileIn, stTileOut; stTileIn.x= s * getOrderS(); stTileIn.y= t * getOrderT(); // transform coordinate to get into the neigbhor patch uint pid= uvLocator.selectPatch(stTileIn); CPatch *nebPatch; uvLocator.locateUV(stTileIn, pid, nebPatch, stTileOut); // transform neigbhor coord in 0..1 coordinate space. stTileOut.x/= nebPatch->getOrderS(); stTileOut.y/= nebPatch->getOrderT(); // and compute vertex. NB: if binded on 2 or 4 patch, it is then possible that stTileOut is on a // a corner ( (0,0), (0,1) ...). // In this case, we must use the precomputed Vertex pos, for completeness. bool onCorner; CVector vertexOnNeb= nebPatch->computeVertexButCorner(stTileOut.x, stTileOut.y, onCorner); // If the neighbor is on a corner, then use its corner value. if(onCorner) return vertexOnNeb; else { // Average the 2 and return this result. vertexOnMe+= vertexOnNeb; vertexOnMe/= 2; return vertexOnMe; } } } // else, std case else return computeVertex(s, t); } // *************************************************************************** CVector CPatch::computeVertexButCorner(float s, float t, bool &onCorner) const { // must be compiled nlassert(Zone); // Test is on a edge/corner of the patch sint edgeIdS= -1; sint edgeIdT= -1; if(s==0) edgeIdS= 0; else if(s==1) edgeIdS= 2; if(t==0) edgeIdT= 3; else if(t==1) edgeIdT= 1; // test if on a corner if(edgeIdS>=0 && edgeIdT>=0) { // indicate that yes, we are on a corner onCorner= true; // return the baseVertex according to edge falgs if(edgeIdS==0 && edgeIdT==3) return BaseVertices[0]->EndPos; if(edgeIdS==0 && edgeIdT==1) return BaseVertices[1]->EndPos; if(edgeIdS==2 && edgeIdT==1) return BaseVertices[2]->EndPos; if(edgeIdS==2 && edgeIdT==3) return BaseVertices[3]->EndPos; nlstop; // Error, but Avoid warning return CVector::Null; } // else, std case else { onCorner= false; return computeVertex(s, t); } } // *************************************************************************** void CPatch::refineAll() { // refineAll. nlassert(Son0); nlassert(Son1); Son0->refineAll(); Son1->refineAll(); } // *************************************************************************** void CPatch::averageTesselationVertices() { nlassert(Son0); nlassert(Son1); // Recompute the BaseVertices. This is useful for Pacs. // Because CLandscape::averageTesselationVertices() is made on a strict order for patchs (map of zones, then // array of patchs), we are sure to overwrite BaseVertices in this order. CTessVertex *a= BaseVertices[0]; CTessVertex *b= BaseVertices[1]; CTessVertex *c= BaseVertices[2]; CTessVertex *d= BaseVertices[3]; // Set positions. a->Pos= a->StartPos= a->EndPos= computeVertex(0,0); b->Pos= b->StartPos= b->EndPos= computeVertex(0,1); c->Pos= c->StartPos= c->EndPos= computeVertex(1,1); d->Pos= d->StartPos= d->EndPos= computeVertex(1,0); // Average the tesselation of sons. Son0->averageTesselationVertices(); Son1->averageTesselationVertices(); } // *************************************************************************** void CPatch::refreshTesselationGeometry() { nlassert(Son0); nlassert(Son1); // Recompute the BaseVertices. CTessVertex *a= BaseVertices[0]; CTessVertex *b= BaseVertices[1]; CTessVertex *c= BaseVertices[2]; CTessVertex *d= BaseVertices[3]; // Set positions. a->Pos= a->StartPos= a->EndPos= computeVertex(0,0); b->Pos= b->StartPos= b->EndPos= computeVertex(0,1); c->Pos= c->StartPos= c->EndPos= computeVertex(1,1); d->Pos= d->StartPos= d->EndPos= computeVertex(1,0); // refresh the tesselation of sons. Son0->refreshTesselationGeometry(); Son1->refreshTesselationGeometry(); } // *************************************************************************** void CPatch::resetRenderFar() { if (_PatchRdrPassFar0) { // free the render pass. Zone->Landscape->freeFarRenderPass (this, _PatchRdrPassFar0, Far0); _PatchRdrPassFar0= NULL; } if (_PatchRdrPassFar1) { // free the render pass. Zone->Landscape->freeFarRenderPass (this, _PatchRdrPassFar1, Far1); _PatchRdrPassFar1= NULL; } Far0= -1; Far1= -1; } // *************************************************************************** void CPatch::serial(NLMISC::IStream &f) { /* Version 7: - remove unused information from CTileColor. just keep 565 color Version 6: - default UnderWater flags for tileElements before version 6. Version 5: - TileLightInfluences serialized. Version 4: - Smooth flag serialized Version 3: - NoiseRotation. - NoiseSmooth. Version 2: - Lumels. Version 1: - Tile color. Version 0: - base version. */ uint ver= f.serialVersion(_Version); // No more compatibility before version 2, because OrderS / OrderT not serialized in preceding version // Doens't matter since CZone::serial() do not support it too. if (ver<2) { throw EOlderStream(f); } f.xmlSerial (Vertices[0], Vertices[1], Vertices[2], Vertices[3], "VERTICIES"); f.xmlPush ("TANGENTS"); f.serial (Tangents[0], Tangents[1], Tangents[2], Tangents[3]); f.serial (Tangents[4], Tangents[5], Tangents[6], Tangents[7]); f.xmlPop (); f.xmlSerial (Interiors[0], Interiors[1], Interiors[2], Interiors[3], "INTERIORS"); f.xmlPush ("TILES"); f.serialCont(Tiles); f.xmlPop (); if(ver>=1) { // Read/Write TileColors. if(ver<=6) { nlassert(f.isReading()); // read old version of tilesColors (ie with LightX/LightY/LightZ, which are deprecated now) vector tmpArray; f.xmlPush ("TILE_COLORS"); f.serialCont(tmpArray); f.xmlPop (); // then just copy to TileColors. TileColors.resize(tmpArray.size()); if(TileColors.size()>0) { memcpy(&TileColors[0], &tmpArray[0], TileColors.size()*sizeof(CTileColor)); } } else { // version >=7, just serial array of TileColors (16 bits TileColor only) f.xmlPush ("TILE_COLORS"); f.serialCont(TileColors); #ifdef NEL_FORCE_NO_USER_COLOR CTileColor color; color.Color565 = 0xffff; uint size = TileColors.size (); TileColors.resize (0); TileColors.resize (size, color); #endif // NEL_FORCE_NO_USER_COLOR f.xmlPop (); } } if(ver>=2) { f.xmlSerial (OrderS, "ORDER_S"); f.xmlSerial (OrderT, "ORDER_T"); f.xmlPush ("COMPRESSED_LUMELS"); f.serialCont(CompressedLumels); f.xmlPop (); } // Else cannot create here the TileColors, because we need the OrderS/OrderT information... Done into CZone serial. if(ver>=3) { f.xmlSerial (NoiseRotation, "NOISE_ROTATION"); f.xmlSerial (_CornerSmoothFlag, "CORNER_SMOOTH_FLAG"); } else { // No Rotation / not smooth by default. NoiseRotation= 0; _CornerSmoothFlag= 0; } if(ver>=4) { f.xmlSerial(Flags, "FLAGS"); } else { Flags=NL_PATCH_SMOOTH_FLAG_MASK; } // Serialize TileLightInfluences if(ver>=5) { f.xmlPush ("TILE_LIGHT_INFLUENCES"); f.serialCont(TileLightInfluences); f.xmlPop (); } else { if(f.isReading()) { // Fill default. resetTileLightInfluences(); } } // if read a too old version, if(ver<6 && f.isReading()) { // reset tileElements vegetableState to AboveWater. for(uint i=0; iunbind(); Son1->unbind(); Son0->forceMerge(); Son1->forceMerge(); // forcemerge should have be completed. nlassert(Son0->isLeaf() && Son1->isLeaf()); // unbind should have be completed. nlassert(Son0->FLeft == NULL); nlassert(Son0->FRight == NULL); nlassert(Son1->FLeft == NULL); nlassert(Son1->FRight == NULL); // unbind Noise. _BindZoneNeighbor[0]= NULL; _BindZoneNeighbor[1]= NULL; _BindZoneNeighbor[2]= NULL; _BindZoneNeighbor[3]= NULL; } // *************************************************************************** CTessFace *CPatch::getRootFaceForEdge(sint edge) const { nlassert(edge>=0 && edge<=3); // See tessellation rules. if(OrderS>=OrderT) { if(edge==0 || edge==1) return Son0; else return Son1; } else { if(edge==0 || edge==3) return Son0; else return Son1; } } // *************************************************************************** CTessVertex *CPatch::getRootVertexForEdge(sint edge) const { // Return the vertex which is the start of edge. nlassert(edge>=0 && edge<=3); // See tessellation rules. if(OrderS>=OrderT) { switch(edge) { case 0: return Son0->VRight; case 1: return Son0->VBase; case 2: return Son0->VLeft; case 3: return Son1->VBase; default: return NULL; } } else { switch(edge) { case 0: return Son0->VBase; case 1: return Son0->VLeft; case 2: return Son1->VBase; case 3: return Son0->VRight; default: return NULL; } } } // *************************************************************************** void CPatch::changeEdgeNeighbor(sint edge, CTessFace *to) { nlassert(edge>=0 && edge<=3); // See tessellation rules. if(OrderS>=OrderT) { switch(edge) { case 0: Son0->FRight= to; break; case 1: Son0->FLeft= to; break; case 2: Son1->FRight= to; break; case 3: Son1->FLeft= to; break; } } else { switch(edge) { case 0: Son0->FLeft= to; break; case 1: Son1->FRight= to; break; case 2: Son1->FLeft= to; break; case 3: Son0->FRight= to; break; } } } // *************************************************************************** CTessFace *CPatch::linkTessFaceWithEdge(const CVector2f &uv0, const CVector2f &uv1, CTessFace *linkTo) { nlassert(Son0 && Son1); // Try to link with Root Son0 CTessFace *face= Son0->linkTessFaceWithEdge(uv0, uv1, linkTo); // if Failed Try to link with Root Son1 if(!face) face= Son1->linkTessFaceWithEdge(uv0, uv1, linkTo); return face; } // *************************************************************************** void CPatch::bind(CBindInfo Edges[4], bool rebind) { // The multiple Patch Face. // By default, Patch==NULL, FLeft, FRight and FBase==NULL so ok! static CTessFace bind1_2[4]; static CTessFace bind1_4[8]; // THIS PATCH MUST BE UNBOUND FIRST!!!!! nlassert(Son0 && Son1); nlassert(Son0->isLeaf() && Son1->isLeaf()); nlassert(Son0->FLeft == NULL); nlassert(Son0->FRight == NULL); nlassert(Son1->FLeft == NULL); nlassert(Son1->FRight == NULL); // bind the Realtime bind info here (before any computeVertex, and before bind too). sint i; for(i=0;i<4;i++) { // just Copy zone (if not NULL). _BindZoneNeighbor[i]= Edges[i].Zone; } if(!rebind) { // Just recompute base vertices. CTessVertex *a= BaseVertices[0]; CTessVertex *b= BaseVertices[1]; CTessVertex *c= BaseVertices[2]; CTessVertex *d= BaseVertices[3]; // Set positions. a->Pos= a->StartPos= a->EndPos= computeVertex(0,0); b->Pos= b->StartPos= b->EndPos= computeVertex(0,1); c->Pos= c->StartPos= c->EndPos= computeVertex(1,1); d->Pos= d->StartPos= d->EndPos= computeVertex(1,0); // NB: no propagation problem, since the patch has root only (since has to be unbound!!!) // Recompute centers. Son0->computeSplitPoint(); Son1->computeSplitPoint(); } else { // Keep old Vertices as computed from neighbors, but reFill the 4 FarVertices. // NB: don't do it on NearVertices because suppose that Near is Off when a bind occurs (often far away). checkFillVertexVBFar(Son0->FVBase); checkFillVertexVBFar(Son0->FVLeft); checkFillVertexVBFar(Son0->FVRight); checkFillVertexVBFar(Son1->FVBase); } // Bind the roots. for(i=0;i<4;i++) { CBindInfo &bind= Edges[i]; nlassert(bind.NPatchs==0 || bind.NPatchs==1 || bind.NPatchs==2 || bind.NPatchs==4 || bind.NPatchs==5); if(bind.NPatchs==1) { // Bind me on Next. this->changeEdgeNeighbor(i, bind.Next[0]->getRootFaceForEdge(bind.Edge[0])); // Bind Next on me. bind.Next[0]->changeEdgeNeighbor(bind.Edge[0], this->getRootFaceForEdge(i)); } else if(bind.NPatchs==2) { // Setup multiple bind. this->changeEdgeNeighbor(i, bind1_2+i); bind1_2[i].FBase= this->getRootFaceForEdge(i); // Setup the multiple face. // Follow the conventions! Make a draw for understand. Small Patchs are numbered in CCW. bind1_2[i].SonRight= bind.Next[0]->getRootFaceForEdge(bind.Edge[0]); bind1_2[i].SonLeft= bind.Next[1]->getRootFaceForEdge(bind.Edge[1]); bind1_2[i].VBase= bind.Next[0]->getRootVertexForEdge(bind.Edge[0]); // Set a "flag" to neighbors, so they know what edge is to be bind on me. bind.Next[0]->changeEdgeNeighbor(bind.Edge[0], &CTessFace::MultipleBindFace); bind.Next[1]->changeEdgeNeighbor(bind.Edge[1], &CTessFace::MultipleBindFace); } else if(bind.NPatchs==4) { // Setup multiple bind level 0. this->changeEdgeNeighbor(i, bind1_2+i); bind1_2[i].FBase= this->getRootFaceForEdge(i); // Setup multiple bind level 1. // Follow the conventions! Make a draw for understand. Small Patchs are numbered in CCW. bind1_2[i].SonRight= bind1_4 + 2*i+0; bind1_2[i].SonLeft= bind1_4 + 2*i+1; bind1_2[i].VBase= bind.Next[1]->getRootVertexForEdge(bind.Edge[1]); // Make first multiple face bind level1. bind1_4[2*i+0].FBase= &CTessFace::MultipleBindFace; // to link correctly when the root face will be splitted. bind1_4[2*i+0].SonRight= bind.Next[0]->getRootFaceForEdge(bind.Edge[0]); bind1_4[2*i+0].SonLeft= bind.Next[1]->getRootFaceForEdge(bind.Edge[1]); bind1_4[2*i+0].VBase= bind.Next[0]->getRootVertexForEdge(bind.Edge[0]); // Make second multiple face bind level1. bind1_4[2*i+1].FBase= &CTessFace::MultipleBindFace; // to link correctly when the root face will be splitted. bind1_4[2*i+1].SonRight= bind.Next[2]->getRootFaceForEdge(bind.Edge[2]); bind1_4[2*i+1].SonLeft= bind.Next[3]->getRootFaceForEdge(bind.Edge[3]); bind1_4[2*i+1].VBase= bind.Next[2]->getRootVertexForEdge(bind.Edge[2]); // Set a "flag" to neighbors, so they know what edge is to be bind on me. bind.Next[0]->changeEdgeNeighbor(bind.Edge[0], &CTessFace::MultipleBindFace); bind.Next[1]->changeEdgeNeighbor(bind.Edge[1], &CTessFace::MultipleBindFace); bind.Next[2]->changeEdgeNeighbor(bind.Edge[2], &CTessFace::MultipleBindFace); bind.Next[3]->changeEdgeNeighbor(bind.Edge[3], &CTessFace::MultipleBindFace); } else if(bind.NPatchs==5) { /* I am binded to a bigger patch. There is 2 cases: - rebind=false. This is an original Bind of all patch of a zone. If my bigger patch has not be bound, I CANNOT do rebind faces, since my bigger neighbor patch is not correctly tesselated. Wait for the BiggerPatch do the correct bind (see above) - rebind=true. This is possible for a patch in border of zone to have some bind 1/X (even if Bind 1/X is not possible across zone, it IS possible that a patch containing a bind 1/X NOT ON EDGE OF ZONE exist (and they do exist...)) If neighbor bind has been done (must be the case for rebind), MUST do the rebind (because of CZoneBindPatch() which first unbind() this, then bind()) */ // if rebind, my neigbhor bind should be done if(rebind==true) { nlassert(bind.Next[0]->_BindZoneNeighbor[bind.Edge[0]]); } // if my neighbor is bound, i have to do the bind (since CZoneBindPatch() had call unbind) if(bind.Next[0]->_BindZoneNeighbor[bind.Edge[0]]) { // First, make the link with the face to which I must connect. // ----------------- // Get the coordinate of the current edge of this patch CVector2f uvi0, uvi1; switch(i) { case 0: uvi0.set(0,0); uvi1.set(0,1); break; case 1: uvi0.set(0,1); uvi1.set(1,1); break; case 2: uvi0.set(1,1); uvi1.set(1,0); break; case 3: uvi0.set(1,0); uvi1.set(0,0); break; }; // mul by OrderS/OrderT for CPatchUVLocator uvi0.x*= OrderS; uvi0.y*= OrderT; uvi1.x*= OrderS; uvi1.y*= OrderT; // build a CPatchUVLocator to transpose coorindate ot this edge in coordinate on the bigger Neighbor patch. CBindInfo bindInfo; getBindNeighbor(i, bindInfo); nlassert(bindInfo.Zone!=NULL && bindInfo.NPatchs==1); CPatchUVLocator puvloc; puvloc.build(this, i, bindInfo); // transpose from this patch coord in neighbor patch coord. CVector2f uvo0, uvo1; uint pid; CPatch *patchNeighbor; // Do it for uvi0 pid= puvloc.selectPatch(uvi0); puvloc.locateUV(uvi0, pid, patchNeighbor, uvo0); nlassert(patchNeighbor == bindInfo.Next[0]); // Do it for uvi1 pid= puvloc.selectPatch(uvi1); puvloc.locateUV(uvi1, pid, patchNeighbor, uvo1); nlassert(patchNeighbor == bindInfo.Next[0]); // Rescale to have uv in 0,1 basis. uvo0.x/= patchNeighbor->OrderS; uvo0.y/= patchNeighbor->OrderT; uvo1.x/= patchNeighbor->OrderS; uvo1.y/= patchNeighbor->OrderT; // Now, traverse the tesselation and find the first CTessFace which use this edge. CTessFace *faceNeighbor; faceNeighbor= patchNeighbor->linkTessFaceWithEdge(uvo0, uvo1, this->getRootFaceForEdge(i)); nlassert(faceNeighbor); // Bind me on Next. this->changeEdgeNeighbor(i, faceNeighbor); } } } // Propagate the binds to sons. Son0->updateBind(); Son1->updateBind(); } // *************************************************************************** void CPatch::forceMergeAtTileLevel() { nlassert(Son0 && Son1); Son0->forceMergeAtTileLevel(); Son1->forceMergeAtTileLevel(); } // *************************************************************************** // *************************************************************************** // Texturing. // *************************************************************************** // *************************************************************************** // *************************************************************************** CPatchRdrPass *CPatch::getTileRenderPass(sint tileId, sint pass) { // All but lightmap. nlassert(pass==NL3D_TILE_PASS_RGB0 || pass==NL3D_TILE_PASS_RGB1 || pass==NL3D_TILE_PASS_RGB2 || pass==NL3D_TILE_PASS_ADD); bool additive= (pass==NL3D_TILE_PASS_ADD); sint passNum= pass-NL3D_TILE_PASS_RGB0; // If additive, take the additve tile of bigger existing pass. if(additive) { // Default: take addtive of pass 0. passNum= 0; // If the pass1 is not empty, may take its tile. if(Tiles[tileId].Tile[1]!=0xFFFF) { passNum= 1; // If the pass2 is not empty, take its tile. if(Tiles[tileId].Tile[2]!=0xFFFF) passNum= 2; } } sint tileNumber= Tiles[tileId].Tile[passNum]; if(tileNumber==0xFFFF) { // Display a "fake" only if pass 0. if(pass==NL3D_TILE_PASS_RGB0) return Zone->Landscape->getTileRenderPass(0xFFFF, false); // Else, this tile do not have such a pass (not a transition). return NULL; } else { // return still may be NULL, in additive case. return Zone->Landscape->getTileRenderPass(uint16(tileNumber), additive); } } // *************************************************************************** void CPatch::getTileUvInfo(sint tileId, sint pass, bool alpha, uint8 &orient, CVector &uvScaleBias, bool &is256x256, uint8 &uvOff) { // All but lightmap. nlassert(pass==NL3D_TILE_PASS_RGB0 || pass==NL3D_TILE_PASS_RGB1 || pass==NL3D_TILE_PASS_RGB2 || pass==NL3D_TILE_PASS_ADD); bool additive= (pass==NL3D_TILE_PASS_ADD); sint passNum= pass-NL3D_TILE_PASS_RGB0; // If additive, take the additve tile of bigger existing pass. if(additive) { // Default: take addtive of pass 0. passNum= 0; // If the pass1 is not empty, may take its tile. if(Tiles[tileId].Tile[1]!=0xFFFF) { passNum= 1; // If the pass2 is not empty, take its tile. if(Tiles[tileId].Tile[2]!=0xFFFF) passNum= 2; } } sint tileNumber= Tiles[tileId].Tile[passNum]; if(tileNumber==0xFFFF) { // dummy... Should not be called here. orient= 0; uvScaleBias.x=0; uvScaleBias.y=0; uvScaleBias.z=1; is256x256=false; uvOff=0; } else { orient= Tiles[tileId].getTileOrient(passNum); Tiles[tileId].getTile256Info(is256x256, uvOff); CTile::TBitmap type; // If alpha wanted, return its UV info (works either for Alpha in Diffuse Pass and Alpha in Additive Pass) if(alpha) type= CTile::alpha; else { if(additive) type= CTile::additive; else type= CTile::diffuse; } uint8 rotalpha; Zone->Landscape->getTileUvScaleBiasRot(uint16(tileNumber), type, uvScaleBias, rotalpha); // Add the special rotation of alpha. if(alpha) orient= (orient+rotalpha)&3; } } // *************************************************************************** void CPatch::deleteTileUvs() { Son0->deleteTileUvs(); Son1->deleteTileUvs(); } // *************************************************************************** void CPatch::recreateTileUvs() { // Reset the Tile rdr list. for(sint tb=0; tb<(sint)TessBlocks.size();tb++) { // Vertices must all be reseted. TessBlocks[tb].NearVertexList.clear(); for(sint i=0;iTileFaceList[pass].clear(); } } } } Son0->recreateTileUvs(); Son1->recreateTileUvs(); } // *************************************************************************** void CPatch::appendTessellationLeaves(std::vector &leaves) const { nlassert(Son0); nlassert(Son1); Son0->appendTessellationLeaves(leaves); Son1->appendTessellationLeaves(leaves); } // *************************************************************************** CLandscape *CPatch::getLandscape () const { return Zone->getLandscape(); } // *************************************************************************** uint8 CPatch::getOrderForEdge(sint8 edge) const { uint e= ((sint)edge + 256)&3; // If an horizontal edge. if( e&1 ) return OrderS; else return OrderT; } // *************************************************************************** // *************************************************************************** // Realtime Bind info. // *************************************************************************** // *************************************************************************** // *************************************************************************** void CPatch::getBindNeighbor(uint edge, CBindInfo &neighborEdge) const { nlassert(edge<4); if(_BindZoneNeighbor[edge]!=NULL) { getZone()->buildBindInfo(PatchId, edge, _BindZoneNeighbor[edge], neighborEdge); } else { neighborEdge.Zone= NULL; neighborEdge.NPatchs= 0; neighborEdge.MultipleBindNum= 0; } } // *************************************************************************** /// debug coloring void CPatch::setupColorsFromTileFlags(const NLMISC::CRGBA colors[4]) { for (uint s = 0; s <= OrderS; ++s) { for (uint t = 0; t <= OrderT; ++t) { uint index = std::min(t, (uint) (OrderT - 1)) * OrderS + std::min(s, (uint) (OrderS - 1)); TileColors[s + t * (OrderS + 1)].Color565 = colors[(uint) (Tiles[index].getVegetableState())].get565(); } } } // *************************************************************************** void CPatch::copyTileFlagsFromPatch(const CPatch *src) { nlassert(OrderS == src->OrderS && OrderT == src->OrderT); for (uint k = 0; k < Tiles.size(); ++k) { Tiles[k].copyFlagsFromOther(src->Tiles[k]); } } // *************************************************************************** // *************************************************************************** // Tiles get interface. // *************************************************************************** // *************************************************************************** // *************************************************************************** CTileElement *CPatch::getTileElement(const CUV &uv) { // compute tile coord and lumel coord. sint ts, tt; // fastFloor: use a precision of 256 to avoid doing OptFastFloorBegin. // add 128, to round and get cneter of lumel. ts= NLMISC::OptFastFloor(uv.U* (OrderS<<8) + 128); ts>>=8; tt= NLMISC::OptFastFloor(uv.V* (OrderT<<8) + 128); tt>>=8; clamp(ts, 0, OrderS-1); clamp(tt, 0, OrderT-1); // get the lumel return &(Tiles[ts+tt*OrderS]); } //*************************************************************** uint32 CPatch::countNumTriFar0() const { uint32 numIndex = MasterBlock.Far0FaceVector ? *MasterBlock.Far0FaceVector : 0; uint nTessBlock= TessBlocks.size(); const CTessBlock *pTessBlock= nTessBlock>0? &TessBlocks[0]: NULL; for(; nTessBlock>0; pTessBlock++, nTessBlock--) { const CTessBlock &tblock= *pTessBlock; // if block visible, render if( tblock.visibleFar0() ) { numIndex += *(tblock.Far0FaceVector); } } return numIndex; } //*************************************************************** uint32 CPatch::countNumTriFar1() const { uint32 numIndex = MasterBlock.Far1FaceVector ? *MasterBlock.Far1FaceVector : 0; uint nTessBlock= TessBlocks.size(); const CTessBlock *pTessBlock= nTessBlock>0? &TessBlocks[0]: NULL; for(; nTessBlock>0; pTessBlock++, nTessBlock--) { const CTessBlock &tblock= *pTessBlock; // if block visible, render if( tblock.visibleFar1() ) { numIndex += *(tblock.Far1FaceVector); } } return numIndex; } } // NL3D