using FSO.SimAntics.Model.Routing; using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Linq; namespace FSO.Common.WorldGeometry { /// <summary> /// Projects one mesh onto another mesh on a given axis, potentially with an offset from the surface. /// Example use case: Projecting a road onto a terrain mesh /// </summary> public class MeshProjector { public MeshProjector(IEnumerable<BaseMeshTriangle> baseMesh, IEnumerable<MeshTriangle> projMesh) { foreach (var tri in baseMesh) tri.GenBounds(); foreach (var tri in projMesh) tri.GenBounds(); BaseMesh = baseMesh; ProjectMesh = projMesh; BaseSet = BaseTriangleSet.RoughBalanced(baseMesh.ToList()); } IEnumerable<BaseMeshTriangle> BaseMesh; BaseTriangleSet BaseSet; IEnumerable<MeshTriangle> ProjectMesh; public List<int> Indices; public List<MeshPoint> Vertices; public void Project() { Indices = new List<int>(); Vertices = new List<MeshPoint>(); //find list of potential intersect tris for a projtri //build clipping edges for projtri foreach (var projTri in ProjectMesh) { //find candidate baseTris var candidates = BaseSet.AllIntersect(projTri); foreach (var baseTri in candidates) { //if (projTri.RoughIntersects(baseTri)) //{ ClipTriangles(baseTri, projTri, Vertices, Indices); //} } } } private void ClipTriangles(BaseMeshTriangle baseTri, MeshTriangle projTri, List<MeshPoint> outverts, List<int> inds) { //Sutherland–Hodgman algorithm //clip a triangle against another by iteratively clipping each edge of the second one //we want to clip against base tri var outputList = new MeshPolygon(projTri); var basePlane = new Plane(baseTri.Vertices[0], baseTri.Vertices[1], baseTri.Vertices[2]); for (int i=0; i<3; i++) { if (outputList.Points.Count == 0) return; var inputList = outputList; var edge = new ClipEdge(baseTri.Vertices[i], baseTri.Vertices[(i + 1) % 3]); outputList = new MeshPolygon(); var lastPoint = inputList.Points.Last(); int j = inputList.Points.Count-1; foreach (var point in inputList.Points) { if (!edge.ShouldClip(point.Position)) { if (edge.ShouldClip(lastPoint.Position)) { outputList.Points.Add(edge.IntersectLine(inputList, j)); } //we still need to project the point onto the surface... var ray = new Ray(point.Position, new Vector3(0, -1, 0)); var intersect2 = ray.Intersects(basePlane); if (intersect2 == null) { ray.Direction *= -1; intersect2 = ray.Intersects(basePlane); if (intersect2 == null) { } intersect2 = -(intersect2 ?? 0f); } point.Position.Y -= intersect2.Value; outputList.Points.Add(point); } else { if (!edge.ShouldClip(lastPoint.Position)) { outputList.Points.Add(edge.IntersectLine(inputList, j)); } } j = (j + 1) % inputList.Points.Count; lastPoint = point; } } if (outputList.Points.Count < 3) return; //? outputList.Triangulate(outverts, inds); } } public class ClipEdge { Vector3 EdgeVec; Vector2 DotVec; Vector3 EdgePos; Vector2 EdgePos2; public ClipEdge(Vector3 from, Vector3 to) { //xz //we assume the triangle is winding clockwise, so points on the left should be clipped EdgeVec = to - from; EdgePos = from; EdgePos2 = new Vector2(from.X, from.Z); DotVec = new Vector2(-EdgeVec.Z, EdgeVec.X); } public bool ShouldClip(Vector3 pos) { return (Vector2.Dot(DotVec, new Vector2(pos.X, pos.Z) - EdgePos2) < 0); } public MeshPoint IntersectLine(MeshPolygon tri, int lineInd) { var points = tri.Points; var lineInd2 = (lineInd + 1) % points.Count; var pt1 = tri.Points[lineInd]; var pt2 = tri.Points[lineInd2]; Vector3 a = EdgeVec; //line 1 Vector3 b = pt2.Position - pt1.Position; //line 2 Vector3 c = EdgePos - pt1.Position; //vec between starts //percent of line 1 where we intersect with line 2 float ip = 1 / (-b.X * a.Z + a.X * b.Z); //projection float t = (b.X * c.Z - b.Z * c.X) * ip; //percent of line 2 where we intersect line 1 float ip2 = 1 / (-a.X * b.Z + b.X * a.Z); float s = (a.X * (-c.Z) - a.Z * (-c.X)) * ip2; //pos + vec * t = pos2 + vec2 * s //vec * t - vec2 * s = pos2 - pos1 float[] newTC = new float[pt1.TexCoords.Length]; float ms = 1 - s; for (int i=0; i<newTC.Length; i++) { newTC[i] = pt1.TexCoords[i] * ms + pt2.TexCoords[i] * s; } return new MeshPoint( //position from the clip triangle (use t) EdgePos + EdgeVec * t, //texcoords from the two points in the poly (use s) newTC ); } } public class BaseMeshTriangle { public float x1; public float y1; public float x2; public float y2; public Vector3[] Vertices; public void GenBounds() { x1 = Vertices[0].X; y1 = Vertices[0].Z; x2 = x1; y2 = y1; for (int i=1; i<Vertices.Length; i++) { var v = Vertices[i]; if (v.X < x1) x1 = v.X; if (v.Z < y1) y1 = v.Z; if (v.X > x2) x2 = v.X; if (v.Z > y2) y2 = v.Z; } } public bool RoughIntersects(BaseMeshTriangle other) { return !(x1 > other.x2 || x2 < other.x1 || y1 > other.y2 || y2 < other.y1); } } public class MeshTriangle : BaseMeshTriangle { public float[][] TexCoords; } public class MeshPoint { public Vector3 Position; public float[] TexCoords; public MeshPoint(Vector3 pos, float[] texCoords) { Position = pos; TexCoords = texCoords; } public MeshPoint(Vector3 pos, Vector2 texCoords) { Position = pos; TexCoords = new float[] { texCoords.X, texCoords.Y }; } } public class MeshPolygon { public List<MeshPoint> Points; public MeshPolygon() { Points = new List<MeshPoint>(); } public MeshPolygon(MeshTriangle tri) { Points = new List<MeshPoint>(); for (int i=0; i<3; i++) { Points.Add(new MeshPoint(tri.Vertices[i], tri.TexCoords[i])); } } public void Triangulate(List<MeshPoint> outverts, List<int> inds) { //simple fan triangle fill var baseInd = outverts.Count; outverts.AddRange(Points); for (int i=2; i<Points.Count; i++) { inds.Add(baseInd); inds.Add(baseInd+i-1); inds.Add(baseInd+i); } } } }