Beispiel #1
0
		/// <summary>
		/// Generates a <see cref="NavMesh"/> given a collection of triangles and some settings.
		/// </summary>
		/// <param name="triangles">The triangles that form the level.</param>
		/// <param name="settings">The settings to generate with.</param>
		/// <returns>A <see cref="NavMesh"/>.</returns>
		public static NavMesh Generate(IEnumerable<Triangle3> triangles, NavMeshGenerationSettings settings)
		{
			BBox3 bounds = triangles.GetBoundingBox(settings.CellSize);
			var hf = new Heightfield(bounds, settings);
			hf.RasterizeTriangles(triangles);
			hf.FilterLedgeSpans(settings.VoxelAgentHeight, settings.VoxelMaxClimb);
			hf.FilterLowHangingWalkableObstacles(settings.VoxelMaxClimb);
			hf.FilterWalkableLowHeightSpans(settings.VoxelAgentHeight);

			var chf = new CompactHeightfield(hf, settings);
			chf.Erode(settings.VoxelAgentRadius);
			chf.BuildDistanceField();
			chf.BuildRegions(2, settings.MinRegionSize, settings.MergedRegionSize);

			var cont = chf.BuildContourSet(settings);

			var polyMesh = new PolyMesh(cont, settings);

			var polyMeshDetail = new PolyMeshDetail(polyMesh, chf, settings);

			var buildData = new NavMeshBuilder(polyMesh, polyMeshDetail, new Pathfinding.OffMeshConnection[0], settings);

			var navMesh = new NavMesh(buildData);
			return navMesh;
		}
        public void BuildRegions_Success()
        {
            //Build a 3x3 heightfield
            Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), (float)(1.0f / 3.0f), 0.02f);
            for (int i = 0; i < 9; i++)
            {
                hf[i].AddSpan(new Span(10, 20, Area.Default));
                hf[i].AddSpan(new Span(25, 30, Area.Default));
            }
            CompactHeightfield chf = new CompactHeightfield(hf, 2, 1);

            chf.BuildDistanceField();
            chf.BuildRegions(1, 2, 3);

            //Most spans do not have a region id because those spans are part of the border
            //Most region ids won't be assigned to any span

            //Total number of regions right now
            Assert.AreEqual(chf.MaxRegions, 7);

            //Center spans should have region id
            Assert.AreEqual((int)chf.Spans[4 * 2 + 0].Region, 5);
            Assert.AreEqual((int)chf.Spans[4 * 2 + 1].Region, 6);

            //Check that the rest of the region ids are not assigned to a span
            for (int i = 0; i < chf.Spans.Length; i++)
            {
                for (int j = 0; j <= 4; j++)
                {
                    Assert.AreNotEqual((int)chf.Spans[i].Region, j);
                }
            }
        }
        public void ConvertSpans_OneCell()
        {
            Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f);
            hf[0].AddSpan(new Span(10, 20, Area.Default));
            hf[0].AddSpan(new Span(25, 30, Area.Default));

            CompactHeightfield chf = new CompactHeightfield(hf, 2, 1);

            Assert.AreEqual(chf.Spans.Length, 2);

            Assert.AreEqual(chf.Spans[0].Minimum, 20);
            Assert.AreEqual(chf.Spans[0].Height, 5);

            Assert.AreEqual(chf.Spans[1].Minimum, 30);
            Assert.AreEqual(chf.Spans[1].Height, int.MaxValue);
        }
		public void ConvertSpans_TwoCells()
		{
			Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f);
			hf[0].AddSpan(new Span(10, 20, Area.Default));
			hf[0].AddSpan(new Span(25, 30, Area.Default));
			hf[1].AddSpan(new Span(5, 15, Area.Default));
			hf[1].AddSpan(new Span(25, 30, Area.Default));
			hf[1].AddSpan(new Span(40, 55, Area.Default));
			CompactHeightfield chf = new CompactHeightfield(hf, 2, 1);

			Assert.AreEqual(chf.Cells.Length, 4);

			Assert.AreEqual(chf.Cells[0].StartIndex, 0);
			Assert.AreEqual(chf.Cells[0].Count, 2);
			Assert.AreEqual(chf.Cells[1].StartIndex, 2);
			Assert.AreEqual(chf.Cells[1].Count, 3);
		}
		public void SetConnection_TwoCells()
		{
			Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.5f, 0.02f);
			hf[0].AddSpan(new Span(10, 20, Area.Default));
			hf[0].AddSpan(new Span(25, 30, Area.Default));
			hf[1].AddSpan(new Span(10, 21, Area.Default));
			hf[1].AddSpan(new Span(25, 30, Area.Default));
			CompactHeightfield chf = new CompactHeightfield(hf, 2, 1);

			Assert.IsTrue(chf.Spans[0].IsConnected(Direction.East));
			Assert.IsTrue(chf.Spans[1].IsConnected(Direction.East));
			Assert.IsTrue(chf.Spans[2].IsConnected(Direction.West));
			Assert.IsTrue(chf.Spans[3].IsConnected(Direction.West));

			Assert.AreEqual(chf.Spans[0].ConnectionEast, 0);
			Assert.AreEqual(chf.Spans[1].ConnectionEast, 1);
			Assert.AreEqual(chf.Spans[2].ConnectionWest, 0);
			Assert.AreEqual(chf.Spans[3].ConnectionWest, 1);
		}
