Files
GoakeFlax/Source/Game/Q3MapImporter.cs
2021-07-07 01:41:29 +03:00

1135 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using FlaxEngine;
using System.IO;
using System.Linq;
using FlaxEngine.Assertions;
using FlaxEngine.Utilities;
using Console = Cabrito.Console;
namespace Game
{
public struct Edge
{
public Vector3 v1, v2;
public Edge(Vector3 v1, Vector3 v2)
{
this.v1 = v1;
this.v2 = v2;
}
public static Edge[] GetEdges(Vector3 v1, Vector3 v2, Vector3 v3)
{
return new[]
{
new Edge(v1, v2),
new Edge(v2, v3),
new Edge(v3, v1),
};
}
public override bool Equals(object obj)
{
if (obj is Edge)
{
var other = (Edge) obj;
var d1a = Math.Abs((v1 - other.v1).Length);
var d1b = Math.Abs((v1 - other.v2).Length);
var d2a = Math.Abs((v2 - other.v2).Length);
var d2b = Math.Abs((v2 - other.v1).Length);
var eps = 1f;
if (d1a < eps && d2a < eps)
return true;
else if (d1b < eps && d2b < eps)
return true;
else
return false;
}
return base.Equals(obj);
}
public static bool operator ==(Edge edge, object obj)
{
return edge.Equals(obj);
}
public static bool operator !=(Edge edge, object obj)
{
return !(edge == obj);
}
}
public class Face
{
public Vector3 v1, v2, v3;
public List<Q3MapImporter.HalfEdge> halfEdges;
public bool visited;
public Face(Vector3 v1, Vector3 v2, Vector3 v3)
{
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
halfEdges = new List<Q3MapImporter.HalfEdge>(3);
}
public Edge[] GetEdges()
{
return new[]
{
new Edge(v1, v2),
new Edge(v2, v3),
new Edge(v3, v1),
};
}
public float DistanceToPoint(Vector3 point)
{
Plane plane = new Plane(v1, v2, v3);
float distance = (point.X * plane.Normal.X) + (point.Y * plane.Normal.Y) +
(point.Z * plane.Normal.Z) + plane.D;
return distance / (float) Math.Sqrt(
(plane.Normal.X * plane.Normal.X) + (plane.Normal.Y * plane.Normal.Y) +
(plane.Normal.Z * plane.Normal.Z));
}
public float DistanceToPlane(Face face)
{
Plane plane = new Plane(v1, v2, v3);
var center = (face.v1 + face.v2 + face.v3) / 3f;
return plane.Normal.X * center.X + plane.Normal.Y * center.Y + plane.Normal.Z * center.Z - plane.D;
}
}
public struct Tetrahedron
{
public Vector3 v1, v2, v3, v4;
public Tetrahedron(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
{
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.v4 = v4;
}
public Face[] GetFaces()
{
return new[]
{
new Face(v1, v2, v3),
new Face(v1, v3, v4),
new Face(v1, v4, v2),
new Face(v2, v4, v3),
};
}
}
public class Q3MapImporter : Script
{
private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube.map";
//private string mapPath = @"C:\dev\Goake\maps\aerowalk\aerowalk.map";
Model model;
public MaterialBase material;
const float epsilon = 0.00001f;
private void SortPoints(List<Vector3> points, Vector3 planeNormal)
{
Vector3 center = Vector3.Zero;
foreach (var vert in points)
{
center += vert;
}
if (points.Count > 0)
center /= points.Count;
points.Sort((v1, v2) =>
{
var dot = Vector3.Dot(planeNormal, Vector3.Cross(v1 - center, v2 - center));
if (dot > 0)
return 1;
else
return -1;
});
}
float PointDistanceFromPlane(Vector3 point, Plane plane)
{
float distance = (point.X * plane.Normal.X) + (point.Y * plane.Normal.Y) +
(point.Z * plane.Normal.Z) + plane.D;
return distance / (float) Math.Sqrt(
(plane.Normal.X * plane.Normal.X) + (plane.Normal.Y * plane.Normal.Y) +
(plane.Normal.Z * plane.Normal.Z));
}
private Face[] CreateInitialSimplex(Vector3[] points)
{
if (false)
{
// TODO: more optimal to find first set of points which are not coplanar?
// find the longest edge
Vector3 v1 = Vector3.Zero;
Vector3 v2 = Vector3.Zero;
foreach (var p1 in points)
{
foreach (var p2 in points)
{
if ((p2 - p1).LengthSquared > (v2 - v1).LengthSquared)
{
v1 = p1;
v2 = p2;
}
}
}
if (v1 == v2)
v1 = v2;
Assert.IsTrue(v1 != v2, "a1 != a2");
// find the furthest point from the edge to form a face
Vector3 v3 = Vector3.Zero;
float furthestDist = 0f;
foreach (var point in points)
{
//if (vert == a1 || vert == a2)
// continue;
var edgeDir = (v2 - v1).Normalized;
var closest = v1 + edgeDir * Vector3.Dot(point - v1, edgeDir);
var dist = (point - closest).Length;
if (dist > furthestDist)
{
v3 = point;
furthestDist = dist;
}
}
Assert.IsTrue(v3 != v1, "furthest != a1");
Assert.IsTrue(v3 != v2, "furthest != a2");
// find the furthest point from he face
Plane plane = new Plane(v1, v2, v3);
Vector3 v4 = Vector3.Zero;
float fourthDist = 0f;
foreach (var point in points)
{
if (point == v1 || point == v2 || point == v3)
continue;
float distance = PointDistanceFromPlane(point, plane);
if (Math.Abs(distance) > fourthDist)
{
v4 = point;
fourthDist = distance;
}
}
// make sure the tetrahedron is in counter-clockwise order
if (fourthDist > 0)
{
return new Face[]
{
new Face(v1, v3, v2),
new Face(v1, v4, v3),
new Face(v1, v2, v4),
new Face(v2, v3, v4),
};
}
else
{
return new Face[]
{
new Face(v1, v2, v3),
new Face(v1, v3, v4),
new Face(v1, v4, v2),
new Face(v2, v4, v3),
};
}
}
else
{
Vector3 v1 = Vector3.Zero, v2 = Vector3.Zero, v3 = Vector3.Zero, v4 = Vector3.Zero;
bool found = false;
foreach (var p1 in points)
{
foreach (var p2 in points)
{
if (p1 == p2)
continue;
if (AreCoincident(p1, p2))
continue;
foreach (var p3 in points)
{
if (p3 == p2 || p3 == p1)
continue;
if (AreCollinear(p1, p2, p3))
continue;
foreach (var p4 in points)
{
if (p4 == p1 || p4 == p2 || p4 == p3)
continue;
if (AreCoplanar(p1, p2, p3, p4))
continue;
found = true;
v1 = p1;
v2 = p2;
v3 = p3;
v4 = p4;
break;
}
}
}
}
if (!found)
throw new Exception("CreateInitialSimplex failed");
Plane plane = new Plane(v1, v2, v3);
var fourthDist = PointDistanceFromPlane(v4, plane);
if (fourthDist > 0)
{
return new Face[]
{
new Face(v1, v3, v2),
new Face(v1, v4, v3),
new Face(v1, v2, v4),
new Face(v2, v3, v4),
};
}
else
{
return new Face[]
{
new Face(v1, v2, v3),
new Face(v1, v3, v4),
new Face(v1, v4, v2),
new Face(v2, v4, v3),
};
}
}
}
public class HalfEdge
{
public Face face;
//public Face oppositeFace;
public HalfEdge opposite;
public HalfEdge previous, next;
public Edge edge;
//public bool horizonVisited;
public HalfEdge(Edge edge, Face face)
{
this.edge = edge;
this.face = face;
face.halfEdges.Add(this);
}
}
//http://algolist.ru/maths/geom/convhull/qhull3d.php
private void PopulateOutsideSet(List<Tuple<Face, Vector3>> outsideSet, Face[] faces, Vector3[] points)
{
foreach (var point in points)
{
foreach (Face face in faces)
{
float distance = face.DistanceToPoint(point);
/*if (Math.Abs(distance) < epsilon)
{
// point is in the plane, this gets merged
distance = distance;
}
else*/
if (distance > 0)
{
//side.outsideSet.Add(point);
outsideSet.Add(new Tuple<Face, Vector3>(face, point));
break;
}
}
}
}
private List<Vector3> QuickHull2(Vector3[] points)
{
Assert.IsTrue(points.Length >= 4, "points.Length >= 4");
var tetrahedron = CreateInitialSimplex(points);
List<Tuple<Face, Vector3>> outsideSet = new List<Tuple<Face, Vector3>>();
PopulateOutsideSet(outsideSet, tetrahedron, points);
// all points not in side.outsideSet are inside in "inside" set
// create half-edges
foreach (var face in tetrahedron)
{
var halfEdges = new List<HalfEdge>(3);
foreach (var edge in face.GetEdges())
halfEdges.Add(new HalfEdge(edge, face));
for (int i = 0; i < halfEdges.Count; i++)
{
halfEdges[i].previous = halfEdges[(i + 2) % 3];
halfEdges[i].next = halfEdges[(i + 1) % 3];
}
}
// verify
{
var tetrapoints = new List<Vector3>();
foreach (var face in tetrahedron)
{
foreach (var he in face.halfEdges)
{
if (!tetrapoints.Contains(he.edge.v1))
tetrapoints.Add(he.edge.v1);
}
}
foreach (var point in tetrapoints)
{
int foundFaces = 0;
foreach (var face in tetrahedron)
{
if (face.v1 == point)
foundFaces++;
else if (face.v2 == point)
foundFaces++;
else if (face.v3 == point)
foundFaces++;
}
Assert.IsTrue(foundFaces == 3, "foundFaces == 3");
}
}
foreach (var face in tetrahedron)
{
Assert.IsTrue(face.halfEdges.Count == 3, "side.halfEdges.Count == 3");
foreach (var halfEdge in face.halfEdges)
{
bool found = false;
foreach (var otherFace in tetrahedron)
{
if (found)
break;
if (face == otherFace)
continue;
foreach (var otherHalfEdge in otherFace.halfEdges)
{
if (otherHalfEdge.opposite != null)
continue;
if (halfEdge.edge == otherHalfEdge.edge)
{
halfEdge.opposite = otherHalfEdge;
otherHalfEdge.opposite = halfEdge;
//halfEdge.oppositeFace = otherFace;
//otherHalfEdge.oppositeFace = face;
found = true;
break;
}
}
}
Assert.IsTrue(halfEdge.previous != null, "halfEdge.previous != null");
Assert.IsTrue(halfEdge.next != null, "halfEdge.next != null");
Assert.IsTrue(halfEdge.opposite != null, "halfEdge.opposite != null");
//Assert.IsTrue(halfEdge.oppositeFace != null, "halfEdge.oppositeFace != null");
Assert.IsTrue(halfEdge.opposite.face != null, "halfEdge.opposite.face != null");
//Assert.IsTrue(halfEdge.oppositeFace == halfEdge.opposite.face, "halfEdge.oppositeFace == halfEdge.opposite.face");
}
}
// grow hull
List<HalfEdge> horizonEdges = new List<HalfEdge>();
List<Face> hullFaces = new List<Face>();
hullFaces.AddRange(tetrahedron);
// stop when none of the faces have any visible outside points
int iterCount = 0;
while (outsideSet.Count > 0)
{
iterCount++;
Tuple<Face, Vector3> pointToAdd = null;
Face pointFace = null;
// get furthest point in outside set
/*for (int sideIndex = 0; sideIndex < sides.Count; sideIndex++)
{
TetrahedronSide side = sides[sideIndex];
if (side.outsideSet.Count == 0)
continue;
float furthestDist = 0f;
foreach (var point in side.outsideSet)
{
Assert.IsTrue(point != side.face.v1, "point != side.face.v1");
Assert.IsTrue(point != side.face.v2, "point != side.face.v2");
Assert.IsTrue(point != side.face.v3, "point != side.face.v3");
float distance = PointDistanceFromPlane(point, side.plane);
if (Math.Abs(distance) > furthestDist)
{
pointToAdd = point;
pointSide = side;
furthestDist = distance;
}
}
}*/
float furthestDist = 0f;
foreach (var fp in outsideSet)
{
var face = fp.Item1;
var point = fp.Item2;
float distance = face.DistanceToPoint(point);
if (Math.Abs(distance) > furthestDist)
//if (distance > furthestDist)
{
pointToAdd = fp;
pointFace = face;
furthestDist = distance;
}
}
Assert.IsTrue(furthestDist > 0, "furthestDist > 0");
Assert.IsTrue(pointToAdd != null, "pointToAdd != null");
outsideSet.Remove(pointToAdd);
foreach (var face in hullFaces)
{
face.visited = false;
foreach (var halfEdge in face.halfEdges)
{
Assert.IsTrue(halfEdge.opposite.opposite == halfEdge, "halfEdge.opposite.opposite == halfEdge");
Assert.IsTrue(hullFaces.Contains(halfEdge.opposite.face),
"hullFaces.Contains(halfEdge.opposite.face)");
}
}
var hullFacesNew = new List<Face>();
var unclaimedPoints = new List<Vector3>();
AddPointToHull(pointToAdd.Item2, pointFace, unclaimedPoints, outsideSet, horizonEdges, hullFacesNew);
// remove lit/seen/visited faces, their points were added to unclaimed points
for (int i = 0; i < hullFaces.Count; i++)
{
if (hullFaces[i].visited)
{
hullFaces.RemoveAt(i);
i--;
}
}
hullFaces.AddRange(hullFacesNew);
foreach (var face in hullFaces)
{
face.visited = false;
foreach (var halfEdge in face.halfEdges)
{
Assert.IsTrue(halfEdge.opposite.opposite == halfEdge,
"2 halfEdge.opposite.opposite == halfEdge");
Assert.IsTrue(hullFaces.Contains(halfEdge.opposite.face),
"2 hullFaces.Contains(halfEdge.opposite.face)");
}
}
foreach (var fb in outsideSet)
unclaimedPoints.Add(fb.Item2);
outsideSet.Clear();
PopulateOutsideSet(outsideSet, hullFaces.ToArray(), unclaimedPoints.ToArray());
//if (iterCount >= 3)
// break;
if (hullFaces.Count > 1000 || iterCount > 1000)
Assert.Fail("overflow");
if (outsideSet.Count > 100000)
Assert.Fail("outsideSet overflow");
}
List<Vector3> hullPoints = new List<Vector3>(hullFaces.Count * 3);
foreach (var face in hullFaces)
{
hullPoints.Add(face.v1);
hullPoints.Add(face.v2);
hullPoints.Add(face.v3);
}
return hullPoints;
}
private void AddUnique(List<Vector3> list, Vector3 point)
{
foreach (var p in list)
{
if ((point - p).Length < epsilon)
return;
}
list.Add(point);
}
bool AreCoincident(Vector3 a, Vector3 b)
{
return (a - b).Length <= epsilon;
}
bool AreCollinear(Vector3 a, Vector3 b, Vector3 c)
{
return Vector3.Cross(c - a, c - b).Length <= epsilon;
}
bool AreCoplanar(Vector3 a, Vector3 b, Vector3 c, Vector3 d)
{
var n1 = Vector3.Cross(c - a, c - b);
var n2 = Vector3.Cross(d - a, d - b);
var m1 = n1.Length;
var m2 = n2.Length;
return m1 > epsilon
&& m2 > epsilon
&& AreCollinear(Vector3.Zero,
(1.0f / m1) * n1,
(1.0f / m2) * n2);
}
private void AddPointToHull(Vector3 point, Face face, List<Vector3> unclaimedPoints,
List<Tuple<Face, Vector3>> outsideSet,
List<HalfEdge> horizonEdges, List<Face> hullFaces)
{
horizonEdges.Clear();
CalculateHorizon(face, point, unclaimedPoints, outsideSet, horizonEdges, face.halfEdges[0]);
// create new faces
if (horizonEdges.Count > 0)
{
List<Face> newFaces = new List<Face>();
HalfEdge firstEdge = horizonEdges.First();
HalfEdge prevEdge = null;
foreach (var edge in horizonEdges)
{
var newFace = new Face(point, edge.edge.v1, edge.edge.v2);
var newPlane = new Plane(newFace.v1, newFace.v2, newFace.v3);
var uniqPoints = new List<Vector3>();
AddUnique(uniqPoints, newFace.v1);
AddUnique(uniqPoints, newFace.v2);
AddUnique(uniqPoints, newFace.v3);
var fourtPoint = edge.opposite.next.edge.v2;
AddUnique(uniqPoints, edge.opposite.next.edge.v1);
AddUnique(uniqPoints, edge.opposite.next.edge.v2);
AddUnique(uniqPoints, edge.opposite.previous.edge.v1);
AddUnique(uniqPoints, edge.opposite.previous.edge.v2);
var distFromPlane = PointDistanceFromPlane(fourtPoint, newPlane);
if (Math.Abs(distFromPlane) < epsilon)
{
// both faces are coplanar, merge them together
distFromPlane = distFromPlane;
if (AreCoplanar(newFace.v1, newFace.v2, newFace.v3, fourtPoint))
distFromPlane = distFromPlane;
}
else if (AreCoplanar(newFace.v1, newFace.v2, newFace.v3, fourtPoint))
{
distFromPlane = distFromPlane;
}
var newEdges = new List<HalfEdge>();
foreach (var ne in newFace.GetEdges())
newEdges.Add(new HalfEdge(ne, newFace));
for (int i = 0; i < newEdges.Count; i++)
{
newEdges[i].previous = newEdges[(i + 2) % 3];
newEdges[i].next = newEdges[(i + 1) % 3];
}
if (prevEdge != null)
{
var prevAdjacentEdge = newFaces.Last().halfEdges.Last();
var lastAdjacentEdge = newEdges.First();
lastAdjacentEdge.opposite = prevAdjacentEdge;
prevAdjacentEdge.opposite = lastAdjacentEdge;
}
//edge.face = newFace;
newEdges[1].opposite = edge.opposite;
edge.opposite.opposite = newEdges[1];
newFaces.Add(newFace);
prevEdge = edge;
}
if (prevEdge != null)
{
var lastAdjacentEdge = newFaces.Last().halfEdges.Last();
var firstAdjacentEdge = newFaces.First().halfEdges.First();
lastAdjacentEdge.opposite = firstAdjacentEdge;
firstAdjacentEdge.opposite = lastAdjacentEdge;
//first.previous.opposite = prev.next;
//prev.next.opposite = first.previous;
}
// merge NONCONVEX_WRT_LARGER_FACE
const float tolerance = -1f;
//List<Face> discardedFaces = new List<Face>();
if (false)
foreach (var newFace in newFaces)
{
HalfEdge edge = newFace.halfEdges[0];
bool convex = true;
do
{
Face oppositeFace = edge.opposite.face;
bool merge = false;
var p1 = new Plane(face.v1, face.v2, face.v3);
var p2 = new Plane(oppositeFace.v1, oppositeFace.v2, oppositeFace.v3);
// ?
float faceArea, oppositeArea;
{
HalfEdge areaEdgeStart = edge;
HalfEdge areaEdge = areaEdgeStart.previous;
Vector3 areaNorm = Vector3.Zero;
do
{
areaNorm += Vector3.Cross(areaEdge.edge.v1 - areaEdgeStart.edge.v1,
areaEdge.next.edge.v1 - areaEdgeStart.edge.v1);
areaEdge = areaEdge.previous;
} while (areaEdge != areaEdgeStart);
faceArea = areaNorm.Length;
}
{
HalfEdge areaEdgeStart = edge.opposite;
HalfEdge areaEdge = areaEdgeStart.previous;
Vector3 areaNorm = Vector3.Zero;
do
{
areaNorm += Vector3.Cross(areaEdge.edge.v1 - areaEdgeStart.edge.v1,
areaEdge.next.edge.v1 - areaEdgeStart.edge.v1);
areaEdge = areaEdge.previous;
} while (areaEdge != areaEdgeStart);
oppositeArea = areaNorm.Length;
}
if (faceArea > oppositeArea)
{
if (edge.face.DistanceToPlane(edge.opposite.face) > -tolerance)
merge = true;
else if (edge.opposite.face.DistanceToPlane(edge.face) > -tolerance)
convex = false;
}
else
{
if (edge.opposite.face.DistanceToPlane(edge.face) > -tolerance)
merge = true;
else if (edge.face.DistanceToPlane(edge.opposite.face) > -tolerance)
convex = false;
}
if (merge)
{
List<Face> discardedFaces = new List<Face>();
{
discardedFaces.Add(oppositeFace);
HalfEdge prev = edge.previous;
HalfEdge next = edge.next;
HalfEdge oppositePrev = edge.opposite.previous;
HalfEdge oppositeNext = edge.opposite.next;
while (prev.opposite.face == oppositeFace)
{
prev = prev.previous;
oppositeNext = oppositeNext.next;
}
while (next.opposite.face == oppositeFace)
{
oppositePrev = oppositePrev.previous;
next = next.next;
}
for (HalfEdge e = oppositeNext; e != oppositePrev.next; e = e.next)
e.face = newFace;
if (edge == face.halfEdges[0])
face.halfEdges[0] = next;
Face discardedFace = ConnectHalfEdges(newFace, oppositePrev, next);
Face discardedFace2 = ConnectHalfEdges(newFace, prev, oppositeNext);
if (discardedFace != null)
discardedFaces.Add(discardedFace);
if (discardedFace2 != null)
discardedFaces.Add(discardedFace2);
}
foreach (var dface in discardedFaces)
{
for (int i=0; i<outsideSet.Count; i++)
{
if (outsideSet[i].Item1 == dface)
{
float distance = face.DistanceToPoint(point);
if (distance > 0)
outsideSet[i] = new Tuple<Face, Vector3>(newFace, outsideSet[i].Item2);
else
unclaimedPoints.Add(outsideSet[i].Item2);
}
}
}
}
} while (edge != newFace.halfEdges[0]);
hullFaces.Add(newFace);
}
else
hullFaces.AddRange(newFaces);
// verify
foreach (var newFace in hullFaces)
{
Assert.IsTrue(newFace.halfEdges.Count == 3, "AddPointToHull: side.halfEdges.Count == 3");
foreach (var halfEdge in newFace.halfEdges)
{
/*bool found = false;
foreach (var otherFace in hullFaces)
{
if (found)
break;
if (newFace == otherFace)
continue;
foreach (var otherHalfEdge in otherFace.halfEdges)
{
if (otherHalfEdge.opposite != null)
continue;
if (halfEdge.edge == otherHalfEdge.edge)
{
halfEdge.opposite = otherHalfEdge;
otherHalfEdge.opposite = halfEdge;
//halfEdge.oppositeFace = otherFace;
//otherHalfEdge.oppositeFace = face;
found = true;
break;
}
}
}*/
Assert.IsTrue(halfEdge.previous != null, "AddPointToHull: halfEdge.previous != null");
Assert.IsTrue(halfEdge.next != null, "AddPointToHull: halfEdge.next != null");
Assert.IsTrue(halfEdge.next.next.next == halfEdge, "AddPointToHull: halfEdge.next.next.next == halfEdge");
Assert.IsTrue(halfEdge.previous.previous.previous == halfEdge, "AddPointToHull: halfEdge.previous.previous.previous == halfEdge");
Assert.IsTrue(halfEdge.opposite != null, "AddPointToHull: halfEdge.opposite != null");
//Assert.IsTrue(halfEdge.oppositeFace != null, "halfEdge.oppositeFace != null");
Assert.IsTrue(halfEdge.opposite.face != null, "AddPointToHull: halfEdge.opposite.face != null");
//Assert.IsTrue(halfEdge.oppositeFace == halfEdge.opposite.face, "halfEdge.oppositeFace == halfEdge.opposite.face");
}
}
}
}
private Face ConnectHalfEdges(Face face, HalfEdge prev, HalfEdge edge)
{
Face discardedFace = null;
if (prev.opposite.face == edge.opposite.face)
{
Face oppFace = edge.opposite.face;
HalfEdge hedgeOpp;
if (prev == face.halfEdges[0])
{
face.halfEdges[0] = edge;
}
/*if (oppFace.numVertices() == 3)*/
{
// then we can get rid of the
// opposite face altogether
hedgeOpp = edge.opposite.previous.opposite;
//oppFace.mark = DELETED;
discardedFace = oppFace;
} /*else {
hedgeOpp = edge.opposite.next;
if (oppFace.halfEdges[0] == hedgeOpp.previous) {
oppFace.halfEdges[0] = hedgeOpp;
}
hedgeOpp.previous = hedgeOpp.previous.prev;
hedgeOpp.previous.next = hedgeOpp;
}*/
edge.previous = prev.previous;
edge.previous.next = edge;
edge.opposite = hedgeOpp;
hedgeOpp.opposite = edge;
// oppFace was modified, so need to recompute
//oppFace.computeNormalAndCentroid();
}
else
{
prev.next = edge;
edge.previous = prev;
}
return discardedFace;
}
// calculates the outermost edges of the geometry seen from the eyePoint
private void CalculateHorizon(Face face, Vector3 eyePoint, List<Vector3> unclaimedPoints,
List<Tuple<Face, Vector3>> outsideSet,
List<HalfEdge> horizonEdges, HalfEdge currentEdge)
{
face.visited = true;
// move outside points of this face to unclaimed points
foreach (var set in outsideSet)
{
if (set.Item1 == face)
unclaimedPoints.Add(set.Item2);
}
HalfEdge startingEdge = currentEdge;
do
{
Face oppositeFace = currentEdge.opposite.face;
if (!oppositeFace.visited)
{
var dist = oppositeFace.DistanceToPoint(eyePoint);
if (dist > epsilon)
{
// positive distance means this is visible
CalculateHorizon(oppositeFace, eyePoint, unclaimedPoints, outsideSet, horizonEdges,
currentEdge.opposite);
}
/*else if (Math.Abs(dist) <= epsilon)
{
dist = dist;
}*/
else
{
if (!horizonEdges.Contains(currentEdge))
horizonEdges.Add(currentEdge);
}
}
currentEdge = currentEdge.next;
} while (currentEdge != startingEdge);
}
public override void OnStart()
{
byte[] mapChars = File.ReadAllBytes(mapPath);
var root = MapParser.Parse(mapChars);
const float cs = 3000f;
Vector3[] cubePoints = new[]
{
new Vector3(-cs, -cs, -cs),
new Vector3(cs, -cs, -cs),
new Vector3(-cs, cs, -cs),
new Vector3(cs, cs, -cs),
new Vector3(-cs, -cs, cs),
new Vector3(cs, -cs, cs),
new Vector3(-cs, cs, cs),
new Vector3(cs, cs, cs),
};
Vector3[] cubeVerts = QuickHull2(cubePoints).ToArray();
List<Vector3> vertices = new List<Vector3>();
foreach (var brush in root.entities[0].brushes.Take(1))
{
List<Vector3> brushVertices = new List<Vector3>(cubeVerts);
//foreach (var plane in new [] { brush.planes.First() })
foreach (var plane in brush.planes.Reverse().Take(2))
//foreach (var plane in brush.planes)
{
Plane p = new Plane(plane.v1, plane.v2, plane.v3);
Vector3 planeNormal = p.Normal;
List<Vector3> newBrushVertices = new List<Vector3>();
List<Vector3> faceVertices = new List<Vector3>();
if (true)
{
Func<float, bool> isFront = (f) => f > epsilon;
Func<float, bool> isBack = (f) => f < -epsilon;
for (int i = 0; i < brushVertices.Count; i++)
{
int i2 = ((i + 1) % 3 == 0) ? (i - 2) : (i + 1);
Vector3 start = brushVertices[i];
Vector3 end = brushVertices[i2];
var d1 = (start.X * planeNormal.X) + (start.Y * planeNormal.Y) + (start.Z * planeNormal.Z) +
p.D;
var d2 = (end.X * planeNormal.X) + (end.Y * planeNormal.Y) + (end.Z * planeNormal.Z) + p.D;
if (isBack(d1))
{
// include back
faceVertices.Add(start);
}
if (isBack(d1) && isFront(d2) || isFront(d1) && isBack(d2))
{
//if (isFront(d2))
{
// include clip
Ray ray2 = new Ray(start, (end - start).Normalized);
if (p.Intersects(ref ray2, out Vector3 intersect2))
faceVertices.Add(intersect2);
else
d1 = d1;
}
}
}
if (true)
{
var newMeshPoints = new List<Vector3>();
int duplis = 0;
foreach (var v in faceVertices)
{
bool found = false;
foreach (var vo in newMeshPoints)
{
if ((v - vo).Length < epsilon)
{
found = true;
duplis++;
break;
}
}
//if (!newMeshPoints.Contains(v))
if (!found)
newMeshPoints.Add(v);
}
if (duplis > 0)
Console.Print("duplicates: " + duplis);
if (newMeshPoints.Count > 0)
{
var hullPoints = QuickHull2(newMeshPoints.ToArray());
newBrushVertices.Clear();
newBrushVertices.AddRange(hullPoints);
}
else
newBrushVertices.Clear();
}
}
Assert.IsTrue(newBrushVertices.Count % 3 == 0,
"invalid amount of vertices: " + newBrushVertices.Count);
//Assert.IsTrue(newBrushVertices.Count > 0,
// "brush was clipped completely, vertices: " + newBrushVertices.Count);
brushVertices.Clear();
brushVertices.AddRange(newBrushVertices);
Console.Print("plane verts: " + newBrushVertices.Count);
}
vertices.AddRange(brushVertices);
}
model = Content.CreateVirtualAsset<Model>();
model.SetupLODs(new int[] {1});
{
var mesh = model.LODs[0].Meshes[0];
List<int> triangles = new List<int>(vertices.Count);
for (int i = 0; i < vertices.Count; i++)
triangles.Add(i);
Console.Print("verts: " + vertices.Count);
mesh.UpdateMesh(vertices.ToArray(), triangles.ToArray(), vertices.ToArray());
}
StaticModel childModel = Actor.AddChild<StaticModel>();
childModel.Name = "MapModel";
childModel.Model = model;
childModel.SetMaterial(0, material);
}
public override void OnEnable()
{
// Here you can add code that needs to be called when script is enabled (eg. register for events)
}
public override void OnDisable()
{
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
}
public override void OnUpdate()
{
// Here you can add code that needs to be called every frame
}
public override void OnDestroy()
{
Destroy(ref model);
base.OnDestroy();
}
}
}