// *************************************************************************** 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(); }
/** Check a zone and report the total number of errors */ static uint CheckZone(std::string middleZoneFile, float weldThreshold, float middleEdgeWeldThreshold) { uint numErrors = 0; uint k, l, m, n, p, q; // some loop counters // This avoid reporting errors twice (for readability) std::set<CPatchIdentPair> errorPairs; //////////////////////////// // Load the zones around // //////////////////////////// std::auto_ptr<CZone> zones[9]; std::string zoneNames[9]; CZoneInfo zoneInfos[9]; uint16 xPos, yPos; const sint16 posOffs[][2] = { {0, 0}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1} }; std::string middleZoneName = CFile::getFilenameWithoutExtension(middleZoneFile); ::getZoneCoordByName(middleZoneName.c_str(), xPos, yPos); try { std::string ext = CFile::getExtension(middleZoneFile); zones[0].reset(::LoadZone(xPos, yPos, ext.empty() ? "" : "." + ext)); if (zones[0].get() == NULL) { nlwarning("Can't load zone %s", middleZoneName.c_str()); return 0; } for (uint k = 1; k < 9; ++k) { zones[k].reset(::LoadZone(xPos + posOffs[k][0], yPos + posOffs[k][1], ext.empty() ? "" : "." + ext)); } } catch (NLMISC::Exception &e) { nlinfo("Zones loading failed : %d", e.what()); return 0; } /////////////////////////////// // retrieve datas from zones // /////////////////////////////// for (k = 0; k < 9; ++k) { ::getZoneNameByCoord(xPos + posOffs[k][0], yPos + posOffs[k][1], zoneNames[k]); if (zones[k].get() != NULL) zones[k]->retrieve(zoneInfos[k]); } // fill the quad grid CAABBox zoneBBox = zones[0]->getZoneBB().getAABBox(); float zoneSize = 2.f * weldThreshold + std::max(zoneBBox.getMax().x - zoneBBox.getMin().x, zoneBBox.getMax().y - zoneBBox.getMin().y); TPVQuadGrid qg; const uint numQGElt = 128; qg.create(numQGElt, zoneSize / numQGElt); // insert vertices in quadgrid for (k = 0; k < 9; ++k) { for (l = 0; l < zoneInfos[k].Patchs.size(); ++l) { CPatchInfo &patch = zoneInfos[k].Patchs[l]; // for each base vertex of the patch for (m = 0; m < 4; ++m) { CVector &pos = patch.Patch.Vertices[m]; CBSphere s(pos, weldThreshold); if (zoneBBox.intersect(s)) // does this vertex and its zone of influence intersect the bbox ? { CVector half(weldThreshold, weldThreshold, weldThreshold); // yes, insert it in the tree qg.insert(pos - half, pos + half, CPatchVertexInfo(k, l, m, pos)); } } } } //////////////////////////////////////////////// // check wether each patch is correctly bound // //////////////////////////////////////////////// for (l = 0; l < zoneInfos[0].Patchs.size(); ++l) { CPatchInfo &patch = zoneInfos[0].Patchs[l]; // deals with each border for (m = 0; m < 4; ++m) { // if this border is said to be bound, no need to test.. if (patch.BindEdges[m].NPatchs == 0) { // maps from an index to a (s, t) couple static const float indexToST[][2] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}}; // index of this border vertices const uint vIndex[] = { m, (m + 1) & 0x03 }; bool errorFound = false; static TPVVect verts[2]; // Get vertices from other patch that could be welded with this patch boder's vertices. for (q = 0; q < 2; ++q) { //nlinfo("pos = %f, %f, %f", patch.Patch.Vertices[vIndex[q]].x, patch.Patch.Vertices[vIndex[q]].y, patch.Patch.Vertices[vIndex[q]].z); ::GetCandidateVertices(patch.Patch.Vertices[vIndex[q]], qg, verts[q], l, 0, weldThreshold); } /////////////////////////// // 1 - 1 connectivity ? // /////////////////////////// // If there is a patch that is present in the 2 lists, then this is a 1-1 error for (n = 0; n < verts[0].size() && !errorFound; ++n) { for (p = 0; p < verts[1].size() && !errorFound; ++p) { if (verts[0][n]->ZoneIndex == verts[1][p]->ZoneIndex && verts[0][n]->PatchIndex == verts[1][p]->PatchIndex) { CPatchIdent pi1(0, l); CPatchIdent pi2(verts[0][n]->ZoneIndex, verts[0][n]->PatchIndex); CPatchIdentPair errPair = std::make_pair(pi1, pi2); // if (std::find(errorPairs.begin(), errorPairs.end(), errPair) == errorPairs.end()) // error already displayed ? { nlinfo("**** Patch %d of zone %s has 1 - 1 connectivity error, try binding it with patch %d of zone %s", l + 1, middleZoneName.c_str(), verts[0][n]->PatchIndex + 1, zoneNames[verts[0][n]->ZoneIndex].c_str()); errorPairs.insert(std::make_pair(pi2, pi1)); ++numErrors; } errorFound = true; } } } if (errorFound) continue; ////////////////////////// // 1 - 2 connectivity ? // ////////////////////////// // get the position at the middle of that border CVector middlePos = patch.Patch.eval( 0.5f * (indexToST[vIndex[0]][0] + indexToST[vIndex[1]][0]), 0.5f * (indexToST[vIndex[0]][1] + indexToST[vIndex[1]][1]) ); // for each vertex of this border for (q = 0; q < 2 && !errorFound; ++q) { for (n = 0; n < verts[q].size() && !errorFound; ++n) { const CPatchVertexInfo &pv = *(verts[q][n]); // ref to the patch that share a vertex with this one const CBezierPatch &bPatch = zoneInfos[pv.ZoneIndex].Patchs[pv.PatchIndex].Patch; sint vertIndex = ::GetWeldableVertex(bPatch, pv.Pos, weldThreshold); nlassert(vertIndex != -1); // should found one.. // Follow this patch edge and see if the next / previous vertex could be welded with the middle const CVector &nextVertPos = bPatch.Vertices[(vertIndex + 1) & 0x03]; const CVector &prevVertPos = bPatch.Vertices[(vertIndex - 1) & 0x03]; if (::CanWeld(nextVertPos, middlePos, middleEdgeWeldThreshold) || ::CanWeld(prevVertPos, middlePos, middleEdgeWeldThreshold) ) { CPatchIdent pi1(0, l); CPatchIdent pi2(pv.ZoneIndex, pv.PatchIndex); CPatchIdentPair errPair = std::make_pair(pi1, pi2); // if (std::find(errorPairs.begin(), errorPairs.end(), errPair) == errorPairs.end()) // error already displayed ? { nlinfo("**** Patch %d of zone %s has 1 - 2 connectivity error, try binding it with patch %d of zone %s", l + 1, middleZoneName.c_str(), pv.PatchIndex + 1, zoneNames[pv.ZoneIndex].c_str()); errorPairs.insert(std::make_pair(pi2, pi1)); ++numErrors; } errorFound = true; break; } } } if (errorFound) continue; ////////////////////////// // 1 - 4 connectivity ? // ////////////////////////// // compute points along the border. CVector borderPos[5]; float lambda = 0.f; for (n = 0; n < 5; ++n) { borderPos[n] = patch.Patch.eval((1.f - lambda) * indexToST[vIndex[0]][0] + lambda * indexToST[vIndex[1]][0], (1.f - lambda) * indexToST[vIndex[0]][1] + lambda * indexToST[vIndex[1]][1]); lambda += 0.25f; } // Try to find a patch that shares 2 consecutives vertices for (k = 0; k < 4 && !errorFound; ++k) { ::GetCandidateVertices(borderPos[k], qg, verts[0], l, 0, middleEdgeWeldThreshold); for (p = 0; p < verts[0].size() && !errorFound; ++p) { const CPatchVertexInfo &pv = *(verts[0][p]); // ref to the patch that share a vertex with this one const CBezierPatch &bPatch = zoneInfos[pv.ZoneIndex].Patchs[pv.PatchIndex].Patch; sint vertIndex = ::GetWeldableVertex(bPatch, pv.Pos, weldThreshold); nlassert(vertIndex != -1); // should found one.. // Follow this patch edge and see if the next/ previous vertex could be welded with the next point const CVector &nextVertPos = bPatch.Vertices[(vertIndex + 1) & 0x03]; const CVector &prevVertPos = bPatch.Vertices[(vertIndex - 1) & 0x03]; if (::CanWeld(nextVertPos, borderPos[k + 1], middleEdgeWeldThreshold) || ::CanWeld(prevVertPos, borderPos[k + 1], middleEdgeWeldThreshold) ) { CPatchIdent pi1(0, l); CPatchIdent pi2(pv.ZoneIndex, pv.PatchIndex); CPatchIdentPair errPair = std::make_pair(pi1, pi2); // if (std::find(errorPairs.begin(), errorPairs.end(), errPair) == errorPairs.end()) // error already displayed ? { nlinfo("**** Patch %d of zone %s has 1 - 4 connectivity error, try binding it with patch %d of zone %s", l + 1, middleZoneName.c_str(), pv.PatchIndex + 1, zoneNames[pv.ZoneIndex].c_str()); ++numErrors; errorPairs.insert(std::make_pair(pi2, pi1)); } errorFound = true; } } } } } } //////////////////////////////// //////////////////////////////// if (numErrors != 0) { nlinfo("%d errors found", numErrors); } return numErrors; }