// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/> // 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 <http://www.gnu.org/licenses/>. #include <math.h> #include <float.h> #include "nel/misc/types_nl.h" #include "nel/misc/debug.h" #include "nel/misc/path.h" #include "nel/misc/file.h" #include "nel/misc/plane.h" #include "nel/misc/triangle.h" #include "nel/misc/polygon.h" #include "nel/3d/landscape.h" #include "nel/3d/zone.h" #include "nel/3d/mesh.h" #include "nel/3d/quad_grid.h" #include "nel/pacs/vector_2s.h" #include "build_surf.h" #include <deque> using namespace std; using namespace NLMISC; using namespace NL3D; // Misc functions... uint16 getZoneIdByPos(CVector &pos) { uint x, y; const float zdim = 160.0f; x = (uint)(pos.x/zdim); y = (uint)(-pos.y/zdim); return x+y*256; } string getZoneNameById(uint16 id) { uint x = id%256; uint y = id/256; char ych[32]; sprintf(ych,"%d_%c%c", y+1, 'A'+x/26, 'A'+x%26); return string(ych); } uint16 getZoneIdByName(string &name) { string upperName = strupr (name); sint y = 0, x = 0; const char *str = upperName.c_str(); while (*str != '_') y = y*10 + *(str++)-'0'; ++str; x = (str[0]-'A')*26+(str[1]-'A'); return (y-1)*256+x; } CAABBox getZoneBBoxById(uint16 id) { CAABBox bbox; uint x, y; const float zdim = 160.0f; x = id%256; y = id/256; bbox.setMinMax(CVector(zdim*x, -zdim*(y+1), -10000.0f), CVector(zdim*(x+1), -zdim*y, +10000.0f)); return bbox; } bool relativeEquals(CVector &a, CVector &b, float epsilon) { float n = (a-b).norm(); float r = a.norm(); return (n/r <= epsilon); } bool absoluteEquals(CVector &a, CVector &b, float epsilon) { float n = (a-b).norm(); return n <= epsilon; } inline CVector vmin(const CVector &a, const CVector &b, const CVector &c) { return CVector(std::min(a.x, std::min(b.x, c.x)), std::min(a.y, std::min(b.y, c.y)), std::min(a.z, std::min(b.z, c.z))); } inline CVector vmax(const CVector &a, const CVector &b, const CVector &c) { return CVector(std::max(a.x, std::max(b.x, c.x)), std::max(a.y, std::max(b.y, c.y)), std::max(a.z, std::max(b.z, c.z))); } inline CVector vmin(const CVector &a, const CVector &b) { return CVector(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)); } inline CVector vmax(const CVector &a, const CVector &b) { return CVector(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)); } static sint64 float2Fixed(float f) { return (sint64)(floor(f*NLPACS::Vector2sAccuracy)); } static float fixed2Float(sint64 s) { return ((float)s)/NLPACS::Vector2sAccuracy; } static void snapAccuracyBit(float &f) { f = fixed2Float(float2Fixed(f)); } static void snapAccuracyBit(CVector &v) { snapAccuracyBit(v.x); snapAccuracyBit(v.y); snapAccuracyBit(v.z); } static CAABBox getSnappedBBox(CVector v0, CVector v1, CVector v2, const CAABBox &bbox) { snapAccuracyBit(v0); snapAccuracyBit(v1); snapAccuracyBit(v2); CAABBox box; box.setCenter(v0); box.extend(v1); box.extend(v2); return box; } static float alg2dArea(const CVector &v0, const CVector &v1, const CVector &v2) { float ux = v1.x-v0.x, uy = v1.y-v0.y, vx = v2.x-v0.x, vy = v2.y-v0.y; return ux*vy - uy*vx; } static double alg2dArea(const CVectorD &v0, const CVectorD &v1, const CVectorD &v2) { double ux = v1.x-v0.x, uy = v1.y-v0.y, vx = v2.x-v0.x, vy = v2.y-v0.y; return ux*vy - uy*vx; } template<class A> class CHashPtr { public: enum { bucket_size = 4, min_buckets = 8, }; typedef A *ptrA; size_t operator() (const ptrA &a) const { return ((uintptr_t)a)/sizeof(A); } bool operator() (const ptrA &a, const ptrA &b) const { return (uintptr_t)a < (uintptr_t)b; } }; bool isInside(const CPolygon &poly, const NLPACS::CSurfElement &elem) { uint i, j; if (poly.getNumVertices() <= 2) return false; CVector pnorm = (poly.Vertices[0]-poly.Vertices[1])^(poly.Vertices[2]-poly.Vertices[1]); pnorm.normalize(); for (i=0; i<3; ++i) { const CVector &v = (*elem.Vertices)[elem.Tri[i]]; bool inside = true; for (j=0; j<poly.Vertices.size(); ++j) { const CVector &v0 = poly.Vertices[j], &v1 = (j == poly.Vertices.size()-1) ? poly.Vertices[0] : poly.Vertices[j+1]; if ((pnorm^(v1-v0)) * (v-v1) > 0.0f) { inside = false; break; } } if (inside) return true; } return false; } /* */ class CAABBoxPred { public: bool operator () (const CAABBox &a, const CAABBox &b) const { return a.getCenter().z < b.getCenter().z; } }; /* * CSurfElement methods * */ void NLPACS::CSurfElement::computeQuantas(CZoneTessellation *zoneTessel) { CVector v0 = (*Vertices)[Tri[0]], v1 = (*Vertices)[Tri[1]], v2 = (*Vertices)[Tri[2]]; CVector nv0 = v0, nv1 = v1, nv2 = v2; nv0.z = 0.0f; nv1.z = 0.0f; nv2.z = 0.0f; CVector n = (nv1-nv0) ^ (nv2-nv0); double hmin = std::min(v0.z, std::min(v1.z, v2.z)); //QuantHeight = ((uint8)(floor((v0.z+v1.z+v2.z)/6.0f)))%255; QuantHeight = ((uint8)floor(hmin/2.0))%255; Area = 0.5f*n.norm(); IsValid = (Normal.z > 0.707f); uint8 bits0 = PrimChecker.get((uint)v0.x, (uint)v0.y); uint8 bits1 = PrimChecker.get((uint)v1.x, (uint)v1.y); uint8 bits2 = PrimChecker.get((uint)v2.x, (uint)v2.y); uint16 ws0 = PrimChecker.index((uint)v0.x, (uint)v0.y); uint16 ws1 = PrimChecker.index((uint)v1.x, (uint)v1.y); uint16 ws2 = PrimChecker.index((uint)v2.x, (uint)v2.y); uint8 bits = bits0|bits1|bits2; if (bits & CPrimChecker::Include) { IsValid = true; } if (bits & CPrimChecker::Exclude) { ForceInvalid = true; IsValid = false; } if (bits & CPrimChecker::ClusterHint && IsValid) { ClusterHint = true; } // if ((bits & CPrimChecker::Cliff) && (bits & CPrimChecker::Water)) // IsValid = false; if (Normal.z <= 0.30f) { ForceInvalid = true; IsValid = false; } else if ((bits & CPrimChecker::Water) != 0 && !ForceInvalid) { bool w0 = ((bits0&CPrimChecker::Water) != 0); bool w1 = ((bits1&CPrimChecker::Water) != 0); bool w2 = ((bits2&CPrimChecker::Water) != 0); uint ws = 0xff; if ((w0 && w1 && ws0 == ws1) || (w0 && w2 && ws0 == ws2)) ws = ws0; else if (w1 && w2 && ws1 == ws2) ws = ws1; else if (w0) ws = ws0; else if (w1) ws = ws1; else if (w2) ws = ws2; else { nlwarning("No WaterShape found for element %d, whereas water detected...", ElemId); } WaterShape = ws; bool exists; float wh = PrimChecker.waterHeight(ws, exists); if (exists && ((*Vertices)[Tri[0]].z < wh || (*Vertices)[Tri[1]].z < wh || (*Vertices)[Tri[2]].z < wh)) { if (bits & CPrimChecker::Cliff) { ForceInvalid = true; IsValid = false; return; } IsValid = true; WaterShape = ws; IsUnderWater = true; } /* else { ForceInvalid = true; IsValid = false; } */ } if (ForceInvalid) IsValid = false; } CAABBox NLPACS::CSurfElement::getBBox() const { CAABBox box; box.setCenter((*Vertices)[Tri[0]]); box.extend((*Vertices)[Tri[1]]); box.extend((*Vertices)[Tri[2]]); return box; } /* * CComputableSurfaceBorder methods implementation * */ void NLPACS::CComputableSurfaceBorder::dump() { sint i; nldebug("dump border between %d and %d", Left, Right); for (i=0; i<(sint)Vertices.size(); ++i) { nldebug(" v[%d]={%g,%g,%g}", i, Vertices[i].x, Vertices[i].y, Vertices[i].z); } } void NLPACS::CComputableSurfaceBorder::smooth(float val) { float minArea; vector<CVector>::iterator it, minIt; uint i; uint before, after; bool allowMoveLeft = (Left != -1); bool allowMoveRight = (Right != -1); // filtering passes uint numPass = 3; for (; numPass>0 && Vertices.size()>3; --numPass) { CVector previous = Vertices[0]; for (i=1; i<Vertices.size()-1; ++i) { CVector newVert = (Vertices[i]*2.0+previous+Vertices[i+1])/4.0f; if (!allowMoveLeft || !allowMoveRight) { float area1 = alg2dArea(previous, Vertices[i], Vertices[i+1]); float area2 = alg2dArea(previous, newVert, Vertices[i+1]); previous = Vertices[i]; if ((!allowMoveLeft && area2 > area1) || (!allowMoveRight && area2 < area1)) Vertices[i] = newVert; } else { previous = Vertices[i]; Vertices[i] = newVert; } } } before = (uint)Vertices.size(); while (Vertices.size()>3) // don't smooth blow 3 vertices to avoid degenrated surfaces { minArea = val; minIt = Vertices.end(); it = Vertices.begin(); ++it; for (i=1; i<Vertices.size()-1; ++i, ++it) { float area; area = 0.5f*((Vertices[i+1]-Vertices[i])^(Vertices[i-1]-Vertices[i])).norm(); if (area < minArea) { minArea = area; minIt = it; } } if (minIt != Vertices.end()) { Vertices.erase(minIt); } else { break; } } after = (uint)Vertices.size(); if (Verbose) nlinfo("smoothed border %d-%d: %d -> %d", Left, Right, before, after); } /* * CComputableSurface methods implementation * */ void NLPACS::CComputableSurface::followBorder(CZoneTessellation *zoneTessel, CSurfElement *first, uint edge, uint sens, vector<CVector> &vstore, bool &loop) { CSurfElement *current = first; CSurfElement *next = current->EdgeLinks[edge]; current->EdgeFlag[edge] = true; const sint32 currentSurfId = current->SurfaceId; const sint32 oppositeSurfId = (next != NULL) ? next->SurfaceId : UnaffectedSurfaceId; const sint32 oppositeZid = current->getZoneIdOnEdge(edge); sint oedge; sint pivot = (edge+sens)%3; sint nextEdge = edge; bool allowThis = true; // adds the pivot to the border and its normal vstore.push_back((*current->Vertices)[current->Tri[pivot]]); uint loopCount = 0; while (true) { ++loopCount; current->IsBorder = true; if (((oppositeSurfId != UnaffectedSurfaceId) && (next == NULL || (next->SurfaceId != oppositeSurfId && next->SurfaceId != currentSurfId))) || ((oppositeSurfId == UnaffectedSurfaceId) && ((next != NULL && next->SurfaceId != currentSurfId) || (next == NULL && current->getZoneIdOnEdge(nextEdge) != oppositeZid))) || ((current->EdgeFlag[nextEdge] || (zoneTessel->VerticesFlags[current->Tri[pivot]]!=0)) && !allowThis)) { // if reaches the end of the border, then quits. loop = (absoluteEquals(vstore.front(), vstore.back(), 1e-2f) && loopCount != 1); break; } else if ((oppositeSurfId != UnaffectedSurfaceId && next->SurfaceId == oppositeSurfId) || (oppositeSurfId == UnaffectedSurfaceId && next == NULL)) { // if the next edge belongs to the border, then go on the same element current->EdgeFlag[nextEdge] = true; if (oppositeSurfId != UnaffectedSurfaceId) { for (oedge=0; oedge<3 && next->EdgeLinks[oedge]!=current; ++oedge) ; nlassert(oedge != 3); nlassert(allowThis || !next->EdgeFlag[oedge]); next->EdgeFlag[oedge] = true; } pivot = (pivot+sens)%3; nextEdge = (nextEdge+sens)%3; next = current->EdgeLinks[nextEdge]; vstore.push_back((*current->Vertices)[current->Tri[pivot]]); } else { // if the next element is inside the surface, then go to the next element nlassert(next->SurfaceId == currentSurfId); for (oedge=0; oedge<3 && next->EdgeLinks[oedge]!=current; ++oedge) ; nlassert(oedge != 3); current = next; pivot = (oedge+3-sens)%3; nextEdge = (oedge+sens)%3; next = current->EdgeLinks[nextEdge]; } allowThis = false; } } void NLPACS::CComputableSurface::buildBorders(CZoneTessellation *zoneTessel) { sint elem, edge; if (Verbose) nlinfo("generate borders for the surface %d (%d elements) - water=%d", SurfaceId, Elements.size(), (IsUnderWater ? 1 : 0)); for (elem=0; elem<(sint)Elements.size(); ++elem) { // for each element, // scan for a edge that points to a different surface nlassert(Elements[elem]->SurfaceId == SurfaceId); for (edge=0; edge<3; ++edge) { if ((Elements[elem]->EdgeLinks[edge] == NULL || Elements[elem]->EdgeLinks[edge]->SurfaceId != SurfaceId) && !Elements[elem]->EdgeFlag[edge]) { BorderIds.push_back((uint16)BorderKeeper->size()); BorderKeeper->resize(BorderKeeper->size()+1); CComputableSurfaceBorder &border = BorderKeeper->back(); border.Left = Elements[elem]->SurfaceId; // ???? //border.DontSmooth = (Elements[elem]->EdgeLinks[edge] != NULL && Elements[elem]->NoLevelSurfaceId == Elements[elem]->EdgeLinks[edge]->SurfaceId); if (Elements[elem]->EdgeLinks[edge] != NULL && Elements[elem]->EdgeLinks[edge]->ZoneId != Elements[elem]->ZoneId) { // link on a neighbor zone border.Right = -2; } if (Elements[elem]->EdgeLinks[edge] == NULL || Elements[elem]->EdgeLinks[edge]->SurfaceId == UnaffectedSurfaceId) { // no link at all border.Right = -1; } else { border.Right = Elements[elem]->EdgeLinks[edge]->SurfaceId; } if (Verbose) nlinfo("generate border %d (%d-%d)", BorderKeeper->size()-1, border.Left, border.Right); bool loop; vector<CVector> bwdVerts; vector<CVector> &fwdVerts = border.Vertices; followBorder(zoneTessel, Elements[elem], edge, 2, bwdVerts, loop); sint i; fwdVerts.reserve(bwdVerts.size()); fwdVerts.clear(); for (i=(sint)(bwdVerts.size()-1); i>=0; --i) { fwdVerts.push_back(bwdVerts[i]); } if (loop) { fwdVerts.push_back(fwdVerts.front()); } else { fwdVerts.resize(fwdVerts.size()-2); followBorder(zoneTessel, Elements[elem], edge, 1, fwdVerts, loop); } } } } } bool intersect(const CVectorD& a0, const CVectorD& a1, const CVectorD& b0, const CVectorD& b1, double& pa, double& pb) { double ax = +(a1.x-a0.x); double ay = +(a1.y-a0.y); double bx = +(b1.x-b0.x); double by = +(b1.y-b0.y); double cx = +(b0.x-a0.x); double cy = +(b0.y-a0.y); double d = -ax*by + ay*bx; if (d == 0.0) return false; double a = (bx*cy - by*cx) / d; double b = (ax*cy - ay*cx) / d; pa = a; pb = b; return d != 0.0 && a >= 0.0 && a <= 1.0 && b >= 0.0 && b <= 1.0; } // Check Surface Consistency bool NLPACS::CComputableSurface::checkConsistency() { bool success = true; std::vector<std::pair<NLMISC::CVectorD, NLMISC::CVectorD> > edges; uint i, j; for (i=0; i<BorderIds.size(); ++i) { CComputableSurfaceBorder& border = (*BorderKeeper)[BorderIds[i]]; for (j=0; j+1<border.Vertices.size(); ++j) edges.push_back(std::make_pair<NLMISC::CVectorD, NLMISC::CVectorD>(border.Vertices[j], border.Vertices[j+1])); } for (i=0; i+1<edges.size(); ++i) { for (j=i+1; j<edges.size(); ++j) { CVectorD a0 = edges[i].first, a1 = edges[i].second; CVectorD b0 = edges[j].first, b1 = edges[j].second; double a, b; bool inters = intersect(a0, a1, b0, b1, a, b); if (!inters) continue; double da = (a1-a0).norm(); double db = (b1-b0).norm(); bool tipa = (fabs(a)*da < 4.0e-2 || fabs(1.0-a)*da < 4.0e-2); bool tipb = (fabs(b)*db < 4.0e-2 || fabs(1.0-b)*db < 4.0e-2); if (tipa && tipb) continue; success = false; } } return success; } /* * CZoneTessellation constructors and methods implementation * * * * * * * * * * */ bool NLPACS::CZoneTessellation::setup(uint16 zoneId, sint16 refinement, const CVector &translation) { CentralZoneId = zoneId; Refinement = refinement; // Translation = translation; // the zone bbox is hard coded for accuracy improvement... OriginalBBox = getZoneBBoxById(zoneId); BBox = OriginalBBox; Translation = -BBox.getCenter(); Translation.x = (float)floor(Translation.x+0.5f); Translation.y = (float)floor(Translation.y+0.5f); Translation.z = (float)floor(Translation.z+0.5f); BBox.setCenter(CVector::Null); BBox.setHalfSize(CVector(80.0f, 80.0f, BBox.getHalfSize().z)); // if zone doesn't exist, don't even setup tessellation try { if (CPath::lookup(getZoneNameById(zoneId)+ZoneExt, false, false) == "") return false; } catch (const EPathNotFound &) { return false; } // setup the square of 9 zones... if (Verbose) nlinfo("setup zone tessellation %d %s", zoneId, getZoneNameById(zoneId).c_str()); { sint zx = zoneId%256, zy = zoneId/256; for (zy=(zoneId/256)-1; zy<=(zoneId/256)+1; ++zy) { for (zx=(zoneId%256)-1; zx<=(zoneId%256)+1; ++zx) { if (zx >= 0 && zx <= 255 && zy >= 0 && zy <= 255) { uint zid = (zy<<8) + (zx); string filename = getZoneNameById(zid)+ZoneExt; if (CPath::lookup(filename, false, false) != "") { _ZoneIds.push_back(zid); } } } } } // sort zones sort(_ZoneIds.begin(), _ZoneIds.end()); return true; } // *************************************************************************** void NLPACS::CZoneTessellation::checkSameLandscapeHmBinds(const NL3D::CLandscape &landscape, const NL3D::CLandscape &landscapeNoHm) { // For all zones for (uint i=0; i<_ZoneIds.size(); ++i) { uint16 zoneId= _ZoneIds[i]; string zoneName; CLandscape::buildZoneName(zoneId, zoneName); // Get the zones const CZone *zone= landscape.getZone(zoneId); const CZone *zoneNoHm= landscapeNoHm.getZone(zoneId); // Check that both are valid, or both are not if( (zone==NULL) != (zoneNoHm==NULL) ) { nlwarning("ERROR: The zone %s is %s in the landscape while it is %s in the landscape_with_No_Heightmap", zoneName.c_str(), zone==NULL?"not present":"present", zoneNoHm==NULL?"not present":"present"); exit(0); } // else if both are valid else if(zone && zoneNoHm) { // check same number of patches uint numPatchs= zone->getNumPatchs(); uint numPatchsNoHm= zoneNoHm->getNumPatchs(); if(numPatchs!=numPatchsNoHm) { nlwarning("ERROR: The zone %s has %d Patchs in the landscape while it has %d Patchs in the landscape_with_No_Heightmap", zoneName.c_str(), numPatchs, numPatchsNoHm); exit(0); } else { // Check all binds of all patches std::vector<string> errors; errors.reserve(100); for(uint j=0;j<numPatchs;j++) { const CZone::CPatchConnect *patch= zone->getPatchConnect(j); const CZone::CPatchConnect *patchNoHm= zoneNoHm->getPatchConnect(j); // Check BaseVertices for(uint v=0;v<4;v++) { if(patch->BaseVertices[v] != patchNoHm->BaseVertices[v]) { errors.push_back(toString(" The Patch %d has not the same %dth vertex: %d/%d", j, v, patch->BaseVertices[v], patchNoHm->BaseVertices[v])); } } // Check BindEdges for(uint b=0;b<4;b++) { CPatchInfo::CBindInfo bind= patch->BindEdges[b]; CPatchInfo::CBindInfo bindNoHm= patchNoHm->BindEdges[b]; bool ok= true; // verify all valid properties only ok= ok && bind.NPatchs == bindNoHm.NPatchs; ok= ok && bind.ZoneId == bindNoHm.ZoneId; uint validNbBinds= min(bind.NPatchs,uint8(4)); for(uint nb=0;nb<validNbBinds;nb++) { ok= ok && bind.Next[nb] == bindNoHm.Next[nb]; ok= ok && bind.Edge[nb] == bindNoHm.Edge[nb]; } // if not ok if(!ok) { // add the error string error; error= toString(" The Patch %d that has not the same %dth bindEdge: NumPatchs: %d/%d; ZoneId: %d/%d", j, b, bind.NPatchs, bindNoHm.NPatchs, bind.ZoneId, bindNoHm.ZoneId); for(uint nb=0;nb<validNbBinds;nb++) { error+= toString("; Edge (%d,%d)/(%d,%d)", bind.Next[nb], bind.Edge[nb], bindNoHm.Next[nb], bindNoHm.Edge[nb]); } errors.push_back(error); } } } // if some error found if(!errors.empty()) { // todo: change the way the landscape with heightmap is applied: it should be applied after welding! // or at least the welding of zones should just keep the same welding as the non heightmapped one nlwarning("ERROR: The zone %s has a different bind strucutre in the landscape and in the landscape_with_No_Heightmap", zoneName.c_str()); nlwarning("ERROR: Hint: Check your heightmap: it may be too precise or has too much noise, causing the zonewelder to behav differently..."); nlwarning("More Details (information landscape / information landscape_with_No_Heightmap):"); for(uint j=0;j<errors.size();j++) { nlwarning("%s", errors[j].c_str()); } exit(0); } } } } } // *************************************************************************** void NLPACS::CZoneTessellation::build() { sint el; uint i, j; NL3D::CLandscape landscape; landscape.init(); vector<CVector> normals; vector<CVector> vectorCheck; bool useNoHmZones = true; { NL3D::CLandscape landscapeNoHm; landscapeNoHm.init(); // // load the 9 landscape zones // for (i=0; i<_ZoneIds.size(); ++i) { string filename = getZoneNameById(_ZoneIds[i])+ZoneExt; CIFile file(CPath::lookup(filename)); CZone zone; zone.serial(file); file.close(); if (Verbose) nlinfo("use zone %s %d", filename.c_str(), zone.getZoneId()); if (zone.getZoneId() != _ZoneIds[i]) { nlwarning ("Zone %s ID is wrong. Abort.", filename.c_str()); return; } landscape.addZone(zone); if (useNoHmZones) { string filenameNH = getZoneNameById(_ZoneIds[i])+ZoneNHExt; string loadZ = CPath::lookup(filenameNH, false, false); if (!loadZ.empty()) { CIFile fileNH(loadZ); CZone zoneNH; zoneNH.serial(fileNH); fileNH.close(); if (zoneNH.getZoneId() != _ZoneIds[i]) { nlwarning ("Zone %s ID is wrong. Abort.", filenameNH.c_str()); return; } landscapeNoHm.addZone(zoneNH); } else { useNoHmZones = false; } } _ZonePtrs.push_back(landscape.getZone(_ZoneIds[i])); } landscape.setNoiseMode(false); landscape.checkBinds(); if (useNoHmZones) { landscapeNoHm.setNoiseMode(false); landscapeNoHm.checkBinds(); } BestFittingBBox.setCenter(CVector::Null); BestFittingBBox.setHalfSize(CVector::Null); BestFittingBBoxSetuped= false; // Compute best fitting bbox for (i=0; i<_ZoneIds.size(); ++i) { if (_ZoneIds[i] == CentralZoneId) { if(_ZonePtrs[i]->getNumPatchs()>0) { BestFittingBBox = _ZonePtrs[i]->getZoneBB().getAABBox(); BestFittingBBoxSetuped= true; } } } CAABBox enlBBox = BestFittingBBox; enlBBox.setHalfSize(enlBBox.getHalfSize()+CVector(8.0f, 8.0f, 1000.0f)); // Add neighbor patch for (i=0; i<_ZoneIds.size(); ++i) { if (_ZoneIds[i] == CentralZoneId) { for (j=0; (sint)j<_ZonePtrs[i]->getNumPatchs(); ++j) { landscape.excludePatchFromRefineAll(_ZoneIds[i], j, false); if (useNoHmZones) landscapeNoHm.excludePatchFromRefineAll(_ZoneIds[i], j, false); } if (Verbose) nlinfo(" - selected %d/%d patches for zone %d", _ZonePtrs[i]->getNumPatchs(), _ZonePtrs[i]->getNumPatchs(), _ZoneIds[i]); } else { uint nump = 0; for (j=0; (sint)j<_ZonePtrs[i]->getNumPatchs(); ++j) { CAABBox pbox = _ZonePtrs[i]->getPatch(j)->buildBBox(); bool inters = enlBBox.intersect(pbox); if (inters) { landscape.excludePatchFromRefineAll(_ZoneIds[i], j, false); if (useNoHmZones) landscapeNoHm.excludePatchFromRefineAll(_ZoneIds[i], j, false); ++nump; } else { landscape.excludePatchFromRefineAll(_ZoneIds[i], j, true); if (useNoHmZones) landscapeNoHm.excludePatchFromRefineAll(_ZoneIds[i], j, true); } } if (Verbose) nlinfo(" - selected %d/%d patches for zone %d", nump, _ZonePtrs[i]->getNumPatchs(), _ZoneIds[i]); } } // tessellate the landscape, get the leaves (the tessellation faces), and convert them // into surf elements if (Verbose) nlinfo("Compute landscape tessellation"); if (Verbose) nlinfo(" - tessellate landscape"); if (useNoHmZones) { // Before tesselate, verify that the 2 landscape zones have at least the same binds! // Else there will be errors because of not the same tesselation checkSameLandscapeHmBinds(landscape, landscapeNoHm); // Tesselate landscapeNoHm.setThreshold(0.0f); landscapeNoHm.setTileMaxSubdivision(TessellateLevel); landscapeNoHm.refineAll(CVector::Null); landscapeNoHm.averageTesselationVertices(); // get the faces vector<const CTessFace *> leavesNoHm; landscapeNoHm.getTessellationLeaves(leavesNoHm); for (el=0; el<(sint)leavesNoHm.size(); ++el) { const CTessFace *face = leavesNoHm[el]; const CVector *v[3]; // get the vertices of the face v[0] = &(face->VBase->EndPos); v[1] = &(face->VLeft->EndPos); v[2] = &(face->VRight->EndPos); normals.push_back( ((*(v[1])-*(v[0])) ^ (*(v[2])-*(v[0]))).normed() ); vectorCheck.push_back(*(v[0])); vectorCheck.push_back(*(v[1])); vectorCheck.push_back(*(v[2])); } } } // Build the lanscape with heightmap landscape.setThreshold(0.0f); landscape.setTileMaxSubdivision(TessellateLevel); landscape.refineAll(CVector::Null); landscape.averageTesselationVertices(); vector<const CTessFace *> leaves; landscape.getTessellationLeaves(leaves); if (Verbose) { if (useNoHmZones) nlinfo(" - used no height map zones"); nlinfo(" - generated %d leaves", leaves.size()); } // If don't use NoHm zones, build normals and vectorCheck directly from std landscape if (!useNoHmZones) { for (el=0; el<(sint)leaves.size(); ++el) { const CTessFace *face = leaves[el]; const CVector *v[3]; // get the vertices of the face v[0] = &(face->VBase->EndPos); v[1] = &(face->VLeft->EndPos); v[2] = &(face->VRight->EndPos); normals.push_back( ((*(v[1])-*(v[0])) ^ (*(v[2])-*(v[0]))).normed() ); vectorCheck.push_back(*(v[0])); vectorCheck.push_back(*(v[1])); vectorCheck.push_back(*(v[2])); } } // check that there is the same number of faces from landscape with and without heightmap if (normals.size() != leaves.size()) { nlwarning ("ERROR : The heightmaped landscape has not the same number of polygon than the nonheightmaped landscape: %d/%d.", normals.size(), leaves.size()); exit (0); } // generate a vector of vertices and of surf element CHashMap<const CVector *, uint32, CHashPtr<const CVector> > vremap; CHashMap<const CVector *, uint32, CHashPtr<const CVector> >::iterator vremapit; CHashMap<const CTessFace *, CSurfElement *, CHashPtr<const CTessFace> > fremap; CHashMap<const CTessFace *, CSurfElement *, CHashPtr<const CTessFace> >::iterator fremapit; _Vertices.clear(); _Tessellation.resize(leaves.size()); if (Verbose) nlinfo(" - make and remap surface elements"); for (el=0; el<(sint)leaves.size(); ++el) fremap[leaves[el]] = &(_Tessellation[el]); uint check = 0; float dist, maxdist = 0.0f; for (el=0; el<(sint)leaves.size(); ++el) { const CTessFace *face = leaves[el]; const CVector *v[3]; CSurfElement &element = _Tessellation[el]; // setup zone id element.ZoneId = face->Patch->getZone()->getZoneId(); // get the vertices of the face v[0] = &(face->VBase->EndPos); v[1] = &(face->VLeft->EndPos); v[2] = &(face->VRight->EndPos); { CVector vcheck; vcheck = vectorCheck[check++] - *(v[0]); vcheck.z = 0; dist = vcheck.norm(); if (dist > maxdist) maxdist = dist; //nlassert(vcheck.norm() < 0.1f); vcheck = vectorCheck[check++] - *(v[1]); vcheck.z = 0; dist = vcheck.norm(); if (dist > maxdist) maxdist = dist; //nlassert(vcheck.norm() < 0.1f); vcheck = vectorCheck[check++] - *(v[2]); vcheck.z = 0; dist = vcheck.norm(); if (dist > maxdist) maxdist = dist; //nlassert(vcheck.norm() < 0.1f); } //element.Normal = ((*(v[1])-*(v[0])) ^ (*(v[2])-*(v[0]))).normed(); element.Normal = normals[el]; // search the vertices in the map for (i=0; i<3; ++i) { // if doesn't exist, create a new vertex if ((vremapit = vremap.find(v[i])) == vremap.end()) { element.Tri[i] = (uint32)_Vertices.size(); _Vertices.push_back(*(v[i])); vremap.insert(make_pair(v[i], element.Tri[i])); } // else use previous else { element.Tri[i] = vremapit->second; } } // setup the vertices pointer element.Vertices = &_Vertices; CTessFace *edge[3]; edge[0] = face->FBase; edge[1] = face->FRight; edge[2] = face->FLeft; for (i=0; i<3; ++i) { fremapit = fremap.find(edge[i]); element.EdgeLinks[i] = (fremapit != fremap.end() ? fremapit->second : NULL); } } for (el=0; el<(sint)_Tessellation.size(); ++el) { // add the element to the list of valid elements Elements.push_back(&(_Tessellation[el])); } landscape.clear(); } void NLPACS::CZoneTessellation::compile() { sint el; uint i; CAABBox tbox = computeBBox(); bool HasInvertedUnderWater = false; // setup cliffs for (el=0; el<(sint)Elements.size(); ++el) { CSurfElement &element = *(Elements[el]); // a cliff ? if (element.Normal.z < 0.0) { CVector &v0 = _Vertices[element.Tri[0]], &v1 = _Vertices[element.Tri[1]], &v2 = _Vertices[element.Tri[2]]; uint8 bits0 = PrimChecker.get((uint)v0.x, (uint)v0.y); uint8 bits1 = PrimChecker.get((uint)v1.x, (uint)v1.y); uint8 bits2 = PrimChecker.get((uint)v2.x, (uint)v2.y); bool w0 = ((bits0&CPrimChecker::Water) != 0); bool w1 = ((bits1&CPrimChecker::Water) != 0); bool w2 = ((bits2&CPrimChecker::Water) != 0); if ((bits0 & CPrimChecker::Water)!=0 || (bits1 & CPrimChecker::Water)!=0 || (bits2 & CPrimChecker::Water)!=0) { uint ws = 0; uint16 ws0 = PrimChecker.index((uint)v0.x, (uint)v0.y); uint16 ws1 = PrimChecker.index((uint)v1.x, (uint)v1.y); uint16 ws2 = PrimChecker.index((uint)v2.x, (uint)v2.y); if ((w0 && w1 && ws0 == ws1) || (w0 && w2 && ws0 == ws2)) ws = ws0; else if (w1 && w2 && ws1 == ws2) ws = ws1; else if (w0) ws = ws0; else if (w1) ws = ws1; else if (w2) ws = ws2; float minz = std::min(_Vertices[element.Tri[0]].z, std::min(_Vertices[element.Tri[1]].z, _Vertices[element.Tri[2]].z)); bool exists; float wh = PrimChecker.waterHeight(ws, exists)+WaterThreshold; // if (minz <= wh) { CPolygon p(v0, v1, v2); PrimChecker.renderBits(p, CPrimChecker::Cliff); HasInvertedUnderWater = true; } } } } if (HasInvertedUnderWater) { nlwarning("zone '%s' has reversed landscape under water", (getZoneNameById((uint16)CentralZoneId)+ZoneExt).c_str()); } // compute elements features if (Verbose) nlinfo("compute elements quantas"); for (el=0; el<(sint)Elements.size(); ++el) { CSurfElement &element = *(Elements[el]); element.ElemId = el; element.computeQuantas(this); } if (ReduceSurfaces) { // optimizes the number of generated segments // it also smoothes a bit the surface border // it seems that 3 consecutive passes are optimal to reduce // nasty granularity if (Verbose) nlinfo("reduce surfaces"); uint i; sint p; for (i=0; i<3; ++i) { for (p=0; p<(sint)Elements.size(); ++p) { CSurfElement &e = *(Elements[p]); CSurfElement &e0 = *e.EdgeLinks[0], &e1 = *e.EdgeLinks[1], &e2 = *e.EdgeLinks[2]; if (e.IsMergable && &e0 != NULL && &e1 != NULL && &e2 != NULL && e.ZoneId == e0.ZoneId && e.ZoneId == e1.ZoneId && e.ZoneId == e2.ZoneId && !e.ForceInvalid) { // Strong optimization // merge the element quantas to the neighbors' quantas which are the most numerous // quantas are evaluated individually if (e0.IsValid && e1.IsValid) e.IsValid = true; if (e1.IsValid && e2.IsValid) e.IsValid = true; if (e0.IsValid && e2.IsValid) e.IsValid = true; if (e0.QuantHeight == e1.QuantHeight) e.QuantHeight = e0.QuantHeight; if (e1.QuantHeight == e2.QuantHeight) e.QuantHeight = e1.QuantHeight; if (e0.QuantHeight == e2.QuantHeight) e.QuantHeight = e2.QuantHeight; if (e0.IsValid && e1.IsValid && e0.WaterShape == e1.WaterShape && e0.IsUnderWater == e1.IsUnderWater) { e.WaterShape = e0.WaterShape; e.IsUnderWater = e0.IsUnderWater; } if (e1.IsValid && e2.IsValid && e1.WaterShape == e2.WaterShape && e1.IsUnderWater == e2.IsUnderWater) { e.WaterShape = e1.WaterShape; e.IsUnderWater = e1.IsUnderWater; } if (e0.IsValid && e2.IsValid && e0.WaterShape == e2.WaterShape && e0.IsUnderWater == e2.IsUnderWater) { e.WaterShape = e2.WaterShape; e.IsUnderWater = e2.IsUnderWater; } } } } for (p=0; p<(sint)Elements.size(); ++p) { CSurfElement &e = *(Elements[p]); CSurfElement &e0 = *e.EdgeLinks[0], &e1 = *e.EdgeLinks[1], &e2 = *e.EdgeLinks[2]; if (&e != NULL && &e0 != NULL && &e1 != NULL && &e2 != NULL && e.IsValid && e0.IsValid && e1.IsValid && e2.IsValid && !e.IsUnderWater && e0.IsUnderWater && e1.IsUnderWater && e2.IsUnderWater) { nlwarning("isolated submerged element '%d' !", p); } } } // translates vertices to the local axis sint64 vx, vy, vz, tx, ty, tz; tx = float2Fixed(Translation.x); ty = float2Fixed(Translation.y); tz = float2Fixed(Translation.z); uint p; for (i=0; i<_Vertices.size(); ++i) { vx = float2Fixed(_Vertices[i].x) + tx; vy = float2Fixed(_Vertices[i].y) + ty; vz = float2Fixed(_Vertices[i].z) + tz; _Vertices[i] = CVector(fixed2Float(vx), fixed2Float(vy), fixed2Float(vz)); } if(BestFittingBBoxSetuped) BestFittingBBox.setCenter(BestFittingBBox.getCenter()+Translation); // //if (false) { // // first pass of flood fill // allow detecting landscape irregularities // if (Verbose) nlinfo("build and flood fill surfaces -- pass 1"); uint32 surfId = 0; // + (ZoneId<<16); for (p=0; p<Elements.size(); ++p) { if (Elements[p]->SurfaceId == UnaffectedSurfaceId) { Surfaces.push_back(CComputableSurface()); CComputableSurface &surf = Surfaces.back(); surf.BorderKeeper = &Borders; surf.floodFill(Elements[p], surfId++, CSurfElemCompareSimple(), this); surf.BBox = BestFittingBBox; bool force = false; if (surf.Area < 30.0f && surf.Elements.size() > 0) { uint i; CAABBox aabbox; aabbox.setCenter((*surf.Elements[0]->Vertices)[surf.Elements[0]->Tri[0]]); for (i=0; i<surf.Elements.size(); ++i) { aabbox.extend((*surf.Elements[i]->Vertices)[surf.Elements[i]->Tri[0]]); aabbox.extend((*surf.Elements[i]->Vertices)[surf.Elements[i]->Tri[1]]); aabbox.extend((*surf.Elements[i]->Vertices)[surf.Elements[i]->Tri[2]]); } // swap all suface elements validity if (!surf.Elements[0]->ForceInvalid && aabbox.getHalfSize().z < 1.5f) { for (i=0; i<surf.Elements.size(); ++i) { surf.Elements[i]->IsValid = !surf.Elements[i]->IsValid; } if (Verbose) nlinfo("Reverted surface %d (%d elements, water=%d)", surfId-1, surf.Elements.size(), (surf.IsUnderWater ? 1 : 0)); } } } } Surfaces.clear(); ExtSurfaces.clear(); } vector<CSurfElement*> elDup; for (el=0; el<(sint)Elements.size(); ++el) if (Elements[el]->IsValid) elDup.push_back(Elements[el]); Elements = elDup; elDup.clear(); for (el=0; el<(sint)Elements.size(); ++el) { CSurfElement &element = *(Elements[el]); element.SurfaceId = UnaffectedSurfaceId; uint i; for (i=0; i<3; ++i) if (element.EdgeLinks[i] != NULL && !element.EdgeLinks[i]->IsValid) element.EdgeLinks[i] = NULL; } // { if (Verbose) nlinfo("build and flood fill surfaces"); uint32 surfId = 0; // + (ZoneId<<16); uint totalSurf = 0; sint32 extSurf = -1024; for (p=0; p<Elements.size(); ++p) { if (Elements[p]->SurfaceId == UnaffectedSurfaceId) { bool elInCentral = (Elements[p]->ZoneId == CentralZoneId); ++totalSurf; sint32 thisSurfId = (elInCentral) ? surfId++ : extSurf--; if (elInCentral) Surfaces.push_back(CComputableSurface()); else ExtSurfaces.push_back(CComputableSurface()); CComputableSurface &surf = (elInCentral) ? Surfaces.back() : ExtSurfaces.back(); surf.BorderKeeper = &Borders; surf.floodFill(Elements[p], thisSurfId, CSurfElemCompareNormal(), this); surf.BBox = BestFittingBBox; } } if (Verbose) { nlinfo("%d surfaces generated", totalSurf); for (p=0; p<Surfaces.size(); ++p) { nlinfo("surf %d: %d elements", p, Surfaces[p].Elements.size()); if (Surfaces[p].Elements.size() == 1) { nlinfo("elm: %d", Surfaces[p].Elements[0]->ElemId); } } } } // flag vertices that are pointed by more than 2 surfaces VerticesFlags.resize(_Vertices.size(), 0); for (p=0; p<Elements.size(); ++p) { CSurfElement *elem = Elements[p]; sint32 s = elem->SurfaceId; sint32 s0 = (elem->EdgeLinks[0] != NULL ? elem->EdgeLinks[0]->SurfaceId : UnaffectedSurfaceId); sint32 s1 = (elem->EdgeLinks[1] != NULL ? elem->EdgeLinks[1]->SurfaceId : UnaffectedSurfaceId); sint32 s2 = (elem->EdgeLinks[2] != NULL ? elem->EdgeLinks[2]->SurfaceId : UnaffectedSurfaceId); if (s != s0 && s != s1 && s0 != s1) { VerticesFlags[elem->Tri[2]] = 1; } if (s != s1 && s != s2 && s1 != s2) { VerticesFlags[elem->Tri[0]] = 1; } if (s != s2 && s != s0 && s2 != s0) { VerticesFlags[elem->Tri[1]] = 1; } } } void NLPACS::CZoneTessellation::generateBorders(float smooth) { sint surf; if (Verbose) nlinfo("generate tessellation borders"); // for each surface, build its border for (surf=0; surf<(sint)Surfaces.size(); ++surf) Surfaces[surf].buildBorders(this); // then, for each border, link the related surfaces... if (Verbose) nlinfo("smooth borders"); sint border; sint totalBefore = 0, totalAfter = 0; for (border=0; border<(sint)Borders.size(); ++border) { CComputableSurfaceBorder& cborder = Borders[border]; sint lsurf = cborder.Left; sint rsurf = cborder.Right; if (CheckConsistency) { if (lsurf >= 0) { CComputableSurface& surf = Surfaces[lsurf]; if (!surf.checkConsistency()) { nlwarning("Before smooth of border '%d', surface '%d' not consistent", border, lsurf); } } if (rsurf >= 0) { CComputableSurface& surf = Surfaces[rsurf]; if (!surf.checkConsistency()) { nlwarning("Before smooth of border '%d', surface '%d' not consistent", border, rsurf); } } } float smScale = (Borders[border].Right < 0) ? 0.2f : 1.0f; uint before = (uint)Borders[border].Vertices.size(); if (SmoothBorders && !Borders[border].DontSmooth) { Borders[border].smooth(smooth*smScale); } Borders[border].computeLength(); uint after = (uint)Borders[border].Vertices.size(); totalBefore += before; totalAfter += after; if (CheckConsistency) { if (lsurf >= 0) { CComputableSurface& surf = Surfaces[lsurf]; if (!surf.checkConsistency()) { nlwarning("After smooth of border '%d', surface '%d' not consistent", border, lsurf); } } if (rsurf >= 0) { CComputableSurface& surf = Surfaces[rsurf]; if (!surf.checkConsistency()) { nlwarning("After smooth of border '%d', surface '%d' not consistent", border, rsurf); } } } } if (Verbose) nlinfo("smooth process: %d -> %d (%.1f percent reduction)", totalBefore, totalAfter, 100.0*(1.0-(double)totalAfter/(double)totalBefore)); } void NLPACS::CZoneTessellation::saveTessellation(COFile &output) { output.serialCont(_Vertices); uint i; for (i=0; i<_Tessellation.size(); ++i) _Tessellation[i].ElemId = i; uint32 numTessel = (uint32)_Tessellation.size(); output.serial(numTessel); for (i=0; i<_Tessellation.size(); ++i) { _Tessellation[i].serial(output, _Tessellation); } } void NLPACS::CZoneTessellation::loadTessellation(CIFile &input) { input.serialCont(_Vertices); uint i; uint32 numTessel; input.serial(numTessel); _Tessellation.resize(numTessel); for (i=0; i<_Tessellation.size(); ++i) { _Tessellation[i].serial(input, _Tessellation); } Elements.resize(_Tessellation.size()); for (i=0; i<_Tessellation.size(); ++i) { Elements[i] = &_Tessellation[i]; } } void NLPACS::CZoneTessellation::clear() { _ZoneIds.clear(); _ZonePtrs.clear(); _Tessellation.clear(); _Vertices.clear(); Elements.clear(); Surfaces.clear(); Borders.clear(); } CAABBox NLPACS::CZoneTessellation::computeBBox() const { CAABBox zbox; bool set = false; uint i; if (_Vertices.size() == 0) return zbox; zbox.setCenter(_Vertices[0]); for (i=1; i<_Vertices.size(); ++i) zbox.extend(_Vertices[i]); return zbox; }