This commit is contained in:
GoaLitiuM
2021-08-31 20:03:02 +03:00
parent 995e9bdbce
commit 8bde0a7f18
11 changed files with 511 additions and 259 deletions

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using FlaxEngine.Assertions;
namespace Cabrito
{
@@ -32,13 +31,12 @@ namespace Cabrito
instance = new ConsoleInstance();
}
private static void Destroy()
public static void Destroy()
{
if (instance != null)
{
instance.Dispose();
instance = null;
FlaxEngine.Debug.Log("Console.Destroy");
}
}
@@ -139,7 +137,10 @@ namespace Cabrito
assemblyName.StartsWith("Mono.") ||
assemblyName == "mscorlib" ||
assemblyName == "Newtonsoft.Json" ||
assemblyName.StartsWith("FlaxEngine."))
assemblyName.StartsWith("FlaxEngine.") ||
assemblyName.StartsWith("JetBrains.") ||
assemblyName.StartsWith("Microsoft.") ||
assemblyName.StartsWith("nunit."))
{
continue;
}

View File

@@ -1,6 +1,7 @@
using FlaxEngine;
using Cabrito;
using System.Diagnostics;
using System.IO;
namespace Game
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using FlaxEngine;
using System.IO;
using System.Linq;
@@ -1082,6 +1083,16 @@ 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
@@ -1103,7 +1114,7 @@ namespace Game
void QuickHull(Vector3[] points, out Vector3[] outVertices)
static void QuickHull(Vector3[] points, out Vector3[] outVertices)
{
var verts = new List<Vector3>();
var tris = new List<int>();
@@ -1424,7 +1435,7 @@ namespace Game
return brushVertices.ToArray();
}
void TriangulateBrush3(MapBrush brush, out Vector3[] vertices)
static public void TriangulateBrush3(MapBrush brush, out Vector3[] vertices)
{
float cs = 3000f;
@@ -1638,13 +1649,13 @@ namespace Game
//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);
//debugPoints = new List<Vector3>(uniqPoints);
Vector3[] hullPoints;
QuickHull(uniqPoints.ToArray(), out hullPoints);
var ms = new MeshSimplifier();
var ms = new MeshSimplifier(1f, 7f);
var optimizedVerts = ms.Simplify(hullPoints);
brushVertices.Clear();
@@ -1652,7 +1663,7 @@ namespace Game
}
else
{
debugPoints = new List<Vector3>(faceVertices);
//debugPoints = new List<Vector3>(faceVertices);
var hullPoints = faceVertices;
@@ -1669,195 +1680,281 @@ namespace Game
vertices = brushVertices.ToArray();
}
/*
Development (game)
cube3: 8.1ms + 123ms
aerowalk: 78ms + 1372ms
Development (editor)
cube3: 4.6ms + 77.3ms
aerowalk: 74ms + 1328ms
Release
cube3: 4.4ms + 61.4ms
aerowalk: 17ms + 511ms
UnitTest release:
aerowalk: 8ms + 267ms
aero unit:
.net6: 667 + 229
.net5: 704 + 237
.net5win7: 697 + 237
.net48 809 + 242
.net472 810 + 246
.net462 810 + 243
.net452 808 + 244
*/
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;
foreach (var brush in root.entities[0].brushes)
{
try
{
List<Vector3> vertices = new List<Vector3>();
List<Vector2> uvs = new List<Vector2>();
List<Vector3> normals = new List<Vector3>();
BrushGeometry geom = new BrushGeometry();
TriangulateBrush3(brush, out Vector3[] brushVertices);
Vector2[] brushUvs = new Vector2[brushVertices.Length];
Vector3[] brushNormals = new Vector3[brushVertices.Length];
TriangulateBrush3(brush, out geom.vertices);
geom.brush = brush;
// FIXME: brush can have multiple textures
MaterialBase brushMaterial;
var textureName = brush.planes[0].texture;
if (materials == null)
{
var customSettings = Engine.GetCustomSettings("BrushMaterials");
BrushMaterialList brushMaterialList = customSettings?.CreateInstance<BrushMaterialList>();
if (brushMaterialList != null)
{
materials = brushMaterialList.materialAssets.ToDictionary(x => x.name, y => y.asset);
Console.Print("materials dictionary with " + materials.Count + " entries");
}
else
{
materials = new Dictionary<string, MaterialBase>();
Console.Print("no materials dictionary found");
}
}
if (!materials.TryGetValue(textureName, out brushMaterial))
{
Console.Print("Material '" + textureName + "' not found for brush");
materials.Add(textureName, 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 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);
if (vertices.Count > 0)
{
uint[] triangles = new uint[vertices.Count];
for (uint i = 0; i < vertices.Count; i++)
triangles[i] = i;
Model 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 = "Brush_" + brushIndex;
childModel.Model = model;
childModel.SetMaterial(0, brushMaterial);
childModel.Parent = mapRootActor;
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
if (collisionData.CookCollision(convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, vertices.ToArray(),
triangles.ToArray()))
{
bool failed = true;
if (convexMesh)
{
// fallback to triangle mesh
failed = collisionData.CookCollision(CollisionDataType.TriangleMesh,
vertices.ToArray(),
triangles.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);
}
catch (Exception e)
{
Console.Print("Failed to hull brush " + brushIndex.ToString() + ": " + e.Message);
Console.Print("Failed to triangulate brush " + brushIndex.ToString() + ": " + e.Message);
}
brushIndex++;
}
sw.Stop();
Console.Print("Pass 1: triangulation: " + sw.Elapsed.TotalMilliseconds + "ms");
// 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)
{
var customSettings = Engine.GetCustomSettings("BrushMaterials");
BrushMaterialList brushMaterialList = customSettings?.CreateInstance<BrushMaterialList>();
if (brushMaterialList != null)
{
materials = brushMaterialList.materialAssets.ToDictionary(x => x.name, y => y.asset);
Console.Print("materials dictionary with " + materials.Count + " entries");
}
else
{
materials = new Dictionary<string, MaterialBase>();
Console.Print("no materials dictionary found");
}
}
// 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);
}
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();
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;
}
sw.Stop();
Console.Print("Pass 3: collision: " + sw.Elapsed.TotalMilliseconds + "ms");
}
else
{

View File

@@ -0,0 +1,32 @@
using System;
using Flax.Build;
public class GoakeTestsTarget : Target
{
/// <inheritdoc />
public GoakeTestsTarget()
{
Name = ProjectName = OutputName = "GoakeTests";
}
/// <inheritdoc />
public override void Init()
{
base.Init();
IsPreBuilt = false;
Type = TargetType.DotNet;
OutputType = TargetOutputType.Library;
/*Platforms = new[]
{
Flax.Build.Platform.BuildPlatform.Target,
};
Configurations = new[]
{
TargetConfiguration.Debug,
TargetConfiguration.Release,
};*/
CustomExternalProjectFilePath = System.IO.Path.Combine("Tests/GoakeTests.csproj");
}
}