mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-07-15 18:46:42 -04:00
Removed NioTSO client and server
- NioTSO client isn't needed because we're using RayLib - Added FreeSO's API server to handle most backend operations
This commit is contained in:
parent
f12ba1502b
commit
22191ce648
591 changed files with 53264 additions and 3362 deletions
253
server/tso.common/WorldGeometry/MeshProjector.cs
Executable file
253
server/tso.common/WorldGeometry/MeshProjector.cs
Executable file
|
@ -0,0 +1,253 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
241
server/tso.common/WorldGeometry/Paths/LinePath.cs
Executable file
241
server/tso.common/WorldGeometry/Paths/LinePath.cs
Executable file
|
@ -0,0 +1,241 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Common.WorldGeometry.Paths
|
||||
{
|
||||
public class LinePath
|
||||
{
|
||||
public List<LinePathSegment> Segments = new List<LinePathSegment>();
|
||||
public bool SharpStart;
|
||||
public bool SharpEnd;
|
||||
public int TemplateNum;
|
||||
|
||||
public float StartOffset;
|
||||
public float Length;
|
||||
|
||||
public LinePath()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LinePath(List<Vector2> line)
|
||||
{
|
||||
for (int i=0; i<line.Count-1; i++)
|
||||
{
|
||||
var seg = new LinePathSegment(line[i], line[i + 1]);
|
||||
Length += seg.Length;
|
||||
Segments.Add(seg);
|
||||
}
|
||||
}
|
||||
|
||||
public Tuple<Vector2, Vector2> GetPositionNormalAt(float offset)
|
||||
{
|
||||
foreach (var seg in Segments)
|
||||
{
|
||||
//is the given offset in this segment?
|
||||
if (offset < seg.Length)
|
||||
{
|
||||
var i = offset / seg.Length;
|
||||
return new Tuple<Vector2, Vector2>(Vector2.Lerp(seg.Start, seg.End, i), Vector2.Lerp(seg.StartNormal, seg.EndNormal, i));
|
||||
}
|
||||
offset -= seg.Length;
|
||||
}
|
||||
var last = Segments.Last();
|
||||
return new Tuple<Vector2, Vector2>(last.End, last.EndNormal);
|
||||
}
|
||||
|
||||
public List<LinePath> Split(float dist, float gap)
|
||||
{
|
||||
var result = new List<LinePath>();
|
||||
var startGap = dist - gap / 2;
|
||||
var endGap = dist + gap / 2;
|
||||
|
||||
bool before = 0 < startGap;
|
||||
LinePath current = new LinePath();
|
||||
if (before)
|
||||
{
|
||||
current.SharpStart = SharpStart;
|
||||
current.SharpEnd = true;
|
||||
current.StartOffset = StartOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
current.SharpStart = true;
|
||||
current.SharpEnd = SharpEnd;
|
||||
current.StartOffset = StartOffset + endGap;
|
||||
}
|
||||
current.TemplateNum = TemplateNum;
|
||||
|
||||
float soFar = 0;
|
||||
foreach (var segment in Segments)
|
||||
{
|
||||
if (before)
|
||||
{
|
||||
if (soFar + segment.Length <= startGap)
|
||||
{
|
||||
//add this segment
|
||||
current.Segments.Add(segment);
|
||||
}
|
||||
else
|
||||
{
|
||||
//this segment extends over the gap.
|
||||
//an additional segment must be added to reach the start gap
|
||||
if (soFar != startGap && segment.Length != 0)
|
||||
{
|
||||
var bridge = new LinePathSegment(segment.Start, Vector2.Lerp(segment.Start, segment.End, (startGap - soFar) / segment.Length));
|
||||
bridge.StartNormal = segment.StartNormal;
|
||||
current.Segments.Add(bridge);
|
||||
}
|
||||
|
||||
current.Length = current.Segments.Sum(x => x.Length);
|
||||
result.Add(current);
|
||||
current = new LinePath();
|
||||
current.SharpStart = true;
|
||||
current.SharpEnd = SharpEnd;
|
||||
current.StartOffset = StartOffset + endGap;
|
||||
current.TemplateNum = TemplateNum;
|
||||
before = false;
|
||||
}
|
||||
}
|
||||
if (!before)
|
||||
{
|
||||
if (current.Segments.Count == 0)
|
||||
{
|
||||
//waiting to get to a segment that ends after the gap.
|
||||
if (soFar + segment.Length > endGap)
|
||||
{
|
||||
var bridge = new LinePathSegment(Vector2.Lerp(segment.Start, segment.End, (endGap - soFar) / segment.Length), segment.End);
|
||||
bridge.EndNormal = segment.EndNormal;
|
||||
current.Segments.Add(bridge);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//add this segment
|
||||
current.Segments.Add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
soFar += segment.Length;
|
||||
}
|
||||
current.Length = current.Segments.Sum(x => x.Length);
|
||||
result.Add(current);
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Vector3> Intersections(LinePath other)
|
||||
{
|
||||
var epsilon = (0.9f * 0.9f) / 0.5f;
|
||||
|
||||
//finds intersections between this linepath and another.
|
||||
var result = new List<Vector3>();
|
||||
float soFar = 0;
|
||||
for (int i=0; i<Segments.Count; i++)
|
||||
{
|
||||
var seg1 = Segments[i];
|
||||
for (int j=0; j<other.Segments.Count; j++)
|
||||
{
|
||||
var seg2 = other.Segments[j];
|
||||
var inter = seg1.Intersect(seg2);
|
||||
|
||||
if (inter != null)
|
||||
{
|
||||
var interc = inter.Value;
|
||||
interc.Z += soFar;
|
||||
result.Add(interc);
|
||||
}
|
||||
}
|
||||
soFar += seg1.Length;
|
||||
}
|
||||
|
||||
//remove dupes
|
||||
result = result.OrderBy(x => x.Z).ToList();
|
||||
for (int i = 0; i < result.Count - 1; i++)
|
||||
{
|
||||
var first = result[i];
|
||||
while (i < result.Count - 1)
|
||||
{
|
||||
var second = result[i + 1];
|
||||
var distance = second - first;
|
||||
distance.Z = 0;
|
||||
if (distance.LengthSquared() < epsilon)
|
||||
{
|
||||
result.RemoveAt(i);
|
||||
}
|
||||
else;
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void PrepareJoins()
|
||||
{
|
||||
LinePathSegment last = null;
|
||||
foreach (var line in Segments)
|
||||
{
|
||||
if (last != null)
|
||||
{
|
||||
last.EndNormal = line.StartNormal = Vector2.Normalize(last.EndNormal + line.StartNormal);
|
||||
}
|
||||
last = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LinePathSegment
|
||||
{
|
||||
public Vector2 Start;
|
||||
public Vector2 End;
|
||||
|
||||
public Vector2 Direction;
|
||||
|
||||
//normals are used when constucting geometry from a line. they face to the right from the line.
|
||||
//to create a seamless line, we average the end normal of this line and the start normal of the last, setting both to the result.
|
||||
public Vector2 StartNormal;
|
||||
public Vector2 EndNormal;
|
||||
|
||||
public float Length;
|
||||
|
||||
public LinePathSegment(Vector2 start, Vector2 end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Direction = end - start;
|
||||
Length = Direction.Length();
|
||||
|
||||
var dirn = Direction;
|
||||
dirn.Normalize();
|
||||
StartNormal = EndNormal = new Vector2(-dirn.Y, dirn.X);
|
||||
}
|
||||
|
||||
public Vector3? Intersect(LinePathSegment other) //xy: point, z: distance along line
|
||||
{
|
||||
if (this.Length == 0 || other.Length == 0) return null;
|
||||
var epsilon = 0.0001f;
|
||||
|
||||
Vector2 a = Direction;
|
||||
Vector2 b = other.Direction;
|
||||
Vector2 c = Start - other.Start;
|
||||
|
||||
//percent of line 1 where we intersect with line 2
|
||||
float ip = 1 / (-b.X * a.Y + a.X * b.Y); //projection
|
||||
float t = (b.X * c.Y - b.Y * c.X) * ip;
|
||||
|
||||
//percent of line 2 where we intersect line 1
|
||||
float ip2 = 1 / (-a.X * b.Y + b.X * a.Y);
|
||||
float s = (a.X * (-c.Y) - a.Y * (-c.X)) * ip2;
|
||||
|
||||
if (float.IsNaN(t) || t < -epsilon || t > 1 + epsilon || float.IsNaN(s) || s < -epsilon || s > 1 + epsilon)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new Vector3(Direction * t + Start, t * Length);
|
||||
}
|
||||
}
|
||||
}
|
161
server/tso.common/WorldGeometry/Paths/SVGParser.cs
Executable file
161
server/tso.common/WorldGeometry/Paths/SVGParser.cs
Executable file
|
@ -0,0 +1,161 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
|
||||
namespace FSO.Common.WorldGeometry.Paths
|
||||
{
|
||||
public enum SVGPathSegmentType
|
||||
{
|
||||
MoveTo,
|
||||
LineTo,
|
||||
CurveTo,
|
||||
Close
|
||||
}
|
||||
|
||||
public class SVGPathSegment
|
||||
{
|
||||
public SVGPathSegmentType Type;
|
||||
public Vector2 Position;
|
||||
public Vector2 ControlPoint1;
|
||||
public Vector2 ControlPoint2;
|
||||
}
|
||||
|
||||
public class SVGPath
|
||||
{
|
||||
public string ID;
|
||||
public List<SVGPathSegment> Segments;
|
||||
|
||||
public SVGPath(string id, List<SVGPathSegment> segs)
|
||||
{
|
||||
ID = id;
|
||||
Segments = segs;
|
||||
}
|
||||
}
|
||||
|
||||
public class SVGParser
|
||||
{
|
||||
public List<SVGPath> Paths;
|
||||
|
||||
public SVGParser(string svgText)
|
||||
{
|
||||
var xml = new XmlDocument();
|
||||
xml.XmlResolver = null;
|
||||
xml.LoadXml(svgText);
|
||||
|
||||
Paths = new List<SVGPath>();
|
||||
|
||||
var paths = xml.GetElementsByTagName("path");
|
||||
foreach (XmlNode path in paths)
|
||||
{
|
||||
var str = path.Attributes["d"].InnerText.Replace(',', ' ');
|
||||
int template = 0;
|
||||
var id = path.Attributes["id"]?.InnerText;
|
||||
var elems = str.Split(' ');
|
||||
var pos = new Vector2(0, 0);
|
||||
|
||||
var newPath = new List<SVGPathSegment>();
|
||||
for (int i = 0; i < elems.Length; i += 0)
|
||||
{
|
||||
var type = elems[i++];
|
||||
if (type.Length == 0) continue;
|
||||
var relative = char.IsLower(type[0]);
|
||||
if (!relative) pos = new Vector2();
|
||||
switch (type.ToLower())
|
||||
{
|
||||
case "m":
|
||||
case "l":
|
||||
//lineto
|
||||
pos += new Vector2(float.Parse(elems[i++], CultureInfo.InvariantCulture), float.Parse(elems[i++], CultureInfo.InvariantCulture));
|
||||
newPath.Add(new SVGPathSegment()
|
||||
{
|
||||
Position = pos,
|
||||
Type = (type.ToLower() == "l") ? SVGPathSegmentType.LineTo : SVGPathSegmentType.MoveTo
|
||||
});
|
||||
break;
|
||||
case "c":
|
||||
var cp1 = new Vector2(float.Parse(elems[i++], CultureInfo.InvariantCulture), float.Parse(elems[i++], CultureInfo.InvariantCulture)) + pos;
|
||||
var cp2 = new Vector2(float.Parse(elems[i++], CultureInfo.InvariantCulture), float.Parse(elems[i++], CultureInfo.InvariantCulture)) + pos;
|
||||
pos += new Vector2(float.Parse(elems[i++], CultureInfo.InvariantCulture), float.Parse(elems[i++], CultureInfo.InvariantCulture));
|
||||
|
||||
newPath.Add(new SVGPathSegment()
|
||||
{
|
||||
Position = pos,
|
||||
ControlPoint1 = cp1,
|
||||
ControlPoint2 = cp2,
|
||||
Type = SVGPathSegmentType.CurveTo
|
||||
});
|
||||
break;
|
||||
case "z":
|
||||
//close
|
||||
newPath.Add(new SVGPathSegment()
|
||||
{
|
||||
Type = SVGPathSegmentType.Close
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
Paths.Add(new SVGPath(id, newPath));
|
||||
}
|
||||
}
|
||||
|
||||
public LinePath ToLinePath(SVGPath inpath)
|
||||
{
|
||||
var segs = inpath.Segments;
|
||||
var line = new List<Vector2>();
|
||||
|
||||
var closed = false;
|
||||
var pos = new Vector2(0, 0);
|
||||
foreach (var seg in segs)
|
||||
{
|
||||
switch (seg.Type)
|
||||
{
|
||||
case SVGPathSegmentType.MoveTo:
|
||||
case SVGPathSegmentType.LineTo:
|
||||
line.Add(seg.Position);
|
||||
break;
|
||||
case SVGPathSegmentType.CurveTo:
|
||||
//subdivided curve. currently 20 subdivisions.
|
||||
var subdiv = 20;
|
||||
var lastPos = line.Last();
|
||||
for (int i=1; i<subdiv; i++)
|
||||
{
|
||||
var t = i / (float)subdiv;
|
||||
var s = 1 - t;
|
||||
line.Add(new Vector2(
|
||||
(s * s * s) * lastPos.X + 3 * (s * s * t) * seg.ControlPoint1.X + 3 * (s * t * t) * seg.ControlPoint2.X + (t * t * t) * seg.Position.X,
|
||||
(s * s * s) * lastPos.Y + 3 * (s * s * t) * seg.ControlPoint1.Y + 3 * (s * t * t) * seg.ControlPoint2.Y + (t * t * t) * seg.Position.Y
|
||||
));
|
||||
}
|
||||
break;
|
||||
case SVGPathSegmentType.Close:
|
||||
//finish at the start.
|
||||
if (line.First() != line.Last()) line.Add(line.First());
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var path = new LinePath(line);
|
||||
|
||||
if (inpath.ID != null && inpath.ID.StartsWith("template"))
|
||||
{
|
||||
path.TemplateNum = int.Parse(inpath.ID.Substring(8));
|
||||
}
|
||||
|
||||
if (closed && path.Segments.Count > 0) {
|
||||
var first = path.Segments.First();
|
||||
var last = path.Segments.Last();
|
||||
first.StartNormal = last.EndNormal = (first.StartNormal + last.EndNormal) / 2;
|
||||
path.SharpEnd = true;
|
||||
path.SharpStart = true;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public List<LinePath> ToLinePaths()
|
||||
{
|
||||
return Paths.Select(x => ToLinePath(x)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
484
server/tso.common/WorldGeometry/RoadGeometry.cs
Executable file
484
server/tso.common/WorldGeometry/RoadGeometry.cs
Executable file
|
@ -0,0 +1,484 @@
|
|||
using FSO.Common.WorldGeometry.Paths;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Common.WorldGeometry
|
||||
{
|
||||
public class RoadMesh
|
||||
{
|
||||
public int LastIndex;
|
||||
public List<int> Indices = new List<int>();
|
||||
public List<MeshPoint> Vertices = new List<MeshPoint>();
|
||||
}
|
||||
|
||||
public class RoadDetectedIntersection
|
||||
{
|
||||
public LinePath MainPath;
|
||||
public LinePath SubPath;
|
||||
public RoadGeometryTemplate Template;
|
||||
public float MainDist;
|
||||
public float SubDist;
|
||||
public bool ThreeWay;
|
||||
|
||||
public Vector2 Center;
|
||||
public Vector2 AlignmentY; //how to transform Y coords
|
||||
public Vector2 AlignmentX; //how to transform X coords
|
||||
}
|
||||
|
||||
public class RoadGeometry
|
||||
{
|
||||
public List<LinePath> Paths;
|
||||
public List<RoadDetectedIntersection> Intersections;
|
||||
public List<RoadGeometryTemplate> Templates;
|
||||
|
||||
public RoadGeometry(List<LinePath> paths, List<RoadGeometryTemplate> templates)
|
||||
{
|
||||
Paths = paths;
|
||||
Templates = templates;
|
||||
}
|
||||
|
||||
private float IntersectionDistance(Vector3 inter1, Vector3 inter2)
|
||||
{
|
||||
return Vector2.Distance(new Vector2(inter1.X, inter1.Y), new Vector2(inter2.X, inter2.Y));
|
||||
}
|
||||
|
||||
public void GenerateIntersections()
|
||||
{
|
||||
Intersections = new List<RoadDetectedIntersection>();
|
||||
for (int i = 0; i < Paths.Count; i++)
|
||||
{
|
||||
var path1 = Paths[i];
|
||||
for (int j = i + 1; j < Paths.Count; j++)
|
||||
{
|
||||
var path2 = Paths[j];
|
||||
|
||||
var inters = path1.Intersections(path2);
|
||||
foreach (var inter in inters)
|
||||
{
|
||||
//find corresponding intersection in path2
|
||||
var inter2n = path2.Intersections(path1).Cast<Vector3?>().FirstOrDefault(x => IntersectionDistance(x.Value, inter) < 1);
|
||||
if (inter2n != null)
|
||||
{
|
||||
var inter2 = inter2n.Value;
|
||||
|
||||
int primaryLine = 0;
|
||||
if (inter2.Z < 1 || inter2.Z > path2.Length-1)
|
||||
{
|
||||
primaryLine = 1;
|
||||
}
|
||||
if (inter.Z < 1 || inter.Z > path1.Length-1)
|
||||
{
|
||||
if (primaryLine != 0)
|
||||
{
|
||||
throw new Exception("2 way intersection currently not supported. Make a curve instead.");
|
||||
}
|
||||
primaryLine = 2;
|
||||
}
|
||||
|
||||
bool threeWay = primaryLine != 0;
|
||||
if (!threeWay) primaryLine = 1;
|
||||
|
||||
var mainPath = (primaryLine == 1) ? path1 : path2;
|
||||
var subPath = (primaryLine == 2) ? path1 : path2;
|
||||
var mainDist = (primaryLine == 1) ? inter.Z : inter2.Z;
|
||||
var subDist = (primaryLine == 2) ? inter.Z : inter2.Z;
|
||||
|
||||
var primaryAlign = mainPath.GetPositionNormalAt(mainDist);
|
||||
|
||||
var vert = primaryAlign.Item2;
|
||||
vert = new Vector2(vert.Y, -vert.X);
|
||||
|
||||
float xflip = 1;
|
||||
if (threeWay)
|
||||
{
|
||||
var normalSub = subPath.GetPositionNormalAt(subDist).Item2;
|
||||
if (subDist < 1) normalSub = -normalSub;
|
||||
|
||||
if (Vector2.Dot(vert, normalSub) < 0) xflip = -1;
|
||||
}
|
||||
|
||||
Intersections.Add(new RoadDetectedIntersection()
|
||||
{
|
||||
MainPath = mainPath,
|
||||
SubPath = subPath,
|
||||
MainDist = mainDist,
|
||||
SubDist = subDist,
|
||||
ThreeWay = threeWay,
|
||||
|
||||
Center = new Vector2(inter.X, inter.Y),
|
||||
AlignmentY = vert, //how to transform Y coords
|
||||
AlignmentX = primaryAlign.Item2 * xflip //how to transform X coords
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//split the road paths based on these intersections.
|
||||
foreach (var intersection in Intersections)
|
||||
{
|
||||
Paths.Remove(intersection.MainPath);
|
||||
Paths.Remove(intersection.SubPath);
|
||||
|
||||
intersection.Template = Templates[Math.Max(intersection.MainPath.TemplateNum, intersection.SubPath.TemplateNum)];
|
||||
|
||||
var mSplit = intersection.MainPath.Split(intersection.MainDist - intersection.MainPath.StartOffset, intersection.Template.IntersectionSize);
|
||||
var sSplit = intersection.SubPath.Split(intersection.SubDist - intersection.SubPath.StartOffset, intersection.Template.IntersectionFromSize);
|
||||
Paths.AddRange(mSplit);
|
||||
Paths.AddRange(sSplit);
|
||||
if (mSplit.Any(x => float.IsNaN(x.Length)) || sSplit.Any(x => float.IsNaN(x.Length))) { }
|
||||
|
||||
//update intersections that use these paths to reference the new split paths
|
||||
foreach (var inter2 in Intersections)
|
||||
{
|
||||
if (inter2 == intersection) continue;
|
||||
if (inter2.MainPath == intersection.MainPath)
|
||||
{
|
||||
if (inter2.MainDist > intersection.MainDist) inter2.MainPath = mSplit.Last();
|
||||
else inter2.MainPath = mSplit[0];
|
||||
}
|
||||
if (inter2.MainPath == intersection.SubPath)
|
||||
{
|
||||
if (inter2.MainDist > intersection.SubDist) inter2.MainPath = sSplit.Last();
|
||||
else inter2.MainPath = sSplit[0];
|
||||
}
|
||||
|
||||
if (inter2.SubPath == intersection.MainPath)
|
||||
{
|
||||
if (inter2.SubDist > intersection.MainDist) inter2.SubPath = mSplit.Last();
|
||||
else inter2.SubPath = mSplit[0];
|
||||
}
|
||||
if (inter2.SubPath == intersection.SubPath)
|
||||
{
|
||||
if (inter2.SubDist > intersection.SubDist) inter2.SubPath = sSplit.Last();
|
||||
else inter2.SubPath = sSplit[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<ushort, RoadMesh> Meshes;
|
||||
|
||||
private void AddTriangle(List<int> indices, int i1, int i2, int i3)
|
||||
{
|
||||
indices.Add(i1);
|
||||
indices.Add(i2);
|
||||
indices.Add(i3);
|
||||
}
|
||||
|
||||
public void GenerateRoadGeometry()
|
||||
{
|
||||
Meshes = new Dictionary<ushort, RoadMesh>();
|
||||
|
||||
foreach (var seg in Templates[0].Segments)
|
||||
{
|
||||
foreach (var line in seg.Lines)
|
||||
{
|
||||
if (!Meshes.ContainsKey(line.FloorTile)) Meshes[line.FloorTile] = new RoadMesh();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var path in Paths)
|
||||
{
|
||||
path.PrepareJoins();
|
||||
var template = Templates[path.TemplateNum];
|
||||
|
||||
if (path.Segments.Count == 0) continue;
|
||||
if (path.Length < 1) { }
|
||||
if (!path.SharpStart)
|
||||
{
|
||||
var seg = path.Segments.First();
|
||||
CapEnd(template, seg.Start, -seg.StartNormal);
|
||||
}
|
||||
|
||||
//generate the line
|
||||
|
||||
float linePosition = 0;
|
||||
float virtualPosition = 0;// path.StartOffset;
|
||||
var startSegment = template.GetSegmentForOffset(virtualPosition);
|
||||
RoadGeometryTemplateSegment currentSegment = startSegment.Item1;
|
||||
float remaining = startSegment.Item2;
|
||||
|
||||
bool end;
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
end = linePosition + remaining >= path.Length;
|
||||
if (end) remaining = path.Length - linePosition;
|
||||
|
||||
foreach (var mesh in Meshes.Values) mesh.LastIndex = mesh.Vertices.Count;
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
var basePos = path.GetPositionNormalAt(linePosition);
|
||||
foreach (var line in currentSegment.Lines)
|
||||
{
|
||||
var mesh = Meshes[line.FloorTile];
|
||||
if (j > 0)
|
||||
{
|
||||
//create triangles
|
||||
AddTriangle(mesh.Indices, mesh.LastIndex, mesh.Vertices.Count, mesh.LastIndex + 1);
|
||||
AddTriangle(mesh.Indices, mesh.Vertices.Count, mesh.Vertices.Count + 1, mesh.LastIndex + 1);
|
||||
|
||||
mesh.LastIndex += 2;
|
||||
}
|
||||
|
||||
var spos2d = basePos.Item1 + basePos.Item2 * line.Start.X;
|
||||
var stc = FloorTC(new Vector2(line.Start.X, virtualPosition) + line.UVOff);
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(spos2d.X, line.Start.Y, spos2d.Y), stc));
|
||||
|
||||
var epos2d = basePos.Item1 + basePos.Item2 * line.End.X;
|
||||
var etc = FloorTC(new Vector2(line.End.X, virtualPosition) + line.UVOff);
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(epos2d.X, line.End.Y, epos2d.Y), etc));
|
||||
}
|
||||
|
||||
i++;
|
||||
if (j == 0)
|
||||
{
|
||||
virtualPosition += remaining;
|
||||
linePosition += remaining;
|
||||
}
|
||||
}
|
||||
currentSegment = currentSegment.Next;
|
||||
} while (!end);
|
||||
|
||||
|
||||
if (!path.SharpEnd)
|
||||
{
|
||||
var seg = path.Segments.Last();
|
||||
CapEnd(template, seg.End, seg.EndNormal);
|
||||
}
|
||||
}
|
||||
|
||||
if (Intersections != null)
|
||||
{
|
||||
foreach (var intersection in Intersections)
|
||||
{
|
||||
PlaceIntersection(intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 FloorTC(Vector2 vec)
|
||||
{
|
||||
return new Vector2(-0.5f + vec.X - vec.Y, 0.5f + vec.X + vec.Y) * 0.5f;
|
||||
}
|
||||
|
||||
public void PlaceIntersection(RoadDetectedIntersection intersection) {
|
||||
var template = intersection.Template;
|
||||
var iTemplate = intersection.ThreeWay ? template.Intersection3Way : template.Intersection4Way;
|
||||
var off = new Vector2(template.IntersectionFromSize, template.IntersectionSize)/2;
|
||||
var ctr = intersection.Center;
|
||||
|
||||
var xm = intersection.AlignmentX;
|
||||
var ym = intersection.AlignmentY;
|
||||
foreach (var rect in iTemplate)
|
||||
{
|
||||
RoadMesh mesh;
|
||||
if (!Meshes.TryGetValue(rect.FloorTile, out mesh))
|
||||
{
|
||||
mesh = new RoadMesh();
|
||||
Meshes[rect.FloorTile] = mesh;
|
||||
}
|
||||
|
||||
var ind = mesh.Vertices.Count;
|
||||
var pos = rect.Rect.Location.ToVector2() - off + rect.Offset;
|
||||
var tcOff = off + new Vector2(0.5f, 0f) - rect.Offset;
|
||||
var pos2 = xm * pos.X + ym * pos.Y + ctr;
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(pos2.X, 0, pos2.Y), FloorTC(pos + tcOff)));
|
||||
|
||||
pos += new Vector2(rect.Rect.Width, 0);
|
||||
pos2 = xm * pos.X + ym * pos.Y + ctr;
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(pos2.X, 0, pos2.Y), FloorTC(pos + tcOff)));
|
||||
|
||||
pos += new Vector2(0, rect.Rect.Height);
|
||||
pos2 = xm * pos.X + ym * pos.Y + ctr;
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(pos2.X, 0, pos2.Y), FloorTC(pos + tcOff)));
|
||||
|
||||
pos += new Vector2(-rect.Rect.Width, 0);
|
||||
pos2 = xm * pos.X + ym * pos.Y + ctr;
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(pos2.X, 0, pos2.Y), FloorTC(pos + tcOff)));
|
||||
|
||||
AddTriangle(mesh.Indices, ind, ind + 1, ind + 2);
|
||||
AddTriangle(mesh.Indices, ind, ind + 2, ind + 3);
|
||||
}
|
||||
}
|
||||
|
||||
public void CapEnd(RoadGeometryTemplate template, Vector2 position, Vector2 normal)
|
||||
{
|
||||
foreach (var mesh in Meshes.Values) mesh.LastIndex = mesh.Vertices.Count;
|
||||
|
||||
var lines = template.EndLines;
|
||||
for (int i=0; i<=template.EndRepeats; i++) {
|
||||
var angle = (i * Math.PI) / template.EndRepeats;
|
||||
var c = (float)Math.Cos(angle);
|
||||
var s = (float)Math.Sin(angle);
|
||||
Vector2 xToCoord = new Vector2(c * normal.X - s * normal.Y, s * normal.X + c * normal.Y);
|
||||
Vector2 xToTc = new Vector2(c, s);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var mesh = Meshes[line.FloorTile];
|
||||
if (line.TriangleCap)
|
||||
{
|
||||
if (i == 0)
|
||||
{ //create the point we rotate around
|
||||
line.TempIndex = mesh.Vertices.Count;
|
||||
var pos2d = position + xToCoord * line.End.X;
|
||||
var tc = FloorTC(xToTc * line.End.X + line.UVOff);
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(pos2d.X, line.End.Y, pos2d.Y), tc));
|
||||
mesh.LastIndex++;
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
//create triangles
|
||||
AddTriangle(mesh.Indices, mesh.LastIndex++, mesh.Vertices.Count, line.TempIndex);
|
||||
}
|
||||
|
||||
var spos2d = position + xToCoord * line.Start.X;
|
||||
var stc = FloorTC(xToTc * line.Start.X + line.UVOff);
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(spos2d.X, line.Start.Y, spos2d.Y), stc));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
//create triangles
|
||||
AddTriangle(mesh.Indices, mesh.LastIndex, mesh.Vertices.Count, mesh.LastIndex + 1);
|
||||
AddTriangle(mesh.Indices, mesh.Vertices.Count, mesh.Vertices.Count + 1, mesh.LastIndex + 1);
|
||||
|
||||
mesh.LastIndex += 2;
|
||||
}
|
||||
|
||||
var spos2d = position + xToCoord * line.Start.X;
|
||||
var stc = FloorTC(new Vector2(line.Start.X, i) + line.UVOff);
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(spos2d.X, line.Start.Y, spos2d.Y), stc));
|
||||
|
||||
var epos2d = position + xToCoord * line.End.X;
|
||||
var etc = FloorTC(new Vector2(line.End.X, i) + line.UVOff);
|
||||
mesh.Vertices.Add(new MeshPoint(new Vector3(epos2d.X, line.End.Y, epos2d.Y), etc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RoadGeometryTemplate
|
||||
{
|
||||
private RoadGeometryTemplateSegment[] _Segments;
|
||||
|
||||
public RoadGeometryTemplateSegment[] Segments
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Segments;
|
||||
}
|
||||
set
|
||||
{
|
||||
RepeatLength = 0;
|
||||
for (int i=0; i<value.Length; i++)
|
||||
{
|
||||
var seg = value[i];
|
||||
RepeatLength += seg.Extent;
|
||||
seg.Next = value[(i + 1) % value.Length];
|
||||
}
|
||||
value[value.Length - 1].Next = value[0];
|
||||
_Segments = value;
|
||||
}
|
||||
}
|
||||
public float RepeatLength; //sum of all segment extents.
|
||||
|
||||
public RoadGeometryTemplateLine[] EndLines; //(x, y) lines to rotate around z = 0. eg. line at left half of road, rotated clockwise through to make a circular sweep finishing at the right.
|
||||
public int EndRepeats; //number of subdivisions the end semicircle is drawn with. Should be about PI * radius if you want to keep pavements consistent.
|
||||
|
||||
public float IntersectionSize; //intersections are expected to be square and rotatable
|
||||
public float IntersectionFromSize;
|
||||
public RoadGeometryTemplateRect[] Intersection4Way;
|
||||
/// <summary>
|
||||
/// Same as Intersection4Way, but inserted when there are only three connecting lines.
|
||||
/// This template represents the y direction being the route for the straight 2 lines, and then x positive being the third (to the right).
|
||||
/// This is appropriately flipped if the intersection is on the left.
|
||||
/// </summary>
|
||||
public RoadGeometryTemplateRect[] Intersection3Way;
|
||||
|
||||
public Tuple<RoadGeometryTemplateSegment, float> GetSegmentForOffset(float offset)
|
||||
{
|
||||
var moffset = offset % RepeatLength;
|
||||
var result = Segments.First();
|
||||
float soFar = 0;
|
||||
|
||||
foreach (var seg in Segments)
|
||||
{
|
||||
if (soFar + seg.Extent > moffset)
|
||||
{
|
||||
//this segment has not ended yet
|
||||
return new Tuple<RoadGeometryTemplateSegment, float>(seg, (soFar + seg.Extent) - moffset);
|
||||
}
|
||||
//otherwise move onto the next
|
||||
soFar += seg.Extent;
|
||||
}
|
||||
return new Tuple<RoadGeometryTemplateSegment, float>(Segments.Last(), (soFar + Segments.Last().Extent) - moffset);
|
||||
}
|
||||
}
|
||||
|
||||
public class RoadGeometryTemplateSegment
|
||||
{
|
||||
public float Extent; //the extent of this segment before moving onto the next segment
|
||||
public RoadGeometryTemplateLine[] Lines; //(x, y) lines to extend into z. x is a horizontal offset depending on the direction of the line
|
||||
|
||||
public RoadGeometryTemplateSegment Next;
|
||||
}
|
||||
|
||||
public class RoadGeometryTemplateLine
|
||||
{
|
||||
public Vector2 Start;
|
||||
public Vector2 End;
|
||||
public Vector2 UVOff;
|
||||
public ushort FloorTile;
|
||||
|
||||
public bool TriangleCap;
|
||||
public int TempIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Liney
|
||||
/// </summary>
|
||||
/// <param name="start">The start of this line.</param>
|
||||
/// <param name="end"></param>
|
||||
/// <param name="floorTile">The floor tile to use for this line.</param>
|
||||
public RoadGeometryTemplateLine(Vector2 start, Vector2 end, ushort floorTile)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
FloorTile = floorTile;
|
||||
|
||||
TriangleCap = End == Vector2.Zero;
|
||||
}
|
||||
|
||||
public RoadGeometryTemplateLine(Vector2 start, Vector2 end, Vector2 uvOff, ushort floorTile) : this(start, end, floorTile)
|
||||
{
|
||||
UVOff = uvOff;
|
||||
}
|
||||
}
|
||||
|
||||
public class RoadGeometryTemplateRect
|
||||
{
|
||||
public Rectangle Rect;
|
||||
public ushort FloorTile;
|
||||
public Vector2 Offset;
|
||||
|
||||
public RoadGeometryTemplateRect(Rectangle rect, ushort floorTile)
|
||||
{
|
||||
Rect = rect;
|
||||
FloorTile = floorTile;
|
||||
}
|
||||
|
||||
public RoadGeometryTemplateRect(Rectangle rect, ushort floorTile, Vector2 offset)
|
||||
{
|
||||
Rect = rect;
|
||||
FloorTile = floorTile;
|
||||
Offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
493
server/tso.common/WorldGeometry/SimplifiedHeightmap.cs
Executable file
493
server/tso.common/WorldGeometry/SimplifiedHeightmap.cs
Executable file
|
@ -0,0 +1,493 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.Common.WorldGeometry
|
||||
{
|
||||
public class SimplifiedHeightmap
|
||||
{
|
||||
public float HeightMultiplier = 1 / 40f;
|
||||
public int Size;
|
||||
public int Size1;
|
||||
public ushort[] Map; //10x resolution of ts1 terrain.
|
||||
public ushort[][] SecondDerivativePyramid;
|
||||
public SimplifiedHeightmap(int size, ushort[] data)
|
||||
{
|
||||
Size = size;
|
||||
Size1 = size - 1;
|
||||
Map = data;
|
||||
BuildSecondDerivative();
|
||||
}
|
||||
|
||||
public void BuildSecondDerivative()
|
||||
{
|
||||
//first, build the full res second derivative map
|
||||
var sd = new ushort[Map.Length];
|
||||
int i = 0;
|
||||
|
||||
//x derivative
|
||||
for (int y = 0; y < Size; y++)
|
||||
{
|
||||
ushort lastValue = Map[i++];
|
||||
ushort firstDerivative = 0;
|
||||
for (int x = 1; x < Size; x++)
|
||||
{
|
||||
ushort value = Map[i];
|
||||
ushort newFirstDerivative = (ushort)Math.Abs(value - lastValue);
|
||||
sd[(i++) - 1] = (ushort)Math.Abs(newFirstDerivative - firstDerivative);
|
||||
firstDerivative = newFirstDerivative;
|
||||
lastValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
//y derivative
|
||||
i = 0;
|
||||
for (int y = 0; y < Size; y++)
|
||||
{
|
||||
i = y;
|
||||
ushort lastValue = Map[i];
|
||||
i += Size;
|
||||
ushort firstDerivative = 0;
|
||||
for (int x = 1; x < Size; x++)
|
||||
{
|
||||
ushort value = Map[i];
|
||||
ushort newFirstDerivative = (ushort)Math.Abs(value - lastValue);
|
||||
sd[i-Size] = Math.Max(sd[i-Size], (ushort)Math.Abs(newFirstDerivative - firstDerivative));
|
||||
i += Size;
|
||||
firstDerivative = newFirstDerivative;
|
||||
lastValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
//build mipLevels
|
||||
var levels = 7; //gen 2x2 through 64x64
|
||||
SecondDerivativePyramid = new ushort[levels][];
|
||||
SecondDerivativePyramid[0] = sd;
|
||||
|
||||
var curLevel = sd;
|
||||
for (int mip = 1; mip < levels; mip++)
|
||||
{
|
||||
var size = (int)Math.Sqrt(curLevel.Length);
|
||||
var mipLevel = new ushort[curLevel.Length / 4];
|
||||
i = 0;
|
||||
for (int y = 0; y < size; y++)
|
||||
{
|
||||
var target = (y / 2) * size / 2;
|
||||
for (int x = 0; x < size; x += 2)
|
||||
{
|
||||
mipLevel[target] = Math.Max(mipLevel[target], Math.Max(curLevel[i++], curLevel[i++]));
|
||||
target++;
|
||||
}
|
||||
}
|
||||
SecondDerivativePyramid[mip] = mipLevel;
|
||||
curLevel = mipLevel;
|
||||
}
|
||||
}
|
||||
|
||||
//second derivitive required to subdivide further.
|
||||
public short[] HighDetailThresholds =
|
||||
{
|
||||
20, //1x1
|
||||
16, //2x2: at least one derivative about half as tall as a block
|
||||
10, //4x4
|
||||
7, //8x8
|
||||
4, //16x16
|
||||
2, //32x32
|
||||
1, //64x64
|
||||
};
|
||||
|
||||
List<HeightmapChunk> Chunks;
|
||||
|
||||
private Dictionary<Point, int> PointToIndex;
|
||||
public List<int> Indices;
|
||||
public List<Vector3> Vertices;
|
||||
|
||||
//how the simplification works:
|
||||
//we generate a "maximum second derivative" map from the heightmap.
|
||||
//we then create a maximum value image pyramid with the maximum value in the 4 pixels beneath
|
||||
//when generating the mesh, we fall into quadrants below if the second derivative is above a threshold.
|
||||
|
||||
public void GenerateFullTree()
|
||||
{
|
||||
//how the algorithm works:
|
||||
//build a base structure with quad trees
|
||||
Chunks = new List<HeightmapChunk>();
|
||||
|
||||
var levels = SecondDerivativePyramid.Length;
|
||||
var chunkSize = 1 << levels;
|
||||
|
||||
var cw = Size / chunkSize;
|
||||
|
||||
for (int y = 0; y < Size; y += chunkSize)
|
||||
{
|
||||
for (int x = 0; x < Size; x += chunkSize)
|
||||
{
|
||||
var chunk = new HeightmapChunk(new Rectangle(x, y, chunkSize, chunkSize), levels,
|
||||
(x == 0) ? null : Chunks.Last(),
|
||||
(y == 0) ? null : Chunks[Chunks.Count - cw]);
|
||||
Chunks.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
var thresholds = HighDetailThresholds;
|
||||
var toTraverse = new Queue<HeightmapQuadTreeNode>(Chunks);
|
||||
while (toTraverse.Count > 0)
|
||||
{
|
||||
var node = toTraverse.Dequeue();
|
||||
var mipLevel = node.MipLevel;
|
||||
var sd = SecondDerivativePyramid[mipLevel-1];
|
||||
var mipWidth = Size >> (mipLevel-1);
|
||||
var pos = (node.Range.X >> (mipLevel - 1)) + (node.Range.Y >> (mipLevel - 1)) * mipWidth;
|
||||
|
||||
//check the max second derivative of the 4 potential derivatives.
|
||||
var threshold = HighDetailThresholds[mipLevel - 1];
|
||||
if (sd[pos] >= threshold) //top left
|
||||
{
|
||||
var newNode = node.GetOrAdd(0, true);
|
||||
if (mipLevel > 1) toTraverse.Enqueue(newNode);
|
||||
}
|
||||
if (sd[pos+1] >= threshold) //top right
|
||||
{
|
||||
var newNode = node.GetOrAdd(1, true);
|
||||
if (mipLevel > 1) toTraverse.Enqueue(newNode);
|
||||
}
|
||||
if (sd[pos + mipWidth] >= threshold) //bottom left
|
||||
{
|
||||
var newNode = node.GetOrAdd(2, true);
|
||||
if (mipLevel > 1) toTraverse.Enqueue(newNode);
|
||||
}
|
||||
if (sd[pos + mipWidth + 1] >= threshold) //top right
|
||||
{
|
||||
var newNode = node.GetOrAdd(3, true);
|
||||
if (mipLevel > 1) toTraverse.Enqueue(newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GenerateMesh()
|
||||
{
|
||||
//traverse the chunk tree, generating meshes for each.
|
||||
PointToIndex = new Dictionary<Point, int>();
|
||||
Vertices = new List<Vector3>();
|
||||
Indices = new List<int>();
|
||||
|
||||
foreach (var chunk in Chunks)
|
||||
{
|
||||
chunk.Triangulate(this);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetVertex(Point pt)
|
||||
{
|
||||
int index;
|
||||
if (!PointToIndex.TryGetValue(pt, out index))
|
||||
{
|
||||
index = Vertices.Count;
|
||||
var x = Math.Min(Size1, pt.X);
|
||||
var y = Math.Min(Size1, pt.Y);
|
||||
Vertices.Add(new Vector3(pt.X, Map[x + y * Size] * HeightMultiplier, pt.Y));
|
||||
PointToIndex[pt] = index;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
public void AddTri(int i1, int i2, int i3)
|
||||
{
|
||||
Indices.Add(i1);
|
||||
Indices.Add(i2);
|
||||
Indices.Add(i3);
|
||||
}
|
||||
}
|
||||
|
||||
public class HeightmapQuadTreeNode
|
||||
{
|
||||
public bool Reduced;
|
||||
public int MipLevel;
|
||||
public int ParentInd = -1; //the
|
||||
HeightmapQuadTreeNode Parent;
|
||||
public Rectangle Range;
|
||||
public HeightmapQuadTreeNode[] Children = new HeightmapQuadTreeNode[]
|
||||
{
|
||||
null, null, null, null //top left, top right, bottom left, bottom right (row order)
|
||||
};
|
||||
|
||||
public HeightmapQuadTreeNode(HeightmapQuadTreeNode parent, Rectangle range)
|
||||
{
|
||||
Parent = parent;
|
||||
Range = range;
|
||||
MipLevel = (parent?.MipLevel ?? 6) - 1;
|
||||
}
|
||||
|
||||
public HeightmapQuadTreeNode GetOrAdd(int index, bool doSpread)
|
||||
{
|
||||
HeightmapQuadTreeNode result;
|
||||
if (Children[index] == null)
|
||||
{
|
||||
var rect = Range;
|
||||
rect.Width /= 2;
|
||||
rect.Height /= 2;
|
||||
if ((index % 2) == 1) rect.X += rect.Width;
|
||||
if (index > 1) rect.Y += rect.Height;
|
||||
result = new HeightmapQuadTreeNode(this, rect);
|
||||
result.ParentInd = index;
|
||||
Children[index] = result;
|
||||
doSpread = true;
|
||||
} else {
|
||||
result = Children[index];
|
||||
}
|
||||
|
||||
if (doSpread) {
|
||||
if (Parent != null)
|
||||
{
|
||||
//find adjacent quad to add to.
|
||||
//for example if we are in index 0 (top left), make sure there is:
|
||||
// - a subdivision in the top right (1) of the tile to our left,
|
||||
// - a subdivision in the bottom left (2) of the tile above us,
|
||||
// - a subdivision in the bottom right (3) of the tile above and left
|
||||
|
||||
//index 1 (top right
|
||||
switch (index)
|
||||
{
|
||||
case 0: //top left
|
||||
{
|
||||
var left = result.FindOrCreateQuadInDirection(3);
|
||||
var up = result.FindOrCreateQuadInDirection(0);
|
||||
if (up != null) up.FindOrCreateQuadInDirection(3);
|
||||
break;
|
||||
}
|
||||
case 1: //top right
|
||||
{
|
||||
var right = result.FindOrCreateQuadInDirection(1);
|
||||
var up = result.FindOrCreateQuadInDirection(0);
|
||||
if (up != null) up.FindOrCreateQuadInDirection(1);
|
||||
break;
|
||||
}
|
||||
case 2: //bottom left
|
||||
{
|
||||
var left = result.FindOrCreateQuadInDirection(3);
|
||||
var bottom = result.FindOrCreateQuadInDirection(2);
|
||||
if (bottom != null) bottom.FindOrCreateQuadInDirection(3);
|
||||
break;
|
||||
}
|
||||
case 3: //bottom right
|
||||
{
|
||||
var right = result.FindOrCreateQuadInDirection(1);
|
||||
var bottom = result.FindOrCreateQuadInDirection(2);
|
||||
if (bottom != null) bottom.FindOrCreateQuadInDirection(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual HeightmapQuadTreeNode FindOrCreateQuadInDirection(int dir)
|
||||
{
|
||||
//dir: up, right, down, left
|
||||
|
||||
if (Parent == null) return null;
|
||||
switch (dir)
|
||||
{
|
||||
case 0: //up
|
||||
//if we're on the bottom row, finding the quad is easy.
|
||||
if (ParentInd > 1)
|
||||
{
|
||||
return Parent.GetOrAdd(ParentInd - 2, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//on the top row. we need to break out to add a quad above.
|
||||
var aboveParent = Parent.FindOrCreateQuadInDirection(dir);
|
||||
//our adjacent should be on the above parent's bottom row.
|
||||
return aboveParent?.GetOrAdd(ParentInd + 2, false);
|
||||
}
|
||||
case 1: //right
|
||||
//if we're on the left row, finding the quad is easy.
|
||||
if ((ParentInd % 2) == 0)
|
||||
{
|
||||
return Parent.GetOrAdd(ParentInd + 1, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//on the right row. we need to break out to add a quad above.
|
||||
var rightParent = Parent.FindOrCreateQuadInDirection(dir);
|
||||
//our adjacent should be on the right parent's left row.
|
||||
return rightParent?.GetOrAdd(ParentInd - 1, false);
|
||||
}
|
||||
case 2: //down
|
||||
//if we're on the top row, finding the quad is easy.
|
||||
if (ParentInd < 2)
|
||||
{
|
||||
return Parent.GetOrAdd(ParentInd + 2, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//on the right row. we need to break out to add a quad above.
|
||||
var belowParent = Parent.FindOrCreateQuadInDirection(dir);
|
||||
//our adjacent should be on the below parent's top row.
|
||||
return belowParent?.GetOrAdd(ParentInd - 2, false);
|
||||
}
|
||||
case 3: //left
|
||||
//if we're on the right row, finding the quad is easy.
|
||||
if ((ParentInd % 2) == 1)
|
||||
{
|
||||
return Parent.GetOrAdd(ParentInd - 1, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//on the left row. we need to break out to add a quad above.
|
||||
var leftParent = Parent.FindOrCreateQuadInDirection(dir);
|
||||
//our adjacent should be on the left parent's right row.
|
||||
return leftParent?.GetOrAdd(ParentInd + 1, false);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Triangulate(SimplifiedHeightmap parent)
|
||||
{
|
||||
var cTriangulated = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
child.Triangulate(parent);
|
||||
cTriangulated++;
|
||||
}
|
||||
}
|
||||
if (cTriangulated == 0)
|
||||
{
|
||||
//no children means we are a leaf. triangulate, cause nobody else is doing it for me.
|
||||
var lt = parent.GetVertex(Range.Location);
|
||||
var rt = parent.GetVertex(Range.Location + new Point(Range.Width, 0));
|
||||
var rb = parent.GetVertex(Range.Location + Range.Size);
|
||||
var lb = parent.GetVertex(Range.Location + new Point(0, Range.Height));
|
||||
|
||||
parent.AddTri(lt, rt, rb);
|
||||
parent.AddTri(lt, rb, lb);
|
||||
}
|
||||
else if (cTriangulated < 4)
|
||||
{
|
||||
//complex: we have children, but we also need to make our own geometry.
|
||||
var ctr = parent.GetVertex(Range.Location + new Point(Range.Width/2, Range.Height/2));
|
||||
var lt = parent.GetVertex(Range.Location);
|
||||
var rt = parent.GetVertex(Range.Location + new Point(Range.Width, 0));
|
||||
var rb = parent.GetVertex(Range.Location + Range.Size);
|
||||
var lb = parent.GetVertex(Range.Location + new Point(0, Range.Height));
|
||||
if (Children[0] == null) //from top left
|
||||
{
|
||||
if (Children[1] == null) //top right
|
||||
{
|
||||
//triangle lt to rt: \/
|
||||
parent.AddTri(lt, rt, ctr);
|
||||
}
|
||||
else
|
||||
{
|
||||
//triangle lt to mt: \|
|
||||
var mt = parent.GetVertex(Range.Location + new Point(Range.Width / 2, 0));
|
||||
parent.AddTri(lt, mt, ctr);
|
||||
}
|
||||
|
||||
if (Children[2] == null) //bottom left
|
||||
{
|
||||
//triangle lt to lb: \
|
||||
// /
|
||||
parent.AddTri(lt, ctr, lb);
|
||||
}
|
||||
else
|
||||
{
|
||||
//triangle lt to lm: _\
|
||||
var lm = parent.GetVertex(Range.Location + new Point(0, Range.Height / 2));
|
||||
parent.AddTri(lt, ctr, lm);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Children[1] == null) //top right but no top left
|
||||
{
|
||||
//triangle mt to rt: |/
|
||||
var mt = parent.GetVertex(Range.Location + new Point(Range.Width / 2, 0));
|
||||
parent.AddTri(mt, rt, ctr);
|
||||
}
|
||||
|
||||
if (Children[2] == null) //bottom left but no top left
|
||||
{
|
||||
//triangle lm to lb: _
|
||||
// /
|
||||
var lm = parent.GetVertex(Range.Location + new Point(0, Range.Height / 2));
|
||||
parent.AddTri(lm, ctr, lb);
|
||||
}
|
||||
}
|
||||
|
||||
if (Children[3] == null) //from bottom right
|
||||
{
|
||||
if (Children[1] == null) //top right
|
||||
{
|
||||
//triangle rt to rb: /
|
||||
// \
|
||||
parent.AddTri(rt, rb, ctr);
|
||||
}
|
||||
else
|
||||
{
|
||||
//triangle rm to rb: _
|
||||
// \
|
||||
var rm = parent.GetVertex(Range.Location + new Point(Range.Width, Range.Height / 2));
|
||||
parent.AddTri(rm, rb, ctr);
|
||||
}
|
||||
|
||||
if (Children[2] == null) //bottom left
|
||||
{
|
||||
//triangle lb to rb: /\
|
||||
parent.AddTri(lb, ctr, rb);
|
||||
}
|
||||
else
|
||||
{
|
||||
//triangle mb to rb: |\
|
||||
var mb = parent.GetVertex(Range.Location + new Point(Range.Width / 2, Range.Height));
|
||||
parent.AddTri(mb, ctr, rb);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Children[1] == null) //top right, no bottom right
|
||||
{
|
||||
//triangle rt to rm: /_
|
||||
var rm = parent.GetVertex(Range.Location + new Point(Range.Width, Range.Height / 2));
|
||||
parent.AddTri(rt, rm, ctr);
|
||||
}
|
||||
|
||||
if (Children[2] == null) //bottom left, no bottom right
|
||||
{
|
||||
//triangle mb to lb: /|
|
||||
var mb = parent.GetVertex(Range.Location + new Point(Range.Width / 2, Range.Height));
|
||||
parent.AddTri(mb, lb, ctr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class HeightmapChunk : HeightmapQuadTreeNode
|
||||
{
|
||||
public HeightmapChunk[] Adjacent = new HeightmapChunk[]
|
||||
{
|
||||
null, null, null, null //up, right, down, left
|
||||
};
|
||||
|
||||
public HeightmapChunk(Rectangle range, int mipLevel, HeightmapChunk left, HeightmapChunk top) : base(null, range)
|
||||
{
|
||||
Adjacent[3] = left;
|
||||
Adjacent[0] = top;
|
||||
if (left != null) left.Adjacent[1] = this;
|
||||
if (top != null) top.Adjacent[2] = this;
|
||||
MipLevel = mipLevel;
|
||||
}
|
||||
|
||||
public override HeightmapQuadTreeNode FindOrCreateQuadInDirection(int dir)
|
||||
{
|
||||
return Adjacent[dir];
|
||||
}
|
||||
}
|
||||
}
|
230
server/tso.common/WorldGeometry/TS1RoadTemplates.cs
Executable file
230
server/tso.common/WorldGeometry/TS1RoadTemplates.cs
Executable file
|
@ -0,0 +1,230 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FSO.Common.WorldGeometry
|
||||
{
|
||||
public static class TS1RoadTemplates
|
||||
{
|
||||
private static ushort ROAD_TILE = 9;
|
||||
private static ushort ROAD_LINE_LT_RB = 10;
|
||||
private static ushort ROAD_LINE_LB_RT = 11;
|
||||
private static ushort PAVEMENT_TILE = 12;
|
||||
|
||||
private static ushort DOWNTOWN_PAVEMENT_LIGHT = 352;
|
||||
private static ushort DOWNTOWN_GRATE_LIGHT = 350;
|
||||
private static ushort DOWNTOWN_MANHOLE_LIGHT = 351;
|
||||
|
||||
private static ushort DOWNTOWN_PAVEMENT_DARK = 355;
|
||||
private static ushort DOWNTOWN_GRATE_DARK = 353;
|
||||
private static ushort DOWNTOWN_MANHOLE_DARK = 354;
|
||||
|
||||
private static ushort VACATION_ROAD = 359; //awfully flat
|
||||
|
||||
private static Vector2 Flat(float xOff)
|
||||
{
|
||||
return new Vector2(xOff, 0);
|
||||
}
|
||||
|
||||
public static RoadGeometryTemplate OLD_TOWN = new RoadGeometryTemplate()
|
||||
{
|
||||
Segments = new RoadGeometryTemplateSegment[]
|
||||
{
|
||||
//without middle line (3 tiles long)
|
||||
new RoadGeometryTemplateSegment()
|
||||
{
|
||||
Extent = 3f,
|
||||
Lines = new RoadGeometryTemplateLine[]
|
||||
{
|
||||
new RoadGeometryTemplateLine(Flat(-5.5f), Flat(-4.5f), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateLine(Flat(-3.5f), Flat(3.5f), ROAD_TILE),
|
||||
new RoadGeometryTemplateLine(Flat(4.5f), Flat(5.5f), PAVEMENT_TILE),
|
||||
}
|
||||
},
|
||||
|
||||
//with middle line (3 tiles long)
|
||||
new RoadGeometryTemplateSegment()
|
||||
{
|
||||
Extent = 3f,
|
||||
Lines = new RoadGeometryTemplateLine[]
|
||||
{
|
||||
new RoadGeometryTemplateLine(Flat(-5.5f), Flat(-4.5f), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateLine(Flat(-3.5f), Flat(-0.5f), ROAD_TILE),
|
||||
new RoadGeometryTemplateLine(Flat(-0.5f), Flat(0.5f), ROAD_LINE_LT_RB),
|
||||
new RoadGeometryTemplateLine(Flat(0.5f), Flat(3.5f), ROAD_TILE),
|
||||
new RoadGeometryTemplateLine(Flat(4.5f), Flat(5.5f), PAVEMENT_TILE),
|
||||
}
|
||||
},
|
||||
},
|
||||
RepeatLength = 6f,
|
||||
EndLines = new RoadGeometryTemplateLine[]
|
||||
{
|
||||
new RoadGeometryTemplateLine(Flat(-5.5f), Flat(-4.5f), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateLine(Flat(-3.5f), Flat(0f), ROAD_TILE),
|
||||
},
|
||||
EndRepeats = 17,
|
||||
|
||||
IntersectionSize = 13, //7 wide road, 1 tile gap on each side, 1 tile pavement on each side, 1 tile gap again
|
||||
IntersectionFromSize = 13,
|
||||
Intersection4Way = new RoadGeometryTemplateRect[]
|
||||
{
|
||||
//pavement
|
||||
new RoadGeometryTemplateRect(new Rectangle(1, 0, 1, 3), PAVEMENT_TILE), //top left cross
|
||||
new RoadGeometryTemplateRect(new Rectangle(0, 1, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(2, 1, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 0, 1, 3), PAVEMENT_TILE), //top right cross
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 1, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 1, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(1, 10, 1, 3), PAVEMENT_TILE), //bottom left cross
|
||||
new RoadGeometryTemplateRect(new Rectangle(0, 11, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(2, 11, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 10, 1, 3), PAVEMENT_TILE), //bottom right cross
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 11, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 11, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
//road
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 3, 7, 7), ROAD_TILE), //center
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 1, 7, 1), ROAD_TILE), //top
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 11, 7, 1), ROAD_TILE), //bottom
|
||||
new RoadGeometryTemplateRect(new Rectangle(1, 3, 1, 7), ROAD_TILE), //left
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 3, 1, 7), ROAD_TILE), //right
|
||||
|
||||
//road lines (vertical)
|
||||
new RoadGeometryTemplateRect(new Rectangle(0, 3, 1, 7), ROAD_LINE_LT_RB),
|
||||
new RoadGeometryTemplateRect(new Rectangle(2, 3, 1, 7), ROAD_LINE_LT_RB),
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 3, 1, 7), ROAD_LINE_LT_RB),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 3, 1, 7), ROAD_LINE_LT_RB),
|
||||
|
||||
//road lines (horizontal)
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 0, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 2, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 10, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 12, 7, 1), ROAD_LINE_LB_RT),
|
||||
|
||||
},
|
||||
|
||||
Intersection3Way = new RoadGeometryTemplateRect[]
|
||||
{
|
||||
//pavement
|
||||
new RoadGeometryTemplateRect(new Rectangle(1, 0, 1, 13), PAVEMENT_TILE), //left pavement (with 2 joins)
|
||||
new RoadGeometryTemplateRect(new Rectangle(2, 1, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(2, 11, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 0, 1, 3), PAVEMENT_TILE), //top right cross
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 1, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 1, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 10, 1, 3), PAVEMENT_TILE), //bottom right cross
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 11, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 11, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
//road
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 3, 7, 7), ROAD_TILE), //center
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 1, 7, 1), ROAD_TILE), //top
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 11, 7, 1), ROAD_TILE), //bottom
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 3, 1, 7), ROAD_TILE), //right
|
||||
|
||||
//road lines (vertical)
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 3, 1, 7), ROAD_LINE_LT_RB),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 3, 1, 7), ROAD_LINE_LT_RB),
|
||||
|
||||
//road lines (horizontal)
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 0, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 2, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 10, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 12, 7, 1), ROAD_LINE_LB_RT),
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
public static RoadGeometryTemplate OLD_TOWN_DUAL = new RoadGeometryTemplate()
|
||||
{
|
||||
Segments = new RoadGeometryTemplateSegment[]
|
||||
{
|
||||
//this road type does not have a middle line.
|
||||
new RoadGeometryTemplateSegment()
|
||||
{
|
||||
Extent = 3f,
|
||||
Lines = new RoadGeometryTemplateLine[]
|
||||
{
|
||||
new RoadGeometryTemplateLine(Flat(-8.5f), Flat(-7.5f), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateLine(Flat(-6.5f), Flat(-2.5f), ROAD_TILE),
|
||||
|
||||
new RoadGeometryTemplateLine(Flat(-2f), Flat(-1f), new Vector2(0.5f, 0), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateLine(Flat(1f), Flat(2f), new Vector2(0.5f, 0), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateLine(Flat(2.5f), Flat(6.5f), ROAD_TILE),
|
||||
|
||||
new RoadGeometryTemplateLine(Flat(7.5f), Flat(8.5f), PAVEMENT_TILE),
|
||||
}
|
||||
},
|
||||
},
|
||||
RepeatLength = 3f,
|
||||
EndLines = new RoadGeometryTemplateLine[]
|
||||
{
|
||||
new RoadGeometryTemplateLine(Flat(-8.5f), Flat(-7.5f), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateLine(Flat(-6.5f), Flat(-2.5f), ROAD_TILE),
|
||||
|
||||
new RoadGeometryTemplateLine(Flat(-2f), Flat(-1f), new Vector2(0.5f, 0), PAVEMENT_TILE),
|
||||
},
|
||||
EndRepeats = 22,
|
||||
|
||||
IntersectionSize = 19, //7 wide road, 1 tile gap on each side, 1 tile pavement on each side, 1 tile gap again
|
||||
IntersectionFromSize = 13, //used for 3 way intersection on special road types. here it is the width of the normal road
|
||||
|
||||
//UNUSED
|
||||
Intersection4Way = OLD_TOWN.Intersection4Way,
|
||||
|
||||
Intersection3Way = new RoadGeometryTemplateRect[]
|
||||
{ //19 tall, 13 wide
|
||||
//pavement
|
||||
new RoadGeometryTemplateRect(new Rectangle(1, 0, 1, 19), PAVEMENT_TILE), //left pavement (with 2 joins)
|
||||
new RoadGeometryTemplateRect(new Rectangle(2, 1, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(2, 17, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 0, 1, 3), PAVEMENT_TILE), //top right cross
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 1, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 1, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 16, 1, 3), PAVEMENT_TILE), //bottom right cross
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 17, 1, 1), PAVEMENT_TILE),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 17, 1, 1), PAVEMENT_TILE),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 7, 1, 5), PAVEMENT_TILE), //right path
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 7, 1, 1), PAVEMENT_TILE, new Vector2(0, 0.5f)), //right off1
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 10, 1, 1), PAVEMENT_TILE, new Vector2(0, 0.5f)), //right off2
|
||||
|
||||
//road
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 3, 7, 13), ROAD_TILE), //center
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 1, 7, 1), ROAD_TILE), //top
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 17, 7, 1), ROAD_TILE), //bottom
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 3, 1, 4), ROAD_TILE), //right
|
||||
new RoadGeometryTemplateRect(new Rectangle(11, 12, 1, 4), ROAD_TILE), //right
|
||||
|
||||
//road lines (vertical)
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 3, 1, 4), ROAD_LINE_LT_RB),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 3, 1, 4), ROAD_LINE_LT_RB),
|
||||
|
||||
new RoadGeometryTemplateRect(new Rectangle(10, 12, 1, 4), ROAD_LINE_LT_RB),
|
||||
new RoadGeometryTemplateRect(new Rectangle(12, 12, 1, 4), ROAD_LINE_LT_RB),
|
||||
|
||||
//road lines (horizontal)
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 0, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 2, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 16, 7, 1), ROAD_LINE_LB_RT),
|
||||
new RoadGeometryTemplateRect(new Rectangle(3, 18, 7, 1), ROAD_LINE_LB_RT),
|
||||
}
|
||||
};
|
||||
|
||||
public static List<RoadGeometryTemplate> OLD_TOWN_DEFAULT_TEMPLATES = new List<RoadGeometryTemplate>()
|
||||
{
|
||||
OLD_TOWN,
|
||||
OLD_TOWN_DUAL
|
||||
};
|
||||
}
|
||||
}
|
359
server/tso.common/WorldGeometry/Utils/TriangleSet.cs
Executable file
359
server/tso.common/WorldGeometry/Utils/TriangleSet.cs
Executable file
|
@ -0,0 +1,359 @@
|
|||
using FSO.Common.Model;
|
||||
using FSO.Common.WorldGeometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FSO.SimAntics.Model.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// (copied from VMObstacleSet)
|
||||
/// A k-d Tree for looking up rectangle intersections
|
||||
/// Ideally much faster lookups for routing rectangular cover, avatar and object movement. O(log n) vs O(n)
|
||||
///
|
||||
/// Concerns:
|
||||
/// - Tree balancing: a random insert may be the best solution, as algorithms for this can be quite complex.
|
||||
/// - Should be mutable, as rect cover routing will add new entries. We also may want to add or remove elements from a "static" set.
|
||||
/// - Tree cloning: wall and object sets would be nice, but for routing ideally we want to add new free-rects to the set dynamically. This means we need to clone the tree.
|
||||
/// - Return true if ANY found, or return ALL found. First useful for routing, second for checking collision validity.
|
||||
/// </summary>
|
||||
public class BaseTriangleSet
|
||||
{
|
||||
public VMObstacleSetNode[] Nodes;
|
||||
protected List<int> FreeList = new List<int>();
|
||||
protected int PoolInd = 0;
|
||||
public int Root = -1;
|
||||
public int Count;
|
||||
|
||||
public BaseTriangleSet()
|
||||
{
|
||||
InitNodes(64);
|
||||
}
|
||||
|
||||
public BaseTriangleSet(BaseTriangleSet last)
|
||||
{
|
||||
if (last.Root != -1)
|
||||
{
|
||||
Count = last.Count;
|
||||
Nodes = (VMObstacleSetNode[])last.Nodes.Clone();
|
||||
Root = last.Root;
|
||||
FreeList = last.FreeList.ToList();
|
||||
PoolInd = last.PoolInd;
|
||||
}
|
||||
else
|
||||
{
|
||||
InitNodes(64);
|
||||
}
|
||||
}
|
||||
|
||||
public BaseTriangleSet(IEnumerable<BaseMeshTriangle> obstacles)
|
||||
{
|
||||
InitNodes(obstacles.Count());
|
||||
foreach (var obstacle in obstacles)
|
||||
Add(obstacle);
|
||||
}
|
||||
|
||||
private void InitNodes(int capacity)
|
||||
{
|
||||
if (Nodes == null)
|
||||
{
|
||||
Nodes = new VMObstacleSetNode[capacity];
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Resize(ref Nodes, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetNode()
|
||||
{
|
||||
if (FreeList.Count > 0)
|
||||
{
|
||||
var free = FreeList.Last();
|
||||
FreeList.RemoveAt(FreeList.Count - 1);
|
||||
return free;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PoolInd++;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetNode(IntersectRectDimension dir, BaseMeshTriangle rect)
|
||||
{
|
||||
var ind = GetNode();
|
||||
Nodes[ind] = new VMObstacleSetNode()
|
||||
{
|
||||
Dimension = dir,
|
||||
Rect = rect,
|
||||
LeftChild = -1,
|
||||
RightChild = -1,
|
||||
Index = ind,
|
||||
|
||||
x1 = rect.x1,
|
||||
x2 = rect.x2,
|
||||
y1 = rect.y1,
|
||||
y2 = rect.y2
|
||||
};
|
||||
return ind;
|
||||
}
|
||||
|
||||
private void Reclaim(int index)
|
||||
{
|
||||
if (index == PoolInd - 1) PoolInd--;
|
||||
else FreeList.Add(index);
|
||||
}
|
||||
|
||||
public void Add(BaseMeshTriangle rect)
|
||||
{
|
||||
if (PoolInd >= Nodes.Length && FreeList.Count == 0) InitNodes(Nodes.Length * 2);
|
||||
Count++;
|
||||
if (Root == -1)
|
||||
{
|
||||
Root = GetNode(IntersectRectDimension.Left, rect);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddAsChild(ref Nodes[Root], rect);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAsChild(ref VMObstacleSetNode node, BaseMeshTriangle rect)
|
||||
{
|
||||
bool rightSide = false;
|
||||
switch (node.Dimension)
|
||||
{
|
||||
case IntersectRectDimension.Top:
|
||||
rightSide = rect.y1 > node.Rect.y1; break;
|
||||
case IntersectRectDimension.Left:
|
||||
rightSide = rect.x1 > node.Rect.x1; break;
|
||||
case IntersectRectDimension.Bottom:
|
||||
rightSide = rect.y2 > node.Rect.y2; break;
|
||||
case IntersectRectDimension.Right:
|
||||
rightSide = rect.x2 > node.Rect.x2; break;
|
||||
}
|
||||
if (rightSide)
|
||||
{
|
||||
if (node.RightChild != -1) AddAsChild(ref Nodes[node.RightChild], rect);
|
||||
else
|
||||
{
|
||||
node.RightChild = GetNode((IntersectRectDimension)(((int)node.Dimension + 1) % 4), rect);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (node.LeftChild != -1) AddAsChild(ref Nodes[node.LeftChild], rect);
|
||||
else
|
||||
{
|
||||
node.LeftChild = GetNode((IntersectRectDimension)(((int)node.Dimension + 1) % 4), rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RecursiveReAdd(VMObstacleSetNode node)
|
||||
{
|
||||
Count--;
|
||||
Reclaim(node.Index);
|
||||
Add(node.Rect);
|
||||
if (node.LeftChild != -1) RecursiveReAdd(Nodes[node.LeftChild]);
|
||||
if (node.RightChild != -1) RecursiveReAdd(Nodes[node.RightChild]);
|
||||
}
|
||||
|
||||
public bool SearchForIntersect(BaseMeshTriangle rect)
|
||||
{
|
||||
if (Root == -1) return false;
|
||||
else
|
||||
{
|
||||
return SearchForIntersect(ref Nodes[Root], rect);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SearchForIntersect(ref VMObstacleSetNode node, BaseMeshTriangle rect)
|
||||
{
|
||||
if (node.Intersects(rect)) return true;
|
||||
//search in child nodes.
|
||||
int dontSearch = 0;
|
||||
switch (node.Dimension)
|
||||
{
|
||||
case IntersectRectDimension.Top:
|
||||
dontSearch = (rect.y2 <= node.y1) ? 2 : 0; break; //if true, do not have to search right (where top greater)
|
||||
case IntersectRectDimension.Left:
|
||||
dontSearch = (rect.x2 <= node.x1) ? 2 : 0; break; //if true, do not have to search right (where left greater)
|
||||
case IntersectRectDimension.Bottom:
|
||||
dontSearch = (rect.y1 >= node.y2) ? 1 : 0; break; //if true, do not have to search left (where bottom less)
|
||||
case IntersectRectDimension.Right:
|
||||
dontSearch = (rect.x1 >= node.x2) ? 1 : 0; break; //if true, do not have to search left (where right less)
|
||||
}
|
||||
|
||||
//may need to search both :'( won't happen often with our small rectangles over large space though.
|
||||
return ((dontSearch != 1 && node.LeftChild != -1 && SearchForIntersect(ref Nodes[node.LeftChild], rect))
|
||||
|| (dontSearch != 2 && node.RightChild != -1 && SearchForIntersect(ref Nodes[node.RightChild], rect)));
|
||||
}
|
||||
|
||||
public List<BaseMeshTriangle> AllIntersect(BaseMeshTriangle rect)
|
||||
{
|
||||
var result = new List<BaseMeshTriangle>();
|
||||
if (Root == -1) return result;
|
||||
else
|
||||
{
|
||||
AllIntersect(ref Nodes[Root], rect, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void AllIntersect(ref VMObstacleSetNode node, BaseMeshTriangle rect, List<BaseMeshTriangle> result)
|
||||
{
|
||||
if (node.Intersects(rect)) result.Add(node.Rect);
|
||||
//search in child nodes.
|
||||
int dontSearch = 0;
|
||||
switch (node.Dimension)
|
||||
{
|
||||
case IntersectRectDimension.Top:
|
||||
dontSearch = (rect.y2 <= node.y1) ? 2 : 0; break; //if true, do not have to search right (where top greater)
|
||||
case IntersectRectDimension.Left:
|
||||
dontSearch = (rect.x2 <= node.x1) ? 2 : 0; break; //if true, do not have to search right (where left greater)
|
||||
case IntersectRectDimension.Bottom:
|
||||
dontSearch = (rect.y1 >= node.y2) ? 1 : 0; break; //if true, do not have to search left (where bottom less)
|
||||
case IntersectRectDimension.Right:
|
||||
dontSearch = (rect.x1 >= node.x2) ? 1 : 0; break; //if true, do not have to search left (where right less)
|
||||
}
|
||||
|
||||
//may need to search both :'( won't happen often with our small rectangles over large space though.
|
||||
//if (node.LeftChild != -1) AllIntersect(ref Nodes[node.LeftChild], rect, result);
|
||||
//if (node.RightChild != -1) AllIntersect(ref Nodes[node.RightChild], rect, result);
|
||||
|
||||
if (dontSearch != 1 && node.LeftChild != -1) AllIntersect(ref Nodes[node.LeftChild], rect, result);
|
||||
if (dontSearch != 2 && node.RightChild != -1) AllIntersect(ref Nodes[node.RightChild], rect, result);
|
||||
}
|
||||
|
||||
public List<BaseMeshTriangle> OnEdge(BaseMeshTriangle rect)
|
||||
{
|
||||
var result = new List<BaseMeshTriangle>();
|
||||
if (Root == -1) return result;
|
||||
else
|
||||
{
|
||||
OnEdge(ref Nodes[Root], rect, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEdge(ref VMObstacleSetNode node, BaseMeshTriangle rect, List<BaseMeshTriangle> result)
|
||||
{
|
||||
if (node.OnEdge(rect)) result.Add(node.Rect);
|
||||
//search in child nodes.
|
||||
//binary search to find equal opposing edges.
|
||||
int dontSearch = 0;
|
||||
switch (node.Dimension)
|
||||
{
|
||||
case IntersectRectDimension.Top:
|
||||
dontSearch = (rect.y2 < node.y1) ? 2 : 0; break; //if true, do not have to search right (where top greater)
|
||||
case IntersectRectDimension.Left:
|
||||
dontSearch = (rect.x2 < node.x1) ? 2 : 0; break; //if true, do not have to search right (where left greater)
|
||||
case IntersectRectDimension.Bottom:
|
||||
dontSearch = (rect.y1 > node.y2) ? 1 : 0; break; //if true, do not have to search left (where bottom less)
|
||||
case IntersectRectDimension.Right:
|
||||
dontSearch = (rect.x1 > node.x2) ? 1 : 0; break; //if true, do not have to search left (where right less)
|
||||
}
|
||||
|
||||
//may need to search both :'( won't happen often with our small rectangles over large space though.
|
||||
//if (node.LeftChild != -1) OnEdge(ref Nodes[node.LeftChild], rect, result);
|
||||
//if (node.RightChild != -1) OnEdge(ref Nodes[node.RightChild], rect, result);
|
||||
|
||||
if (dontSearch != 1 && node.LeftChild != -1) OnEdge(ref Nodes[node.LeftChild], rect, result);
|
||||
if (dontSearch != 2 && node.RightChild != -1) OnEdge(ref Nodes[node.RightChild], rect, result);
|
||||
}
|
||||
|
||||
public static BaseTriangleSet RoughBalanced(List<BaseMeshTriangle> input)
|
||||
{
|
||||
//roughly attempts to balance the set.
|
||||
//...currently by random shuffle. at least it's deterministic?
|
||||
var rand = new Random(1);
|
||||
for (int i = 1; i < input.Count; i++)
|
||||
{
|
||||
var swap = input[i - 1];
|
||||
var ind = rand.Next(input.Count - i) + i;
|
||||
input[i - 1] = input[ind];
|
||||
input[ind] = swap;
|
||||
}
|
||||
|
||||
return new BaseTriangleSet(input);
|
||||
}
|
||||
|
||||
/*
|
||||
public bool Delete(VMEntityObstacle rect)
|
||||
{
|
||||
if (Root == -1) return false;
|
||||
else
|
||||
{
|
||||
var result = Delete(ref Nodes[Root], rect, ref Nodes[Root]);
|
||||
if (result) { Count--; }
|
||||
return result;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public bool Delete(ref VMObstacleSetNode node, VMEntityObstacle rect, ref VMObstacleSetNode parent)
|
||||
{
|
||||
if (rect.Parent == (node.Rect as VMEntityObstacle).Parent)
|
||||
{
|
||||
if (parent.Index == node.Index)
|
||||
{
|
||||
Root = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent.LeftChild == node.Index) parent.LeftChild = -1;
|
||||
if (parent.RightChild == node.Index) parent.RightChild = -1;
|
||||
}
|
||||
if (node.LeftChild != -1) RecursiveReAdd(Nodes[node.LeftChild]);
|
||||
if (node.RightChild != -1) RecursiveReAdd(Nodes[node.RightChild]);
|
||||
Reclaim(node.Index);
|
||||
return true;
|
||||
}
|
||||
//search in child nodes.
|
||||
//binary search to find equal opposing edges.
|
||||
|
||||
bool rightSide = false;
|
||||
switch (node.Dimension)
|
||||
{
|
||||
case IntersectRectDimension.Top:
|
||||
rightSide = rect.y1 > node.y1; break;
|
||||
case IntersectRectDimension.Left:
|
||||
rightSide = rect.x1 > node.x1; break;
|
||||
case IntersectRectDimension.Bottom:
|
||||
rightSide = rect.y2 > node.y2; break;
|
||||
case IntersectRectDimension.Right:
|
||||
rightSide = rect.x2 > node.x2; break;
|
||||
}
|
||||
|
||||
return ((rightSide && node.RightChild != -1 && Delete(ref Nodes[node.RightChild], rect, ref node))
|
||||
|| (!rightSide && node.LeftChild != -1 && Delete(ref Nodes[node.LeftChild], rect, ref node)));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public struct VMObstacleSetNode
|
||||
{
|
||||
public int LeftChild;
|
||||
public int RightChild;
|
||||
public IntersectRectDimension Dimension;
|
||||
public BaseMeshTriangle Rect;
|
||||
public int Index;
|
||||
|
||||
public float x1;
|
||||
public float x2;
|
||||
public float y1;
|
||||
public float y2;
|
||||
|
||||
public bool Intersects(BaseMeshTriangle other)
|
||||
{
|
||||
return !((other.x1 >= x2 || other.x2 <= x1) || (other.y1 >= y2 || other.y2 <= y1));
|
||||
}
|
||||
|
||||
public bool OnEdge(BaseMeshTriangle other)
|
||||
{
|
||||
return (x2 == other.x1) || (x1 == other.x2) || (y1 == other.y2) || (y2 == other.y1);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue