// ***************************************************************************
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();
}