diff --git a/Assets/Maps/cube.map b/Assets/Maps/cube.map new file mode 100644 index 0000000..8da68d5 --- /dev/null +++ b/Assets/Maps/cube.map @@ -0,0 +1,15 @@ +// Game: Goake +// Format: Quake3 +// entity 0 +{ +"classname" "worldspawn" +// 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 +} +} diff --git a/Content/GameSettings.json b/Content/GameSettings.json index ed6548d..e00aac6 100644 --- a/Content/GameSettings.json +++ b/Content/GameSettings.json @@ -1,11 +1,11 @@ { "ID": "3c7bc3854d42f9b1b0fea9ba0d7fa8e9", "TypeName": "FlaxEditor.Content.Settings.GameSettings", - "EngineBuild": 6219, + "EngineBuild": 6221, "Data": { "ProductName": "Goake", "CompanyName": "GoaLitiuM", - "FirstScene": "a0165b834429c74b9d547c88fb3a0de0", + "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 69d9724..f348012 100644 Binary files a/Content/Materials/dev/dev_128.flax and b/Content/Materials/dev/dev_128.flax differ diff --git a/Content/Materials/dev/dev_128_lit.flax b/Content/Materials/dev/dev_128_lit.flax index b29187b..30432af 100644 Binary files a/Content/Materials/dev/dev_128_lit.flax and b/Content/Materials/dev/dev_128_lit.flax differ diff --git a/Content/SceneData/Scene 0/CSG_Collision.flax b/Content/SceneData/Scene 0/CSG_Collision.flax new file mode 100644 index 0000000..9758c7e Binary files /dev/null and b/Content/SceneData/Scene 0/CSG_Collision.flax differ diff --git a/Content/SceneData/Scene 0/CSG_Data.flax b/Content/SceneData/Scene 0/CSG_Data.flax new file mode 100644 index 0000000..253c376 Binary files /dev/null and b/Content/SceneData/Scene 0/CSG_Data.flax differ diff --git a/Content/SceneData/Scene 0/CSG_Mesh.flax b/Content/SceneData/Scene 0/CSG_Mesh.flax new file mode 100644 index 0000000..7257c38 Binary files /dev/null and b/Content/SceneData/Scene 0/CSG_Mesh.flax differ diff --git a/Content/Scenes/Scene 0.scene b/Content/Scenes/Scene 0.scene new file mode 100644 index 0000000..efbd36f --- /dev/null +++ b/Content/Scenes/Scene 0.scene @@ -0,0 +1,386 @@ +{ + "ID": "194e05f445ece24ec5448d886e1334df", + "TypeName": "FlaxEngine.SceneAsset", + "EngineBuild": 6221, + "Data": [ + { + "ID": "194e05f445ece24ec5448d886e1334df", + "TypeName": "FlaxEngine.Scene", + "LightmapSettings": { + "IndirectLightingIntensity": 1.0, + "GlobalObjectsScale": 1.0, + "ChartsPadding": 3, + "AtlasSize": 1024, + "BounceCount": 1, + "CompressLightmaps": true, + "UseGeometryWithNoMaterials": true, + "Quality": 10 + } + }, + { + "ID": "c95a3dab492c1b2046ce2191daa2b111", + "TypeName": "Game.Q3MapImporter", + "ParentID": "194e05f445ece24ec5448d886e1334df", + "V": { + "material": "076df2f449a475bd96087ebb03e0cfc6" +} + }, + { + "ID": "19bf0c4f41c16717c90e05a0bdc62e44", + "TypeName": "FlaxEngine.RigidBody", + "ParentID": "194e05f445ece24ec5448d886e1334df", + "Name": "PlayerPrefab 0", + "Transform": { + "Translation": { + "X": -32.320621490478519, + "Y": 7.693744659423828, + "Z": -187.24435424804688 + } + }, + "OverrideMass": true, + "Mass": 10.0, + "LinearDamping": 0.0, + "AngularDamping": 0.0, + "Constraints": 56, + "IsKinematic": true, + "EnableGravity": false + }, + { + "ID": "cd7b08774b33983314862eae1a7e6567", + "TypeName": "Game.CameraMovement", + "ParentID": "19bf0c4f41c16717c90e05a0bdc62e44", + "V": {} + }, + { + "ID": "39992ccc4fac12dab97aa4bfb52f0041", + "TypeName": "FlaxEngine.EmptyActor", + "ParentID": "19bf0c4f41c16717c90e05a0bdc62e44", + "Name": "RootActor" + }, + { + "ID": "f54fa132472299e63c8f1aa4f587c179", + "TypeName": "FlaxEngine.Camera", + "ParentID": "39992ccc4fac12dab97aa4bfb52f0041", + "Name": "Camera", + "Transform": { + "Translation": { + "X": 0.0, + "Y": 28.7337646484375, + "Z": 0.0 + } + }, + "Near": 1.0 + }, + { + "ID": "a42bade1470cd988c7c76685802b0156", + "TypeName": "FlaxEngine.AudioListener", + "ParentID": "39992ccc4fac12dab97aa4bfb52f0041", + "Name": "AudioListener" + }, + { + "ID": "89ff5a9540fdffc5f3ef52b3f38bf491", + "TypeName": "FlaxEngine.CapsuleCollider", + "ParentID": "19bf0c4f41c16717c90e05a0bdc62e44", + "IsActive": false, + "Name": "CapsuleCollider", + "Transform": { + "Orientation": { + "X": 0.0, + "Y": 0.0, + "Z": 0.7071068286895752, + "W": 0.7071068286895752 + } + }, + "ContactOffset": 0.0, + "Material": "d99b8b6f40198beea4703dadd642150e", + "Radius": 16.0, + "Height": 52.0 + }, + { + "ID": "5a747ea94050dc31c24017a01556aecd", + "TypeName": "FlaxEngine.BoxCollider", + "ParentID": "19bf0c4f41c16717c90e05a0bdc62e44", + "Name": "BoxCollider", + "ContactOffset": 0.0, + "Material": "ffe0e84c49607480f67a3994a7fe11a8", + "Size": { + "X": 32.0, + "Y": 84.0, + "Z": 32.0 + } + }, + { + "ID": "bfc9d0ee45a71ce96838aea25a540f9c", + "TypeName": "FlaxEngine.MeshCollider", + "ParentID": "19bf0c4f41c16717c90e05a0bdc62e44", + "IsActive": false, + "Name": "CylinderCollider", + "Transform": { + "Scale": { + "X": 0.3199999928474426, + "Y": 0.8399999737739563, + "Z": 0.3199999928474426 + } + }, + "CollisionData": "593d92914c4bd54679ddec9e539bba80" + }, + { + "ID": "ff6b6db54b5aa08e7286ef86246149ef", + "TypeName": "FlaxEngine.UICanvas", + "ParentID": "194e05f445ece24ec5448d886e1334df", + "Name": "DefaultHudPrefab 0", + "Transform": { + "Orientation": { + "X": 1.0, + "Y": 4.371138828673793e-8, + "Z": 4.371138828673793e-8, + "W": 1.910685465164705e-15 + } + }, + "V": {} + }, + { + "ID": "791756db4da9af04d7513f9f305e4652", + "TypeName": "Cabrito.FpsScript", + "ParentID": "ff6b6db54b5aa08e7286ef86246149ef", + "V": { + "control": "d15fa816476f9d66ebdfc7ae25c3c6ea" +} + }, + { + "ID": "b20f2b0a486762a15e6a7caf72ae66c2", + "TypeName": "Cabrito.ConsoleScript", + "ParentID": "ff6b6db54b5aa08e7286ef86246149ef", + "V": { + "ConsoleFont": "43f32bec443158643f53699f07b2e09c", + "BackgroundColor": { + "R": 0.0, + "G": 0.0, + "B": 0.0, + "A": 1.0 + } +} + }, + { + "ID": "d15fa816476f9d66ebdfc7ae25c3c6ea", + "TypeName": "FlaxEngine.UIControl", + "ParentID": "ff6b6db54b5aa08e7286ef86246149ef", + "Name": "FPSUIControl", + "Transform": { + "Translation": { + "X": 0.0, + "Y": 506.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", + "TextColor": { + "R": 1.0, + "G": 1.0, + "B": 1.0, + "A": 1.0 + }, + "TextColorHighlighted": { + "R": 1.0, + "G": 1.0, + "B": 1.0, + "A": 1.0 + }, + "HorizontalAlignment": 0, + "VerticalAlignment": 0, + "Wrapping": 0, + "Font": { + "Font": "4508d98f4aa1f0bd59362b81d47e38f4", + "Size": 10 + }, + "Margin": { + "Left": 0.0, + "Right": 0.0, + "Top": 0.0, + "Bottom": 0.0 + }, + "ClipText": false, + "AutoWidth": true, + "AutoHeight": true, + "AutoFitText": false, + "AutoFitTextRange": { + "X": 0.1, + "Y": 100.0 + }, + "ClipChildren": true, + "CullChildren": true, + "AnchorMin": { + "X": 0.0, + "Y": 1.0 + }, + "AnchorMax": { + "X": 0.0, + "Y": 1.0 + }, + "Offsets": { + "Left": 0.0, + "Right": 143.0, + "Top": -80.0, + "Bottom": 112.0 + }, + "Scale": { + "X": 1.0, + "Y": 1.0 + }, + "Pivot": { + "X": 0.5, + "Y": 0.5 + }, + "Shear": { + "X": 0.0, + "Y": 0.0 + }, + "Rotation": 0.0, + "BackgroundColor": { + "R": 0.0, + "G": 0.0, + "B": 0.0, + "A": 0.0 + }, + "Enabled": true, + "Visible": true +} + }, + { + "ID": "7261228a42b41682cf48e89d910ddcff", + "TypeName": "FlaxEngine.UIControl", + "ParentID": "ff6b6db54b5aa08e7286ef86246149ef", + "Name": "UIControl" + }, + { + "ID": "bd1e2ea8425a68342d01f4975e4cdf3e", + "TypeName": "FlaxEngine.UIControl", + "ParentID": "ff6b6db54b5aa08e7286ef86246149ef", + "Name": "ContainerControl 0", + "Transform": { + "Translation": { + "X": 45279.0, + "Y": -113.0, + "Z": 0.0 + } + }, + "Control": "FlaxEngine.GUI.ContainerControl", + "Data": { + "ClipChildren": true, + "CullChildren": true, + "AnchorMin": { + "X": 1.0, + "Y": 0.5 + }, + "AnchorMax": { + "X": 1.0, + "Y": 0.5 + }, + "Offsets": { + "Left": 44188.0, + "Right": 100.0, + "Top": -406.0, + "Bottom": 30.0 + }, + "Scale": { + "X": 1.0, + "Y": 1.0 + }, + "Pivot": { + "X": 0.5, + "Y": 0.5 + }, + "Shear": { + "X": 0.0, + "Y": 0.0 + }, + "Rotation": 0.0, + "BackgroundColor": { + "R": 0.0, + "G": 0.0, + "B": 0.0, + "A": 0.0 + }, + "Enabled": true, + "Visible": true +} + }, + { + "ID": "9b0c532d4247061970c809a0af58c82a", + "TypeName": "FlaxEngine.PostFxVolume", + "ParentID": "194e05f445ece24ec5448d886e1334df", + "Name": "PostFxVolume 0", + "Size": { + "X": 10.0, + "Y": 10.0, + "Z": 10.0 + }, + "IsBounded": false, + "Settings": { + "AO": { + "OverrideFlags": 1, + "Enabled": false + }, + "Bloom": { + "OverrideFlags": 1, + "Enabled": false + }, + "ToneMapping": { + "OverrideFlags": 4, + "Mode": 0 + }, + "ColorGrading": {}, + "EyeAdaptation": { + "OverrideFlags": 1, + "Mode": 0 + }, + "CameraArtifacts": { + "OverrideFlags": 1, + "VignetteIntensity": 0.0, + "GrainAmount": 0.0 + }, + "LensFlares": {}, + "DepthOfField": {}, + "MotionBlur": { + "OverrideFlags": 1, + "Enabled": false + }, + "SSR": { + "OverrideFlags": 1, + "Intensity": 0.0 + }, + "AA": { + "OverrideFlags": 1, + "Mode": 0 + }, + "PostFxMaterials": {} + } + }, + { + "ID": "d5ffa50f41e7f68e73b8c98bb98c84e6", + "TypeName": "FlaxEngine.StaticModel", + "ParentID": "194e05f445ece24ec5448d886e1334df", + "Name": "Capsule", + "Transform": { + "Translation": { + "X": -42.142974853515628, + "Y": 1.731597900390625, + "Z": 261.0594482421875 + } + }, + "Model": "3f82e57744f57c28285150b646ad2198", + "Buffer": { + "Entries": [ + { + "Material": "076df2f449a475bd96087ebb03e0cfc6", + "ShadowsMode": 3, + "Visible": true, + "ReceiveDecals": true + } + ] + } + } + ] +} \ No newline at end of file diff --git a/Content/Settings/WindowsPlatformSettings.json b/Content/Settings/WindowsPlatformSettings.json index 81fce6c..7773f4a 100644 --- a/Content/Settings/WindowsPlatformSettings.json +++ b/Content/Settings/WindowsPlatformSettings.json @@ -1,17 +1,18 @@ { "ID": "4a5eec97484253fed72934860ae62c40", "TypeName": "FlaxEditor.Content.Settings.WindowsPlatformSettings", - "EngineBuild": 6216, + "EngineBuild": 6221, "Data": { "WindowMode": 0, "ScreenWidth": 1280, "ScreenHeight": 720, - "ResizableWindow": false, - "RunInBackground": false, + "ResizableWindow": true, + "RunInBackground": true, "ForceSingleInstance": false, - "SupportDX12": true, + "OverrideIcon": "00000000-0000-0000-0000-000000000000", + "SupportDX12": false, "SupportDX11": true, - "SupportDX10": true, - "SupportVulkan": true + "SupportDX10": false, + "SupportVulkan": false } } \ No newline at end of file diff --git a/Content/Textures/dev/dev_128_gray.flax b/Content/Textures/dev/dev_128_gray.flax index 27266d2..fde8df1 100644 Binary files a/Content/Textures/dev/dev_128_gray.flax and b/Content/Textures/dev/dev_128_gray.flax differ diff --git a/Content/aerowalk/dev dev_128_gray.flax b/Content/aerowalk/dev dev_128_gray.flax index 1e6152f..88dc859 100644 Binary files a/Content/aerowalk/dev dev_128_gray.flax and b/Content/aerowalk/dev dev_128_gray.flax differ diff --git a/Source/Game/Cabrito/Console/ConsoleContentTextBox.cs b/Source/Game/Cabrito/Console/ConsoleContentTextBox.cs index bdab04e..71cece7 100644 --- a/Source/Game/Cabrito/Console/ConsoleContentTextBox.cs +++ b/Source/Game/Cabrito/Console/ConsoleContentTextBox.cs @@ -22,7 +22,15 @@ namespace Cabrito protected TextLayoutOptions _layout; - public FontReference Font; + private FontReference Font; + + public int FontHeight + { + get + { + return Font.GetFont().Height; + } + } public float LineSpacing = 1.0f; @@ -65,12 +73,14 @@ namespace Cabrito { } - public ConsoleContentTextBox(ConsoleInputTextBox inputBox, float x, float y, float width, float height) : base( + public ConsoleContentTextBox(FontReference font, ConsoleInputTextBox inputBox, float x, float y, float width, float height) : base( x, y, width, height) { this.inputBox = inputBox; Height = height; + Font = font; + _layout = TextLayoutOptions.Default; _layout.VerticalAlignment = TextAlignment.Near; _layout.TextWrapping = TextWrapping.WrapChars; diff --git a/Source/Game/Cabrito/Console/ConsoleScript.cs b/Source/Game/Cabrito/Console/ConsoleScript.cs index 528e07b..ba45c38 100644 --- a/Source/Game/Cabrito/Console/ConsoleScript.cs +++ b/Source/Game/Cabrito/Console/ConsoleScript.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using FlaxEditor; using FlaxEngine; +using FlaxEngine.Assertions; using FlaxEngine.GUI; namespace Cabrito @@ -85,13 +86,12 @@ namespace Cabrito if (consoleBox == null) { //consoleBox = new ConsoleContentTextBox(null, 0, 0, consoleSize.X, consoleSize.Y - fontHeight); - consoleBox = new ConsoleContentTextBox(null, 0, 0, 0, 0); + consoleBox = new ConsoleContentTextBox(fontReference, null, 0, 0, 0, 0); consoleBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, true); //consoleBox.AnchorMax = new Vector2(1.0f, ConsoleHeight); //consoleBox.Height = consoleSize.Y - fontHeight; - consoleBox.Font = fontReference; //consoleBox.HorizontalAlignment = TextAlignment.Near; //consoleBox.VerticalAlignment = TextAlignment.Near; @@ -113,14 +113,12 @@ namespace Cabrito if (consoleNotifyBox == null) { //consoleBox = new ConsoleContentTextBox(null, 0, 0, consoleSize.X, consoleSize.Y - fontHeight); - consoleNotifyBox = new ConsoleContentTextBox(null, 0, 0, 0, 0); + consoleNotifyBox = new ConsoleContentTextBox(fontReference, null, 0, 0, 0, 0); consoleNotifyBox.HeightMultiplier = 0; consoleNotifyBox.Height = ConsoleNotifyLines * fontHeight; consoleNotifyBox.SetAnchorPreset(AnchorPresets.HorizontalStretchTop, true); //consoleBox.AnchorMax = new Vector2(1.0f, ConsoleHeight); - consoleNotifyBox.Font = fontReference; - //consoleBox.HorizontalAlignment = TextAlignment.Near; //consoleBox.VerticalAlignment = TextAlignment.Near; //consoleNotifyBox.HeightMultiplier = ConsoleHeight; @@ -263,11 +261,23 @@ namespace Cabrito private void OnSendExceptionLog(Exception exception, FlaxEngine.Object obj) { - Console.Print("[EXCEP] " + exception.Message); + AssertionException assert = exception as AssertionException; + if (assert != null) + { + var assertLines = assert.Message.Split('\n'); + if (assertLines.Length > 2) + Console.Print("Assert Failure: " + assertLines[2]); + else + Console.Print("Assert Failure: " + assert.Message); + } + else + Console.Print("[EXCEP] " + exception.Message); } public override void OnDestroy() { + base.OnDestroy(); + //consoleInputEvent.Triggered -= OnConsoleInputEvent; consoleInputEvent?.Dispose(); consoleBox?.Dispose(); @@ -325,7 +335,6 @@ namespace Cabrito public void OnConsoleClose() { - Console.Print("closed console"); Screen.CursorVisible = false; Screen.CursorLock = CursorLockMode.Locked; @@ -383,7 +392,7 @@ namespace Cabrito } else if (!Console.IsOpen) { - int fontHeight = (int) (consoleNotifyBox.Font.GetFont().Height / Platform.DpiScale); + int fontHeight = (int) (consoleNotifyBox.FontHeight / Platform.DpiScale); if (location.Y < (-conHeight * ConsoleHeight) + fontHeight) { consoleNotifyBox.Visible = true; @@ -395,7 +404,7 @@ namespace Cabrito public void OnPrint(string text) { - int fontHeight = (int) (consoleNotifyBox.Font.GetFont().Height / Platform.DpiScale); + int fontHeight = (int) (consoleNotifyBox.FontHeight / Platform.DpiScale); consoleNotifyBox.Height = Math.Min(ConsoleNotifyLines, Console.Lines.Count) * fontHeight; } diff --git a/Source/Game/MapParser/MapParser.cs b/Source/Game/MapParser/MapParser.cs new file mode 100644 index 0000000..8219d53 --- /dev/null +++ b/Source/Game/MapParser/MapParser.cs @@ -0,0 +1,497 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using FlaxEngine; + +// https://web.archive.org/web/20160316213335/http://forums.ubergames.net/topic/2658-understanding-the-quake-3-map-format/ +// https://web.archive.org/web/20210228125854/https://forums.thedarkmod.com/index.php?/topic/15668-plugin-request-save-map-in-quake-3-format/ + +namespace Game +{ + public struct MapFacePlane + { + public Vector3 v1, v2, v3; + public string texture; + public Vector2 offset; + public float rotation; + public Vector2 scale; + public int contentFlags, surfaceFlags, surfaceValue; + } + + public class MapBrush + { + public MapFacePlane[] planes; + } + + public class MapPatch + { + public string name; + } + + public struct PatchVertex + { + public Vector3 v; + public Vector2 uv; + } + + public class MapEntity + { + public Dictionary properties = new Dictionary(); + public List entities = new List(); + public List brushes = new List(); + public List patches = new List(); + } + + public static class MapParser + { + static public MapEntity Parse(byte[] data) + { + if (data.Length == 0) + return null; + + MapEntity rootEntity = new MapEntity(); + MapEntity currentEntity = rootEntity; + + int level = 0; + int index = 0; + //for (int i=0; i 2) + // throw new Exception("Failed to parse .map file: unexpected entity found at line " + lineNumber.ToString()); + } while (++index < data.Length); + + return rootEntity; + } + + static void ParseComment(byte[] data, ref int index) + { + for (; index < data.Length; index++) + { + if (data[index] == '\n') + break; + } + } + + static void ParseEntity(MapEntity currentEntity, byte[] data, ref int index) + { + bool entityParsed = false; + do + { + char c = (char) data[index]; + char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; + + switch (c) + { + case '\n': + case '\r': + break; + + case '/': + if (c1 == '/') + ParseComment(data, ref index); + else + throw new Exception("unexpected character: '" + c + "'"); + break; + + // "name" "value" + case '"': + { + string propName = ParseQuotedString(data, ref index); + string propValue = ParseQuotedString(data, ref index); + + if (currentEntity.properties.ContainsKey(propName)) + throw new Exception("Failed to parse .map file: multiple properties defined for " + + propName + + " at line ?");// + lineNumber.ToString()); + currentEntity.properties.Add(propName, propValue); + break; + } + + // brush + case '{': + { + index++; + for (; index < data.Length; index++) + { + if (data[index] != ' ' && data[index] != '\r' && data[index] != '\n') + break; + index++; + } + if (index >= data.Length) + break; + + if (data[index] == '(') + currentEntity.brushes.Add(ParseBrush(data, ref index)); + else if (char.IsLetter((char)data[index]) || char.IsNumber((char)data[index])) + currentEntity.patches.Add(ParsePatch(data, ref index)); + break; + } + case '}': + { + entityParsed = true; + break; + } + + default: + throw new Exception("unsupported character: '" + c + "'"); + } + } while (index++ < data.Length && !entityParsed); + } + + static string ParseQuotedString(byte[] data, ref int index) + { + StringBuilder sb = new StringBuilder(); + index++; + + for (; index < data.Length; index++) + { + if (data[index] == '"') + break; + sb.Append((char)data[index]); + } + index++; + + while (index < data.Length) + { + if (data[index] != ' ') + break; + index++; + } + + return sb.ToString(); + } + + static string ParseString(byte[] data, ref int index) + { + StringBuilder sb = new StringBuilder(); + + for (; index < data.Length; index++) + { + if (data[index] == ' ' || data[index] == '\r' || data[index] == '\n') + break; + sb.Append((char)data[index]); + } + index++; + + while (index < data.Length) + { + if (data[index] != ' ') + break; + index++; + } + + return sb.ToString(); + } + + static float ParseFloat(byte[] data, ref int index) + { + string fs = ParseString(data, ref index); + + if (float.TryParse(fs, NumberStyles.Float, CultureInfo.InvariantCulture, out float value)) + return value; + //else if (float.TryParse(fs, CultureInfo.InvariantCulture, out intValue)) + // return intValue; + else + throw new Exception("failed to ParseFloat: " + fs); + } + + static int ParseInt(byte[] data, ref int index) + { + string fs = ParseString(data, ref index); + + if (int.TryParse(fs, out int value)) + return value; + else + throw new Exception("failed to ParseInt: " + fs); + } + + static Vector3 ParseVector3(byte[] data, ref int index) + { + return new Vector3( + ParseFloat(data, ref index), + ParseFloat(data, ref index), + ParseFloat(data, ref index) + ); + } + + static Vector2 ParseVector2(byte[] data, ref int index) + { + return new Vector2( + ParseFloat(data, ref index), + ParseFloat(data, ref index) + ); + } + + static Vector3 ParsePlaneVector3(byte[] data, ref int index) + { + index++; + while (index < data.Length) + { + if (data[index] != ' ') + break; + index++; + } + + Vector3 vector = ParseVector3(data, ref index); + + while (index < data.Length) + { + if (data[index] == ')') + break; + index++; + } + + index++; + while (index < data.Length) + { + if (data[index] != ' ') + break; + index++; + } + + return vector; + } + + static MapBrush ParseBrush(byte[] data, ref int index) + { + MapBrush brush = new MapBrush(); + + List planes = new List(6); + + bool brushParsed = false; + do + { + char c = (char) data[index]; + //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; + + switch (c) + { + case '\r': + case '\n': + break; + + // brush face + // ( ) ( ) ( ) + case '(': + { + MapFacePlane plane; + plane.v1 = ParsePlaneVector3(data, ref index); + plane.v2 = ParsePlaneVector3(data, ref index); + plane.v3 = ParsePlaneVector3(data, ref index); + plane.texture = ParseString(data, ref index); + plane.offset = ParseVector2(data, ref index); + plane.rotation = ParseFloat(data, ref index); + plane.scale = ParseVector2(data, ref index); + plane.contentFlags = ParseInt(data, ref index); + plane.surfaceFlags = ParseInt(data, ref index); + plane.surfaceValue = ParseInt(data, ref index); + + planes.Add(plane); + break; + } + + case '}': + { + brushParsed = true; + break; + } + + default: + if (char.IsLetter(c) || char.IsNumber(c)) + { + // patch name + + } + throw new Exception("unsupported character: '" + c + "'"); + } + } while (index++ < data.Length && !brushParsed); + + brush.planes = planes.ToArray(); + + return brush; + } + + static MapPatch ParsePatch(byte[] data, ref int index) + { + MapPatch patch = new MapPatch(); + + patch.name = ParseString(data, ref index); + + bool patchParsed = false; + do + { + char c = (char) data[index]; + //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; + + switch (c) + { + case '\r': + case '\n': + break; + + case '{': + { + index++; + ParsePatchInner(patch, data, ref index); + break; + } + + case '}': + { + patchParsed = true; + break; + } + + default: + throw new Exception("unsupported character: '" + c + "'"); + } + } while (index++ < data.Length && !patchParsed); + + return patch; + } + + // unfinished and untested + static void ParsePatchInner(MapPatch patch, byte[] data, ref int index) + { + string shaderName = ParseString(data, ref index); + + while (index < data.Length) + { + if (data[index] == '(') + break; + index++; + } + index++; + + int width = ParseInt(data, ref index); + int height = ParseInt(data, ref index); + int dummy1 = ParseInt(data, ref index); + int dummy2 = ParseInt(data, ref index); + int dummy3 = ParseInt(data, ref index); + + while (index < data.Length) + { + if (data[index] == ')') + break; + index++; + } + index++; + + while (index < data.Length) + { + if (data[index] == '(') + break; + index++; + } + index++; + + PatchVertex[] vertices = new PatchVertex[width * height]; + + bool verticesParsed = false; + int vertexIndex = 0; + do + { + char c = (char) data[index]; + //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; + + switch (c) + { + case '\r': + case '\n': + break; + + case '(': + { + index++; + + for (int iw = 0; iw < width; iw++) + { + while (index < data.Length) + { + if (data[index] == '(') + break; + index++; + } + + index++; + + while (index < data.Length) + { + if (data[index] != ' ') + break; + index++; + } + + vertices[vertexIndex].v = ParseVector3(data, ref index); + vertices[vertexIndex].uv = new Vector2(ParseFloat(data, ref index), ParseFloat(data, ref index)); + vertexIndex++; + + while (index < data.Length) + { + if (data[index] == ')') + break; + index++; + } + index++; + } + + + break; + } + + case '}': + { + verticesParsed = true; + break; + } + + default: + throw new Exception("unsupported character: '" + c + "'"); + } + } while (index++ < data.Length && !verticesParsed); + } + + } +} \ No newline at end of file diff --git a/Source/Game/Q3MapImporter.cs b/Source/Game/Q3MapImporter.cs index 61a92af..8d22bc7 100644 --- a/Source/Game/Q3MapImporter.cs +++ b/Source/Game/Q3MapImporter.cs @@ -2,91 +2,1055 @@ 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 class MapBrush + 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 MapEntity + public class Face { - public Dictionary properties = new Dictionary(); - public List entities = new List(); - public List brushes = new List(); + public Vector3 v1, v2, v3; + public List halfEdges; + public bool visited; + + public Face(Vector3 v1, Vector3 v2, Vector3 v3) + { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + halfEdges = new List(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 { - string mapPath = @"C:\dev\Goake\maps\aerowalk\aerowalk.map"; + private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube.map"; + //private string mapPath = @"C:\dev\Goake\maps\aerowalk\aerowalk.map"; - public override void OnStart() + Model model; + public MaterialBase material; + + const float epsilon = 0.00001f; + + private List FindHull(List points, Edge edge, Vector3 planeNormal) { - string[] lines = File.ReadAllLines(mapPath); + // 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. - MapEntity rootEntity = new MapEntity(); - MapEntity currentEntity = rootEntity; + if (points.Count == 0) + return new List(); - int level = 0; - uint lineNumber = 0; - foreach (string lineRaw in lines) + // furthest point from edge + Vector3 furthest = Vector3.Zero; + float furthestDist = 0f; + foreach (var vert in points) { - lineNumber++; - string line = lineRaw.TrimStart(); - if (line.StartsWith("//")) + 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 set1 = new List(); + List set2 = new List(); + + 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; - if (line[0] == '{') - { - level++; + 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; - if (level == 1) - { - currentEntity = new MapEntity(); - rootEntity.entities.Add(currentEntity); - } - else - { - } + 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 if (line[0] == '}') + else { - level--; - currentEntity = rootEntity; + // normals pointing different directions, eliminate + continue; } - //if (level < 0 || level > 2) - // throw new Exception("Failed to parse .map file: unexpected entity found at line " + lineNumber.ToString()); - if (line[0] == '"') + var norm = (vert - edge.v1).Normalized; + var dd = Vector3.Dot(edgeNormal.Normalized, norm); + if (dd > epsilon) { - string[] prop = line.Split('\"'); - if (prop.Length != 5) - throw new Exception("Failed to parse .map file: failed to parse property at line " + - lineNumber.ToString()); - - string propName = prop[1]; - string propValue = prop[3]; - - if (currentEntity.properties.ContainsKey(propName)) - throw new Exception("Failed to parse .map file: multiple properties defined for " + propName + - " at line " + lineNumber.ToString()); - currentEntity.properties.Add(propName, propValue); + set1.Add(vert); } - else if (line[0] == '(') + else if (dd < -epsilon) { - //"( -16 302 431 ) ( -16 302 361 ) ( -16 321 361 ) dev/dev_128_gray 0 0 0 0.0625 0.0625 0 0 0" - string[] bru = line.Split(new char[] {'(', ')'}); + set2.Add(vert); + } + } - // TODO: make geometry out of this, use planes? - foreach (var b in bru) + List set = new List(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 QuickHull(List 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) { - //Console.WriteLine(b); + a1 = v1; + a2 = v2; } } } + var edgeNormal = Vector3.Cross(a2 - a1, planeNormal); + List set1 = new List(); + List set2 = new List(); - // Here you can add code that needs to be called when script is created, just before the first game update + 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 set = new List(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 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 QuickHull2(Vector3[] points) + { + var tetrahedron = CreateInitialSimplex(points); + + List> outsideSet = new List>(); + 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, 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(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(); + 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> unclaimedPoints2 = new List>(); + List horizonEdges = new List(); + + List hullFaces = new List(); + hullFaces.AddRange(tetrahedron); + + // stop when none of the faces have any visible outside points + int iterCount = 0; + while (outsideSet.Count > 0) + { + iterCount++; + Tuple 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(); + + 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>(unclaimedPoints2); + unclaimedPoints2.Clear(); + + if (iterCount >= 3) + break; + + if (hullFaces.Count > 1000 || iterCount > 1000) + Assert.Fail("overflow"); + } + + List hullPoints = new List(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> unclaimedPoints2, + List> outsideSet, + List horizonEdges, List hullFaces) + { + horizonEdges.Clear(); + + CalculateHorizon(face, point, unclaimedPoints2, outsideSet, horizonEdges, face.halfEdges[0]); + + // create new faces + if (horizonEdges.Count > 0) + { + List newFaces = new List(); + 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(); + 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> unclaimedPoints2, + List> outsideSet, + List 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 vertices = new List(); + + // 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 brushVertices = new List(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 clippedVerts = new List(); + List newBrushVertices = new List(); + List faceVertices = new List(); + + if (true) + { + Func isFront = (f) => f > 0; + Func 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 clippedVerts2 = new List(); + 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(); + levelActor.Name = "LevelGeometry"; + + int brushIndex = 1; + foreach (var brush in root.entities[0].brushes) + { + BoxBrush boxBrush = levelActor.AddChild(); + 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(); + + 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.SetupLODs(new int[] {1}); + { + 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()); + } + + StaticModel childModel = Actor.AddChild(); + childModel.Name = "MapModel"; + childModel.Model = model; + childModel.SetMaterial(0, material); } public override void OnEnable() @@ -103,5 +1067,11 @@ namespace Game { // Here you can add code that needs to be called every frame } + + public override void OnDestroy() + { + Destroy(ref model); + base.OnDestroy(); + } } } \ No newline at end of file diff --git a/Source/GameTarget.Build.cs b/Source/GameTarget.Build.cs index bd52f5c..6db75dd 100644 --- a/Source/GameTarget.Build.cs +++ b/Source/GameTarget.Build.cs @@ -7,6 +7,8 @@ public class GameTarget : GameProjectTarget { base.Init(); + //OutputName = "Goake"; + // Reference the modules for game Modules.Add("Game"); //Modules.Add("Cabrito"); diff --git a/copydll.bat b/copydll.bat new file mode 100644 index 0000000..61ac38f --- /dev/null +++ b/copydll.bat @@ -0,0 +1,4 @@ +@echo off +echo copying +copy C:\dev\GoakeFlax\Binaries\GameTarget\Windows\x64\Development\Game.CSharp.* C:\dev\GoakeFlax\Output\WindowsDevelopment\ /Y +echo copied? \ No newline at end of file diff --git a/launchbg.bat b/launchbg.bat new file mode 100644 index 0000000..7731196 --- /dev/null +++ b/launchbg.bat @@ -0,0 +1,4 @@ +@echo off +echo launching +start /B %* +echo launched? \ No newline at end of file