1077 lines
26 KiB
C#
1077 lines
26 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 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 List<Vector3> FindHull(List<Vector3> points, Edge edge, Vector3 planeNormal)
|
|
{
|
|
// picks the furthest point of the edge, filter out points from the formed triangle,
|
|
// and recursively repeat this until we have only the outermost points of the convex
|
|
// hull.
|
|
|
|
if (points.Count == 0)
|
|
return new List<Vector3>();
|
|
|
|
// furthest point from edge
|
|
Vector3 furthest = Vector3.Zero;
|
|
float furthestDist = 0f;
|
|
foreach (var vert in points)
|
|
{
|
|
var edgeDir = (edge.v2 - edge.v1).Normalized;
|
|
var closest = edge.v1 + edgeDir * Vector3.Dot(vert - edge.v1, edgeDir);
|
|
|
|
var dist = (vert - closest).Length;
|
|
if (dist > furthestDist)
|
|
{
|
|
furthest = vert;
|
|
furthestDist = dist;
|
|
}
|
|
}
|
|
|
|
List<Vector3> set1 = new List<Vector3>();
|
|
List<Vector3> set2 = new List<Vector3>();
|
|
|
|
var edgeNormal = Vector3.Cross(edge.v2 - edge.v1, planeNormal);
|
|
for (int i = 0; i < points.Count; i++)
|
|
{
|
|
var vert = points[i];
|
|
if (vert == edge.v1 || vert == edge.v2 || vert == furthest)
|
|
continue;
|
|
|
|
var normal1 = new Plane(vert, edge.v1, edge.v2).Normal;
|
|
var normal2 = new Plane(vert, edge.v2, furthest).Normal;
|
|
var normal3 = new Plane(vert, furthest, edge.v1).Normal;
|
|
|
|
var d1 = Vector3.Dot(planeNormal, normal1);
|
|
var d2 = Vector3.Dot(planeNormal, normal2);
|
|
var d3 = Vector3.Dot(planeNormal, normal3);
|
|
|
|
if ((d1 > 0 && d2 > 0 && d3 > 0) ||
|
|
(d1 < 0 && d2 < 0 && d3 < 0))
|
|
{
|
|
// all triangles formed by these points have similar normals,
|
|
}
|
|
else
|
|
{
|
|
// normals pointing different directions, eliminate
|
|
continue;
|
|
}
|
|
|
|
var norm = (vert - edge.v1).Normalized;
|
|
var dd = Vector3.Dot(edgeNormal.Normalized, norm);
|
|
if (dd > epsilon)
|
|
{
|
|
set1.Add(vert);
|
|
}
|
|
else if (dd < -epsilon)
|
|
{
|
|
set2.Add(vert);
|
|
}
|
|
}
|
|
|
|
List<Vector3> set = new List<Vector3>(new[] {furthest});
|
|
set.AddRange(FindHull(set1, new Edge(edge.v1, furthest), planeNormal));
|
|
set.AddRange(FindHull(set2, new Edge(furthest, edge.v2), planeNormal));
|
|
return set;
|
|
}
|
|
|
|
private List<Vector3> QuickHull(List<Vector3> points, Vector3 planeNormal)
|
|
{
|
|
// longest pair
|
|
Vector3 a1 = Vector3.Zero;
|
|
Vector3 a2 = Vector3.Zero;
|
|
foreach (var v1 in points)
|
|
{
|
|
foreach (var v2 in points)
|
|
{
|
|
if ((v2 - v1).LengthSquared > (a2 - a1).LengthSquared)
|
|
{
|
|
a1 = v1;
|
|
a2 = v2;
|
|
}
|
|
}
|
|
}
|
|
|
|
var edgeNormal = Vector3.Cross(a2 - a1, planeNormal);
|
|
List<Vector3> set1 = new List<Vector3>();
|
|
List<Vector3> set2 = new List<Vector3>();
|
|
|
|
foreach (var vert in points)
|
|
{
|
|
if (vert == a1 || vert == a2)
|
|
continue;
|
|
|
|
var norm = (vert - a1) /*.Normalized*/;
|
|
var dd = Vector3.Dot(edgeNormal /*.Normalized*/, norm);
|
|
if (dd > epsilon)
|
|
{
|
|
set1.Add(vert);
|
|
}
|
|
else if (dd < -epsilon)
|
|
{
|
|
set2.Add(vert);
|
|
}
|
|
else
|
|
set1.Add(vert);
|
|
}
|
|
|
|
List<Vector3> set = new List<Vector3>(new[] {a1, a2});
|
|
set.AddRange(FindHull(set1, new Edge(a2, a1), planeNormal));
|
|
set.AddRange(FindHull(set2, new Edge(a1, a2), planeNormal));
|
|
return set;
|
|
}
|
|
|
|
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)
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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),
|
|
};
|
|
}
|
|
}
|
|
|
|
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 List<Vector3> QuickHull2(Vector3[] points)
|
|
{
|
|
var tetrahedron = CreateInitialSimplex(points);
|
|
|
|
List<Tuple<Face, Vector3>> outsideSet = new List<Tuple<Face, Vector3>>();
|
|
foreach (var point in points)
|
|
{
|
|
foreach (Face face in tetrahedron)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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<Tuple<Face, Vector3>> unclaimedPoints2 = new List<Tuple<Face, Vector3>>();
|
|
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)
|
|
{
|
|
pointToAdd = fp;
|
|
pointFace = face;
|
|
furthestDist = distance;
|
|
}
|
|
}
|
|
|
|
Assert.IsTrue(pointToAdd != null, "pointToAdd != null");
|
|
|
|
outsideSet.Remove(pointToAdd);
|
|
|
|
foreach (var face in hullFaces)
|
|
face.visited = false;
|
|
|
|
var hullFacesNew = new List<Face>();
|
|
|
|
AddPointToHull(pointToAdd.Item2, pointFace, unclaimedPoints2, 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);
|
|
|
|
|
|
//outsideSet.AddRange(unclaimedPoints2);
|
|
//unclaimedPoints2.Clear();
|
|
outsideSet = new List<Tuple<Face, Vector3>>(unclaimedPoints2);
|
|
unclaimedPoints2.Clear();
|
|
|
|
if (iterCount >= 3)
|
|
break;
|
|
|
|
if (hullFaces.Count > 1000 || iterCount > 1000)
|
|
Assert.Fail("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 AddPointToHull(Vector3 point, Face face, List<Tuple<Face, Vector3>> unclaimedPoints2,
|
|
List<Tuple<Face, Vector3>> outsideSet,
|
|
List<HalfEdge> horizonEdges, List<Face> hullFaces)
|
|
{
|
|
horizonEdges.Clear();
|
|
|
|
CalculateHorizon(face, point, unclaimedPoints2, outsideSet, horizonEdges, face.halfEdges[0]);
|
|
|
|
// create new faces
|
|
if (horizonEdges.Count > 0)
|
|
{
|
|
List<Face> newFaces = new List<Face>();
|
|
HalfEdge first = horizonEdges.First();
|
|
HalfEdge prev = null;
|
|
foreach (var edge in horizonEdges)
|
|
{
|
|
var newFace = new Face(point, edge.edge.v1, edge.edge.v2);
|
|
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 (prev != null)
|
|
{
|
|
var prevEdge = newFaces.Last().halfEdges.Last();
|
|
var lastEdge = newEdges.First();
|
|
lastEdge.opposite = prevEdge;
|
|
prevEdge.opposite = lastEdge;
|
|
}
|
|
|
|
//edge.face = newFace;
|
|
|
|
newEdges[1].opposite = edge.opposite;
|
|
|
|
newFaces.Add(newFace);
|
|
prev = edge;
|
|
}
|
|
|
|
if (prev != null)
|
|
{
|
|
var lastEdge = newFaces.Last().halfEdges.Last();
|
|
var firstEdge = newFaces.First().halfEdges.First();
|
|
lastEdge.opposite = firstEdge;
|
|
firstEdge.opposite = lastEdge;
|
|
//first.previous.opposite = prev.next;
|
|
//prev.next.opposite = first.previous;
|
|
}
|
|
|
|
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.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");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// calculates the outermost edges of the geometry seen from the eyePoint
|
|
private void CalculateHorizon(Face face, Vector3 eyePoint, List<Tuple<Face, Vector3>> unclaimedPoints2,
|
|
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)
|
|
unclaimedPoints2.Add(set);
|
|
}
|
|
|
|
HalfEdge startingEdge = currentEdge;
|
|
do
|
|
{
|
|
Face oppositeFace = currentEdge.opposite.face;
|
|
if (!oppositeFace.visited)
|
|
{
|
|
if (oppositeFace.DistanceToPoint(eyePoint) > epsilon)
|
|
{
|
|
// positive distance means this is visible
|
|
CalculateHorizon(oppositeFace, eyePoint, unclaimedPoints2, outsideSet, horizonEdges, currentEdge.opposite);
|
|
}
|
|
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 = 300f;
|
|
|
|
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 = new[]
|
|
{
|
|
cubePoints[2],
|
|
cubePoints[3],
|
|
cubePoints[1],
|
|
cubePoints[2],
|
|
cubePoints[1],
|
|
cubePoints[0],
|
|
|
|
cubePoints[0],
|
|
cubePoints[1],
|
|
cubePoints[4],
|
|
cubePoints[1],
|
|
cubePoints[5],
|
|
cubePoints[4],
|
|
|
|
cubePoints[7],
|
|
cubePoints[5],
|
|
cubePoints[1],
|
|
cubePoints[7],
|
|
cubePoints[1],
|
|
cubePoints[3],
|
|
|
|
cubePoints[6],
|
|
cubePoints[7],
|
|
cubePoints[3],
|
|
cubePoints[6],
|
|
cubePoints[3],
|
|
cubePoints[2],
|
|
|
|
cubePoints[4],
|
|
cubePoints[6],
|
|
cubePoints[2],
|
|
cubePoints[4],
|
|
cubePoints[2],
|
|
cubePoints[0],
|
|
|
|
cubePoints[4],
|
|
cubePoints[5],
|
|
cubePoints[6],
|
|
cubePoints[5],
|
|
cubePoints[7],
|
|
cubePoints[6],
|
|
};
|
|
|
|
Vector3[] cubeVerts2 = new[]
|
|
{
|
|
cubePoints[2],
|
|
cubePoints[3],
|
|
cubePoints[1],
|
|
cubePoints[0],
|
|
|
|
cubePoints[0],
|
|
cubePoints[1],
|
|
cubePoints[4],
|
|
cubePoints[5],
|
|
|
|
cubePoints[7],
|
|
cubePoints[5],
|
|
cubePoints[1],
|
|
cubePoints[3],
|
|
|
|
cubePoints[6],
|
|
cubePoints[7],
|
|
cubePoints[3],
|
|
cubePoints[2],
|
|
|
|
cubePoints[4],
|
|
cubePoints[6],
|
|
cubePoints[2],
|
|
cubePoints[0],
|
|
|
|
cubePoints[4],
|
|
cubePoints[5],
|
|
cubePoints[6],
|
|
cubePoints[7],
|
|
};
|
|
|
|
List<Vector3> vertices = new List<Vector3>();
|
|
|
|
// convert planes
|
|
vertices = QuickHull2(cubePoints);
|
|
//Assert.IsTrue(vertices.Count == 36, "vertices.Count == 36, count: " + vertices.Count);
|
|
if (false)
|
|
foreach (var brush in root.entities[0].brushes)
|
|
{
|
|
List<Vector3> brushVertices = new List<Vector3>(cubeVerts);
|
|
//foreach (var plane in new [] { brush.planes.First() })
|
|
foreach (var plane in brush.planes.Skip(0).Take(1))
|
|
//foreach (var plane in brush.planes)
|
|
{
|
|
Plane p = new Plane(plane.v1, plane.v2, plane.v3);
|
|
List<Vector3> clippedVerts = new List<Vector3>();
|
|
List<Vector3> newBrushVertices = new List<Vector3>();
|
|
List<Vector3> faceVertices = new List<Vector3>();
|
|
|
|
if (true)
|
|
{
|
|
Func<float, bool> isFront = (f) => f > 0;
|
|
Func<float, bool> isBack = (f) => f < 0;
|
|
|
|
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 * p.Normal.X) + (start.Y * p.Normal.Y) + (start.Z * p.Normal.Z) + p.D;
|
|
var d2 = (end.X * p.Normal.X) + (end.Y * p.Normal.Y) + (end.Z * p.Normal.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);
|
|
clippedVerts.Add(intersect2);
|
|
}
|
|
else
|
|
d1 = d1;
|
|
}
|
|
}
|
|
|
|
|
|
if (i % 3 == 2)
|
|
{
|
|
if (faceVertices.Count == 4)
|
|
{
|
|
// quad, triangulize
|
|
/*newBrushVertices.Add(faceVertices[0]);
|
|
newBrushVertices.Add(faceVertices[1]);
|
|
newBrushVertices.Add(faceVertices[2]);
|
|
|
|
newBrushVertices.Add(faceVertices[2]);
|
|
newBrushVertices.Add(faceVertices[3]);
|
|
newBrushVertices.Add(faceVertices[0]);*/
|
|
|
|
var hullPoints = QuickHull(faceVertices, -p.Normal);
|
|
|
|
Assert.IsTrue(hullPoints.Count > 3, "QuickHull reduced quad to triangle!");
|
|
|
|
SortPoints(hullPoints, p.Normal);
|
|
|
|
for (int j = 1; j <= hullPoints.Count - 2; j++)
|
|
{
|
|
Vector3 v1 = hullPoints[0];
|
|
Vector3 v2 = hullPoints[j];
|
|
Vector3 v3 = hullPoints[j + 1];
|
|
|
|
var normal = new Plane(v1, v2, v3).Normal;
|
|
if (Vector3.Dot(p.Normal, normal) < 0)
|
|
{
|
|
v2 = hullPoints[j + 1];
|
|
v3 = hullPoints[j];
|
|
}
|
|
|
|
newBrushVertices.Add(v1);
|
|
newBrushVertices.Add(v2);
|
|
newBrushVertices.Add(v3);
|
|
}
|
|
}
|
|
else if (faceVertices.Count == 3)
|
|
{
|
|
// ok, either nothing was clipped or two got clipped
|
|
newBrushVertices.AddRange(faceVertices);
|
|
}
|
|
else if (faceVertices.Count == 0)
|
|
{
|
|
// ok, everything got clipped
|
|
d1 = d1;
|
|
}
|
|
else
|
|
d1 = d1;
|
|
|
|
faceVertices.Clear();
|
|
}
|
|
}
|
|
|
|
if (true)
|
|
{
|
|
List<Vector3> clippedVerts2 = new List<Vector3>();
|
|
foreach (var v in clippedVerts)
|
|
{
|
|
if (!clippedVerts2.Any(x => (v - x).Length < 0.01))
|
|
clippedVerts2.Add(v);
|
|
}
|
|
|
|
var hullPoints = QuickHull(clippedVerts2, -p.Normal);
|
|
SortPoints(hullPoints, p.Normal);
|
|
|
|
for (int i = 1; i <= hullPoints.Count - 2; i++)
|
|
{
|
|
Vector3 v1 = hullPoints[0];
|
|
Vector3 v2 = hullPoints[i];
|
|
Vector3 v3 = hullPoints[i + 1];
|
|
|
|
var normal = new Plane(v1, v2, v3).Normal;
|
|
if (Vector3.Dot(p.Normal, normal) < 0)
|
|
{
|
|
v2 = hullPoints[i + 1];
|
|
v3 = hullPoints[i];
|
|
}
|
|
|
|
newBrushVertices.Add(v1);
|
|
newBrushVertices.Add(v2);
|
|
newBrushVertices.Add(v3);
|
|
}
|
|
|
|
/*var brushPoints = QuickHull(newBrushVertices);
|
|
newBrushVertices.Clear();
|
|
|
|
for (int i = 1; i <= brushPoints.Count - 2; i++)
|
|
{
|
|
Vector3 v1 = hullPoints[0];
|
|
Vector3 v2 = hullPoints[i];
|
|
Vector3 v3 = hullPoints[i+1];
|
|
|
|
var normal = new Plane(v1, v2, v3).Normal;
|
|
if (Vector3.Dot(p.Normal, normal) < 0)
|
|
{
|
|
v2 = hullPoints[i + 1];
|
|
v3 = hullPoints[i];
|
|
}
|
|
|
|
newBrushVertices.Add(v1);
|
|
newBrushVertices.Add(v2);
|
|
newBrushVertices.Add(v3);
|
|
|
|
//flip = !flip;
|
|
}*/
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/*Actor levelActor = Actor.AddChild<Actor>();
|
|
levelActor.Name = "LevelGeometry";
|
|
|
|
int brushIndex = 1;
|
|
foreach (var brush in root.entities[0].brushes)
|
|
{
|
|
BoxBrush boxBrush = levelActor.AddChild<BoxBrush>();
|
|
boxBrush.Size = new Vector3(1000f);
|
|
boxBrush.Mode = BrushMode.Additive;
|
|
boxBrush.Name = "Brush " + brushIndex;
|
|
|
|
int planeIndex = 1;
|
|
foreach (var brushPlane in brush.planes)
|
|
{
|
|
Plane plane = new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3);
|
|
|
|
BoxBrush planeBrush = levelActor.AddChild<BoxBrush>();
|
|
|
|
planeBrush.Mode = BrushMode.Subtractive;
|
|
planeBrush.Position = -plane.Normal * plane.D;
|
|
planeBrush.Orientation = Quaternion.LookRotation(plane.Normal);
|
|
planeBrush.Position = planeBrush.Position - (planeBrush.Transform.Forward * 1500f);
|
|
planeBrush.Size = new Vector3(3000f);
|
|
planeBrush.Name = "ClipPlane " + brushIndex + " - " + planeIndex;
|
|
planeIndex++;
|
|
}
|
|
|
|
break;
|
|
|
|
brushIndex++;
|
|
}
|
|
|
|
levelActor.Orientation = Quaternion.Euler(90f, 0, 0);
|
|
|
|
Scene.BuildCSG(200);*/
|
|
|
|
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();
|
|
}
|
|
}
|
|
} |