diff --git a/Assets/Maps/cube.map b/Assets/Maps/cube.map index c68842f..516fe2d 100644 --- a/Assets/Maps/cube.map +++ b/Assets/Maps/cube.map @@ -3,29 +3,39 @@ // entity 0 { "classname" "worldspawn" +"_tb_textures" "textures/common;textures/dev" // brush 0 { -( -64 -64 -16 ) ( -64 -63 -16 ) ( -64 -64 -15 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( -64 -64 -16 ) ( -64 -64 -15 ) ( -63 -64 -16 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( -64 -64 -16 ) ( -63 -64 -16 ) ( -64 -63 -16 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 64 64 16 ) ( 64 65 16 ) ( 65 64 16 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 64 64 16 ) ( 65 64 16 ) ( 64 64 17 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 64 64 16 ) ( 64 64 17 ) ( 64 65 16 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( 0 576 -32 ) ( 0 577 -32 ) ( 0 576 -31 ) common/slick 0 0 0 4 4 0 0 0 +( 224 0 -32 ) ( 224 0 -31 ) ( 225 0 -32 ) common/slick 0 0 0 4 4 0 0 0 +( 224 576 -512 ) ( 225 576 -512 ) ( 224 577 -512 ) common/slick 0 0 0 4 4 0 0 0 +( 256 608 0 ) ( 256 609 0 ) ( 257 608 0 ) common/slick 0 0 0 4 4 0 0 0 +( 256 512 -16 ) ( 257 512 -16 ) ( 256 512 -15 ) common/slick 0 0 0 4 4 0 0 0 +( 512 608 -16 ) ( 512 608 -15 ) ( 512 609 -16 ) common/slick 0 0 0 4 4 0 0 0 } // brush 1 { -( -32 -32 48 ) ( -32 32 48 ) ( 0 0 80 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 0 0 80 ) ( 32 -32 48 ) ( -32 -32 48 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 32 32 48 ) ( -32 32 48 ) ( -32 -32 48 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 0 0 80 ) ( -32 32 48 ) ( 32 32 48 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 0 0 80 ) ( 32 32 48 ) ( 32 -32 48 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( -64 -64 16 ) ( -64 -63 16 ) ( -64 -64 17 ) common/slick 0 0 0 1 1 0 0 0 +( -64 -64 16 ) ( -64 -64 17 ) ( -63 -64 16 ) common/slick 0 0 0 1 1 0 0 0 +( -64 -64 16 ) ( -63 -64 16 ) ( -64 -63 16 ) common/slick 0 0 0 1 1 0 0 0 +( 64 64 48 ) ( 64 65 48 ) ( 65 64 48 ) common/slick 0 0 0 1 1 0 0 0 +( 64 64 48 ) ( 65 64 48 ) ( 64 64 49 ) common/slick 0 0 0 1 1 0 0 0 +( 64 64 48 ) ( 64 64 49 ) ( 64 65 48 ) common/slick 0 0 0 1 1 0 0 0 } // brush 2 { -( 0 0 80 ) ( -32 32 112 ) ( -32 -32 112 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 0 0 80 ) ( -32 -32 112 ) ( 32 -32 112 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( -32 32 112 ) ( 32 32 112 ) ( 32 -32 112 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 0 0 80 ) ( 32 32 112 ) ( -32 32 112 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 -( 32 -32 112 ) ( 32 32 112 ) ( 0 0 80 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( -32 -32 48 ) ( -32 32 48 ) ( 0 0 80 ) common/slick 0 0 0 1 1 0 0 0 +( 0 0 80 ) ( 32 -32 48 ) ( -32 -32 48 ) common/slick 0 0 0 1 1 0 0 0 +( 32 32 48 ) ( -32 32 48 ) ( -32 -32 48 ) common/slick 0 0 0 1 1 0 0 0 +( 0 0 80 ) ( -32 32 48 ) ( 32 32 48 ) common/slick 0 0 0 1 1 0 0 0 +( 0 0 80 ) ( 32 32 48 ) ( 32 -32 48 ) common/slick 0 0 0 1 1 0 0 0 +} +// brush 3 +{ +( 0 0 80 ) ( -32 32 112 ) ( -32 -32 112 ) common/slick 0 0 0 1 1 0 0 0 +( 0 0 80 ) ( -32 -32 112 ) ( 32 -32 112 ) common/slick 0 0 0 1 1 0 0 0 +( -32 32 112 ) ( 32 32 112 ) ( 32 -32 112 ) common/slick 0 0 0 1 1 0 0 0 +( 0 0 80 ) ( 32 32 112 ) ( -32 32 112 ) common/slick 0 0 0 1 1 0 0 0 +( 32 -32 112 ) ( 32 32 112 ) ( 0 0 80 ) common/slick 0 0 0 1 1 0 0 0 } } diff --git a/Assets/Maps/problematic.map b/Assets/Maps/problematic.map new file mode 100644 index 0000000..d90479f --- /dev/null +++ b/Assets/Maps/problematic.map @@ -0,0 +1,33 @@ +// Game: Goake +// Format: Quake3 +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( 467.19999999999999 0 241.59999999999999 ) ( 442.21699999999998 249.83000000000001 291.56599999999997 ) ( 355.47199999999998 -55.863999999999997 465.05500000000001 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 0 160 8 ) ( 0 160 -248 ) ( -256 160 8 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 342 160 492 ) ( 426 160 492 ) ( 426 224 492 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 0 0 504 ) ( 256 0 504 ) ( 0 -256 504 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 0 224 8 ) ( 0 224 264 ) ( -256 224 8 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 147.19999999999999 0 -65.599999999999994 ) ( 122.217 -249.83000000000001 -115.566 ) ( 258.928 -55.863999999999997 157.85499999999999 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +} +// brush 1 +{ +( 460 160 458 ) ( 460 160 360 ) ( 460 224 360 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( -88 0 184 ) ( 111.864 124.91500000000001 283.93200000000002 ) ( 23.728000000000002 -223.45500000000001 239.864 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 0 160 8 ) ( 0 160 -248 ) ( -256 160 8 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 0 0 360 ) ( -256 0 360 ) ( 0 -256 360 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 0 224 8 ) ( 0 224 264 ) ( -256 224 8 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 472 0 8 ) ( 472 -256 8 ) ( 472 0 264 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +} +// brush 2 +{ +( 296 160 464 ) ( 296 224 464 ) ( 336 224 504 ) dev/dev_128_gray 0 0 -0 0.0625 0.0625 0 0 0 +( 241.59999999999999 0 491.19999999999999 ) ( 41.735999999999997 124.91500000000001 591.13199999999995 ) ( 129.87200000000001 -223.45500000000001 547.06399999999996 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 0 160 8 ) ( 0 160 -248 ) ( -256 160 8 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 0 224 8 ) ( 0 224 264 ) ( -256 224 8 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 308 160 458 ) ( 342 160 492 ) ( 342 224 492 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +( 467.19999999999999 0 241.59999999999999 ) ( 492.18299999999999 -249.83000000000001 191.63399999999999 ) ( 355.47199999999998 -55.863999999999997 465.05500000000001 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0 +} +} diff --git a/Content/GameSettings.json b/Content/GameSettings.json index abbe4b1..5c28e07 100644 --- a/Content/GameSettings.json +++ b/Content/GameSettings.json @@ -1,11 +1,11 @@ { "ID": "3c7bc3854d42f9b1b0fea9ba0d7fa8e9", "TypeName": "FlaxEditor.Content.Settings.GameSettings", - "EngineBuild": 6221, + "EngineBuild": 6222, "Data": { "ProductName": "Goake", "CompanyName": "GoaLitiuM", - "FirstScene": "0733cc9b40d3d05366be64bbd9b59e21", + "FirstScene": "194e05f445ece24ec5448d886e1334df", "NoSplashScreen": true, "Time": "a55dc3c04da4ea3744b7f1994565beac", "Audio": "492a33824049ba25a8bdcdb148179e67", diff --git a/Content/Materials/dev/dev_128.flax b/Content/Materials/dev/dev_128.flax index 623915f..6115235 100644 Binary files a/Content/Materials/dev/dev_128.flax and b/Content/Materials/dev/dev_128.flax differ diff --git a/Content/Scenes/MainScene.scene b/Content/Scenes/MainScene.scene index aeea6dc..2e695a0 100644 --- a/Content/Scenes/MainScene.scene +++ b/Content/Scenes/MainScene.scene @@ -1,7 +1,7 @@ { "ID": "0733cc9b40d3d05366be64bbd9b59e21", "TypeName": "FlaxEngine.SceneAsset", - "EngineBuild": 6219, + "EngineBuild": 6223, "Data": [ { "ID": "0733cc9b40d3d05366be64bbd9b59e21", @@ -168,13 +168,13 @@ "Transform": { "Translation": { "X": 0.0, - "Y": 734.0, + "Y": 735.0, "Z": 0.0 } }, "Control": "FlaxEngine.GUI.Label", "Data": { - "Text": "uFPS: 504\nrFPS: 493\npFPS: 30\nCon: NaNms\nDirectX11\nGC memory: 9.256392MB", + "Text": "eFPS: 120\nuFPS: -2147483648\nrFPS: -2147483648\npFPS: -2147483648\nCon: 0ms\nDirectX11\nGC memory: 11.2093MB", "TextColor": { "R": 1.0, "G": 1.0, @@ -220,9 +220,9 @@ }, "Offsets": { "Left": 0.0, - "Right": 143.0, + "Right": 136.0, "Top": -80.0, - "Bottom": 96.0 + "Bottom": 112.0 }, "Scale": { "X": 1.0, @@ -260,8 +260,8 @@ "Name": "ContainerControl 0", "Transform": { "Translation": { - "X": 45676.0, - "Y": 1.0, + "X": 44855.0, + "Y": 1.5, "Z": 0.0 } }, diff --git a/Content/Scenes/Scene 0.scene b/Content/Scenes/TestImportScene.scene similarity index 94% rename from Content/Scenes/Scene 0.scene rename to Content/Scenes/TestImportScene.scene index efbd36f..e3cd1b0 100644 --- a/Content/Scenes/Scene 0.scene +++ b/Content/Scenes/TestImportScene.scene @@ -1,7 +1,7 @@ { "ID": "194e05f445ece24ec5448d886e1334df", "TypeName": "FlaxEngine.SceneAsset", - "EngineBuild": 6221, + "EngineBuild": 6223, "Data": [ { "ID": "194e05f445ece24ec5448d886e1334df", @@ -33,7 +33,7 @@ "Transform": { "Translation": { "X": -32.320621490478519, - "Y": 7.693744659423828, + "Y": 203.23764038085938, "Z": -187.24435424804688 } }, @@ -51,6 +51,13 @@ "ParentID": "19bf0c4f41c16717c90e05a0bdc62e44", "V": {} }, + { + "ID": "d9d420ce476865e395a966913b5b32bd", + "TypeName": "Game.PlayerMovement", + "ParentID": "19bf0c4f41c16717c90e05a0bdc62e44", + "V": {}, + "Enabled": false + }, { "ID": "39992ccc4fac12dab97aa4bfb52f0041", "TypeName": "FlaxEngine.EmptyActor", @@ -169,13 +176,13 @@ "Transform": { "Translation": { "X": 0.0, - "Y": 506.0, + "Y": 484.0, "Z": 0.0 } }, "Control": "FlaxEngine.GUI.Label", "Data": { - "Text": "eFPS: 120\nuFPS: 120\nrFPS: 120\npFPS: 30\nCon: NaNms\nDirectX11\nGC memory: 8.026576MB", + "Text": "eFPS: 121\nuFPS: -2147483648\nrFPS: -2147483648\npFPS: -2147483648\nCon: 0ms\nDirectX11\nGC memory: 9.544752MB", "TextColor": { "R": 1.0, "G": 1.0, @@ -261,8 +268,8 @@ "Name": "ContainerControl 0", "Transform": { "Translation": { - "X": 45279.0, - "Y": -113.0, + "X": 44635.0, + "Y": -124.0, "Z": 0.0 } }, diff --git a/Content/Settings/BuildSettings.json b/Content/Settings/BuildSettings.json index b642ed1..8a0ab18 100644 --- a/Content/Settings/BuildSettings.json +++ b/Content/Settings/BuildSettings.json @@ -1,13 +1,17 @@ { "ID": "af2e52554f7faed7b4937181dd22d166", "TypeName": "FlaxEditor.Content.Settings.BuildSettings", - "EngineBuild": 6216, + "EngineBuild": 6222, "Data": { "MaxAssetsPerPackage": 4096, "MaxPackageSizeMB": 1024, "ContentKey": 0, "ForDistribution": false, "SkipPackaging": false, + "AdditionalAssets": [ + "54a1ff4a42af9f018234f7b39b1366b3", + "1ef4565844a4b36cdfda54b51f338c77" + ], "ShadersNoOptimize": false, "ShadersGenerateDebugData": false, "Presets": [ diff --git a/Content/Textures/dev/slick.flax b/Content/Textures/dev/slick.flax new file mode 100644 index 0000000..9ec27d8 Binary files /dev/null and b/Content/Textures/dev/slick.flax differ diff --git a/Source/Game/Cabrito/Console/Console.cs b/Source/Game/Cabrito/Console/Console.cs index 19aecce..62348fc 100644 --- a/Source/Game/Cabrito/Console/Console.cs +++ b/Source/Game/Cabrito/Console/Console.cs @@ -264,33 +264,53 @@ namespace Cabrito // Echoes text to Console public void Print(string text) { - ConsoleLine line = new ConsoleLine(text); - consoleLines.Add(line); - OnPrint?.Invoke(text); + foreach (var line in text.Split(new []{'\n'})) + { + ConsoleLine lineEntry = new ConsoleLine(line); + consoleLines.Add(lineEntry); + OnPrint?.Invoke(text); + } } // Echoes warning text to Console public void PrintWarning(string text) { - ConsoleLine line = new ConsoleLine(text); - consoleLines.Add(line); - OnPrint?.Invoke(text); + foreach (var line in text.Split(new[] { '\n' })) + { + ConsoleLine lineEntry = new ConsoleLine(line); + consoleLines.Add(lineEntry); + OnPrint?.Invoke(text); + } } // Echoes error text to Console public void PrintError(string text) { - ConsoleLine line = new ConsoleLine(text); - consoleLines.Add(line); - OnPrint?.Invoke(text); + foreach (var line in text.Split(new[] { '\n' })) + { + ConsoleLine lineEntry = new ConsoleLine(line); + consoleLines.Add(lineEntry); + OnPrint?.Invoke(text); + } + + if (Debugger.IsAttached) + { + Debugger.Break(); + text = text; + } + else + throw new Exception(text); } // Echoes developer/debug text to Console public void PrintDebug(string text) { - ConsoleLine line = new ConsoleLine(text); - consoleLines.Add(line); - OnPrint?.Invoke(text); + foreach (var line in text.Split(new[] { '\n' })) + { + ConsoleLine lineEntry = new ConsoleLine(line); + consoleLines.Add(lineEntry); + OnPrint?.Invoke(text); + } } // Opens the Console diff --git a/Source/Game/CameraMovement.cs b/Source/Game/CameraMovement.cs index 4498324..b6ab060 100644 --- a/Source/Game/CameraMovement.cs +++ b/Source/Game/CameraMovement.cs @@ -9,13 +9,12 @@ namespace Game [Limit(0, 9000), Tooltip("Camera speed")] public float MoveSpeed { get; set; } = 400; - private float _pitch; - private float _yaw; + private float viewPitch; + private float viewYaw; + private float viewRoll; private float xAxis; private float yAxis; - private float inputH; - private float inputV; private InputEvent onExit = new InputEvent("Exit"); @@ -40,33 +39,45 @@ namespace Game public override void OnStart() { var initialEulerAngles = Actor.Orientation.EulerAngles; - _pitch = initialEulerAngles.X; - _yaw = initialEulerAngles.Y; + viewPitch = initialEulerAngles.X; + viewYaw = initialEulerAngles.Y; + viewRoll = initialEulerAngles.Z; } public override void OnUpdate() { var camTrans = Actor.Transform; + var rootActor = Actor.GetChild(0); + var camera = rootActor.GetChild(); - xAxis = InputManager.GetAxis("Mouse X"); - yAxis = InputManager.GetAxis("Mouse Y"); - + float xAxis = InputManager.GetAxisRaw("Mouse X"); + float yAxis = InputManager.GetAxisRaw("Mouse Y"); if (xAxis != 0.0f || yAxis != 0.0f) { - _pitch += yAxis; - _yaw += xAxis; - camTrans.Orientation = Quaternion.Euler(_pitch, _yaw, 0); + + + viewPitch += yAxis; + viewYaw += xAxis; + + viewPitch = Mathf.Clamp(viewPitch, -90.0f, 90.0f); + + + // root orientation must be set first + rootActor.Orientation = Quaternion.Euler(0, viewYaw, 0); + camera.Orientation = Quaternion.Euler(viewPitch, viewYaw, viewRoll); } - inputH = InputManager.GetAxis("Horizontal"); - inputV = InputManager.GetAxis("Vertical"); + + + float inputH = InputManager.GetAxis("Horizontal"); + float inputV = InputManager.GetAxis("Vertical"); var move = new Vector3(inputH, 0.0f, inputV); if (!move.IsZero) { move.Normalize(); - move = camTrans.TransformDirection(move) * MoveSpeed; + move = camera.Transform.TransformDirection(move) * MoveSpeed; { Vector3 delta = move * Time.UnscaledDeltaTime; diff --git a/Source/Game/MapParser/MapParser.cs b/Source/Game/MapParser/MapParser.cs index 8219d53..0c5ea98 100644 --- a/Source/Game/MapParser/MapParser.cs +++ b/Source/Game/MapParser/MapParser.cs @@ -275,6 +275,13 @@ namespace Game } Vector3 vector = ParseVector3(data, ref index); + // rounding + /*float temp = vector.Z; + vector.Z = vector.Y; + vector.Y = temp;*/ + /*vector.X = (float)Math.Round(vector.X, 1); + vector.Y = (float)Math.Round(vector.Y, 1); + vector.Z = (float)Math.Round(vector.Z, 1);*/ while (index < data.Length) { diff --git a/Source/Game/MeshSimplifier.cs b/Source/Game/MeshSimplifier.cs index 421787c..2ddec5c 100644 --- a/Source/Game/MeshSimplifier.cs +++ b/Source/Game/MeshSimplifier.cs @@ -167,26 +167,101 @@ namespace Game public List Simplify(Vector3[] input) { - triangles = new List(input.Length/3); - vertices = new List(input.Length); + triangles = new List(input.Length / 3); + vertices = new List(); + + // TODO: no overlapping vertices, vertices must be unique + + { - int i = 0; - foreach (var vec in input) + + Dictionary verticeMap = new Dictionary(); + for (int i = 0; i < input.Length; i++) + { + if (!verticeMap.ContainsKey(input[i])) + verticeMap[input[i]] = verticeMap.Count; + } + + for (int i = 0; i < input.Length; i += 3) + { + int i1 = i + 0; + int i2 = i + 1; + int i3 = i + 2; + Vector3 v1 = input[i1]; + Vector3 v2 = input[i2]; + Vector3 v3 = input[i3]; + + if (verticeMap.ContainsKey(v1)) + i1 = verticeMap[v1]; + else + verticeMap.Add(v1, i1); + + if (verticeMap.ContainsKey(v2)) + i2 = verticeMap[v2]; + else + verticeMap.Add(v2, i2); + + if (verticeMap.ContainsKey(v3)) + i3 = verticeMap[v3]; + else + verticeMap.Add(v3, i3); + + triangles.Add(new Triangle() + { + v = new Int3(i1, i2, i3), + }); + } + + foreach (KeyValuePair kvp in verticeMap) + { + vertices.Add(new Vertex() + { + p = kvp.Key, + q = new SymetricMatrix(), + }); + } + + + /*foreach (var vec in input) + { + vertices.Add(new Vertex() + { + p = vec, + q = new SymetricMatrix(), + }); + }*/ + } + return Simplify(); + } + + public List Simplify(Vector3[] verts, int[] indices) + { + triangles = new List(indices.Length / 3); + vertices = new List(verts.Length); + + { + foreach (var vec in verts) { vertices.Add(new Vertex() { p = vec, q = new SymetricMatrix(), }); - i += 3; } - for (i = 0; i Simplify() + { // main iteration loop int deleted_triangles=0; @@ -198,12 +273,14 @@ namespace Game //int iteration = 0; //loop(iteration,0,100) - for (int iteration = 0; iteration < 9999; iteration ++) + int iteration; + for (iteration = 0; iteration < 9999; iteration++) { - if (ratio < 1.0f && triangle_start_count-deleted_triangles<=target_count) break; + if (ratio < 1.0f && triangle_start_count-deleted_triangles<=target_count) + break; - // update mesh constantly - update_mesh(iteration); + if (ratio >= 1.0f || iteration % 5 == 0) + update_mesh(iteration); // clear dirty flag for (int i = 0; i < triangles.Count; i++) triangles[i].dirty=false; @@ -214,7 +291,7 @@ namespace Game // If it does not, try to adjust the 3 parameters // //double threshold = 0.001; //1.0E-3 EPS; - double threshold = 1.0E-3; + double threshold = 1.0E-3;//1.0E-9; if (ratio < 1.0f) threshold = 0.000000001 * Math.Pow((double)(iteration+3),agressiveness); //if (verbose) { @@ -225,15 +302,26 @@ namespace Game for (int i = 0; i < triangles.Count; i++) { Triangle t = triangles[i]; - if(t.err[3]>threshold) continue; - if(t.deleted) continue; - if(t.dirty) continue; + if (t.err[3] > threshold) + { + t = t; + continue; + } + + if(t.deleted) + continue; + if(t.dirty) + continue; for (int j = 0; j < 3; j++) - if(t.err[j] threshold) + continue; + + int i0=t.v[ j ]; + Vertex v0 = vertices[i0]; + int i1=t.v[(j+1)%3]; + Vertex v1 = vertices[i1]; // Border check if(v0.border != v1.border) @@ -284,11 +372,16 @@ namespace Game v0.tcount=tcount; break; } - if (ratio < 1.0f && triangle_start_count-deleted_triangles<=target_count) break; + if (ratio < 1.0f && triangle_start_count-deleted_triangles<=target_count) + break; + } + + if (ratio >= 1.0f) + { + if (deleted_triangles <= 0) + break; + deleted_triangles = 0; } - if(deleted_triangles<=0) - break; - deleted_triangles=0; } //for each iteration // clean up mesh compact_mesh(); @@ -296,8 +389,15 @@ namespace Game if (triangles.Count == 0) return null; + List finalVerts = new List(); + foreach (var t in triangles) + { + finalVerts.Add(vertices[t.v[0]].p); + finalVerts.Add(vertices[t.v[1]].p); + finalVerts.Add(vertices[t.v[2]].p); + } - return null; + return finalVerts; } // Check if a triangle flips when this edge is removed @@ -374,25 +474,6 @@ namespace Game } } - /*private Matrix MatrixPlane(float a, float b, float c, float d) - { - Matrix m = new Matrix(); - m[0] = a*a; m[1] = a*b; m[2] = a*c; m[3] = a*d; - m[4] = b*b; m[5] = b*c; m[6] = b*d; - m[7 ] = c*c; m[8 ] = c*d; - m[9 ] = d*d; - return m; - } - - private double MatrixDet(Matrix m, int a11, int a12, int a13, - int a21, int a22, int a23, - int a31, int a32, int a33) - { - double det = m[a11]*m[a22]*m[a33] + m[a13]*m[a21]*m[a32] + m[a12]*m[a23]*m[a31] - - m[a13]*m[a22]*m[a31] - m[a11]*m[a23]*m[a32]- m[a12]*m[a21]*m[a33]; - return det; - }*/ - // compact triangles, compute edge error and build reference list private void update_mesh(int iteration) @@ -416,23 +497,22 @@ namespace Game // if( iteration == 0 ) { - for (int i = 0; i < vertices.Count; i++) - vertices[i].q=new SymetricMatrix();//vertices[i].q=Matrix(0.0); + //for (int i = 0; i < vertices.Count; i++) + // vertices[i].q=new SymetricMatrix();//vertices[i].q=Matrix(0.0); for (int i = 0; i < triangles.Count; i++) { - if (i == 18) - Console.Print(i.ToString()); Triangle t=triangles[i]; - Vector3 n = new Vector3(); + Vector3[] p = new Vector3[3]; for (int j = 0; j<3; j++) p[j]=vertices[t.v[j]].p; - n = Vector3.Cross(p[1]-p[0],p[2]-p[0]); + + Vector3 n = Vector3.Cross(p[1]-p[0],p[2]-p[0]); n.Normalize(); t.n=n; for (int j = 0; j<3; j++) - vertices[t.v[j]].q = vertices[t.v[j]].q + new SymetricMatrix(n.X,n.Y,n.Z,-Vector3.Dot(n, p[0])); + vertices[t.v[j]].q = vertices[t.v[j]].q + new SymetricMatrix(n.X,n.Y,n.Z,Vector3.Dot(-n, p[0])); } for (int i = 0; i < triangles.Count; i++) { @@ -453,7 +533,8 @@ namespace Game for (int i = 0; i < triangles.Count; i++) { Triangle t=triangles[i]; - for (int j = 0; j<3; j++) vertices[t.v[j]].tcount++; + for (int j = 0; j<3; j++) + vertices[t.v[j]].tcount++; } int tstart=0; for (int i = 0; i < vertices.Count; i++) diff --git a/Source/Game/Q3MapImporter.cs b/Source/Game/Q3MapImporter.cs index a7f0bae..0e900f4 100644 --- a/Source/Game/Q3MapImporter.cs +++ b/Source/Game/Q3MapImporter.cs @@ -4,6 +4,7 @@ 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; @@ -272,7 +273,7 @@ namespace Game } ExportMesh(points, splitVerts, ref verts, ref tris, ref normals); - VerifyMesh(points, ref verts, ref tris); + //VerifyMesh(points, ref verts, ref tris); } /// @@ -389,22 +390,22 @@ namespace Game // before those. openSetTail = openSet.Count - 5; - Assert(openSet.Count == points.Count); + Assert.IsTrue(openSet.Count == points.Count); // Assign all points of the open set. This does basically the same // thing as ReassignPoints() for (int i = 0; i <= openSetTail; i++) { - Assert(openSet[i].Face == UNASSIGNED); - Assert(openSet[openSetTail].Face == UNASSIGNED); - Assert(openSet[openSetTail + 1].Face == INSIDE); + Assert.IsTrue(openSet[i].Face == UNASSIGNED); + Assert.IsTrue(openSet[openSetTail].Face == UNASSIGNED); + Assert.IsTrue(openSet[openSetTail + 1].Face == INSIDE); var assigned = false; var fp = openSet[i]; - Assert(faces.Count == 4); - Assert(faces.Count == faceCount); + Assert.IsTrue(faces.Count == 4); + Assert.IsTrue(faces.Count == faceCount); for (int j = 0; j < 4; j++) { - Assert(faces.ContainsKey(j)); + Assert.IsTrue(faces.ContainsKey(j)); var face = faces[j]; @@ -483,8 +484,8 @@ namespace Game /// from its face. /// void GrowHull(List points) { - Assert(openSetTail >= 0); - Assert(openSet[0].Face != INSIDE); + Assert.IsTrue(openSetTail >= 0); + Assert.IsTrue(openSet[0].Face != INSIDE); // Find farthest point and first lit face. var farthestPoint = 0; @@ -540,7 +541,7 @@ namespace Game litFaces.Add(fi); - Assert(PointFaceDistance(point, points[face.Vertex0], face) > 0.0f); + Assert.IsTrue(PointFaceDistance(point, points[face.Vertex0], face) > 0.0f); // For the rest of the recursive search calls, we first check if the // triangle has already been visited and is part of litFaces. @@ -609,10 +610,10 @@ namespace Game /// Recursively search to find the horizon or lit set. /// void SearchHorizon(List points, Vector3 point, int prevFaceIndex, int faceCount, Face face) { - Assert(prevFaceIndex >= 0); - Assert(litFaces.Contains(prevFaceIndex)); - Assert(!litFaces.Contains(faceCount)); - Assert(faces[faceCount].Equals(face)); + Assert.IsTrue(prevFaceIndex >= 0); + Assert.IsTrue(litFaces.Contains(prevFaceIndex)); + Assert.IsTrue(!litFaces.Contains(faceCount)); + Assert.IsTrue(faces[faceCount].Equals(face)); litFaces.Add(faceCount); @@ -641,7 +642,7 @@ namespace Game edge1 = face.Vertex1; edge2 = face.Vertex2; } else { - Assert(prevFaceIndex == face.Opposite2); + Assert.IsTrue(prevFaceIndex == face.Opposite2); nextFaceIndex0 = face.Opposite0; nextFaceIndex1 = face.Opposite1; @@ -704,7 +705,7 @@ namespace Game /// void ConstructCone(List points, int farthestPoint) { foreach (var fi in litFaces) { - Assert(faces.ContainsKey(fi)); + Assert.IsTrue(faces.ContainsKey(fi)); faces.Remove(fi); } @@ -759,14 +760,14 @@ namespace Game var horizonFace = faces[horizon[i].Face]; if (horizonFace.Vertex0 == v1) { - Assert(v2 == horizonFace.Vertex2); + Assert.IsTrue(v2 == horizonFace.Vertex2); horizonFace.Opposite1 = fi; } else if (horizonFace.Vertex1 == v1) { - Assert(v2 == horizonFace.Vertex0); + Assert.IsTrue(v2 == horizonFace.Vertex0); horizonFace.Opposite2 = fi; } else { - Assert(v1 == horizonFace.Vertex2); - Assert(v2 == horizonFace.Vertex1); + Assert.IsTrue(v1 == horizonFace.Vertex2); + Assert.IsTrue(v2 == horizonFace.Vertex1); horizonFace.Opposite0 = fi; } @@ -987,12 +988,12 @@ namespace Game void VerifyOpenSet(List points) { for (int i = 0; i < openSet.Count; i++) { if (i > openSetTail) { - Assert(openSet[i].Face == INSIDE); + Assert.IsTrue(openSet[i].Face == INSIDE); } else { - Assert(openSet[i].Face != INSIDE); - Assert(openSet[i].Face != UNASSIGNED); + Assert.IsTrue(openSet[i].Face != INSIDE); + Assert.IsTrue(openSet[i].Face != UNASSIGNED); - Assert(PointFaceDistance( + Assert.IsTrue(PointFaceDistance( points[openSet[i].Point], points[faces[openSet[i].Face].Vertex0], faces[openSet[i].Face]) > 0.0f); @@ -1009,8 +1010,8 @@ namespace Game for (int i = 0; i < horizon.Count; i++) { var prev = i == 0 ? horizon.Count - 1 : i - 1; - Assert(horizon[prev].Edge1 == horizon[i].Edge0); - Assert(HasEdge(faces[horizon[i].Face], horizon[i].Edge1, horizon[i].Edge0)); + Assert.IsTrue(horizon[prev].Edge1 == horizon[i].Edge0); + Assert.IsTrue(HasEdge(faces[horizon[i].Face], horizon[i].Edge1, horizon[i].Edge0)); } } @@ -1024,23 +1025,23 @@ namespace Game var fi = kvp.Key; var face = kvp.Value; - Assert(faces.ContainsKey(face.Opposite0)); - Assert(faces.ContainsKey(face.Opposite1)); - Assert(faces.ContainsKey(face.Opposite2)); + Assert.IsTrue(faces.ContainsKey(face.Opposite0)); + Assert.IsTrue(faces.ContainsKey(face.Opposite1)); + Assert.IsTrue(faces.ContainsKey(face.Opposite2)); - Assert(face.Opposite0 != fi); - Assert(face.Opposite1 != fi); - Assert(face.Opposite2 != fi); + Assert.IsTrue(face.Opposite0 != fi); + Assert.IsTrue(face.Opposite1 != fi); + Assert.IsTrue(face.Opposite2 != fi); - Assert(face.Vertex0 != face.Vertex1); - Assert(face.Vertex0 != face.Vertex2); - Assert(face.Vertex1 != face.Vertex2); + Assert.IsTrue(face.Vertex0 != face.Vertex1); + Assert.IsTrue(face.Vertex0 != face.Vertex2); + Assert.IsTrue(face.Vertex1 != face.Vertex2); - Assert(HasEdge(faces[face.Opposite0], face.Vertex2, face.Vertex1)); - Assert(HasEdge(faces[face.Opposite1], face.Vertex0, face.Vertex2)); - Assert(HasEdge(faces[face.Opposite2], face.Vertex1, face.Vertex0)); + Assert.IsTrue(HasEdge(faces[face.Opposite0], face.Vertex2, face.Vertex1)); + Assert.IsTrue(HasEdge(faces[face.Opposite1], face.Vertex0, face.Vertex2)); + Assert.IsTrue(HasEdge(faces[face.Opposite2], face.Vertex1, face.Vertex0)); - Assert((face.Normal - Normal( + Assert.IsTrue((face.Normal - Normal( points[face.Vertex0], points[face.Vertex1], points[face.Vertex2])).Length < EPSILON); @@ -1053,7 +1054,7 @@ namespace Game /// if DEBUG_QUICKHULL if defined. /// void VerifyMesh(List points, ref List verts, ref List tris) { - Assert(tris.Count % 3 == 0); + Assert.IsTrue(tris.Count % 3 == 0); for (int i = 0; i < points.Count; i++) { for (int j = 0; j < tris.Count; j+=3) { @@ -1061,7 +1062,10 @@ namespace Game var t1 = verts[tris[j + 1]]; var t2 = verts[tris[j + 2]]; - Assert(Dot(points[i] - t0, Vector3.Cross(t1 - t0, t2 - t0)) <= EPSILON); + var dot = Dot(points[i] - t0, Vector3.Cross(t1 - t0, t2 - t0)); + //Assert.IsTrue(dot <= EPSILON, $"not convex hull: {dot} > {EPSILON}"); + if (!(dot <= EPSILON)) + Console.PrintError($"not convex hull: {dot} > {EPSILON}"); } } @@ -1076,21 +1080,6 @@ namespace Game || (f.Vertex1 == e0 && f.Vertex2 == e1) || (f.Vertex2 == e0 && f.Vertex0 == e1); } - - /// - /// Assert method, conditionally compiled with DEBUG_QUICKHULL. - /// - /// I could just use Debug.Assert or the Assertions class, but I like - /// the idea of just writing Assert(something), and I also want it to - /// be conditionally compiled out with the same #define as the other - /// debug methods. - /// - static void Assert(bool condition) { - if (!condition) { - FlaxEngine.Assertions.Assert.Fail("Assertion failed"); - //throw new UnityEngine.Assertions.AssertionException("Assertion failed", ""); - } - } } @@ -1099,13 +1088,19 @@ namespace Game { private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube.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.00001f; + const float epsilon = 0.01f; - Vector3[] QuickHull(Vector3[] points) + + + + + + void QuickHull(Vector3[] points, out Vector3[] outVertices) { var verts = new List(); var tris = new List(); @@ -1121,22 +1116,142 @@ namespace Game finalPoints.Add(verts[tri]); } - return MergeCoplanarFaces(finalPoints.ToArray()); + outVertices = finalPoints.ToArray(); } - Vector3[] MergeCoplanarFaces(Vector3[] points) + private Color[] planeColors = new Color[] { - // degenerate faces + Color.Red, + Color.Orange, + Color.Yellow, + Color.Green, + Color.Cyan, + Color.Blue, + Color.Purple, + Color.Magenta, + }; - return points; + private int skipPlanes = 0; + private int takePlanes = 5; + + private List debugPoints = new List(); + + public override void OnDebugDraw() + { + 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; - public override void OnStart() + private IEnumerable> DifferentCombinations(IEnumerable elements, int k) { - byte[] mapChars = File.ReadAllBytes(mapPath); - var root = MapParser.Parse(mapChars); + return k == 0 ? new[] { new T[0] } : + elements.SelectMany((e, i) => + DifferentCombinations(elements.Skip(i + 1), (k - 1)).Select(c => (new[] {e}).Concat(c))); + } + /// + /// Triangulates the brush by calculating intersection points between triplets of planes. + /// Does not work well with off-axis aligned planes. + /// + void TriangulateBrush(MapBrush brush, out Vector3[] vertices) + { + HashSet planePoints = new HashSet(); + + List planes = new List(); + foreach (var brushPlane in brush.planes) + planes.Add(new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3)); + + var combinations = DifferentCombinations(planes, 3).ToList(); + foreach (var comb in Enumerable.Reverse(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 (Math.Abs(denom) < 0.000001f) + continue; // multiple or no intersections + + if (Math.Abs(denom) < 0.000001f) + denom = denom; + + 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; + + // Flip Y and Z + /*var temp = intersection.Y; + intersection.Y = intersection.Z; + intersection.Z = temp;*/ + + //if (intersection.Length >= maxDist) + // temp = temp; + + planePoints.Add(intersection); + } + + if (planePoints.Count > 0) + { + QuickHull(planePoints.ToArray(), out vertices); + return; + } + + vertices = new Vector3[0]; + } + + Vector3[] TriangulateBrush2(MapBrush brush) + { const float cs = 3000f; Vector3[] cubePoints = new[] @@ -1150,123 +1265,573 @@ namespace Game new Vector3(-cs, cs, cs), new Vector3(cs, cs, cs), }; - Vector3[] cubeVerts = QuickHull(cubePoints).ToArray(); + Vector3[] cubeVerts; + QuickHull(cubePoints, out cubeVerts); + List brushVertices = new List(cubeVerts); - List vertices = new List(); - foreach (var brush in root.entities[0].brushes.Take(1)) + foreach (var brushPlane in brush.planes.Take(1)) { - List brushVertices = new List(cubeVerts); - //foreach (var plane in new [] { brush.planes.First() }) - foreach (var plane in brush.planes.Reverse().Take(1)) - //foreach (var plane in brush.planes) + Plane plane = new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3); + List faceVertices = new List(); + List clippedVertices = new List(); + + Func isFront = (f) => f > epsilon; + Func isBack = (f) => f < -epsilon; + + for (int i = 0; i < brushVertices.Count; i++) { - Plane p = new Plane(plane.v1, plane.v2, plane.v3); - Vector3 planeNormal = p.Normal; - List newBrushVertices = new List(); - List faceVertices = new List(); + int i2 = ((i + 1) % 3 == 0) ? (i - 2) : (i + 1); + Vector3 start = brushVertices[i]; + Vector3 end = brushVertices[i2]; - if (true) + var d1 = Plane.DotCoordinate(plane, start); + var d2 = Plane.DotCoordinate(plane, end); + + if (isBack(d1)) { - Func isFront = (f) => f > epsilon; - Func isBack = (f) => f < -epsilon; + // include the point behind the clipping plane + faceVertices.Add(start); + } - for (int i = 0; i < brushVertices.Count; i++) + if (isBack(d1) && isFront(d2) || isFront(d1) && isBack(d2)) + { + // the cutting plane clips the edge + //if (isFront(d2)) { - 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)) + Ray ray = new Ray(start, (end - start).Normalized); + if (plane.Intersects(ref ray, out Vector3 point)) { - // include back - faceVertices.Add(start); - } - if (isBack(d1) && isFront(d2) || isFront(d1) && isBack(d2)) - { - //if (isFront(d2)) + //faceVertices.Add(point); + //clippedVertices.Add(point); + + /* + intersect + start __._ (end) + | |/ + | / + |/ + next + */ + if (isBack(d1) && 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; - } - } - } + // finish the current triangle and start the next one + // [start, intersect, next], [intersect, ...] - if (true) - { - var newMeshPoints = new List(); - int duplis = 0; - foreach (var v in faceVertices) - { - bool found = false; - foreach (var vo in newMeshPoints) - { - if ((v - vo).Length < epsilon) + faceVertices.Add(point); + + if ((faceVertices.Count % 3) == 2) { - found = true; - duplis++; - break; + int i3 = ((i2 + 1) % 3 == 0) ? (i2 - 2) : (i2 + 1); + Vector3 next = brushVertices[i3]; + faceVertices.Add(next); } + else + ray = ray; + + faceVertices.Add(point); } - //if (!newMeshPoints.Contains(v)) - if (!found) - newMeshPoints.Add(v); - } + /* - if (duplis > 0) - Console.Print("duplicates: " + duplis); + ____ (start) + | |/ + | * intersect2 + |/ + end + */ + else if (isFront(d1) && isBack(d2)) + { + // continue where we left off + // [intersect, intersect2, ...] - if (newMeshPoints.Count > 0) - { - var hullPoints = QuickHull(newMeshPoints.ToArray()); - var ms = new MeshSimplifier(); - var optimizedVerts = ms.Simplify(hullPoints); - newBrushVertices.Clear(); - newBrushVertices.AddRange(hullPoints); + 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 - newBrushVertices.Clear(); + d1 = d1; + } + } + } + + brushVertices.Clear(); + brushVertices.AddRange(faceVertices); + + /*var newMeshPoints = new List(); + 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; } } - Assert.IsTrue(newBrushVertices.Count % 3 == 0, - "invalid amount of vertices: " + newBrushVertices.Count); - //Assert.IsTrue(newBrushVertices.Count > 0, - // "brush was clipped completely, vertices: " + newBrushVertices.Count); + //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(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(newBrushVertices); - Console.Print("plane verts: " + newBrushVertices.Count); + brushVertices.AddRange(hullPoints); + } + else + brushVertices.Clear(); + */ + + } + + return brushVertices.ToArray(); + } + + void TriangulateBrush3(MapBrush brush, out Vector3[] vertices) + { + float cs = 3000f; + + float maxD = 0f; + float minD = 0f; + foreach (var brushPlane in brush.planes) + { + var p = new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3); + minD = Mathf.Min(p.D); + maxD = Mathf.Max(p.D); + } + + //cs = maxD*2; + + + 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 brushVertices = new List(cubeVerts); + + int asdf = 0; + //foreach (var brushPlane in brush.planes.Skip(skipPlanes).Take(takePlanes)) + foreach (var brushPlane in brush.planes) + { + Plane plane = new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3); + //if (asdf % 2 == 0) + //plane = new Plane(-plane.Normal, -plane.D); + + + List faceVertices = new List(); + List clippedVertices = new List(); + + Func isFront = (f) => f < epsilon; + Func isBack = (f) => f > epsilon; + + List> edges = new List>(); + List> faceEdges = new List>(); + + void TriangulateEdges() + { + if (edges.Count > 0) + { + // heal discontinued edges + for (int j = 0; j < edges.Count; j++) + { + var edgePrev = edges[j]; + var edgeNext = edges[(j + 1) % edges.Count]; + + //if (edgePrev.Item2 != edgeNext.Item1) + if ((edgePrev.Item2 - edgeNext.Item1).Length > 0.0001f) + { + var newEdge = new Tuple(edgePrev.Item2, edgeNext.Item1); + edges.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; + + edges.Clear(); } - vertices.AddRange(brushVertices); + for (int i = 0; i < brushVertices.Count; i++) + { + if (i > 0 && i % 3 == 0) + TriangulateEdges(); + + 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; + + Tuple edge = new Tuple(edgeStart, edgeEnd); + edges.Add(edge); + } + + TriangulateEdges(); + if (false) + { + // create edges from clipped points + var clippedEdges = new List>(); + + //foreach (var e in edges) + // newEdges.Add(new Tuple(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(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) + { + List uniqPoints = new List(); + 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(uniqPoints); + + + Vector3[] hullPoints; + QuickHull(uniqPoints.ToArray(), out hullPoints); + + var ms = new MeshSimplifier(); + var optimizedVerts = ms.Simplify(hullPoints); + + brushVertices.Clear(); + brushVertices.AddRange(optimizedVerts); + } + else + { + debugPoints = new List(faceVertices); + + var hullPoints = faceVertices; + + var ms = new MeshSimplifier(); + var optimizedVerts = hullPoints; //ms.Simplify(hullPoints); + + brushVertices.Clear(); + brushVertices.AddRange(optimizedVerts); + } + + asdf++; + + /*var newMeshPoints = new List(); + 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(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(); + } - model = Content.CreateVirtualAsset(); - model.SetupLODs(new int[] {1}); + + public override void OnStart() + { + byte[] mapChars = File.ReadAllBytes(mapPath); + root = MapParser.Parse(mapChars); + + List vertices = new List(); + List uvs = new List(); + List normals = new List(); + if (true) { - var mesh = model.LODs[0].Meshes[0]; - List triangles = new List(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()); + int brushIndex = 0; + //foreach (var brush in root.entities[0].brushes.Skip(1).Take(1)) + foreach (var brush in root.entities[0].brushes) + { + try + { + Vector3[] brushVertices; + TriangulateBrush3(brush, out brushVertices); + + Vector2[] brushUvs = new Vector2[brushVertices.Length]; + Vector3[] brushNormals = new Vector3[brushVertices.Length]; + + Vector2[] brushScaling = new Vector2[brushVertices.Length]; + + + for (int i=0; i dotY && dotX > dotZ) + theUp = Vector3.Right; + else if (dotZ > dotX && dotZ > dotY) + theUp = Vector3.Forward; + else if (dotY > dotX && dotY > dotZ) + theUp = Vector3.Up; + + + //rot = Quaternion.LookRotation(theUp, Vector3.Dot(normal, Vector3.Forward) < -0.01f ? Vector3.Forward : Vector3.Up); + + //theUp = Vector3.Right; + //theUp = new Vector3(theUp.X, theUp.Z, theUp.Y); + var up1 = Vector3.Up; + var up2 = Vector3.Forward; + //theUp = Vector3.Forward; + Quaternion rot = Quaternion.LookRotation(theUp, Mathf.Abs(Vector3.Dot(theUp, up1)) > 0.01f ? up2 : up1); + + + Vector2 uvScale = new Vector2(1f / 16); + float uvRotation = 0f; + bool found = false; + foreach (var brushPlane in brush.planes) + { + Plane plane = new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3); + if ((plane.Normal - normal).Length < 0.01f) + { + uvScale = 1f / brushPlane.scale / 64f; + //uvScale = brushPlane.scale; + uvRotation = brushPlane.rotation; + //uvScale = 1f / 512f; + found = true; + break; + } + } + if (!found) + Console.Print("no found:/"); + + /*v1 = new Vector3(v1.X, v1.Z, v1.Y); + v2 = new Vector3(v2.X, v2.Z, v2.Y); + v3 = new Vector3(v3.X, v3.Z, v3.Y);*/ + + brushUvs[i + 0] = (Vector2)(v1 * rot) * uvScale; + brushUvs[i + 1] = (Vector2)(v2 * rot) * uvScale; + brushUvs[i + 2] = (Vector2)(v3 * rot) * uvScale; + + 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++; + } } - StaticModel childModel = Actor.AddChild(); - childModel.Name = "MapModel"; - childModel.Model = model; - childModel.SetMaterial(0, material); + 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.SetupLODs(new int[] { 1 }); + model.LODs[0].Meshes[0].UpdateMesh(vertices.ToArray(), (int[])(object)triangles, normals.ToArray(), null, uvs.ToArray()); + + StaticModel childModel = Actor.AddChild(); + childModel.Name = "MapModel"; + childModel.Model = model; + childModel.SetMaterial(0, material); + + CollisionData collisionData = Content.CreateVirtualAsset(); + if (collisionData.CookCollision(CollisionDataType.TriangleMesh, vertices.ToArray(), triangles.ToArray())) + throw new Exception("failed to cook final collision"); + var meshCollider = childModel.AddChild(); + meshCollider.CollisionData = collisionData; + + // TODO: flip Y and Z + childModel.Orientation = Quaternion.RotationYawPitchRoll(180f*Mathf.DegreesToRadians, -90f*Mathf.DegreesToRadians, 0f); + childModel.Scale = new Vector3(1f, -1f, 1f); + } } public override void OnEnable() @@ -1281,7 +1846,6 @@ namespace Game public override void OnUpdate() { - // Here you can add code that needs to be called every frame } public override void OnDestroy()