Beispiel #6
0
        private void GenerateNavMesh()
        {
            Console.WriteLine("Generating NavMesh");

            Stopwatch sw = new Stopwatch();
            sw.Start();
            long prevMs = 0;
            try
            {
                //level.SetBoundingBoxOffset(new SVector3(settings.CellSize * 0.5f, settings.CellHeight * 0.5f, settings.CellSize * 0.5f));
                var levelTris = level.GetTriangles();
                var triEnumerable = TriangleEnumerable.FromTriangle(levelTris, 0, levelTris.Length);
                BBox3 bounds = triEnumerable.GetBoundingBox();

                heightfield = new Heightfield(bounds, settings);

                Console.WriteLine("Heightfield");
                Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                /*Area[] areas = AreaGenerator.From(triEnumerable, Area.Default)
                    .MarkAboveHeight(areaSettings.MaxLevelHeight, Area.Null)
                    .MarkBelowHeight(areaSettings.MinLevelHeight, Area.Null)
                    .MarkBelowSlope(areaSettings.MaxTriSlope, Area.Null)
                    .ToArray();
                heightfield.RasterizeTrianglesWithAreas(levelTris, areas);*/
                heightfield.RasterizeTriangles(levelTris, Area.Default);

                Console.WriteLine(" + Rasterization\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                Console.WriteLine(" + Filtering");
                prevMs = sw.ElapsedMilliseconds;

                heightfield.FilterLedgeSpans(settings.VoxelAgentHeight, settings.VoxelMaxClimb);

                Console.WriteLine("   + Ledge Spans\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                heightfield.FilterLowHangingWalkableObstacles(settings.VoxelMaxClimb);

                Console.WriteLine("   + Low Hanging Obstacles\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                heightfield.FilterWalkableLowHeightSpans(settings.VoxelAgentHeight);

                Console.WriteLine("   + Low Height Spans\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                compactHeightfield = new CompactHeightfield(heightfield, settings);

                Console.WriteLine("CompactHeightfield");
                Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                compactHeightfield.Erode(settings.VoxelAgentRadius);

                Console.WriteLine(" + Erosion\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                compactHeightfield.BuildDistanceField();

                Console.WriteLine(" + Distance Field\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                compactHeightfield.BuildRegions(0, settings.MinRegionSize, settings.MergedRegionSize);

                Console.WriteLine(" + Regions\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                Random r = new Random();
                regionColors = new Color4[compactHeightfield.MaxRegions];
                regionColors[0] = Color4.Black;
                for (int i = 1; i < regionColors.Length; i++)
                    regionColors[i] = new Color4((byte)r.Next(0, 255), (byte)r.Next(0, 255), (byte)r.Next(0, 255), 255);

                Console.WriteLine(" + Colors\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                contourSet = compactHeightfield.BuildContourSet(settings);

                Console.WriteLine("ContourSet");
                Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                polyMesh = new PolyMesh(contourSet, settings);

                Console.WriteLine("PolyMesh");
                Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                polyMeshDetail = new PolyMeshDetail(polyMesh, compactHeightfield, settings);

                Console.WriteLine("PolyMeshDetail");
                Console.WriteLine(" + Ctor\t\t\t\t" + (sw.ElapsedMilliseconds - prevMs).ToString("D3") + " ms");
                prevMs = sw.ElapsedMilliseconds;

                hasGenerated = true;

            }
            catch (Exception e)
            {
                if (!interceptExceptions)
                    throw;
                else
                    Console.WriteLine("Navmesh generation failed with exception:" + Environment.NewLine + e.ToString());
            }
            finally
            {
                sw.Stop();
            }

            if (hasGenerated)
            {
                try
                {
                    GeneratePathfinding();

                    //Pathfinding with multiple units
                    GenerateCrowd();
                }
                catch (Exception e)
                {
                    Console.WriteLine("Pathfinding generation failed with exception" + Environment.NewLine + e.ToString());
                    hasGenerated = false;
                }

                Label l = (Label)statusBar.FindChildByName("GenTime");
                l.Text = "Generation Time: " + sw.ElapsedMilliseconds + "ms";

                Console.WriteLine("Navmesh generated successfully in " + sw.ElapsedMilliseconds + "ms.");
                Console.WriteLine("Rasterized " + level.GetTriangles().Length + " triangles.");
                Console.WriteLine("Generated " + contourSet.Count + " regions.");
                Console.WriteLine("PolyMesh contains " + polyMesh.VertCount + " vertices in " + polyMesh.PolyCount + " polys.");
                Console.WriteLine("PolyMeshDetail contains " + polyMeshDetail.VertCount + " vertices and " + polyMeshDetail.TrisCount + " tris in " + polyMeshDetail.MeshCount + " meshes.");
            }
        }
        public void DistanceField_Simple_Success()
        {
            //Build a 3x3 heightfield
            Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), (float)(1.0f/3.0f), 0.02f);
            for (int i = 0; i < 9; i++)
            {
                hf[i].AddSpan(new Span(10, 20, Area.Default));
                hf[i].AddSpan(new Span(25, 30, Area.Default));
            }
            CompactHeightfield chf = new CompactHeightfield(hf, 2, 1);

            //make sure connections are set
            Assert.AreEqual(chf.Spans[0 * 2].ConnectionCount, 2); //corner
            Assert.AreEqual(chf.Spans[1 * 2].ConnectionCount, 3); //edge
            Assert.AreEqual(chf.Spans[2 * 2].ConnectionCount, 2); //corner
            Assert.AreEqual(chf.Spans[3 * 2].ConnectionCount, 3); //edge
            Assert.AreEqual(chf.Spans[4 * 2].ConnectionCount, 4); //center

            chf.BuildDistanceField();

            //check distance field values
            Assert.AreEqual(chf.MaxDistance, 2);

            //1st row
            Assert.AreEqual(chf.Distances[0 * 2], 0); //boundary
            Assert.AreEqual(chf.Distances[1 * 2], 0); //boundary
            Assert.AreEqual(chf.Distances[2 * 2], 0); //boundary

            //2nd row
            Assert.AreEqual(chf.Distances[3 * 2], 0); //boundary
            Assert.AreEqual(chf.Distances[4 * 2], 2); //center span
            Assert.AreEqual(chf.Distances[5 * 2], 0); //boundary
        }
        public void DistanceField_Medium_Success()
        {
            //Build a 5x5 heightfield
            Heightfield hf = new Heightfield(new BBox3(Vector3.Zero, Vector3.One), 0.2f, 0.02f);
            for (int i = 0; i < 25; i++)
            {
                hf[i].AddSpan(new Span(10, 20, Area.Default));
                hf[i].AddSpan(new Span(25, 30, Area.Default));
            }
            CompactHeightfield chf = new CompactHeightfield(hf, 2, 1);

            chf.BuildDistanceField();

            //Before box blur, MaxDistance is 4
            //After box blur, MaxDistance is 2
            Assert.AreEqual(chf.MaxDistance, 2);

            //1st row
            Assert.AreEqual(chf.Distances[0 * 2], 0); //boundary
            Assert.AreEqual(chf.Distances[4 * 2], 0); //boundary

            //2nd row
            Assert.AreEqual(chf.Distances[5 * 2], 0); //boundary
            Assert.AreEqual(chf.Distances[6 * 2], 2); //near boundary
            Assert.AreEqual(chf.Distances[7 * 2], 2); //near boundary
            Assert.AreEqual(chf.Distances[8 * 2], 2); //near boundary
            Assert.AreEqual(chf.Distances[9 * 2], 0); //boundary

            //3rd row
            Assert.AreEqual(chf.Distances[10 * 2], 0); //boundary
            Assert.AreEqual(chf.Distances[11 * 2], 2); //near boundary
            Assert.AreEqual(chf.Distances[12 * 2], 2); //center (box blurred distance is (2*8 + 5)/9)
            Assert.AreEqual(chf.Distances[13 * 2], 2); //near boundary
            Assert.AreEqual(chf.Distances[14 * 2], 0); //boundary
        }
Beispiel #9
0
		/// <summary>
		/// Initializes a new instance of the <see cref="PolyMeshDetail"/> class.
		/// </summary>
		/// <remarks>
		/// <see cref="PolyMeshDetail"/> uses a <see cref="CompactHeightfield"/> to add in details to a
		/// <see cref="PolyMesh"/>. This detail is triangulated into a new mesh and can be used to approximate height in the walkable
		/// areas of a scene.
		/// </remarks>
		/// <param name="mesh">The <see cref="PolyMesh"/>.</param>
		/// <param name="compactField">The <see cref="CompactHeightfield"/> used to add height detail.</param>
		/// <param name="sampleDist">The sampling distance.</param>
		/// <param name="sampleMaxError">The maximum sampling error allowed.</param>
		public PolyMeshDetail(PolyMesh mesh, CompactHeightfield compactField, float sampleDist, float sampleMaxError)
		{
			if (mesh.VertCount == 0 || mesh.PolyCount == 0)
				return;

			Vector3 origin = mesh.Bounds.Min;

			int maxhw = 0, maxhh = 0;

			BBox2i[] bounds = new BBox2i[mesh.PolyCount];
			Vector3[] poly = new Vector3[mesh.NumVertsPerPoly];

			var storedVertices = new List<Vector3>();
			var storedTriangles = new List<TriangleData>();

			//find max size for polygon area
			for (int i = 0; i < mesh.PolyCount; i++)
			{
				var p = mesh.Polys[i];

				int xmin = compactField.Width;
				int xmax = 0;
				int zmin = compactField.Length;
				int zmax = 0;

				for (int j = 0; j < mesh.NumVertsPerPoly; j++)
				{
					var pj = p.Vertices[j];
					if (pj == PolyMesh.NullId)
						break;

					var v = mesh.Verts[pj];

					xmin = Math.Min(xmin, v.X);
					xmax = Math.Max(xmax, v.X);
					zmin = Math.Min(zmin, v.Z);
					zmax = Math.Max(zmax, v.Z);
				}

				xmin = Math.Max(0, xmin - 1);
				xmax = Math.Min(compactField.Width, xmax + 1);
				zmin = Math.Max(0, zmin - 1);
				zmax = Math.Min(compactField.Length, zmax + 1);

				if (xmin >= xmax || zmin >= zmax)
					continue;

				maxhw = Math.Max(maxhw, xmax - xmin);
				maxhh = Math.Max(maxhh, zmax - zmin);

				bounds[i] = new BBox2i(xmin, zmin, xmax, zmax);
			}

			HeightPatch hp = new HeightPatch(0, 0, maxhw, maxhh);

			this.meshes = new MeshData[mesh.PolyCount];

			for (int i = 0; i < mesh.PolyCount; i++)
			{
				var p = mesh.Polys[i];

				//store polygon vertices for processing
				int npoly = 0;
				for (int j = 0; j < mesh.NumVertsPerPoly; j++)
				{
					int pvi = p.Vertices[j];
					if (pvi == PolyMesh.NullId)
						break;

					PolyVertex pv = mesh.Verts[pvi];
					Vector3 v = new Vector3(pv.X, pv.Y, pv.Z);
					v.X *= mesh.CellSize;
					v.Y *= mesh.CellHeight;
					v.Z *= mesh.CellSize;
					poly[j] = v;
					npoly++;
				}

				//get height data from area of polygon
				BBox2i bound = bounds[i];
				hp.Resize(bound.Min.X, bound.Min.Y, bound.Max.X - bound.Min.X, bound.Max.Y - bound.Min.Y);
				GetHeightData(compactField, p, npoly, mesh.Verts, mesh.BorderSize, hp);

				List<Vector3> tempVerts = new List<Vector3>();
				List<TriangleData> tempTris = new List<TriangleData>(128);
				List<EdgeInfo> edges = new List<EdgeInfo>(16);
				List<SamplingData> samples = new List<SamplingData>(128);
				BuildPolyDetail(poly, npoly, sampleDist, sampleMaxError, compactField, hp, tempVerts, tempTris, edges, samples);

				//more detail verts
				for (int j = 0; j < tempVerts.Count; j++)
				{
					Vector3 tv = tempVerts[j];

					Vector3 v;
					v.X = tv.X + origin.X;
					v.Y = tv.Y + origin.Y + compactField.CellHeight;
					v.Z = tv.Z + origin.Z;

					tempVerts[j] = v;
				}

				for (int j = 0; j < npoly; j++)
				{
					Vector3 po = poly[j];

					po.X += origin.X;
					po.Y += origin.Y;
					po.Z += origin.Z;

					poly[j] = po;
				}

				//save data
				this.meshes[i].VertexIndex = storedVertices.Count;
				this.meshes[i].VertexCount = tempVerts.Count;
				this.meshes[i].TriangleIndex = storedTriangles.Count;
				this.meshes[i].TriangleCount = tempTris.Count;

				//store vertices
				storedVertices.AddRange(tempVerts);
				
				//store triangles
				for (int j = 0; j < tempTris.Count; j++)
				{
					storedTriangles.Add(new TriangleData(tempTris[j], tempVerts, poly, npoly));
				}
			}

			this.verts = storedVertices.ToArray();
			this.tris = storedTriangles.ToArray();
		}
Beispiel #10
0
		/// <summary>
		/// Generate the PolyMeshDetail using the PolyMesh and HeightPatch
		/// </summary>
		/// <param name="polyMeshVerts">PolyMesh Vertex data</param>
		/// <param name="numMeshVerts">Number of PolyMesh vertices</param>
		/// <param name="sampleDist">Sampling distance</param>
		/// <param name="sampleMaxError">Maximum sampling error</param>
		/// <param name="compactField">THe compactHeightfield</param>
		/// <param name="hp">The heightPatch</param>
		/// <param name="verts">Detail verts</param>
		/// <param name="tris">Detail triangles</param>
		/// <param name="edges">The edge array</param>
		/// <param name="samples">The samples array</param>
		private void BuildPolyDetail(Vector3[] polyMeshVerts, int numMeshVerts, float sampleDist, float sampleMaxError, CompactHeightfield compactField, HeightPatch hp, List<Vector3> verts, List<TriangleData> tris, List<EdgeInfo> edges, List<SamplingData> samples)
		{
			const int MAX_VERTS = 127;
			const int MAX_TRIS = 255;
			const int MAX_VERTS_PER_EDGE = 32;
			Vector3[] edge = new Vector3[MAX_VERTS_PER_EDGE + 1];
			List<int> hull = new List<int>(MAX_VERTS);

			//fill up vertex array
			for (int i = 0; i < numMeshVerts; ++i)
				verts.Add(polyMeshVerts[i]);

			float cs = compactField.CellSize;
			float ics = 1.0f / cs;

			float minExtent = PolyMinExtent(polyMeshVerts);

			//tessellate outlines
			if (sampleDist > 0)
			{
				for (int i = 0, j = verts.Count - 1; i < verts.Count; j = i++)
				{
					Vector3 vi = verts[i];
					Vector3 vj = verts[j];
					bool swapped = false;

					//make sure order is correct, otherwise swap data
					if (Math.Abs(vj.X - vi.X) < 1E-06f)
					{
						if (vj.Z > vi.Z)
						{
							Vector3 temp = vj;
							vj = vi;
							vi = temp;
							swapped = true;
						}
					}
					else if (vj.X > vi.X)
					{
						Vector3 temp = vj;
						vj = vi;
						vi = temp;
						swapped = true;
					}

					//create samples along the edge
					Vector3 dv;
					Vector3.Subtract(ref vi, ref vj, out dv);
					float d = (float)Math.Sqrt(dv.X * dv.X + dv.Z * dv.Z);
					int nn = 1 + (int)Math.Floor(d / sampleDist);

					if (nn >= MAX_VERTS_PER_EDGE)
						nn = MAX_VERTS_PER_EDGE - 1;

					if (verts.Count + nn >= MAX_VERTS)
						nn = MAX_VERTS - 1 - verts.Count;

					for (int k = 0; k <= nn; k++)
					{
						float u = (float)k / (float)nn;
						Vector3 pos;

						Vector3 tmp;
						Vector3.Multiply(ref dv, u, out tmp);
						Vector3.Add(ref vj, ref tmp, out pos);

						pos.Y = GetHeight(pos, ics, compactField.CellHeight, hp) * compactField.CellHeight;

						edge[k] = pos;
					}

					//simplify samples
					int[] idx = new int[MAX_VERTS_PER_EDGE];
					idx[0] = 0;
					idx[1] = nn;
					int nidx = 2;

					for (int k = 0; k < nidx - 1;)
					{
						int a = idx[k];
						int b = idx[k + 1];
						Vector3 va = edge[a];
						Vector3 vb = edge[b];

						//find maximum deviation along segment
						float maxd = 0;
						int maxi = -1;
						for (int m = a + 1; m < b; m++)
						{
							float dev = Distance.PointToSegmentSquared(ref edge[m], ref va, ref vb);
							if (dev > maxd)
							{
								maxd = dev;
								maxi = m;
							}
						}

						if (maxi != -1 && maxd > (sampleMaxError * sampleMaxError))
						{
							//shift data to the right
							for (int m = nidx; m > k; m--)
								idx[m] = idx[m - 1];

							//set new value
							idx[k + 1] = maxi;
							nidx++;
						}
						else
						{
							k++;
						}
					}

					hull.Add(j);

					//add new vertices
					if (swapped)
					{
						for (int k = nidx - 2; k > 0; k--)
						{
							hull.Add(verts.Count);
							verts.Add(edge[idx[k]]);
						}
					}
					else
					{
						for (int k = 1; k < nidx - 1; k++)
						{
							hull.Add(verts.Count);
							verts.Add(edge[idx[k]]);
						}
					}
				}
			}

			//tesselate base mesh
			edges.Clear();
			tris.Clear();

			if (minExtent < sampleDist * 2)
			{
				TriangulateHull(verts, hull, tris);
				return;
			}

			TriangulateHull(verts, hull, tris);

			if (tris.Count == 0)
			{
				Console.WriteLine("Can't triangulate polygon, adding default data.");
				return;
			}

			if (sampleDist > 0)
			{
				//create sample locations
				BBox3 bounds = new BBox3();
				bounds.Min = polyMeshVerts[0];
				bounds.Max = polyMeshVerts[0];

				for (int i = 1; i < numMeshVerts; i++)
				{
					Vector3Extensions.ComponentMin(ref bounds.Min, ref polyMeshVerts[i], out bounds.Min);
					Vector3Extensions.ComponentMax(ref bounds.Max, ref polyMeshVerts[i], out bounds.Max); 
				}

				int x0 = (int)Math.Floor(bounds.Min.X / sampleDist);
				int x1 = (int)Math.Ceiling(bounds.Max.X / sampleDist);
				int z0 = (int)Math.Floor(bounds.Min.Z / sampleDist);
				int z1 = (int)Math.Ceiling(bounds.Max.Z / sampleDist);

				samples.Clear();

				for (int z = z0; z < z1; z++)
				{
					for (int x = x0; x < x1; x++)
					{
						Vector3 pt = new Vector3(x * sampleDist, (bounds.Max.Y + bounds.Min.Y) * 0.5f, z * sampleDist);

						//make sure samples aren't too close to edge
						if (Distance.PointToPolygonSquared(pt, polyMeshVerts, numMeshVerts) > -sampleDist * 0.5f)
							continue;

						SamplingData sd = new SamplingData(x, GetHeight(pt, ics, compactField.CellHeight, hp), z, false);
						samples.Add(sd);
					}
				}

				//added samples
				for (int iter = 0; iter < samples.Count; iter++)
				{
					if (verts.Count >= MAX_VERTS)
						break;

					//find sample with most error
					Vector3 bestPt = Vector3.Zero;
					float bestDistance = 0;
					int bestIndex = -1;

					for (int i = 0; i < samples.Count; i++)
					{
						SamplingData sd = samples[i];
						if (sd.IsSampled)
							continue;

						//jitter sample location to remove effects of bad triangulation
						Vector3 pt;
						pt.X = sd.X * sampleDist + GetJitterX(i) * compactField.CellSize * 0.1f;
						pt.Y = sd.Y * compactField.CellHeight;
						pt.Z = sd.Z * sampleDist + GetJitterY(i) * compactField.CellSize * 0.1f;
						float d = DistanceToTriMesh(pt, verts, tris);

						if (d < 0)
							continue;

						if (d > bestDistance)
						{
							bestDistance = d;
							bestIndex = i;
							bestPt = pt;
						}
					}

					if (bestDistance <= sampleMaxError || bestIndex == -1)
						break;

					SamplingData bsd = samples[bestIndex];
					bsd.IsSampled = true;
					samples[bestIndex] = bsd;

					verts.Add(bestPt);

					//create new triangulation
					edges.Clear();
					tris.Clear();
					DelaunayHull(verts, hull, tris, edges);
				}
			}

			int ntris = tris.Count;
			if (ntris > MAX_TRIS)
			{
				//TODO we're using lists... let the user have super detailed meshes?
				//Perhaps just a warning saying there's a lot of tris?
				//tris.RemoveRange(MAX_TRIS + 1, tris.Count - MAX_TRIS);
				//Console.WriteLine("WARNING: shrinking number of triangles.");
			}
		}
Beispiel #11
0
		/// <summary>
		/// Initializes a new instance of the <see cref="PolyMeshDetail"/> class.
		/// </summary>
		/// <param name="mesh">The <see cref="PolyMesh"/>.</param>
		/// <param name="compactField">The <see cref="CompactHeightfield"/> used to add height detail.</param>
		/// <param name="settings">The settings to build with.</param>
		public PolyMeshDetail(PolyMesh mesh, CompactHeightfield compactField, NavMeshGenerationSettings settings)
			: this(mesh, compactField, settings.SampleDistance, settings.MaxSampleError)
		{
		}
Beispiel #12
0
		/// <summary>
		/// Floodfill heightfield to get 2D height data, starting at vertex locations
		/// </summary>
		/// <param name="compactField">Original heightfield data</param>
		/// <param name="poly">Polygon in PolyMesh</param>
		/// <param name="polyCount">Number of vertices per polygon</param>
		/// <param name="verts">PolyMesh Vertices</param>
		/// <param name="borderSize">Heightfield border size</param>
		/// <param name="hp">HeightPatch which extracts heightfield data</param>
		/// <param name="stack">Temporary stack of CompactSpanReferences</param>
		private void GetHeightDataSeedsFromVertices(CompactHeightfield compactField, PolyMesh.Polygon poly, int polyCount, PolyVertex[] verts, int borderSize, HeightPatch hp, List<CompactSpanReference> stack)
		{
			hp.SetAll(0);

			//use poly vertices as seed points
			for (int j = 0; j < polyCount; j++)
			{
				var csr = new CompactSpanReference(0, 0, -1);
				int dmin = int.MaxValue;

				var v = verts[poly.Vertices[j]];

				for (int k = 0; k < 9; k++)
				{
					//get vertices and offset x and z coordinates depending on current drection
					int ax = v.X + VertexOffset[k * 2 + 0];
					int ay = v.Y;
					int az = v.Z + VertexOffset[k * 2 + 1];

					//skip if out of bounds
					if (ax < hp.X || ax >= hp.X + hp.Width || az < hp.Y || az >= hp.Y + hp.Length)
						continue;

					//get new cell
					CompactCell c = compactField.Cells[(az + borderSize) * compactField.Width + (ax + borderSize)];
					
					//loop through all the spans
					for (int i = c.StartIndex, end = c.StartIndex + c.Count; i < end; i++)
					{
						CompactSpan s = compactField.Spans[i];
						
						//find minimum y-distance
						int d = Math.Abs(ay - s.Minimum);
						if (d < dmin)
						{
							csr = new CompactSpanReference(ax, az, i);
							dmin = d;
						}
					}
				}

				//only add if something new found
				if (csr.Index != -1)
				{
					stack.Add(csr);
				}
			}

			//find center of polygon using flood fill
			int pcx = 0, pcz = 0;
			for (int j = 0; j < polyCount; j++)
			{
				var v = verts[poly.Vertices[j]];
				pcx += v.X;
				pcz += v.Z;
			}

			pcx /= polyCount;
			pcz /= polyCount;

			//stack groups 3 elements as one part
			foreach (var cell in stack)
			{
				int idx = (cell.Y - hp.Y) * hp.Width + (cell.X - hp.X);
				hp[idx] = 1;
			}

			//process the entire stack
			while (stack.Count > 0)
			{
				var cell = stack[stack.Count - 1];
				stack.RemoveAt(stack.Count - 1);

				//check if close to center of polygon
				if (Math.Abs(cell.X - pcx) <= 1 && Math.Abs(cell.Y - pcz) <= 1)
				{
					//clear the stack and add a new group
					stack.Clear();

					stack.Add(cell);
					break;
				}

				CompactSpan cs = compactField[cell];

				//check all four directions
				for (var dir = Direction.West; dir <= Direction.South; dir++)
				{
					//skip if disconnected
					if (!cs.IsConnected(dir))
						continue;

					//get neighbor
					int ax = cell.X + dir.GetHorizontalOffset();
					int ay = cell.Y + dir.GetVerticalOffset();

					//skip if out of bounds
					if (ax < hp.X || ax >= (hp.X + hp.Width) || ay < hp.Y || ay >= (hp.Y + hp.Length))
						continue;

					if (hp[(ay - hp.Y) * hp.Width + (ax - hp.X)] != 0)
						continue;

					//get the new index
					int ai = compactField.Cells[(ay + borderSize) * compactField.Width + (ax + borderSize)].StartIndex + CompactSpan.GetConnection(ref cs, dir);

					//save data
					int idx = (ay - hp.Y) * hp.Width + (ax - hp.X);
					hp[idx] = 1;

					//push to stack
					stack.Add(new CompactSpanReference(ax, ay, ai));
				}
			}

			//clear the heightpatch
			hp.Clear();

			//mark start locations
			for (int i = 0; i < stack.Count; i++)
			{
				var c = stack[i];

				//set new heightpatch data
				int idx = (c.Y - hp.Y) * hp.Width + (c.X - hp.X);
				CompactSpan cs = compactField.Spans[c.Index];
				hp[idx] = cs.Minimum;

				stack[i] = new CompactSpanReference(c.X + borderSize, c.Y + borderSize, c.Index);
			}
		}
Beispiel #13
0
		private void GetHeightData(CompactHeightfield compactField, PolyMesh.Polygon poly, int polyCount, PolyVertex[] verts, int borderSize, HeightPatch hp)
		{
			var stack = new List<CompactSpanReference>();
			bool empty = true;
			hp.Clear();

			for (int y = 0; y < hp.Length; y++)
			{
				int hy = hp.Y + y + borderSize;
				for (int x = 0; x < hp.Width; x++)
				{
					int hx = hp.X + x + borderSize;
					var cells = compactField.Cells[hy * compactField.Width + hx];
					for (int i = cells.StartIndex, end = cells.StartIndex + cells.Count; i < end; i++)
					{
						var span = compactField.Spans[i];

						if (span.Region == poly.RegionId)
						{
							hp[x, y] = span.Minimum;
							empty = false;

							bool border = false;
							for (var dir = Direction.West; dir <= Direction.South; dir++)
							{
								if (span.IsConnected(dir))
								{
									int ax = hx + dir.GetHorizontalOffset();
									int ay = hy + dir.GetVerticalOffset();
									int ai = compactField.Cells[ay * compactField.Width + ax].StartIndex + CompactSpan.GetConnection(ref span, dir);

									if (compactField.Spans[ai].Region != poly.RegionId)
									{
										border = true;
										break;
									}
								}
							}

							if (border)
								stack.Add(new CompactSpanReference(hx, hy, i));

							break;
						}
					}
				}
			}

			if (empty)
				GetHeightDataSeedsFromVertices(compactField, poly, polyCount, verts, borderSize, hp, stack);

			const int RetractSize = 256;
			int head = 0;

			while (head < stack.Count)
			{
				var cell = stack[head++];
				var cs = compactField[cell];

				if (head >= RetractSize)
				{
					head = 0;
					if (stack.Count > RetractSize)
					{
						for (int i = 0; i < stack.Count - RetractSize; i++)
							stack[i] = stack[i + RetractSize];
					}

					int targetSize = stack.Count % RetractSize;
					while (stack.Count > targetSize)
						stack.RemoveAt(stack.Count - 1);
				}

				//loop in all four directions
				for (var dir = Direction.West; dir <= Direction.South; dir++)
				{
					//skip
					if (!cs.IsConnected(dir))
						continue;

					int ax = cell.X + dir.GetHorizontalOffset();
					int ay = cell.Y + dir.GetVerticalOffset();
					int hx = ax - hp.X - borderSize;
					int hy = ay - hp.Y - borderSize;

					if (hx < 0 || hx >= hp.Width || hy < 0 || hy >= hp.Length)
						continue;

					//only continue if height is unset
					if (hp.IsSet(hy * hp.Width + hx))
						continue;

					//get new span
					int ai = compactField.Cells[ay * compactField.Width + ax].StartIndex + CompactSpan.GetConnection(ref cs, dir);
					CompactSpan ds = compactField.Spans[ai];

					hp[hx, hy] = ds.Minimum;

					stack.Add(new CompactSpanReference(ax, ay, ai));
				}
			}
		}