using CodeWalker.Core.GameFiles.FileTypes.Builders; using CodeWalker.GameFiles; using CodeWalker.World; using SharpDX; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using WeifenLuo.WinFormsUI.Docking; namespace CodeWalker.Project.Panels { public partial class GenerateNavMeshPanel : ProjectPanel { public ProjectForm ProjectForm { get; set; } public ProjectFile CurrentProjectFile { get; set; } public GenerateNavMeshPanel(ProjectForm projectForm) { ProjectForm = projectForm; InitializeComponent(); Tag = "GenerateNavMeshPanel"; if (ProjectForm?.WorldForm == null) { //could happen in some other startup mode - world form is required for this.. GenerateButton.Enabled = false; UpdateStatus("Unable to generate - World View not available!"); } } public void SetProject(ProjectFile project) { CurrentProjectFile = project; } private void GenerateButton_Click(object sender, EventArgs e) { var space = ProjectForm?.WorldForm?.Space; if (space == null) return; var gameFileCache = ProjectForm?.WorldForm?.GameFileCache; if (gameFileCache == null) return; Vector2 min = FloatUtil.ParseVector2String(MinTextBox.Text); Vector2 max = FloatUtil.ParseVector2String(MaxTextBox.Text); if (min == max) { MessageBox.Show("Unable to generate - No valid area was specified!\nMake sure Min and Max form a box around the area you want to generate the nav meshes for."); return; } if ((min.X < -6000) || (min.Y < -6000) || (max.X > 9000) || (max.Y > 9000))//it's over 9000 { if (MessageBox.Show("Warning: min/max goes outside the possible navmesh area - valid range is from -6000 to 9000 (X and Y).\nDo you want to continue anyway?", "Warning - specified area out of range", MessageBoxButtons.YesNo) != DialogResult.Yes) { return; } } //if (string.IsNullOrEmpty(ProjectForm?.CurrentProjectFile?.Filepath)) //{ // MessageBox.Show("Please save the current project first. Generated navmeshes will be placed in the project folder."); // return; //} GenerateButton.Enabled = false; float density = 0.5f; //distance between vertices for the initial grid //float clipdz = 0.5f; //any polygons with greater steepness should be removed //Vector2 vertexCounts = (max - min) / density; //int vertexCountX = (int)vertexCounts.X; //int vertexCountY = (int)vertexCounts.Y; //int vertexCountTot = vertexCountX * vertexCountY; var layers = new[] { true, false, false }; //collision layers to use int hitTestCount = 0; //statistic for number of hit tests done int hitCount = 0;//statistic for total ray hits int newCount = 0;//statistic for total new control vertices Task.Run(() => { //find vertices in one world cell at a time, by raycasting in a grid pattern. //then filter those based on polygon slope deltas (and materials) to reduce the detail. //then add the generated verts for the cell into a master quadtree/bvh/grid //after all verts are generated, do voronoi tessellation with those to generate the nav polys. //finally, remove any polys that are steeper than the threshold. var vgrid = new VertexGrid(); var vert = new GenVertex(); var builder = new YnvBuilder(); var polys = new List(); var vertexCountXY = (max - min) / density; int vertexCountX = (int)vertexCountXY.X+1; int vertexCountY = (int)vertexCountXY.Y+1; //int vertexCountTot = vertexCountX * vertexCountY; vgrid.BeginGrid(vertexCountX, vertexCountY); Ray ray = new Ray(Vector3.Zero, new Vector3(0, 0, -1));//for casting with UpdateStatus("Loading YBNs..."); var bmin = new Vector3(min, 0); var bmax = new Vector3(max, 0); var boundslist = space.BoundsStore.GetItems(ref bmin, ref bmax); //pre-warm the bounds cache for this area, and find the min/max Z foreach (var boundsitem in boundslist) { YbnFile ybn = gameFileCache.GetYbn(boundsitem.Name); if (ybn == null) { continue; } //ybn not found? if (!ybn.Loaded) //ybn not loaded yet... { UpdateStatus("Loading ybn: " + boundsitem.Name.ToString() + " ..."); int waitCount = 0; while (!ybn.Loaded) { waitCount++; if (waitCount > 10000) { UpdateStatus("Timeout waiting for ybn " + boundsitem.Name.ToString() + " to load!"); Thread.Sleep(1000); //just to let the message display for a second... break; } Thread.Sleep(20);//~50fps should be fine ybn = gameFileCache.GetYbn(boundsitem.Name); //try queue it again.. } } if (ybn.Loaded && (ybn.Bounds != null)) { bmin.Z = Math.Min(bmin.Z, ybn.Bounds.BoxMin.Z); bmax.Z = Math.Max(bmax.Z, ybn.Bounds.BoxMax.Z); } } //ray-cast each XY vertex position, and find the height and surface from ybn's //continue casting down to find more surfaces... UpdateStatus("Processing..."); for (int vx = 0; vx < vertexCountX; vx++) { for (int vy = 0; vy < vertexCountY; vy++) { vgrid.BeginCell(vx, vy); var vcoffset = new Vector3(vx, vy, 0) * density; ray.Position = bmin + vcoffset; ray.Position.Z = bmax.Z + 1.0f;//start the ray at the top of the cell var intres = space.RayIntersect(ray, float.MaxValue, layers); hitTestCount++; while (intres.Hit)// && (intres.HitDist > 0)) { if (intres.HitDist > 0) { hitCount++; vert.Position = intres.Position; vert.Normal = intres.Normal; vert.Material = intres.Material.Type; vert.PolyFlags = (ushort)intres.Material.Flags; vert.PrevIDX = -1; vert.PrevIDY = -1; vert.NextIDX = -1; vert.NextIDY = -1; vert.CompPrevX = false; vert.CompPrevY = false; vert.CompNextX = false; vert.CompNextY = false; vert.PolyID = -1; vgrid.AddVertex(ref vert); if (vgrid.CurVertexCount > 15) //too many hits? { break; } } //continue down until no more hits..... step by 3m ray.Position.Z = intres.Position.Z - 3.0f; intres = space.RayIntersect(ray, float.MaxValue, layers); } vgrid.EndCell(vx, vy); } } vgrid.EndGrid(); //build vertex array vgrid.ConnectVertices(); var genPolys = vgrid.GenPolys2(); polys.AddRange(genPolys); newCount += genPolys.Count; //try merge generated polys into bigger ones, while keeping convex! UpdateStatus("Building edge dictionary..."); var edgeDict = new Dictionary(); var tryGetEdge = new Func((v1, v2) => { var key1 = new GenEdgeKey(v1, v2); var key2 = new GenEdgeKey(v2, v1); GenEdge edge = null; if (edgeDict.TryGetValue(key1, out edge) || edgeDict.TryGetValue(key2, out edge)) { return edge; } return null; }); var tryRemoveEdge = new Action((v1, v2) => { var key1 = new GenEdgeKey(v1, v2); var key2 = new GenEdgeKey(v2, v1); edgeDict.Remove(key1); edgeDict.Remove(key2); }); var buildEdgeDict = new Action(() => { for (int p = 0; p < polys.Count; p++) //build edge dict { var poly = polys[p]; poly.Index = p; for (int i = 0; i < poly.Vertices.Length; i++) { var ip = (i + 1) % poly.Vertices.Length; var edge = tryGetEdge(poly.Vertices[i], poly.Vertices[ip]); if (edge != null) { if (edge.Poly2 != null) { } //edge already assigned a second poly! shouldn't happen... edge.Poly2 = poly; edge.EdgeIndex2 = i; } else { var key = new GenEdgeKey(poly.Vertices[i], poly.Vertices[ip]); edge = new GenEdge(poly, i); edgeDict[key] = edge; } } } }); buildEdgeDict(); UpdateStatus("Merging polygons..."); float plthresh = 0.3f;//threshold for plane dist test float dpthresh = 0.75f;//threshold for plane normals test float dthresh = 6.0f;//absolute distance thresh for (int p = 0; p < polys.Count; p++) { //UpdateStatus("Merging polygons... (" + p.ToString() + "/" + polys.Count.ToString() + ")"); var poly = polys[p]; if (poly == null) continue; if (poly.Merged) continue; var p1cnt = poly.Vertices.Length; if (p1cnt < 3) continue; var vplane = new Plane(poly.Vertices[0], poly.Normal); var polycenter = poly.GetCenter(); for (int i = 0; i < poly.Vertices.Length; i++) { var ip = (i + 1) % poly.Vertices.Length; var eind1 = i; var edge = tryGetEdge(poly.Vertices[i], poly.Vertices[ip]); if (edge == null) continue; var poly2 = edge.Poly1; var eind2 = edge.EdgeIndex1; if (poly2 == poly) { poly2 = edge.Poly2; eind2 = edge.EdgeIndex2; } if (poly2 == poly) continue;//can't merge with itself! redundant edges/verts... if (poly2 == null) continue; if (poly2.Merged) continue;//new merged poly will get checked later.. if (poly.Material.Index != poly2.Material.Index) continue; if (poly.PolyFlags != poly2.PolyFlags) continue; var poly2center = poly2.GetCenter(); var npdist = Math.Abs(Plane.DotCoordinate(vplane, poly2center)); if (npdist > plthresh) continue; var dpval = Vector3.Dot(poly.Normal, poly2.Normal); if (dpval < dpthresh) continue; var dist = (polycenter - poly2center).Length(); if (dist > dthresh) continue; //if we got here, can merge these 2 polys.... var newverts = new List(); //add verts from poly1 from 0 to poly1 edge index (ip) //add verts from poly2 from poly2 edge index+2 to poly2 edge index (wrap/mod!) //add verts from poly1 from poly1 edge index+2 to last var p2cnt = poly2.Vertices.Length; var l2beg = (eind2 + 2) % p2cnt; var l2end = eind2; if (l2end < l2beg) l2end += p2cnt; var l1beg = (eind1 + 2); if (l1beg > p1cnt) l2end--;//don't add the first vertex again in this case! for (int j = 0; j <= eind1; j++) newverts.Add(poly.Vertices[j]); for (int j = l2beg; j <= l2end; j++) newverts.Add(poly2.Vertices[j % p2cnt]); for (int j = l1beg; j < p1cnt; j++) newverts.Add(poly.Vertices[j]); var varr = newverts.ToArray(); var remredun = true; while (remredun) { remredun = false; newverts.Clear(); // remove redundant edges! for (int j = 0; j < varr.Length; j++) { var j0 = j - 1; if (j0 < 0) j0 += varr.Length; var j2 = j + 1; j2 = j2 % varr.Length; var v0 = varr[j0]; var v1 = varr[j]; var v2 = varr[j2]; if (v0 == v2) { if (j2 > j) { j = j2; } else { if (j == varr.Length - 1) { newverts = newverts.GetRange(0, newverts.Count - 1); } else { } j = varr.Length; } remredun = true; } else { newverts.Add(v1); } } varr = newverts.ToArray(); if (remredun) { } } var newpoly = new GenPoly(newverts.ToArray(), poly); newpoly.Index = polys.Count; polys.Add(newpoly);//try merge this poly again later... //for all the edges in this new poly, need to update all the values!!! polys and indices! for (int j = 0; j < newpoly.Vertices.Length; j++) { var jp = (j + 1) % newpoly.Vertices.Length; var v = newpoly.Vertices[j]; var vp = newpoly.Vertices[jp]; var tedge = tryGetEdge(v, vp); if (tedge == null) { continue; }//shouldn't happen.. if (tedge.Poly1 == poly) { tedge.Poly1 = newpoly; tedge.EdgeIndex1 = j; } if (tedge.Poly2 == poly) { tedge.Poly2 = newpoly; tedge.EdgeIndex2 = j; } if (tedge.Poly1 == poly2) { tedge.Poly1 = newpoly; tedge.EdgeIndex1 = j; } if (tedge.Poly2 == poly2) { tedge.Poly2 = newpoly; tedge.EdgeIndex2 = j; } if (tedge.Poly1 == tedge.Poly2) { } //why does this happen..? probably when an edge can't be removed due to an enclosed poly } //tryRemoveEdge(poly.Vertices[i], poly.Vertices[ip]); polys[p] = null;//free up some memory..? polys[poly2.Index] = null; poly.Merged = true; poly2.Merged = true; break;//go to the next poly: don't do more than 1 merge at a time.. } } var mergedPolys = new List(); foreach (var poly in polys) { if (poly == null) continue; if (poly.Merged) continue; mergedPolys.Add(poly); } polys = mergedPolys; UpdateStatus("Merging edges..."); edgeDict = new Dictionary(); buildEdgeDict(); float dpthresh1 = 0.5f; float dpthresh2 = 0.7f; //try preserve shape more when not attached foreach (var poly in polys) { if (poly?.Vertices == null) continue; if (poly.Vertices.Length < 5) continue; for (int i = 1; i < poly.Vertices.Length; i++) { var ni = i - 1; var edge0 = tryGetEdge(poly.Vertices[ni], poly.Vertices[i]); if (edge0 == null) { continue; }//really shouldn't happen var poly0 = (edge0.Poly1 != poly) ? edge0.Poly1 : edge0.Poly2; var vert0 = poly.Vertices[ni]; var ip = (i + 1) % poly.Vertices.Length; var ip2 = (i + 2) % poly.Vertices.Length; var edge1 = tryGetEdge(poly.Vertices[i], poly.Vertices[ip]); if (edge1 == null) { continue; }//really shouldn't happen var poly1 = (edge1.Poly1 != poly) ? edge1.Poly1 : edge1.Poly2; var vert1 = poly.Vertices[ip]; var verti = poly.Vertices[i]; var vert2 = poly.Vertices[ip2]; var dp = Vector3.Dot(Vector3.Normalize(verti - vert0), Vector3.Normalize(vert2 - verti)); var dp2 = Vector3.Dot(Vector3.Normalize(verti - vert0), Vector3.Normalize(vert1 - verti)); var usedpthresh = ((poly0 == null) || (poly0 == poly)) ? dpthresh2 : dpthresh1; if ((poly0 != poly1) || (dp < usedpthresh) || (dp2 < -0.05))//can't merge, move on to next edge { continue; } if ((poly0 != null) && (poly0.Vertices.Length < 5)) { continue; } //remove the relevant vertex from both polys, and start again for this poly (reset i to 1) poly.RemoveVertex(verti); poly0?.RemoveVertex(verti);//if poly0==poly, remove same vertex twice? //remove merged edges from edge dict, and add new edge to it tryRemoveEdge(vert0, verti); tryRemoveEdge(verti, vert1); var key = new GenEdgeKey(vert0, vert1); var edge = new GenEdge(poly, i-1); edge.Poly2 = poly0; edge.EdgeIndex2 = poly0?.FindVertex(vert0) ?? -1; //(edge0.Poly2 != poly0) ? edge0.EdgeIndex1 : edge0.EdgeIndex2; edgeDict[key] = edge; i = 0;//will be incremented to 1 before next loop if (poly.Vertices.Length < 5) break;//don't make polys disappear! shouldn't happen anyway } } UpdateStatus("Convexifying polygons..."); mergedPolys = new List(); var getAngle = new Func((poly, i1, i2) => { var edge0 = poly.Vertices[i2] - poly.Vertices[i1]; return (float)Math.Atan2(edge0.Y, edge0.X); }); var getAngleDiff = new Func((a1, a2) => { var angldiff = a2 - a1; if (angldiff > Math.PI) angldiff -= (float)(Math.PI * 2); if (angldiff < -Math.PI) angldiff += (float)(Math.PI * 2); return angldiff; }); var findInflection = new Func((poly, starti) => { var vcnt = poly.Vertices.Length; var i0 = starti % vcnt; var i1 = (i0 + 1) % vcnt; var angl0 = getAngle(poly, i0, i1); var curangl = angl0; for (int i = starti+1; i <= vcnt; i++) { i0 = i % vcnt; i1 = (i0 + 1) % vcnt; angl0 = getAngle(poly, i0, i1); var angldiff = getAngleDiff(curangl, angl0); if (angldiff < 0) { return i0; } curangl = angl0; } return -1; }); var findIntersection = new Func((poly, i0, i1) => { var vcnt = poly.Vertices.Length; var v0 = poly.Vertices[i0]; var v1 = poly.Vertices[i1]; var minx0 = Math.Min(v0.X, v1.X); var miny0 = Math.Min(v0.Y, v1.Y); var maxx0 = Math.Max(v0.X, v1.X); var maxy0 = Math.Max(v0.Y, v1.Y); for (int i = 1; i < vcnt; i++) { var i2 = (i + i0) % vcnt; var i3 = (i2 + 1) % vcnt; if (i3 == i1) break; var v2 = poly.Vertices[i2]; var v3 = poly.Vertices[i3]; if ((v0 == v2) || (v0 == v3) || (v1 == v2) || (v1 == v3)) continue; //don't test if sharing a vertex. //https://rosettacode.org/wiki/Find_the_intersection_of_two_lines float a1 = v1.Y - v0.Y; float b1 = v0.X - v1.X; float c1 = a1 * v0.X + b1 * v0.Y; float a2 = v3.Y - v2.Y; float b2 = v2.X - v3.X; float c2 = a2 * v2.X + b2 * v2.Y; float delta = a1 * b2 - a2 * b1; if (delta != 0) { var deltai = 1.0f / delta; var vix = (b2 * c1 - b1 * c2) * deltai; var viy = (a1 * c2 - a2 * c1) * deltai; var minx1 = Math.Min(v2.X, v3.X); var miny1 = Math.Min(v2.Y, v3.Y); var maxx1 = Math.Max(v2.X, v3.X); var maxy1 = Math.Max(v2.Y, v3.Y); if ((vix >= minx0) && (vix >= minx1) && (vix <= maxx0) && (vix <= maxx1) && (viy >= miny0) && (viy >= miny1) && (viy <= maxy0) && (viy <= maxy1)) { return i2; } } } return -1; }); var findConvexSplit = new Func((poly, starti) => { var vcnt = poly.Vertices.Length; //step backwards to find a valid split var i0 = starti - 1; if (i0 < 0) i0 += vcnt; var curangl = getAngle(poly, i0, starti); var prevangl = curangl; var iok = starti - 2; if (iok < 0) iok += vcnt; var anyok = false; for (int i = -2; i >= -vcnt; i--) { var i1 = i + starti; if (i1 < 0) i1 += vcnt; //i1 = i1 % vcnt; var angl0 = getAngle(poly, starti, i1); var angldiff0 = getAngleDiff(curangl, angl0); if (angldiff0 < 0) { break;//split line would not be convex at starti } var i2 = (i1 + 1) % vcnt; var angl1 = getAngle(poly, i1, i2); var angldiff1 = getAngleDiff(angl0, angl1); if (angldiff1 < 0) { break;//split line would not be convex at i1 } var angl2 = getAngle(poly, i1, i2); var angldiff2 = getAngleDiff(angl2, prevangl); if (angldiff2 < 0) { break;//this step back is not convex } var inti = findIntersection(poly, starti, i1); if (inti >= 0) { break;//split line intersects a poly edge! } prevangl = angl2; anyok = true; iok = i1; } if (anyok) { return iok; } //couldn't split by stepping backwards... so try split by stepping forwards! i0 = (starti + 1) % vcnt; curangl = getAngle(poly, starti, i0); prevangl = curangl; iok = (starti + 2) % vcnt; for (int i = 2; i <= vcnt; i++) { var i1 = (i + starti) % vcnt; var angl0 = getAngle(poly, i1, starti); var angldiff0 = getAngleDiff(angl0, curangl); if (angldiff0 < 0) { break;//split line would not be convex at starti } var i2 = (i1 - 1); if (i2 < 0) i2 += vcnt; var angl1 = getAngle(poly, i2, i1); var angldiff1 = getAngleDiff(angl1, angl0); if (angldiff1 < 0) { break;//split line would not be convex at i1 } var angl2 = getAngle(poly, i2, i1); var angldiff2 = getAngleDiff(prevangl, angl2); if (angldiff2 < 0) { break;//this step forward is not convex.. } var inti = findIntersection(poly, i1, starti); if (inti >= 0) { break;//split line intersects poly edge! } prevangl = angl2; anyok = true; iok = i1; } if (anyok) { return iok | 0x40000000;//set this flag to indicate polys got switched } //can't go either way... what now? { } return -1; }); foreach (var poly in polys) { if (poly?.Vertices == null) continue; var infi = findInflection(poly, 0); var infi1 = infi; //bool split = false; while (infi >= 0) { //split = true; var convi = findConvexSplit(poly, infi); if (convi >= 0) { var flag = 0x40000000; var reversed = (convi & flag) == flag; convi = convi & 0x3FFFFFFF;//mask out that flag (don't care about sign bit) //make a new poly, starting at convi and ending at spliti var newverts = new List(); var vcnt = poly.Vertices.Length; var endi = infi; if (endi < convi) endi += vcnt; for (int i = convi; i <= endi; i++) { var i0 = i % vcnt; newverts.Add(poly.Vertices[i0]); } var varr1 = newverts.ToArray(); //remove the clipped vertices from the current poly newverts.Clear(); if (convi < endi) convi += vcnt; for (int i = endi; i <= convi; i++) { var i0 = i % vcnt; newverts.Add(poly.Vertices[i0]); } var varr2 = newverts.ToArray(); var newpoly = new GenPoly((reversed ? varr2 : varr1), poly); newpoly.Index = mergedPolys.Count; mergedPolys.Add(newpoly); poly.Vertices = (reversed ? varr1 : varr2); infi = findInflection(poly, 0); infi1 = infi; } else { //couldn't split at this inflection point, move on to the next... var infi2 = findInflection(poly, infi); if (infi2 != infi1) { infi = infi2; } else { infi = -1;//don't get stuck in the loop! } } } //if (split) continue; //else //{ } //poly is already convex.. poly.Index = mergedPolys.Count; mergedPolys.Add(poly); } polys = mergedPolys; edgeDict = new Dictionary(); buildEdgeDict(); newCount = polys.Count; UpdateStatus("Building YNVs..."); foreach (var poly in polys) { if (poly.Vertices == null) continue; var ypoly = builder.AddPoly(poly.Vertices); if (ypoly == null) { continue; } //TODO: add poly edges! ypoly.B02_IsFootpath = (poly.Material.Index == 1); ypoly.B18_IsRoad = (poly.Material.Index == 4);//4,5,6 } var ynvs = builder.Build(false);//todo:vehicles! UpdateStatus("Creating YNV files..."); var path = ProjectForm.CurrentProjectFile?.GetFullFilePath("navmeshes") + "\\"; foreach (var ynv in ynvs) { var bytes = ynv.Save(); var fpath = path + ynv.Name + ".ynv"; //File.WriteAllBytes(fpath, bytes); YnvFile nynv = new YnvFile(); nynv.RpfFileEntry = new RpfResourceFileEntry(); nynv.RpfFileEntry.Name = ynv.Name + ".ynv"; nynv.FilePath = fpath; nynv.Name = ynv.RpfFileEntry.Name; nynv.Load(bytes); ProjectForm.Invoke((MethodInvoker)delegate { ProjectForm.AddYnvToProject(nynv); }); } var statf = "{0} hit tests, {1} hits, {2} new polys"; var stats = string.Format(statf, hitTestCount, hitCount, newCount); UpdateStatus("Process complete. " + stats); GenerateComplete(); }); } private struct GenVertex { public Vector3 Position; public Vector3 Normal; public BoundsMaterialType Material; public ushort PolyFlags; public int PrevIDX; public int PrevIDY; public int NextIDX; public int NextIDY; public bool CompPrevX; public bool CompPrevY; public bool CompNextX; public bool CompNextY; public int PolyID; } private struct GenEdgeKey { public Vector3 V1; public Vector3 V2; public GenEdgeKey(Vector3 v1, Vector3 v2) { V1 = v1; V2 = v2; } //public int V1X; //public int V1Y; //public int V1Z; //public int V2X; //public int V2Y; //public int V2Z; //public GenEdgeKey(Vector3 v1, Vector3 v2) //{ // V1X = (int)(v1.X * 100); // V1Y = (int)(v1.Y * 100); // V1Z = (int)(v1.Z * 100); // V2X = (int)(v2.X * 100); // V2Y = (int)(v2.Y * 100); // V2Z = (int)(v2.Z * 100); //} } private class GenEdge { public GenPoly Poly1; public GenPoly Poly2; public int EdgeIndex1; public int EdgeIndex2; public GenEdge(GenPoly p1, int e1) { Poly1 = p1; EdgeIndex1 = e1; } public GenEdge(GenPoly p1, GenPoly p2, int e1, int e2) { Poly1 = p1; Poly2 = p2; EdgeIndex1 = e1; EdgeIndex2 = e2; } } private class GenPoly { public int Index; public Vector3 Normal; public BoundsMaterialType Material; public ushort PolyFlags; public int[] CornerIndices; public Vector3[] Vertices; //public GenEdge[] Edges; public bool Merged = false; public GenPoly() { } public GenPoly(Vector3[] verts, ref GenVertex vert) { Vertices = verts; Normal = vert.Normal; Material = vert.Material; PolyFlags = vert.PolyFlags; } public GenPoly(Vector3[] verts, GenPoly orig) { Vertices = verts; Normal = orig.Normal; Material = orig.Material; PolyFlags = orig.PolyFlags; } public Vector3 GetCenter() { var c = Vector3.Zero; if (Vertices?.Length > 0) { for (int i = 0; i < Vertices.Length; i++) { c += Vertices[i]; } c /= Vertices.Length; } return c; } public int FindVertex(Vector3 v) { if (Vertices != null) { for (int i = 0; i < Vertices.Length; i++) { if (Vertices[i] == v) return i; } } return -1; } public void RemoveVertex(Vector3 v) { var newverts = new List(); bool removed = false; if (Vertices != null) { for (int i = 0; i < Vertices.Length; i++) { if (Vertices[i] == v) { removed = true; } else { newverts.Add(Vertices[i]); } } } if (removed) { Vertices = newverts.ToArray(); } else { }//probably shouldn't happen } } private class VertexGrid { public List VertexList = new List(); public GenVertex[] Vertices; public int[,] VertexOffsets; public int[,] VertexCounts; public int VertexCountX; public int VertexCountY; public int CurVertexCount; private List CornersB = new List(); private List CornersT = new List(); private List VerticesB = new List(); private List VerticesT = new List(); public void BeginGrid(int vertexCountX, int vertexCountY) { VertexList.Clear(); Vertices = null; VertexOffsets = new int[vertexCountX, vertexCountY]; VertexCounts = new int[vertexCountX, vertexCountY]; VertexCountX = vertexCountX; VertexCountY = vertexCountY; } public void EndGrid() { Vertices = VertexList.ToArray(); } public void BeginCell(int x, int y) { VertexOffsets[x, y] = VertexList.Count; CurVertexCount = 0; } public void EndCell(int x, int y) { VertexCounts[x, y] = CurVertexCount; } public void AddVertex(ref GenVertex v) { VertexList.Add(v); CurVertexCount++; } public int FindVertex(int x, int y, float z, float thresh) { //return the index of the closest vertex in the x,y cell that is within the Z threshold int offset = VertexOffsets[x, y]; int count = VertexCounts[x, y]; int lasti = offset + count; float minz = float.MaxValue; int mini = -1; for (int i = offset; i < lasti; i++) { float vz = Vertices[i].Position.Z; float dz = Math.Abs(vz - z); if ((dz < thresh) && (dz < minz)) { minz = dz; mini = i; } } return mini; } public bool CompareVertexTypes(int i1, int i2) { if (Vertices[i1].Material.Index != Vertices[i2].Material.Index) return false; if (Vertices[i1].PolyFlags != Vertices[i2].PolyFlags) return false; return true; } public void ConnectVertices() { var connectThresh = 0.4f; var density = 0.5f;//to match vertex density (x/y distance) for (int vx = 0; vx < VertexCountX; vx++) { int px = vx - 1; for (int vy = 0; vy < VertexCountY; vy++) { int py = vy - 1; int imin = VertexOffsets[vx, vy]; int imax = VertexCounts[vx, vy] + imin; for (int i = imin; i < imax; i++) { var vz = Vertices[i].Position.Z; var vn = Vertices[i].Normal; var vxz = vz + (vn.X / Math.Max(vn.Z, 1e-5f)) * density; var vyz = vz + (vn.Y / Math.Max(vn.Z, 1e-5f)) * density; var prevIDX = (px < 0) ? -1 : FindVertex(px, vy, vxz, connectThresh); var prevIDY = (py < 0) ? -1 : FindVertex(vx, py, vyz, connectThresh); var compPrevX = (prevIDX < 0) ? false : CompareVertexTypes(i, prevIDX); var compPrevY = (prevIDY < 0) ? false : CompareVertexTypes(i, prevIDY); Vertices[i].PrevIDX = prevIDX; Vertices[i].PrevIDY = prevIDY; Vertices[i].CompPrevX = compPrevX; Vertices[i].CompPrevY = compPrevY; if (prevIDX >= 0) { Vertices[prevIDX].NextIDX = i; Vertices[prevIDX].CompNextX = compPrevX; } if (prevIDY >= 0) { Vertices[prevIDY].NextIDY = i; Vertices[prevIDY].CompNextY = compPrevY; } } } } } public List GenPolys() { List polys = new List(); //find new polygon edges and assign grid vertices for (int vx = 0; vx < VertexCountX; vx++) { for (int vy = 0; vy < VertexCountY; vy++) { int imin = VertexOffsets[vx, vy]; int imax = VertexCounts[vx, vy] + imin; for (int i = imin; i < imax; i++) { if (Vertices[i].PolyID >= 0) continue; //already assigned if ((Vertices[i].PrevIDX < 0) && (Vertices[i].PrevIDY < 0) && (Vertices[i].NextIDX < 0) && (Vertices[i].NextIDY < 0)) continue; //(not connected to anything) //if (!(Vertices[i].CompPrevX || Vertices[i].CompPrevY || Vertices[i].CompNextX || Vertices[i].CompNextY)) //continue; //all joins are different - discard this vertex GenPoly poly = new GenPoly(); //start a new poly poly.Index = polys.Count; poly.Normal = Vertices[i].Normal; poly.Material = Vertices[i].Material; poly.PolyFlags = Vertices[i].PolyFlags; //polys.Add(poly); //poly.AddGenVert(i); //Vertices[i].PolyID = poly.Index; Plane vplane = new Plane(Vertices[i].Position, Vertices[i].Normal); float plthresh = 0.25f; //threshold for plane dist test int dpx = FindPolyEdgeDist(ref vplane, plthresh, i, 0); int dpy = FindPolyEdgeDist(ref vplane, plthresh, i, 1); int dnx = FindPolyEdgeDist(ref vplane, plthresh, i, 2); int dny = FindPolyEdgeDist(ref vplane, plthresh, i, 3); bool addpoly = true; int qnx = 0, qny = 0, qpy = 0, qdir = 0; if ((dpx == 0) && (dpy == 0) && (dnx == 0) && (dny == 0)) { //single vertex poly... connect to something else? currently remove addpoly = false; } else if ((dpx >= dnx) && (dpx >= dpy) && (dpx >= dny)) { //dpx is largest, move along -X (dpx, dpy, dny, 0) qnx = dpx; qny = dpy; qpy = dny; qdir = 0; } else if ((dpy >= dnx) && (dpy >= dny)) { //dpy is largest, move along -Y (dpy, dnx, dpx, 1) qnx = dpy; qny = dnx; qpy = dpx; qdir = 1; } else if ((dnx >= dny)) { //dnx is largest, move along +X (dnx, dny, dpy, 2) qnx = dnx; qny = dny; qpy = dpy; qdir = 2; } else { //dny is largest, move along +Y (dny, dpx, dnx, 3) qnx = dny; qny = dpx; qpy = dnx; qdir = 3; } if (addpoly) { AssignVertices2(ref vplane, plthresh, i, qnx, qny, qpy, qdir, poly); if (poly.CornerIndices?.Length > 2) { polys.Add(poly); } } { //if (dnx > 0) //can move along +X //{ // AssignVertices(ref vplane, plthresh, i, dnx, dny, dpy, 2, poly); //} //else if (dny > 0) //can move along +Y //{ // AssignVertices(ref vplane, plthresh, i, dny, dpx, dnx, 3, poly); //} //else if (dpx > 0) //can move along -X //{ // AssignVertices(ref vplane, plthresh, i, dpx, dpy, dny, 0, poly); //} //else if (dpy > 0) //can move along -Y //{ // AssignVertices(ref vplane, plthresh, i, dpy, dnx, dpx, 1, poly); //} //else //single vertex poly... connected to something else //{ // addpolys = false; //} //if (addpolys) //{ // polys.Add(poly); //} } } } } //create corner vertex vectors and edges for the new polys foreach (var poly in polys) { if (poly.CornerIndices == null) continue; if (poly.CornerIndices.Length < 3) continue; var verts = new Vector3[poly.CornerIndices.Length]; for (int i = 0; i < poly.CornerIndices.Length; i++) { int id = poly.CornerIndices[i]; verts[i] = Vertices[id].Position;//TODO: find actual corners } poly.Vertices = verts; } return polys; } private void AssignVertices(ref Plane vpl, float plt, int i, int dnx, int dny, int dpy, int dir, GenPoly poly) { int pid = poly.Index; int qi = i; int maxdnx = Math.Min(dnx, 40); int maxdpy = 50;// dpy;// int maxdny = 50;// dny;// int cdpy = dpy; int cdny = dny; int vertexCountP = 0; int vertexCountN = 0; int lastqx = 0; int lastqi = i; CornersB.Clear(); CornersT.Clear(); int dirpy, dirny; switch (dir) //lookup perpendicular directions { default: case 0: dirpy = 3; dirny = 1; break; case 1: dirpy = 0; dirny = 2; break; case 2: dirpy = 1; dirny = 3; break; case 3: dirpy = 2; dirny = 0; break; } for (int qx = 0; qx <= maxdnx; qx++)//go along the row until the next poly is hit... { lastqi = qi; int qipy = qi;//bottom vertex id for this column int qiny = qi;//top vertex id for this column for (int qy = 0; qy <= cdpy; qy++)//assign this row of verts to the poly { Vertices[qipy].PolyID = pid; vertexCountP++; if (qy < cdpy) qipy = GetNextID(qipy, dirpy); } for (int qy = 0; qy <= cdny; qy++) { Vertices[qiny].PolyID = pid; vertexCountN++; if (qy < cdny) qiny = GetNextID(qiny, dirny); } qi = GetNextID(qi, dir); //move on to the next column... if (qx == dnx)//last column { if (qipy != lastqi) CornersB.Add(qipy);//lastqi will be added anyway, don't duplicate it if (qiny != lastqi) CornersT.Add(qiny); break; } if (qi < 0)//can't go any further.. most likely hit the end { break; }//(shouldn't hit here because of above break) if (Vertices[qi].PolyID >= 0) //already assigned to a poly.. stop! { break; }//(shouldn't hit here because maxdnx shouldn't go that far!) int ndpy = FindPolyEdgeDist(ref vpl, plt, qi, dirpy);//height for the next col.. int ndny = FindPolyEdgeDist(ref vpl, plt, qi, dirny); int ddpy = ndpy - cdpy; int ddny = ndny - cdny; //TODO: step further along to find slope fraction if eg ddpy==0 if (ddpy > maxdpy/*+1*/) ddpy = maxdpy/*+1*/;//########### BAD else if (ddpy < maxdpy) //bottom corner vertex { maxdpy = ddpy; CornersB.Add(qipy); } if (ddny > maxdny/*+1*/) ddny = maxdny/*+1*/;//########### BAD else if (ddny < maxdny) //top corner vertex.. { maxdny = ddny; CornersT.Add(qiny); } cdpy = cdpy + ddpy; //update comparison distances with limits, for next loop cdny = cdny + ddny; if ((cdpy < 0) || (cdny < 0))//can't go any further.. limit slope hit the axis { if (qipy != lastqi) CornersB.Add(qipy);//lastqi will be added anyway, don't duplicate it if (qiny != lastqi) CornersT.Add(qiny); break; } lastqx = qx; } var totverts = vertexCountN + vertexCountP; var fracused = (float)(lastqx+1) / dnx; CornersB.Add(lastqi); int cc = CornersB.Count + CornersT.Count - 1; int[] corners = new int[cc]; int ci = 0; for (int c = 0; c < CornersB.Count; c++) { corners[ci] = CornersB[c]; ci++; } for (int c = CornersT.Count - 1; c > 0; c--) { corners[ci] = CornersT[c]; ci++; } poly.CornerIndices = corners; if (corners.Length < 3) { }//debug } private void AssignVertices2(ref Plane vpl, float plt, int i, int dnx, int dny, int dpy, int dir, GenPoly poly) { int pid = poly.Index; int qi = i; CornersB.Clear(); CornersT.Clear(); int dirpy, dirny, dirpx; switch (dir) //lookup perpendicular directions { default: case 0: dirpy = 3; dirny = 1; dirpx = 2; break; case 1: dirpy = 0; dirny = 2; dirpx = 3; break; case 2: dirpy = 1; dirny = 3; dirpx = 0; break; case 3: dirpy = 2; dirny = 0; dirpx = 1; break; } int ti = i; while (CanPolyIncludeNext(ref vpl, plt, ti, dirpx, out ti)) { qi = ti; //make sure to start at the leftmost point... } //loop until top and bottom lines intersect, or moved more than max dist float slopeb = FindSlope(ref vpl, plt, qi, dir, dirpy, dirny, 100); float slopet = FindSlope(ref vpl, plt, qi, dir, dirny, dirpy, 100); int syb = MaxOffsetFromSlope(slopeb); int syt = MaxOffsetFromSlope(slopet); int ony = 0; int ldyb = 0; int ldyt = 0; //int corndxb = 0; //int corndxt = 0; for (int x = 0; x < 50; x++) { //fill the column (assign the verts to this poly) int qib = qi; int qit = qi; int dyb = 0; int dyt = 0; int nyb = ldyb + syb; int nyt = ldyt + syt; for (int yb = 0; yb <= nyb; yb++) { if (!CanPolyIncludeNext(ref vpl, plt, qib, dirpy, out ti)) break; Vertices[ti].PolyID = pid; qib = ti; dyb++; } for (int yt = 0; yt <= nyt; yt++) { if (!CanPolyIncludeNext(ref vpl, plt, qit, dirny, out ti)) break; Vertices[ti].PolyID = pid; qit = ti; dyt++; } //move on to the next column //find the start point (and y offset) for the next column //if none found, can't go further int nxi = qi; bool cgx = CanPolyIncludeNext(ref vpl, plt, qi, dir, out nxi); if (!cgx) { int ybi = qi; for (int yb = 0; yb <= dyb; yb++) { ybi = GetNextID(ybi, dirpy); ony--; if (CanPolyIncludeNext(ref vpl, plt, ybi, dir, out nxi)) { cgx = true; break; } } } if (!cgx) { int yti = qi; for (int yt = 0; yt <= dyt; yt++) { yti = GetNextID(yti, dirny); ony++; if (CanPolyIncludeNext(ref vpl, plt, yti, dir, out nxi)) { cgx = true; break; } } } if (!cgx) { //can't go further... end of the poly break; } if (nxi < 0) { break; }//shouldn't happen? int nextyb; int nextyt; int nextib = FindPolyEdgeID(ref vpl, plt, nxi, dirpy, out nextyb); int nextit = FindPolyEdgeID(ref vpl, plt, nxi, dirny, out nextyt); //int remyb = nyb - dyb; //int remyt = nyt - dyt; //int compyb = nextyb - ony; //int compyt = nextyt + ony; //int predyb = dyb + syb + ony; //int predyt = dyt + syt - ony; int nextsyb = nextyb - ony - dyb; int nextsyt = nextyt + ony - dyt; //corndxb++; //corndxt++; bool iscornerb = false; if (slopeb > 1) { if (nextsyb < syb) iscornerb = true; if (nextsyb > syb) nextsyb = syb; } else if (slopeb == 1) { if (nextsyb < syb) iscornerb = true; if (nextsyb > 1) nextsyb = 1; } else if (slopeb > 0) { } else if (slopeb == 0) { if (nextsyb < 0) iscornerb = true; if (nextsyb > 0) nextsyb = 0; } else if (slopeb > -1) { } else if (slopeb == -1) { } else // (slopeb < -1) { if (nextsyb < syb) iscornerb = true; if (nextsyb > syb) nextsyb = syb; } if (iscornerb) { } qi = nxi; syb = nextsyb;// nextyb - dyb; syt = nextsyt;// nextyt - dyt; ldyb = dyb; ldyt = dyt; //find top/bottom max dists and limit them according to slope //check if slopes intersect at this column, stop if they do } } private void AssignVertices3(ref Plane vpl, float plt, int i, int dir, GenPoly poly) { int pid = poly.Index; int qi = i; CornersB.Clear(); CornersT.Clear(); VerticesB.Clear(); VerticesT.Clear(); int dirpy, dirny, dirpx; switch (dir) //lookup perpendicular directions { default: case 0: dirpy = 3; dirny = 1; dirpx = 2; break; case 1: dirpy = 0; dirny = 2; dirpx = 3; break; case 2: dirpy = 1; dirny = 3; dirpx = 0; break; case 3: dirpy = 2; dirny = 0; dirpx = 1; break; } int ti = i; while (CanPolyIncludeNext(ref vpl, plt, ti, dirpx, out ti)) { qi = ti; //make sure to start at the leftmost point... } //find the bottom and top leftmost points to start the first col, and fill the col int qib = qi; int qit = qi; int dyb = 0; int dyt = 0; while (CanPolyIncludeNext(ref vpl, plt, qib, dirpy, out ti)) { Vertices[ti].PolyID = pid; qib = ti; dyb++; } while (CanPolyIncludeNext(ref vpl, plt, qit, dirny, out ti)) { Vertices[ti].PolyID = pid; qit = ti; dyt++; } int dy = dyb + dyt; //total distance between bottom and top CornersB.Add(qib); CornersT.Add(qit); //find bottom and top slopes float slopeb = FindSlope(ref vpl, plt, qib, dir, dirpy, dirny, dyb > 0 ? dyb : 100); float slopet = FindSlope(ref vpl, plt, qit, dir, dirny, dirpy, dyt > 0 ? dyt : 100); int syob = MaxOffsetFromSlope(slopeb); int syot = MaxOffsetFromSlope(slopet); //find the next bottom and top indexes, step by the max offset int nqib = qib; int nqit = qit; //int ndyb = 0; //int ndyt = 0; } public List GenPolys2() { List polys = new List(); //do marching squares on the grid, assuming each vertex starts a cell for (int vx = 0; vx < VertexCountX; vx++) { for (int vy = 0; vy < VertexCountY; vy++) { int imin = VertexOffsets[vx, vy]; int imax = VertexCounts[vx, vy] + imin; for (int i = imin; i < imax; i++) { var nidx = Vertices[i].NextIDX; var nidy = Vertices[i].NextIDY; var nidxy = -1; var nidyx = -1; if ((nidx < 0) || (nidy < 0)) continue; //(can't form a square... try with less verts?) //try to find the index of the opposite corner... //there's 2 possibilities, can only form the square if they are both the same... //what to do if they're different..? just choose one? nidxy = Vertices[nidx].NextIDY; nidyx = Vertices[nidy].NextIDX; if (nidxy != nidyx) { } if (nidxy == -1) { if (nidyx == -1) { continue; } //can't form a square! could use the 3? nidxy = nidyx; } bool f0 = CompareVertexTypes(i, nidx); bool f1 = CompareVertexTypes(nidx, nidxy); bool f2 = CompareVertexTypes(nidy, nidxy); bool f3 = CompareVertexTypes(i, nidy); //bool f4 = CompareVertexTypes(i, nidxy); //diagonal //bool f5 = CompareVertexTypes(nidx, nidy); //diagonal var v0 = Vertices[i]; var v1 = Vertices[nidx]; var v2 = Vertices[nidxy]; var v3 = Vertices[nidy]; var p0 = v0.Position; var p1 = v1.Position; var p2 = v2.Position; var p3 = v3.Position; var s0 = (p0 + p1) * 0.5f; //edge splits var s1 = (p1 + p2) * 0.5f; var s2 = (p2 + p3) * 0.5f; var s3 = (p3 + p0) * 0.5f; var sc = (s0 + s2) * 0.5f;//square center var id = (f0 ? 8 : 0) + (f1 ? 4 : 0) + (f2 ? 2 : 0) + (f3 ? 1 : 0); switch (id) { case 15: //all corners same polys.Add(new GenPoly(new[] { p0, p1, p2, p3 }, ref v0)); break; case 3://single split cases polys.Add(new GenPoly(new[] { s0, p1, s1 }, ref v1)); polys.Add(new GenPoly(new[] { s1, p2, p3, p0, s0 }, ref v2)); break; case 5: polys.Add(new GenPoly(new[] { s0, p1, p2, s2 }, ref v1)); polys.Add(new GenPoly(new[] { s2, p3, p0, s0 }, ref v3)); break; case 6: polys.Add(new GenPoly(new[] { s0, p1, p2, p3, s3 }, ref v1)); polys.Add(new GenPoly(new[] { s3, p0, s0 }, ref v0)); break; case 9: polys.Add(new GenPoly(new[] { s1, p2, s2 }, ref v2)); polys.Add(new GenPoly(new[] { s2, p3, p0, p1, s1 }, ref v3)); break; case 10: polys.Add(new GenPoly(new[] { s1, p2, p3, s3 }, ref v2)); polys.Add(new GenPoly(new[] { s3, p0, p1, s1 }, ref v0)); break; case 12: polys.Add(new GenPoly(new[] { s2, p3, s3 }, ref v3)); polys.Add(new GenPoly(new[] { s3, p0, p1, p2, s2 }, ref v0)); break; case 1://double split cases polys.Add(new GenPoly(new[] { p0, s0, sc, s2, p3 }, ref v0)); polys.Add(new GenPoly(new[] { p1, s1, sc, s0 }, ref v1)); polys.Add(new GenPoly(new[] { p2, s2, sc, s1 }, ref v2)); break; case 2: polys.Add(new GenPoly(new[] { p0, s0, sc, s3 }, ref v0)); polys.Add(new GenPoly(new[] { p1, s1, sc, s0 }, ref v1)); polys.Add(new GenPoly(new[] { p2, p3, s3, sc, s1 }, ref v2)); break; case 4: polys.Add(new GenPoly(new[] { p0, s0, sc, s3 }, ref v0)); polys.Add(new GenPoly(new[] { p1, p2, s2, sc, s0 }, ref v1)); polys.Add(new GenPoly(new[] { p3, s3, sc, s2 }, ref v3)); break; case 8: polys.Add(new GenPoly(new[] { p0, p1, s1, sc, s3 }, ref v0)); polys.Add(new GenPoly(new[] { p2, s2, sc, s1 }, ref v2)); polys.Add(new GenPoly(new[] { p3, s3, sc, s2 }, ref v3)); break; case 0: //all corners different? maybe check diagonals? polys.Add(new GenPoly(new[] { p0, s0, sc, s3 }, ref v0)); polys.Add(new GenPoly(new[] { p1, s1, sc, s0 }, ref v1)); polys.Add(new GenPoly(new[] { p2, s2, sc, s1 }, ref v2)); polys.Add(new GenPoly(new[] { p3, s3, sc, s2 }, ref v3)); break; default://shouldn't happen? break; } } } } return polys; } private int FindNextID(ref Plane vpl, float plt, int i, int dirnx, int dirny, int dirpy, float slope, out int dx, out int dy) { //find the next vertex along the slope in the given direction int ti = i; int qi = i; bool cgx = CanPolyIncludeNext(ref vpl, plt, i, dirnx, out ti); dx = 0; dy = 0; return i; } private int MaxOffsetFromSlope(float s) { if (s >= 1) return (int)s; if (s > 0) return 1; if (s > -1) return 0; return -1; //return ((s>=1)||(s<=-1))?(int)s : (s>0)?1 : (s<0)?-1 : 0; } private int GetNextID(int i, int dir) { switch (dir) { default: case 0: return Vertices[i].PrevIDX; case 1: return Vertices[i].PrevIDY; case 2: return Vertices[i].NextIDX; case 3: return Vertices[i].NextIDY; } } private bool CanPolyIncludeNext(ref Plane vplane, float plthresh, int i, int dir, out int ni) { if ((i < 0) || (i >= Vertices.Length)) { ni = -1; return false; } bool ct; switch (dir) { default: case 0: ni = Vertices[i].PrevIDX; ct = Vertices[i].CompPrevX; break; case 1: ni = Vertices[i].PrevIDY; ct = Vertices[i].CompPrevY; break; case 2: ni = Vertices[i].NextIDX; ct = Vertices[i].CompNextX; break; case 3: ni = Vertices[i].NextIDY; ct = Vertices[i].CompNextY; break; } if (ni < 0) return false; //not connected if (!ct) return false; //next one is a different type if (Vertices[ni].PolyID >= 0) { return false; } //already assigned a poly.. var npdist = Math.Abs(Plane.DotCoordinate(vplane, Vertices[ni].Position)); return (npdist <= plthresh); } private int FindPolyEdgeDist(ref Plane vplane, float plthresh, int i, int dir) { //d: 0=prevX, 1=prevY, 2=nextX, 3=nextY //find how many cells are between given vertex(id) and the edge of a poly, //in the specified direction int dist = 0; int ci = i; while (dist < 100) { int ni; if (!CanPolyIncludeNext(ref vplane, plthresh, ci, dir, out ni)) break; ci = ni; dist++; } return dist; } private int FindPolyEdgeID(ref Plane vplane, float plthresh, int i, int dir) { //d: 0=prevX, 1=prevY, 2=nextX, 3=nextY //find the last id of a vertex contained in this poly, starting from i, //in the specified direction int dist = 0; int ci = i; while (dist < 100) { int ni; if (!CanPolyIncludeNext(ref vplane, plthresh, ci, dir, out ni)) break; ci = ni; dist++; } return ci; } private int FindPolyEdgeID(ref Plane vplane, float plthresh, int i, int dir, out int dist) { //d: 0=prevX, 1=prevY, 2=nextX, 3=nextY //find how many cells are between given vertex(id) and the edge of a poly, //in the specified direction dist = 0; int ci = i; while (dist < 100) { int ni; if (!CanPolyIncludeNext(ref vplane, plthresh, ci, dir, out ni)) break; ci = ni; dist++; } return ci; } private float FindSlope(ref Plane vpl, float plt, int i, int dirnx, int dirny, int dirpy, float maxslope) { //find a slope from the given corner/start point that's less than the max slope int ti = i; int qi = i; float slope = maxslope; bool cgx = CanPolyIncludeNext(ref vpl, plt, i, dirnx, out ti); if (cgx && (slope >= 0)) //new slope should be >=0 { int dy0 = FindPolyEdgeDist(ref vpl, plt, qi, dirny); int dy1 = FindPolyEdgeDist(ref vpl, plt, ti, dirny); int dy = dy1 - dy0; if (dy1 > 1) { if (dy < 0) return Math.Min(slope, dy1); //can move up to next max if (dy0 > dy) return Math.Min(slope, dy0);//first step was steepest if (dy >= 1) return Math.Min(slope, dy);//second step steeper //only (dy==0)&&(dy0==0) case remaining, shouldn't be possible here } if (dy1 == 1) return Math.Min(slope, 1);//can only go +1Y or slope limit if (dy1 == 0) { //step +X until can't go further, or can step +Y int dx = 1; int xi = ti;//starting from y1 while (CanPolyIncludeNext(ref vpl, plt, xi, dirnx, out ti)) { xi = ti; dx++; if (CanPolyIncludeNext(ref vpl, plt, xi, dirny, out ti)) { //can move +Y now, calc new slope which is >0, <1 return Math.Min(slope, 1.0f / dx); } } //couldn't go further +X or +Y... //needs a corner at this next point at slope=0 //or could be "trapped" in a corner return Math.Min(slope, 0);//should always return 0.. } } else //new slope must be <0 { if (!CanPolyIncludeNext(ref vpl, plt, i, dirpy, out ti)) { return Math.Min(slope, 0); //can't move -Y.. could only happen at the end } int dx0 = FindPolyEdgeDist(ref vpl, plt, qi, dirnx); int dx1 = FindPolyEdgeDist(ref vpl, plt, ti, dirnx); int dx = dx1 - dx0; if (dx1 > 1) { if (dx < 0) return Math.Min(slope, 0); //end corner, next slope is going backwards if (dx0 > dx) return Math.Min(slope, -1.0f / dx0);//first step went furthest if (dx >= 1) return Math.Min(slope, -1.0f / dx);//second step furthest //only (dx==0)&&(dy0==0) case remaining, shouldn't be possible here } if (dx1 == 1) return Math.Min(slope, -1); if (dx1 == 0) { //step -Y until can't go further, or can step +X int dy = 1; int yi = ti; while(CanPolyIncludeNext(ref vpl, plt, yi, dirpy, out ti)) { yi = ti; dy++; if (CanPolyIncludeNext(ref vpl, plt, yi, dirnx, out ti)) { //can move +X now, calc new slope for <=-1 return Math.Min(slope, -dy); } } //couldn't go further +Y or +X //slope should be negative vertical return Math.Min(slope, -100); } } return slope; } private int FindNextCornerID(ref Plane vpl, float plt, int i, int dirnx, int dirny, int dirpy, float slope, out int dx, out int dy) { dx = 0; dy = 0; //try to step along the slope until can't go further int ti = i; int qi = i; int mx = 0; int my = 0; int diry = (slope > 0) ? dirny : dirpy; int incy = (slope > 0) ? 1 : -1; if ((slope >= 1) || (slope <= -1)) { int sy = (int)Math.Abs(slope); while (my < sy) { if (CanPolyIncludeNext(ref vpl, plt, qi, diry, out ti)) { qi = ti; my++; dy += incy; if (my == sy) { if (CanPolyIncludeNext(ref vpl, plt, qi, dirnx, out ti)) { qi = ti; my = 0; mx++; dx++; } else//can't go further! { return qi; } } } else if ((mx == 0) && (CanPolyIncludeNext(ref vpl, plt, qi, dirnx, out ti))) { //second chance to make beginning of the line qi = ti; my = 0; mx++; dx++; } else//can't go further! { return qi; } } return qi;//shouldn't get here? } else if (slope != 0) { int sx = (int)Math.Abs(1.0f / slope); while (mx < sx) { if (CanPolyIncludeNext(ref vpl, plt, qi, dirnx, out ti)) { qi = ti; mx++; dx++; if (mx == sx) { if (CanPolyIncludeNext(ref vpl, plt, qi, diry, out ti)) { qi = ti; mx = 0; my++; dy += incy; } else//can't go further! { return qi; } } } else if ((my == 0) && CanPolyIncludeNext(ref vpl, plt, qi, diry, out ti)) { //second chance to make beginning of the line qi = ti; mx = 0; my++; dy += incy; } else//can't go further! { return qi; } } return qi;//shouldn't get here? } else //slope==0 { for (int x = 0; x < 50; x++) //just try go +X until there's a hit. { if (CanPolyIncludeNext(ref vpl, plt, qi, dirnx, out ti)) { qi = ti; dx++; } else { return qi; } } return qi;//could go further, but don't.. } } } private void GenerateComplete() { try { if (InvokeRequired) { Invoke(new Action(() => { GenerateComplete(); })); } else { GenerateButton.Enabled = true; } } catch { } } private void UpdateStatus(string text) { try { if (InvokeRequired) { BeginInvoke(new Action(() => { UpdateStatus(text); })); } else { StatusLabel.Text = text; } } catch { } } } }