Files
GoakeFlax/Source/Game/Q3MapImporter.cs
2021-08-31 20:03:23 +03:00

1182 lines
32 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using FlaxEngine;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using FlaxEngine.Assertions;
using FlaxEngine.Utilities;
using Console = Cabrito.Console;
// TODO: remove coplanar/degenerate faces from the final mesh
// It seems the original algorithm is working but removing degenerate faces does not work
namespace Game
{
public class BrushGeometry
{
public MapBrush brush;
public Vector3[] vertices;
public uint[] indices;
public Vector2[] uvs;
public Vector3[] normals;
public Model model = null;
public MaterialBase brushMaterial = null; // FIXME: brush can have multiple textures
}
public class Q3MapImporter : Script
{
//private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube_q1.map";
private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube_q3.map";
//private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube_valve.map";
//private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\dm4.map";
//private string mapPath = @"C:\dev\Goake\maps\aerowalk\aerowalk.map";
//private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\problematic.map";
Model model;
public MaterialBase material;
const float epsilon = 0.01f;
static void QuickHull(Vector3[] points, out Vector3[] outVertices)
{
var verts = new List<Vector3>();
var tris = new List<int>();
var normals = new List<Vector3>();
var calc = new ConvexHullCalculator();
calc.GenerateHull(points.ToList(), true, ref verts, ref tris, ref normals);
var finalPoints = new List<Vector3>();
foreach (var tri in tris)
{
finalPoints.Add(verts[tri]);
}
outVertices = finalPoints.ToArray();
}
private Color[] planeColors = new Color[]
{
Color.Red,
Color.Orange,
Color.Yellow,
Color.Green,
Color.Cyan,
Color.Blue,
Color.Purple,
Color.Magenta,
};
private int skipPlanes = 0;
private int takePlanes = 5;
private List<Vector3> debugPoints = new List<Vector3>();
private List<Tuple<Vector3, Vector3>> debugNormals = new List<Tuple<Vector3, Vector3>>();
public override void OnDebugDraw()
{
foreach (var cn in debugNormals)
{
DebugDraw.DrawLine(cn.Item1, cn.Item1 + cn.Item2 * 50f, Color.Red, 0f, false);
}
return;
if (root == null)
return;
//foreach (var p in debugPoints)
// DebugDraw.DrawSphere(new BoundingSphere(p, 30f), Color.LightBlue, 0f, false);
foreach (var brush in root.entities[0].brushes.Skip(1).Take(1))
{
int planeI = skipPlanes;
foreach (var plane in brush.planes.Take(takePlanes))
//foreach (var plane in brush.planes)
{
Plane p = new Plane(plane.v1, plane.v2, plane.v3);
Vector3 planeNormal = -p.Normal;
const float w = 300f;
Vector3 p1 = new Vector3(-w, -w, 0f);
Vector3 p2 = new Vector3(w, -w, 0f);
Vector3 p3 = new Vector3(-w, w, 0f);
Vector3 p4 = new Vector3(w, w, 0f);
Vector3 uu = Vector3.Up;
if (Mathf.Abs(Vector3.Dot(planeNormal, uu)) > 0.9999f)
uu = Vector3.Forward;
var q = Quaternion.LookAt(Vector3.Zero, planeNormal, -uu);
p1 = p1 * q;
p2 = p2 * q;
p3 = p3 * q;
p4 = p4 * q;
p1 += p.D * planeNormal;
p2 += p.D * planeNormal;
p3 += p.D * planeNormal;
p4 += p.D * planeNormal;
var color = planeColors[planeI%planeColors.Length] * 0.5f;
DebugDraw.DrawTriangle(p1, p2, p3, color);
DebugDraw.DrawTriangle(p2, p3, p4, color);
planeI++;
}
}
}
private MapEntity root;
private static IEnumerable<IEnumerable<T>> DifferentCombinations<T>(IEnumerable<T> elements, int k)
{
return k == 0 ? new[] { new T[0] } :
elements.SelectMany((e, i) =>
DifferentCombinations(elements.Skip(i + 1), (k - 1)).Select(c => (new[] {e}).Concat(c)));
}
/// <summary>
/// Triangulates the brush by calculating intersection points between triplets of planes.
/// Does not work well with off-axis aligned planes.
/// </summary>
public static void TriangulateBrush(MapBrush brush, out Vector3[] vertices)
{
HashSet<Vector3> planePoints = new HashSet<Vector3>();
List<Plane> planes = new List<Plane>();
float maxDist = 0f;
foreach (var brushPlane in brush.planes)
{
if (Mathf.Abs(brushPlane.plane.D) > maxDist)
maxDist = Mathf.Abs(brushPlane.plane.D);
planes.Add(brushPlane.plane);
}
maxDist *= Mathf.Sqrt(3);
var combinations = DifferentCombinations(planes, 3).ToList();
// pass 1: get all intersection points
foreach (var comb in combinations)
{
var p1 = comb.Skip(0).First();
var p2 = comb.Skip(1).First();
var p3 = comb.Skip(2).First();
//var maxDist = Math.Abs(p1.D * p2.D * p3.D);//Math.Max(p1.D, Math.Max(p2.D, p3.D));
// intersection of three planes
double denom = Vector3.Dot(p1.Normal, Vector3.Cross(p2.Normal, p3.Normal));
//if (denom < 0.0000000001)
// continue;
var intersection = (Vector3.Cross(p2.Normal, p3.Normal) * -p1.D +
Vector3.Cross(p3.Normal, p1.Normal) * -p2.D +
Vector3.Cross(p1.Normal, p2.Normal) * -p3.D) / (float)denom;
if (Mathf.Abs(intersection.X) > maxDist * 1f || Mathf.Abs(intersection.Y) > maxDist * 1f ||
Mathf.Abs(intersection.Z) > maxDist * 1f)
{
denom = denom;
continue;
}
if (Math.Abs(denom) < 0.0000000001)
{
denom = denom;
continue;
}
//if (intersection.Length > maxDist*2f)
// continue;
// Flip Y and Z
/*var temp = intersection.Y;
intersection.Y = intersection.Z;
intersection.Z = temp;*/
//if (intersection.Length >= maxDist)
// temp = temp;
planePoints.Add(intersection);
}
// pass 2: cull points behind clipping planes
var planePoints2 = planePoints;
planePoints = new HashSet<Vector3>();
foreach (var p in planePoints2)
{
bool front = true;
foreach (var brushPlane in brush.planes)
{
var dot = -Plane.DotCoordinate(brushPlane.plane, p);
if (dot < -0.01f)
{
front = false;
break;
}
}
if (front)
planePoints.Add(p);
}
if (planePoints.Count > 0)
{
QuickHull(planePoints.ToArray(), out vertices);
return;
}
vertices = new Vector3[0];
}
static public void TriangulateBrush2(MapBrush brush, out Vector3[] vertices)
{
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;
QuickHull(cubePoints, out cubeVerts);
List<Vector3> brushVertices = new List<Vector3>(cubeVerts);
foreach (var brushPlane in brush.planes)
{
Plane plane = brushPlane.plane;
List<Vector3> faceVertices = new List<Vector3>();
List<Vector3> clippedVertices = new List<Vector3>();
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 = Plane.DotCoordinate(plane, start);
var d2 = Plane.DotCoordinate(plane, end);
if (isBack(d1))
{
// include the point behind the clipping plane
faceVertices.Add(start);
}
if (isBack(d1) && isFront(d2) || isFront(d1) && isBack(d2))
{
// the cutting plane clips the edge
//if (isFront(d2))
{
Ray ray = new Ray(start, (end - start).Normalized);
Ray ray2 = new Ray(end, (start - end).Normalized);
if (plane.Intersects(ref ray, out Vector3 point) || plane.Intersects(ref ray2, out point))
{
//faceVertices.Add(point);
//clippedVertices.Add(point);
/*
intersect
start __._ (end)
| |/
| /
|/
next
*/
if (isBack(d1) && isFront(d2))
{
// finish the current triangle and start the next one
// [start, intersect, next], [intersect, ...]
faceVertices.Add(point);
if ((faceVertices.Count % 3) == 2)
{
int i3 = ((i2 + 1) % 3 == 0) ? (i2 - 2) : (i2 + 1);
Vector3 next = brushVertices[i3];
faceVertices.Add(next);
}
else
ray = ray;
faceVertices.Add(point);
}
/*
____ (start)
| |/
| * intersect2
|/
end
*/
else if (isFront(d1) && isBack(d2))
{
// continue where we left off
// [intersect, intersect2, ...]
faceVertices.Add(point);
if ((i % 3) == 2)
{
int i3 = ((i2 + 1) % 3 == 0) ? (i2 - 2) : (i2 + 1);
Vector3 next = brushVertices[i3];
faceVertices.Add(next);
}
else
ray = ray;
}
else
ray = ray;
}
else
d1 = d1;
}
}
}
brushVertices.Clear();
brushVertices.AddRange(faceVertices);
Assert.IsTrue(faceVertices.Count % 3 == 0);
/*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 tempPoints = newMeshPoints;
newMeshPoints = new List<Vector3>(tempPoints.Count);
foreach (var tp in tempPoints)
{
// Flip Y and Z
newMeshPoints.Add(new Vector3(tp.X, tp.Z, tp.Y));
}
var hullPoints = QuickHull(newMeshPoints.ToArray());
var ms = new MeshSimplifier();
var optimizedVerts = ms.Simplify(hullPoints);
brushVertices.Clear();
brushVertices.AddRange(hullPoints);
}
else
brushVertices.Clear();
*/
}
vertices = brushVertices.ToArray();
}
static public void TriangulateBrush3(MapBrush brush, out Vector3[] vertices)
{
float cs = 4000f;
float maxD = 0f;
float minD = 0f;
foreach (var brushPlane in brush.planes)
{
var p = brushPlane.plane;
minD = Mathf.Min(p.D);
maxD = Mathf.Max(p.D);
}
//cs = maxD*2;
Vector3[] cubePoints = new[]
{
new Vector3(-1, -1, -1) * cs,
new Vector3(1, -1, -1) * cs,
new Vector3(-1, 1, -1) * cs,
new Vector3(1, 1, -1) * cs,
new Vector3(-1, -1, 1) * cs,
new Vector3(1, -1, 1) * cs,
new Vector3(-1, 1, 1) * cs,
new Vector3(1, 1, 1) * cs,
};
Vector3[] cubeVerts;
QuickHull(cubePoints, out cubeVerts);
List<Vector3> brushVertices = new List<Vector3>(cubeVerts);
//foreach (var brushPlane in brush.planes.Skip(skipPlanes).Take(takePlanes))
foreach (var brushPlane in brush.planes)
{
Plane plane = brushPlane.plane;
List<Vector3> faceVertices = new List<Vector3>();
List<Vector3> clippedVertices = new List<Vector3>();
Func<float, bool> isFront = (f) => f > epsilon;
Func<float, bool> isBack = (f) => f < epsilon;
List<Tuple<Vector3, Vector3>> planeEdges = new List<Tuple<Vector3, Vector3>>();
var faceEdges = new List<Tuple<Vector3, Vector3>>();
void ProcessEdges()
{
if (planeEdges.Count > 0)
{
// heal discontinued edges
for (int j = 0; j < planeEdges.Count; j++)
{
var edgePrev = planeEdges[j];
var edgeNext = planeEdges[(j + 1) % planeEdges.Count];
//if (edgePrev.Item2 != edgeNext.Item1)
if ((edgePrev.Item2 - edgeNext.Item1).Length > 0.0001f)
{
var newEdge = new Tuple<Vector3, Vector3>(edgePrev.Item2, edgeNext.Item1);
planeEdges.Insert(j + 1, newEdge);
j--;
}
}
// triangulate edges
/*for (int j = 0; j < edges.Count - 1; j++)
{
var edgePrev = edges[j];
var edgeNext = edges[(j + 1) % edges.Count];
Vector3 v0 = edges[0].Item1;
Vector3 v1 = edgePrev.Item2;
Vector3 v2 = edgeNext.Item2;
faceVertices.Add(v0);
faceVertices.Add(v1);
faceVertices.Add(v2);
}*/
// triangulate clipped face
/*for (int j = 0; j < clippedVertices.Count-1; j++)
{
Vector3 v0 = clippedVertices[0];
Vector3 v1 = edgePrev.Item2;
Vector3 v2 = edgeNext.Item2;
}*/
// TODO: maybe optimize the triangles here instead of using QuickHull
}
else
plane = plane;
faceEdges.AddRange(planeEdges);
planeEdges = new List<Tuple<Vector3, Vector3>>();
//edges.Clear();
}
for (int i = 0; i < brushVertices.Count; i++)
{
if (i > 0 && i % 3 == 0)
ProcessEdges();
int i2 = ((i + 1) % 3 == 0) ? (i - 2) : (i + 1);
Vector3 start = brushVertices[i];
Vector3 end = brushVertices[i2];
var d1 = Plane.DotCoordinate(plane, start);
var d2 = Plane.DotCoordinate(plane, end);
Vector3 edgeStart = start;
Vector3 edgeEnd = end;
if (isBack(d1))
{
edgeStart = start;
}
if (isBack(d1) && isFront(d2) || isFront(d1) && isBack(d2))
{
Ray ray = new Ray(start, (end - start).Normalized);
Ray ray2 = new Ray(end, (start - end).Normalized);
if (plane.Intersects(ref ray, out Vector3 point) || plane.Intersects(ref ray2, out point))
{
edgeEnd = point;
clippedVertices.Add(point);
if (isFront(d1))
{
edgeStart = edgeEnd;
edgeEnd = end;
}
}
}
if (isFront(d1) && isFront(d2))
continue;
var abs = Mathf.Abs((edgeEnd-edgeStart).Length);
if (abs < 0.000001f)
{
abs = abs;
//continue;
}
Tuple<Vector3, Vector3> edge = new Tuple<Vector3, Vector3>(edgeStart, edgeEnd);
planeEdges.Add(edge);
}
ProcessEdges();
if (true)
{
// triangulate edges
faceVertices.Clear();
//foreach (var edges in faceEdges)
{
// merge same points in edges
for (int j = 0; j < faceEdges.Count; j++)
{
Vector3 v0 = faceEdges[j].Item1;
Vector3 v1 = faceEdges[j].Item2;
var edgeNext = faceEdges[(j + 1) % faceEdges.Count];
Vector3 v2 = edgeNext.Item1;
Vector3 v3 = edgeNext.Item2;
var dot = Vector3.Dot(v1.Normalized, v2.Normalized);
if (v1 != v2 && dot > 0.9999999f)
{
v1 = v1;
//faceEdges[(j + 1) % faceEdges.Count] = new Tuple<Vector3, Vector3>(v1, v3);
}
}
List<Tuple<Vector3, Vector3>> newEdges = new List<Tuple<Vector3, Vector3>>();
for (int j = 0; j < faceEdges.Count; j++)
{
Vector3 v0 = faceEdges[j].Item1;
Vector3 v1 = faceEdges[j].Item2;
var abs = Mathf.Abs((v1-v0).Length);
if (abs < 0.000001f)
{
v1 = v1;
//continue;
}
Tuple<Vector3, Vector3> newEdge = new Tuple<Vector3, Vector3>(v0, v1);
while (true)
{
var edgeNext = faceEdges[(j + 1) % faceEdges.Count];
Vector3 v2 = edgeNext.Item1;
Vector3 v3 = edgeNext.Item2;
//var dot = Vector3.Dot((v3 - v0).Normalized, (v1 - v0).Normalized);
/*var dot = Vector3.Dot((v3 - v2).Normalized, (v1 - v0).Normalized);
if (dot > 0.9f)
{
newEdge = new Tuple<Vector3, Vector3>(v0, v3);
j++;
}
else*/
break;
/*var dot = Vector3.Dot((v3 - v2).Normalized, (v1 - v0).Normalized);
if (dot > 0.9f)
{
newEdge = new Tuple<Vector3, Vector3>(v0, v3);
j++;
}
else
break;*/
}
newEdges.Add(newEdge);
}
for (int j = 0; j < newEdges.Count - 1; j++)
{
var edgePrev = newEdges[j];
var edgeNext = newEdges[(j + 1) % newEdges.Count];
Vector3 v0 = newEdges[0].Item1;
Vector3 v1 = edgePrev.Item2;
Vector3 v2 = edgeNext.Item2;
faceVertices.Add(v0);
faceVertices.Add(v1);
faceVertices.Add(v2);
}
}
}
List<Vector3> uniqPoints = new List<Vector3>();
foreach (var v in faceVertices)
{
bool found = false;
foreach (var v2 in uniqPoints)
{
if ((v - v2).Length < 0.01f)
{
found = true;
break;
}
}
if (!found)
uniqPoints.Add(v);
//uniqPoints.Add(new Vector3((float)Math.Round(v.X, 3), (float)Math.Round(v.Y, 3), (float)Math.Round(v.Z, 3)));
}
//debugPoints = new List<Vector3>(uniqPoints);
Vector3[] hullPoints;
QuickHull(uniqPoints.ToArray(), out hullPoints);
var hullVerts = new MeshSimplifier().Simplify(hullPoints);
if (false)
{
// create edges from clipped points
var clippedEdges = new List<Tuple<Vector3, Vector3>>();
//foreach (var e in edges)
// newEdges.Add(new Tuple<Vector3, Vector3>(e.Item1, e.Item2));
for (int i = 0; i < clippedVertices.Count; i++)
{
int i2 = (i + 1) % clippedVertices.Count;
Vector3 start = clippedVertices[i];
Vector3 end = clippedVertices[i2];
while (i < clippedVertices.Count)
{
int i3 = (i + 2) % clippedVertices.Count;
Vector3 end2 = clippedVertices[i3];
var edgeDirection = (end - start).Normalized;
var edgeDirection2 = (end2 - start).Normalized;
if ((edgeDirection2 - edgeDirection).Length < 0.0001f)
{
end = end2;
i++;
}
else
break;
}
clippedEdges.Add(new Tuple<Vector3, Vector3>(start, end));
}
// triangulate edges
for (int j = 0; j < clippedEdges.Count; j++)
{
var edgePrev = clippedEdges[j];
var edgeNext = clippedEdges[(j + 1) % clippedEdges.Count];
Vector3 v0 = clippedEdges[0].Item1;
Vector3 v1 = edgePrev.Item2;
Vector3 v2 = edgeNext.Item2;
faceVertices.Add(v0);
faceVertices.Add(v1);
faceVertices.Add(v2);
}
}
if (true)
{
uniqPoints = uniqPoints;
hullVerts = hullVerts;
hullPoints = hullPoints;
faceVertices = faceVertices;
var optimizedVerts = hullVerts;//new MeshSimplifier().Simplify(faceVertices.ToArray());
brushVertices.Clear();
brushVertices.AddRange(optimizedVerts);
}
else
{
//debugPoints = new List<Vector3>(faceVertices);
//var hullPoints = faceVertices;
var ms = new MeshSimplifier();
var optimizedVerts = hullPoints; //ms.Simplify(hullPoints);
brushVertices.Clear();
brushVertices.AddRange(optimizedVerts);
}
}
vertices = brushVertices.ToArray();
}
public override void OnStart()
{
byte[] mapChars = File.ReadAllBytes(mapPath);
Stopwatch sw = Stopwatch.StartNew();
root = MapParser.Parse(mapChars);
sw.Stop();
Console.Print("Map parsing time: " + sw.Elapsed.TotalMilliseconds + "ms");
bool oneMesh = false;
bool convexMesh = true;
if (!oneMesh)
{
Dictionary<string, MaterialBase> materials = null;
var mapRootActor = Actor.AddChild<Actor>();
mapRootActor.Name = "MapRootActor";
List<BrushGeometry> brushGeometries = new List<BrushGeometry>(root.entities[0].brushes.Count);
// pass 1: triangulation
sw.Restart();
int brushIndex = 0;
int totalverts = 0;
foreach (var brush in root.entities[0].brushes)
{
try
{
BrushGeometry geom = new BrushGeometry();
TriangulateBrush(brush, out geom.vertices);
geom.brush = brush;
brushGeometries.Add(geom);
totalverts += geom.vertices.Length;
Assert.IsTrue(geom.vertices.Length > 0);
}
catch (Exception e)
{
Console.Print("Failed to triangulate brush " + brushIndex.ToString() + ": " + e.Message);
//FlaxEngine.Engine.RequestExit();
}
brushIndex++;
}
sw.Stop();
Console.Print("Pass 1: triangulation: " + sw.Elapsed.TotalMilliseconds + "ms, total verts: " + totalverts + ", should be 1002");
// pass 2: texturing
sw.Restart();
foreach (var geom in brushGeometries)
{
var brushVertices = geom.vertices;
Vector2[] brushUvs = new Vector2[brushVertices.Length];
Vector3[] brushNormals = new Vector3[brushVertices.Length];
var textureName = geom.brush.planes[0].texture;
if (materials == null)
{
materials = new Dictionary<string, MaterialBase>();
BrushMaterialList brushMaterialList = Engine.GetCustomSettings("BrushMaterials")
?.CreateInstance<BrushMaterialList>();
if (brushMaterialList != null)
{
foreach (var m in brushMaterialList.materialAssets)
materials.Add(m.name, m.asset);
Console.Print("materials dictionary with " + materials.Count + " entries");
}
else
{
Console.Print("no materials dictionary found");
}
BrushMaterialList brushMaterialList2 = Engine.GetCustomSettings("BrushMaterialsLegacy")
?.CreateInstance<BrushMaterialList>();
if (brushMaterialList2 != null)
{
foreach (var m in brushMaterialList2.materialAssets)
materials.Add(m.name, m.asset);
}
}
// FIXME: brush can have multiple textures
if (!materials.TryGetValue(textureName, out geom.brushMaterial))
{
Console.Print("Material '" + textureName + "' not found for brush");
materials.Add(textureName, material);
geom.brushMaterial = material;
}
for (int i = 0; i < brushVertices.Length; i += 3)
{
Vector3 v1 = brushVertices[i + 0];
Vector3 v2 = brushVertices[i + 1];
Vector3 v3 = brushVertices[i + 2];
Vector3 normal = -Vector3.Cross(v3 - v1, v2 - v1).Normalized;
// fetch the texture parameters from the plane with matching normal
Vector2 uvScale = new Vector2(0f);
float uvRotation = 0f;
Vector2 uvOffset = new Vector2(0f);
bool found = false;
foreach (var brushPlane in geom.brush.planes)
{
if ((brushPlane.plane.Normal - normal).Length < 0.01f)
{
normal = brushPlane.plane.Normal; // for consistency
uvScale = 1f / brushPlane.scale;
uvRotation = brushPlane.rotation;
uvOffset = brushPlane.offset * brushPlane.scale;
found = true;
break;
}
}
if (!found)
Console.Print("no matching plane found, bad geometry?");
Vector2 uv1, uv2, uv3;
// if quake format
{
// The texture is projected to the surface from three angles, the axis with least
// distortion is chosen here.
// Attempt to workaround most rounding errors at 45-degree angles which causes bias towards one axis.
// This behaviour is seemingly random in different engines and editors, so let's not bother.
Vector3 textureNormal = new Vector3((float)Math.Round(normal.X, 4),
(float)Math.Round(normal.Y, 4), (float)Math.Round(normal.Z, 4));
var dotX = Math.Abs(Vector3.Dot(textureNormal, Vector3.Right));
var dotY = Math.Abs(Vector3.Dot(textureNormal, Vector3.Up));
var dotZ = Math.Abs(Vector3.Dot(textureNormal, Vector3.Forward));
Vector3 axis;
if (dotY >= dotX && dotY >= dotZ)
axis = -Vector3.Up;
else if (dotX >= dotY && dotX >= dotZ)
axis = Vector3.Right;
else if (dotZ >= dotX && dotZ >= dotY)
axis = -Vector3.Forward;
else
axis = Vector3.Right;
var axisForward = Mathf.Abs(Vector3.Dot(axis, Vector3.Up)) > 0.01f
? -Vector3.Forward
: Vector3.Up;
var axisForward2 = Mathf.Abs(Vector3.Dot(axis, Vector3.Up)) > 0.01f
? Vector3.Up
: -Vector3.Forward;
Quaternion rot = Quaternion.Identity;
rot = rot * Quaternion.LookRotation(axis, axisForward);
rot = rot * Quaternion.RotationAxis(-Vector3.Forward,
180f * Mathf.DegreesToRadians);
rot = rot * Quaternion.RotationAxis(
Mathf.Abs(Vector3.Dot(axis, Vector3.Right)) > 0.01f
? Vector3.Right
: axisForward2,
uvRotation * Mathf.DegreesToRadians);
uv1 = ((Vector2)(v1 * rot) + uvOffset) * uvScale;
uv2 = ((Vector2)(v2 * rot) + uvOffset) * uvScale;
uv3 = ((Vector2)(v3 * rot) + uvOffset) * uvScale;
}
brushUvs[i + 0] = uv1;
brushUvs[i + 1] = uv2;
brushUvs[i + 2] = uv3;
brushNormals[i + 0] = normal;
brushNormals[i + 1] = normal;
brushNormals[i + 2] = normal;
}
geom.vertices = brushVertices;
geom.uvs = brushUvs;
geom.normals = brushNormals;
geom.indices = new uint[geom.vertices.Length];
for (uint i = 0; i < geom.vertices.Length; i++)
geom.indices[i] = i;
if (geom.vertices.Length > 0)
{
geom.model = Content.CreateVirtualAsset<Model>();
geom.model.SetupLODs(new int[] { 1 });
geom.model.LODs[0].Meshes[0].UpdateMesh(geom.vertices, geom.indices, geom.normals,
null, geom.uvs);
/*
StaticModel childModel = Actor.AddChild<StaticModel>();
childModel.Name = "Brush_" + brushIndex;
childModel.Model = geom.model;
childModel.SetMaterial(0, geom.brushMaterial);
childModel.Parent = mapRootActor;
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
if (collisionData.CookCollision(convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, geom.vertices.ToArray(),
geom.indices.ToArray()))
{
bool failed = true;
if (convexMesh)
{
// fallback to triangle mesh
failed = collisionData.CookCollision(CollisionDataType.TriangleMesh,
geom.vertices.ToArray(),
geom.indices.ToArray());
if (!failed)
Console.PrintWarning("Hull brush " + brushIndex.ToString() + " is not convex");
}
if (failed)
throw new Exception("failed to cook final collision");
}
var meshCollider = childModel.AddChild<MeshCollider>();
meshCollider.CollisionData = collisionData;
*/
}
//brushGeometries.Add(geom);
}
sw.Stop();
Console.Print("Pass 2: texturing: " + sw.Elapsed.TotalMilliseconds + "ms");
// pass 3: collision
sw.Restart();
brushIndex = 0;
foreach (var geom in brushGeometries)
{
StaticModel childModel = Actor.AddChild<StaticModel>();
childModel.Name = "Brush_" + brushIndex;
childModel.Model = geom.model;
childModel.SetMaterial(0, geom.brushMaterial);
childModel.Parent = mapRootActor;
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
if (collisionData.CookCollision(convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, geom.vertices,
geom.indices))
{
bool failed = true;
if (convexMesh)
{
// fallback to triangle mesh
failed = collisionData.CookCollision(CollisionDataType.TriangleMesh,
geom.vertices,
geom.indices);
if (!failed)
Console.PrintWarning("Hull brush " + brushIndex.ToString() + " is not convex");
}
if (failed)
throw new Exception("failed to cook final collision");
}
var meshCollider = childModel.AddChild<MeshCollider>();
meshCollider.CollisionData = collisionData;
brushIndex++;
}
sw.Stop();
Console.Print("Pass 3: collision: " + sw.Elapsed.TotalMilliseconds + "ms");
}
else
{
List<Vector3> vertices = new List<Vector3>();
List<Vector2> uvs = new List<Vector2>();
List<Vector3> normals = new List<Vector3>();
int brushIndex = 0;
foreach (var brush in root.entities[0].brushes)
{
try
{
TriangulateBrush3(brush, out Vector3[] brushVertices);
Vector2[] brushUvs = new Vector2[brushVertices.Length];
Vector3[] brushNormals = new Vector3[brushVertices.Length];
for (int i = 0; i < brushVertices.Length; i += 3)
{
Vector3 v1 = brushVertices[i + 0];
Vector3 v2 = brushVertices[i + 1];
Vector3 v3 = brushVertices[i + 2];
Vector3 normal = -Vector3.Cross(v3 - v1, v2 - v1).Normalized;
// fetch the texture parameters from the plane with matching normal
Vector2 uvScale = new Vector2(0f);
float uvRotation = 0f;
Vector2 uvOffset = new Vector2(0f);
bool found = false;
foreach (var brushPlane in brush.planes)
{
if ((brushPlane.plane.Normal - normal).Length < 0.01f)
{
normal = brushPlane.plane.Normal; // for consistency
uvScale = 1f / brushPlane.scale;
uvRotation = brushPlane.rotation;
uvOffset = brushPlane.offset * brushPlane.scale;
found = true;
break;
}
}
if (!found)
Console.Print("no matching plane found, bad geometry?");
Vector2 uv1, uv2, uv3;
// if quake format
{
// The texture is projected to the surface from three angles, the axis with least
// distortion is chosen here.
// Attempt to workaround most rounding errors at 45-degree angles which causes bias towards one axis.
// This behaviour is seemingly random in different engines and editors, so let's not bother.
Vector3 textureNormal = new Vector3((float)Math.Round(normal.X, 4),
(float)Math.Round(normal.Y, 4), (float)Math.Round(normal.Z, 4));
var dotX = Math.Abs(Vector3.Dot(textureNormal, Vector3.Right));
var dotY = Math.Abs(Vector3.Dot(textureNormal, Vector3.Up));
var dotZ = Math.Abs(Vector3.Dot(textureNormal, Vector3.Forward));
Vector3 axis;
if (dotY >= dotX && dotY >= dotZ)
axis = -Vector3.Up;
else if (dotX >= dotY && dotX >= dotZ)
axis = Vector3.Right;
else if (dotZ >= dotX && dotZ >= dotY)
axis = -Vector3.Forward;
else
axis = Vector3.Right;
var axisForward = Mathf.Abs(Vector3.Dot(axis, Vector3.Up)) > 0.01f
? -Vector3.Forward
: Vector3.Up;
var axisForward2 = Mathf.Abs(Vector3.Dot(axis, Vector3.Up)) > 0.01f
? Vector3.Up
: -Vector3.Forward;
Quaternion rot = Quaternion.Identity;
rot = rot * Quaternion.LookRotation(axis, axisForward);
rot = rot * Quaternion.RotationAxis(-Vector3.Forward,
180f * Mathf.DegreesToRadians);
rot = rot * Quaternion.RotationAxis(
Mathf.Abs(Vector3.Dot(axis, Vector3.Right)) > 0.01f
? Vector3.Right
: axisForward2,
uvRotation * Mathf.DegreesToRadians);
uv1 = ((Vector2)(v1 * rot) + uvOffset) * uvScale;
uv2 = ((Vector2)(v2 * rot) + uvOffset) * uvScale;
uv3 = ((Vector2)(v3 * rot) + uvOffset) * uvScale;
}
brushUvs[i + 0] = uv1;
brushUvs[i + 1] = uv2;
brushUvs[i + 2] = uv3;
brushNormals[i + 0] = normal;
brushNormals[i + 1] = normal;
brushNormals[i + 2] = normal;
}
vertices.AddRange(brushVertices);
uvs.AddRange(brushUvs);
normals.AddRange(brushNormals);
}
catch (Exception e)
{
Console.Print("Failed to hull brush " + brushIndex.ToString() + ": " + e.Message);
}
brushIndex++;
}
if (vertices.Count > 0)
{
uint[] triangles = new uint[vertices.Count];
for (uint i = 0; i < vertices.Count; i++)
triangles[i] = i;
model = Content.CreateVirtualAsset<Model>();
model.SetupLODs(new int[] { 1 });
model.LODs[0].Meshes[0].UpdateMesh(vertices.ToArray(), (int[])(object)triangles, normals.ToArray(),
null, uvs.ToArray());
StaticModel childModel = Actor.AddChild<StaticModel>();
childModel.Name = "MapModel";
childModel.Model = model;
childModel.SetMaterial(0, material);
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
if (collisionData.CookCollision(CollisionDataType.TriangleMesh, vertices.ToArray(),
triangles.ToArray()))
throw new Exception("failed to cook final collision");
var meshCollider = childModel.AddChild<MeshCollider>();
meshCollider.CollisionData = collisionData;
}
}
}
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()
{
}
public override void OnDestroy()
{
Destroy(ref model);
base.OnDestroy();
}
}
}