├── .gitignore ├── ExampleImages ├── InputExample1.png └── Result1.png ├── LICENSE ├── README.md ├── VMFGeneration.sln └── VMFGeneration ├── EasyInputLayer.cs ├── EntityTemplates.cs ├── GenerationMethod.cs ├── Generator.cs ├── LineEquation.cs ├── Models.cs ├── PolygonTriangulator.cs ├── Program.cs ├── Shape.cs ├── Textures.cs ├── Tool2D.cs ├── VMFDebug.cs ├── VMFGenerator.csproj └── Visgroups.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /ExampleImages/InputExample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7ark/CSGO-VMF-Generator/8e9cfea9f5aaf70d8079aacc4100671ec9eb4d0b/ExampleImages/InputExample1.png -------------------------------------------------------------------------------- /ExampleImages/Result1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7ark/CSGO-VMF-Generator/8e9cfea9f5aaf70d8079aacc4100671ec9eb4d0b/ExampleImages/Result1.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cory Koseck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSGO VMF Generator 2 | 3 | This is a generator with the purpose of making an easy way to generate CS:GO maps or making it easier to generate things in the VMF format (With a focus on CS:GO) 4 | 5 | See my broad technical explanation here: [CS:GO VMF Generator Explained](http://www.corykoseck.com/2020/08/21/csgo-vmf-generator-explained/) 6 | 7 | This is a fairly basic library, and I'm open to pull requests for improvements. I've really only done lots of early setup, and it's missing a lot of potentially great features. 8 | Currently it supports: 9 | - Generating cubes, stairs, slopes, and any flat 2D polygon (concave or convex, it will automatically convert) that can be rotated. 10 | - Wall generation, simply passing in another created brush and it will create walls around that shape. 11 | - Primitive map generation from a black and white image. Examples shown below. 12 | - Adding visgroups to generated brushes, with included [TAR](https://github.com/Terri00/CS-GO-Auto-Radar) visgroup names for easily generating radars. 13 | - Texture support. UVs are a bit wonky, but you can easily select what textures you want for each brush. 14 | - Entity support. Included manually are only a light environment, T and CT spawns, however it is fairly easy to add a new entity to EntityTemplates.cs 15 | 16 | Examples of what can be made: 17 | 18 | Input: 19 | 20 | ![Image of Input](https://github.com/7ark/CSGO-VMF-Generator/blob/master/ExampleImages/InputExample1.png) 21 | 22 | Result: 23 | 24 | ![Image of Result](https://github.com/7ark/CSGO-VMF-Generator/blob/master/ExampleImages/Result1.png) 25 | 26 | ## How do I run this? 27 | Well to run what I've provided by default, you simply run the EXE and it'll generate the basic stuff I've provided. 28 | If your CS:GO folder isn't in the default C drive location I've entered (seen in the Program.cs), you'll need to pass it an argument with the path you want the VMF generated. 29 | To do more you'll need to edit a little bit of code. 30 | 31 | ## Code Guide: 32 | There are several key classes you'll need to know about to edit the code from a basic to advanced level. Starting at the basics, EasyInputLayer.cs is where you can easily change or 33 | add new shapes to generate. Here I've already setup examples, where I add GenerationMethods to a list and return that. Generation methods return a list of shapes (You can see them 34 | and their types in GenerationMethods.cs). 35 | 36 | ### Basic 37 | Currently I have 3 basic ones, a Misc which is simply what I used for testing, it is making a simple shape and some stairs. 38 | Then theres the HollowCube, which is self explanatory. I'm using that for the skybox in this example. 39 | Then the last I have commented out for the moment. This is the image generation. If you uncomment it (I suggest commenting out the Misc if you do this or it'll get cluttered) 40 | you will need to make sure to create an input folder in the same folder as the exe (in the default case this is the debug folder) if it doesnt already exist, and then you'll 41 | add a black and white image such as the example above. This isn't perfect, but it does a pretty good job with a lot of shapes. There are still plenty of improvements to that entire algorithm that could be made though. 42 | 43 | Then in the EasyInputLayer.cs theres also the entities below that. I am simply adding strings generated from the EntityTemplates.cs class, which simply fills out the format for 44 | that particular entity, it should be fairly easy to add your own if you wish. I provide examples of adding a light environment and spawns. 45 | 46 | ### Advanced 47 | If you wish to get more involved in the code, edit it to add more shapes, or make pull requests etc. then you'll end up getting deeper into the other classes. 48 | The main classes to keep an eye on are GenerationMethod.cs and Shape.cs, these are where the base areas for creating new shapes and such are. Its mostly combining things to create 49 | new shapes from the Polygon shape. For example slopes are create by making a flat Polygon in the shape of a slope and rotating it to the correct position. You can see an example 50 | of me doing this in the StairGenerator for the clipping slope. 51 | 52 | For deeper subjects theres a few things. 53 | Image generation is handled in GenerationMethods at the Image generation method. Theres a lot going on here, and it will probably take a lot to absord. I am taking an image, adding 54 | artifical lines to essentially make all of the white space one non-looping polygon. I do this because what I'm using to create polygons out of images isn't able to handle looping 55 | images, it simply deletes the inner section. I am unable to write anything to do more with this, so I create extremely thin lines at key points to get around this. 56 | All of that is handled in that one class, I've tried to comment the important bits. 57 | 58 | If you want to make the triangulation better, cleaner, or even change it to quadrangulation (to make "better" looking brushes) you can find all of that in the PolygonTriangulator.cs. 59 | Here I follow a [great tutorial](https://www.gamedev.net/tutorials/programming/graphics/polygon-triangulation-r3334/) to implement triangulation, and then add my own adjustments 60 | to accomodate what the tool requires. 61 | 62 | For anything else, it should all be fairly self explanatory. You'll need to look through the code and experiment. 63 | 64 | ## Pull Requests 65 | I am open to any pull requests to improve the code base, adding new features, even if its just adding new Entity Templates, Textures, etc. 66 | This is not a project I plan to support heavily, it is something I update in my spare time to expand and improve. 67 | 68 | ## Personal Support 69 | If you’re interested in supporting me personally, you can follow me on [Twitter](https://twitter.com/The7ark) or [Instagram](https://www.instagram.com/the7ark/). 70 | If you want to support me financially, I have a [Patreon](https://www.patreon.com/7ark) and a [Ko-fi](https://ko-fi.com/sevenark). 71 | -------------------------------------------------------------------------------- /VMFGeneration.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29926.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VMFGenerator", "VMFGeneration\VMFGenerator.csproj", "{B8B7F840-BD5F-4017-99EC-A2CFF9FCD978}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B8B7F840-BD5F-4017-99EC-A2CFF9FCD978}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {B8B7F840-BD5F-4017-99EC-A2CFF9FCD978}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {B8B7F840-BD5F-4017-99EC-A2CFF9FCD978}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {B8B7F840-BD5F-4017-99EC-A2CFF9FCD978}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {FB967547-45C7-4400-ABE5-A5B1E41C63C8} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /VMFGeneration/EasyInputLayer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Numerics; 6 | using System.Text; 7 | using VMFGenerator; 8 | 9 | namespace VMFGenerator 10 | { 11 | public static class EasyInputLayer 12 | { 13 | public static void GetInput(out List generationMethods, out List entities) 14 | { 15 | //If enabled, will draw images to a created debug folder 16 | VMFDebug.DebugMode = true; 17 | 18 | //Generation Methods 19 | generationMethods = new List(); 20 | //generationMethods.Add(new AimMapGenerationMethod() 21 | //{ 22 | // mapSize = 1024, 23 | // overrideMinStairsCount = 7 24 | //}); 25 | //generationMethods.Add(new ImageGenerationMethod() 26 | //{ 27 | // InputFilePath = Directory.GetCurrentDirectory() + @"\Input\InputImage.png" 28 | //}); 29 | //generationMethods.Add(new HollowCubeGenerationMethod() 30 | //{ 31 | // Position = new Vector3(0, 0, 5f), 32 | // Texture = Textures.SKYBOX, 33 | // Scalar = 64, 34 | // Size = new Vector3(30, 30, 10), 35 | // Thickness = 0.5f 36 | //}); 37 | generationMethods.Add(new GridGenerationMethod() 38 | { 39 | displacementSidesPerBlock = { SideFacing.Top }, 40 | blockDepth = 256 41 | }); 42 | //generationMethods.Add(new BasicSpawnsGenerationMethod()); 43 | 44 | //Entities 45 | entities = new List(); 46 | entities.Add(EntityTemplates.LightEnvironment( 47 | lightColor: Color.FromArgb(255, 240, 240), 48 | ambientLightColor: Color.FromArgb(240, 240, 255), 49 | origin: new Vector3(0, 0, 256), 50 | brightness: 400, 51 | angles: new Vector3(-40, -60, 0), 52 | pitch: -60)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /VMFGeneration/EntityTemplates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Numerics; 4 | 5 | namespace VMFGenerator 6 | { 7 | public static class EntityTemplates 8 | { 9 | public static int BlockEntityID = 0; 10 | public static int LastID = -1; 11 | 12 | public enum BlockEntityType { func_detail, func_buyzone_terrorist, func_buyzone_counterterrorist, func_buyzone_all, trigger_hurt } 13 | 14 | public static string ToValue(this BlockEntityType type) 15 | { 16 | switch (type) 17 | { 18 | case BlockEntityType.func_buyzone_terrorist: 19 | return "func_buyzone\"" + Environment.NewLine + 20 | "\t\"TeamNum\" \"2"; 21 | case BlockEntityType.func_buyzone_counterterrorist: 22 | return "func_buyzone\"" + Environment.NewLine + 23 | "\t\"TeamNum\" \"3"; 24 | case BlockEntityType.func_buyzone_all: 25 | return "func_buyzone\"" + Environment.NewLine + 26 | "\t\"TeamNum\" \"0"; 27 | } 28 | 29 | return type.ToString(); 30 | } 31 | 32 | public static string LightEnvironment(Color lightColor = new Color(), float brightness = 20, Color ambientLightColor = new Color(), float ambientBrightness = 200, Vector3 angles = new Vector3(), float pitch = -90, float sunSpreadAngle = 0, Vector3 origin = new Vector3()) 33 | { 34 | if (lightColor.A == 0) 35 | { 36 | lightColor = Color.White; 37 | } 38 | if (ambientLightColor.A == 0) 39 | { 40 | ambientLightColor = Color.White; 41 | } 42 | 43 | return 44 | "entity" + Environment.NewLine + 45 | "{" + Environment.NewLine + 46 | "\t\"id\" \"" + (++LastID) + "\"" + Environment.NewLine + 47 | "\t\"classname\" \"light_environment\"" + Environment.NewLine + 48 | "\t\"_ambient\" \"" + ambientLightColor.R + " " + ambientLightColor.G + " " + ambientLightColor.B + " " + ambientBrightness + "\"" + Environment.NewLine + 49 | "\t\"_ambientHDR\" \"-1 -1 -1 1\"" + Environment.NewLine + 50 | "\t\"_AmbientScaleHDR\" \"1\"" + Environment.NewLine + 51 | "\t\"_light\" \"" + lightColor.R + " " + lightColor.G + " " + lightColor.B + " " + brightness + "\"" + Environment.NewLine + 52 | "\t\"_lightHDR\" \"-1 -1 -1 1\"" + Environment.NewLine + 53 | "\t\"_lightscaleHDR\" \"1\"" + Environment.NewLine + 54 | "\t\"angles\" \"" + angles.X + " " + angles.Y + " " + angles.Z + "\"" + Environment.NewLine + 55 | "\t\"pitch\" \"" + pitch + "\"" + Environment.NewLine + 56 | "\t\"SunSpreadAngle\" \"" + sunSpreadAngle + "\"" + Environment.NewLine + 57 | "\t\"origin\" \"" + origin.X + " " + origin.Y + " " + origin.Z + "\"" + Environment.NewLine + 58 | "\teditor" + Environment.NewLine + 59 | "\t{" + Environment.NewLine + 60 | "\t\t\"color\" \"220 30 220\"" + Environment.NewLine + 61 | "\t\t\"visgroupshown\" \"1\"" + Environment.NewLine + 62 | "\t\t\"visgroupautoshown\" \"1\"" + Environment.NewLine + 63 | "\t\t\"logicalpos\" \"[0 0]\"" + Environment.NewLine + 64 | "\t}" + Environment.NewLine + 65 | "}"; 66 | } 67 | 68 | public static string InfoPlayerTerrorist(Vector3 angles = new Vector3(), Vector3 origin = new Vector3()) 69 | { 70 | return 71 | "entity" + Environment.NewLine + 72 | "{" + Environment.NewLine + 73 | "\t\"id\" \"" + (++LastID) + "\"" + Environment.NewLine + 74 | "\t\"classname\" \"info_player_terrorist\"" + Environment.NewLine + 75 | "\t\"angles\" \"" + angles.X + " " + angles.Y + " " + angles.Z + "\"" + Environment.NewLine + 76 | "\t\"enabled\" \"1\"" + Environment.NewLine + 77 | "\t\"origin\" \"" + origin.X + " " + origin.Y + " " + origin.Z + "\"" + Environment.NewLine + 78 | "\teditor" + Environment.NewLine + 79 | "\t{" + Environment.NewLine + 80 | "\t\t\"color\" \"220 30 220\"" + Environment.NewLine + 81 | "\t\t\"visgroupshown\" \"1\"" + Environment.NewLine + 82 | "\t\t\"visgroupautoshown\" \"1\"" + Environment.NewLine + 83 | "\t\t\"logicalpos\" \"[0 0]\"" + Environment.NewLine + 84 | "\t}" + Environment.NewLine + 85 | "}"; 86 | } 87 | public static string InfoPlayerCounterTerrorist(Vector3 angles = new Vector3(), Vector3 origin = new Vector3()) 88 | { 89 | return 90 | "entity" + Environment.NewLine + 91 | "{" + Environment.NewLine + 92 | "\t\"id\" \"" + (++LastID) + "\"" + Environment.NewLine + 93 | "\t\"classname\" \"info_player_counterterrorist\"" + Environment.NewLine + 94 | "\t\"angles\" \"" + angles.X + " " + angles.Y + " " + angles.Z + "\"" + Environment.NewLine + 95 | "\t\"enabled\" \"1\"" + Environment.NewLine + 96 | "\t\"origin\" \"" + origin.X + " " + origin.Y + " " + origin.Z + "\"" + Environment.NewLine + 97 | "\teditor" + Environment.NewLine + 98 | "\t{" + Environment.NewLine + 99 | "\t\t\"color\" \"220 30 220\"" + Environment.NewLine + 100 | "\t\t\"visgroupshown\" \"1\"" + Environment.NewLine + 101 | "\t\t\"visgroupautoshown\" \"1\"" + Environment.NewLine + 102 | "\t\t\"logicalpos\" \"[0 0]\"" + Environment.NewLine + 103 | "\t}" + Environment.NewLine + 104 | "}"; 105 | } 106 | public static string PropStatic(string modelName, Vector3 angles = new Vector3(), Vector3 origin = new Vector3()) 107 | { 108 | return 109 | "entity" + Environment.NewLine + 110 | "{" + Environment.NewLine + 111 | "\t\"id\" \"" + (++LastID) + "\"" + Environment.NewLine + 112 | "\t\"classname\" \"prop_static\"" + Environment.NewLine + 113 | "\t\"angles\" \"" + angles.X + " " + angles.Y + " " + angles.Z + "\"" + Environment.NewLine + 114 | "\t\"disableflashlight\" \"0\"" + Environment.NewLine + 115 | "\t\"disableselfshadowing\" \"0\"" + Environment.NewLine + 116 | "\t\"disableshadowdepth\" \"0\"" + Environment.NewLine + 117 | "\t\"disableshadows\" \"0\"" + Environment.NewLine + 118 | "\t\"disablevertexlighting\" \"0\"" + Environment.NewLine + 119 | "\t\"disableX360\" \"0\"" + Environment.NewLine + 120 | "\t\"drawinfastreflection\" \"0\"" + Environment.NewLine + 121 | "\t\"enablelightbounce\" \"0\"" + Environment.NewLine + 122 | "\t\"fademaxdist\" \"0\"" + Environment.NewLine + 123 | "\t\"fademindist\" \"-1\"" + Environment.NewLine + 124 | "\t\"fadescale\" \"1\"" + Environment.NewLine + 125 | "\t\"ignorenormals\" \"0\"" + Environment.NewLine + 126 | "\t\"maxcpulevel\" \"0\"" + Environment.NewLine + 127 | "\t\"maxgpulevel\" \"0\"" + Environment.NewLine + 128 | "\t\"mincpulevel\" \"0\"" + Environment.NewLine + 129 | "\t\"mingpulevel\" \"0\"" + Environment.NewLine + 130 | "\t\"model\" \"" + modelName + ".mdl\"" + Environment.NewLine + 131 | "\t\"preventpropcombine\" \"0\"" + Environment.NewLine + 132 | "\t\"renderamt\" \"255\"" + Environment.NewLine + 133 | "\t\"rendercolor\" \"255 255 255\"" + Environment.NewLine + 134 | "\t\"screenspacefade\" \"0\"" + Environment.NewLine + 135 | "\t\"shadowdepthnocache\" \"0\"" + Environment.NewLine + 136 | "\t\"skin\" \"0\"" + Environment.NewLine + 137 | "\t\"solid\" \"6\"" + Environment.NewLine + 138 | "\t\"uniformscale\" \"1\"" + Environment.NewLine + 139 | "\t\"origin\" \"" + origin.X + " " + origin.Y + " " + origin.Z + "\"" + Environment.NewLine + 140 | "\teditor" + Environment.NewLine + 141 | "\t{" + Environment.NewLine + 142 | "\t\t\"color\" \"220 30 220\"" + Environment.NewLine + 143 | "\t\t\"visgroupshown\" \"1\"" + Environment.NewLine + 144 | "\t\t\"visgroupautoshown\" \"1\"" + Environment.NewLine + 145 | "\t\t\"logicalpos\" \"[0 0]\"" + Environment.NewLine + 146 | "\t}" + Environment.NewLine + 147 | "}"; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /VMFGeneration/GenerationMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Drawing; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Numerics; 8 | using System.Security.Principal; 9 | using VMFGenerator; 10 | 11 | namespace VMFGenerator 12 | { 13 | public class ImageGenerationMethod : GenerationMethod 14 | { 15 | private struct PixelFlat 16 | { 17 | public Point Point; 18 | public float FlatnessValue; 19 | } 20 | 21 | public string InputFilePath; 22 | 23 | public override List GetBrushes(out List entities) 24 | { 25 | entities = new List(); 26 | List shapes = new List(); 27 | 28 | if(!File.Exists(InputFilePath)) 29 | { 30 | Console.WriteLine("ERROR: Given file path " + InputFilePath + " does not exist! Not running image generation."); 31 | return shapes; 32 | } 33 | 34 | List edgePositions = new List(); 35 | Bitmap map = new Bitmap(InputFilePath); 36 | map.RotateFlip(RotateFlipType.Rotate180FlipX); 37 | map = new Bitmap(map, new Size(map.Width * 2, map.Height * 2)); 38 | int[,] grid = new int[map.Width, map.Height]; 39 | int[,] testingGrid = new int[map.Width, map.Height]; 40 | 41 | for (int x = 0; x < grid.GetLength(0); x++) 42 | { 43 | for (int y = 0; y < grid.GetLength(1); y++) 44 | { 45 | grid[x, y] = map.GetPixel(x, y).G > 50 ? 1 : 0; 46 | testingGrid[x, y] = grid[x, y]; 47 | } 48 | } 49 | 50 | List> emptySpots = new List>(); 51 | 52 | //Finds all the space between the white space 53 | while (true) 54 | { 55 | Point nextEmptyArea = new Point(); 56 | bool foundSpot = false; 57 | for (int x = 0; x < testingGrid.GetLength(0); x++) 58 | { 59 | for (int y = 0; y < testingGrid.GetLength(1); y++) 60 | { 61 | if(testingGrid[x, y] == 0) 62 | { 63 | nextEmptyArea = new Point(x, y); 64 | foundSpot = true; 65 | break; 66 | } 67 | } 68 | if(foundSpot) 69 | { 70 | break; 71 | } 72 | } 73 | 74 | if(foundSpot) 75 | { 76 | emptySpots.Add(GetEmptySpace(ref testingGrid, nextEmptyArea.X, nextEmptyArea.Y)); 77 | } 78 | else 79 | { 80 | break; 81 | } 82 | } 83 | 84 | if(emptySpots.Count > 1) 85 | { 86 | 87 | //Gets just the bottom edge of the empty space 88 | List> allBottomEdges = new List>(); 89 | for (int i = 0; i < emptySpots.Count; i++) 90 | { 91 | List bottomEdges = new List(); 92 | for (int j = 0; j < emptySpots[i].Count; j++) 93 | { 94 | bool edge = GetNeighbor(grid, emptySpots[i][j].X, emptySpots[i][j].Y, 0, 1) == 1; 95 | 96 | if (edge) 97 | { 98 | bottomEdges.Add(emptySpots[i][j]); 99 | } 100 | } 101 | 102 | allBottomEdges.Add(bottomEdges); 103 | } 104 | 105 | Dictionary hitPointDistances = new Dictionary(); 106 | Dictionary hitPoints = new Dictionary(); 107 | 108 | //Further filters the edges to only get the ones that don't collide with themselves (when aiming downward) 109 | for (int i = 0; i < allBottomEdges.Count; i++) 110 | { 111 | for (int j = allBottomEdges[i].Count - 1; j >= 0; j--) 112 | { 113 | List tested = new List(); 114 | bool thisHitsSelf = false; 115 | Point original = new Point(allBottomEdges[i][j].X, allBottomEdges[i][j].Y); 116 | Point curr = original; 117 | while (true) 118 | { 119 | tested.Add(curr); 120 | 121 | int below = GetNeighbor(grid, curr.X, curr.Y, 0, 1); 122 | if (below == -1) 123 | { 124 | thisHitsSelf = true; 125 | break; 126 | } 127 | Point belowPoint = new Point(curr.X, curr.Y + 1); 128 | if (below == 0) 129 | { 130 | for (int k = 0; k < emptySpots[i].Count; k++) 131 | { 132 | if (belowPoint == emptySpots[i][k]) 133 | { 134 | thisHitsSelf = true; 135 | break; 136 | } 137 | } 138 | 139 | hitPointDistances.Add(original, Vector2.Distance(new Vector2(original.X, original.Y), new Vector2(belowPoint.X, belowPoint.Y))); 140 | hitPoints.Add(original, belowPoint); 141 | break; 142 | } 143 | 144 | if (thisHitsSelf) 145 | { 146 | break; 147 | } 148 | 149 | curr = belowPoint; 150 | } 151 | 152 | if (thisHitsSelf) 153 | { 154 | allBottomEdges[i].RemoveAt(j); 155 | } 156 | 157 | } 158 | } 159 | 160 | List> closestOptions = new List>(); 161 | int amountOfClosestPointsToGet = 100; 162 | for (int i = 0; i < allBottomEdges.Count; i++) 163 | { 164 | List closestPoints = new List(); 165 | for (int k = 0; k < amountOfClosestPointsToGet; k++) 166 | { 167 | float closestDist = 100000; 168 | Point closest = new Point(); 169 | for (int j = allBottomEdges[i].Count - 1; j >= 0; j--) 170 | { 171 | float dist = hitPointDistances[allBottomEdges[i][j]]; 172 | 173 | if (dist < closestDist) 174 | { 175 | closestDist = dist; 176 | closest = allBottomEdges[i][j]; 177 | } 178 | } 179 | 180 | closestPoints.Add(new Vector2(closest.X, closest.Y)); 181 | allBottomEdges[i].Remove(closest); 182 | if (allBottomEdges[i].Count <= 0) 183 | { 184 | break; 185 | } 186 | } 187 | closestOptions.Add(closestPoints); 188 | } 189 | 190 | Dictionary> fromConnections = new Dictionary>(); 191 | List> linesToCreate = new List>(); 192 | 193 | for (int i = 0; i < emptySpots.Count; i++) 194 | { 195 | fromConnections.Add(i, new List()); 196 | } 197 | 198 | for (int i = 0; i < closestOptions.Count; i++) 199 | { 200 | List line = new List(); 201 | 202 | int rayLength = 20; 203 | 204 | List flatnessValues = new List(); 205 | for (int j = 0; j < closestOptions[i].Count; j++) 206 | { 207 | Console.WriteLine("Calculating best split location, checking point " + (j + 1)); 208 | 209 | //Top 210 | Point evalPointStart = new Point((int)closestOptions[i][j].X, (int)closestOptions[i][j].Y); 211 | Point insideRightStart = PixelRaycast(grid, evalPointStart, new Point(1, 0), 1, rayLength); 212 | Point insideLeftStart = PixelRaycast(grid, evalPointStart, new Point(-1, 0), 1, rayLength); 213 | Point outsideRightStart = PixelRaycast(grid, new Point(evalPointStart.X, evalPointStart.Y + 1), new Point(1, 0), 0, rayLength); 214 | Point outsideLeftStart = PixelRaycast(grid, new Point(evalPointStart.X, evalPointStart.Y + 1), new Point(-1, 0), 0, rayLength); 215 | 216 | Vector2 outsideMiddleStart = Vector2.Lerp(new Vector2(outsideLeftStart.X, outsideLeftStart.Y), new Vector2(outsideRightStart.X, outsideRightStart.Y), 0.5f); 217 | float outsideCheckStart = (rayLength * 0.5f) * Vector2.Distance(outsideMiddleStart, new Vector2(evalPointStart.X, evalPointStart.Y)); 218 | Vector2 insideMiddleStart = Vector2.Lerp(new Vector2(insideLeftStart.X, insideLeftStart.Y), new Vector2(insideRightStart.X, insideRightStart.Y), 0.5f); 219 | float insideCheckStart = (rayLength * 0.5f) * Vector2.Distance(insideMiddleStart, new Vector2(evalPointStart.X, evalPointStart.Y)); 220 | 221 | float outsideLengthStart = Vector2.Distance(new Vector2(outsideLeftStart.X, outsideLeftStart.Y), new Vector2(outsideRightStart.X, outsideRightStart.Y)); 222 | float insideLengthStart = Vector2.Distance(new Vector2(insideLeftStart.X, insideLeftStart.Y), new Vector2(insideRightStart.X, insideRightStart.Y)); 223 | float lengthDifferenceStart = (rayLength * 4) - (outsideLengthStart + insideLengthStart); 224 | 225 | float totalValidAreaStart = outsideCheckStart + insideCheckStart + lengthDifferenceStart; 226 | 227 | //Bottom 228 | Point evalPointEnd = hitPoints[evalPointStart]; 229 | Point insideRightEnd = PixelRaycast(grid, new Point(evalPointEnd.X, evalPointEnd.Y + 1), new Point(1, 0), 1, rayLength); 230 | Point insideLeftEnd = PixelRaycast(grid, new Point(evalPointEnd.X, evalPointEnd.Y + 1), new Point(-1, 0), 1, rayLength); 231 | Point outsideRightEnd = PixelRaycast(grid, new Point(evalPointEnd.X, evalPointEnd.Y - 1), new Point(1, 0), 0, rayLength); 232 | Point outsideLeftEnd = PixelRaycast(grid, new Point(evalPointEnd.X, evalPointEnd.Y - 1), new Point(-1, 0), 0, rayLength); 233 | 234 | Vector2 outsideMiddleEnd = Vector2.Lerp(new Vector2(outsideLeftEnd.X, outsideLeftEnd.Y), new Vector2(outsideRightEnd.X, outsideRightEnd.Y), 0.5f); 235 | float outsideCheckEnd = (rayLength * 0.5f) * Vector2.Distance(outsideMiddleEnd, new Vector2(evalPointEnd.X, evalPointEnd.Y)); 236 | Vector2 insideMiddleEnd = Vector2.Lerp(new Vector2(insideLeftEnd.X, insideLeftEnd.Y), new Vector2(insideRightEnd.X, insideRightEnd.Y), 0.5f); 237 | float insideCheckEnd = (rayLength * 0.5f) * Vector2.Distance(insideMiddleEnd, new Vector2(evalPointEnd.X, evalPointEnd.Y)); 238 | 239 | float outsideLengthEnd = Vector2.Distance(new Vector2(outsideLeftEnd.X, outsideLeftEnd.Y), new Vector2(outsideRightEnd.X, outsideRightEnd.Y)); 240 | float insideLengthEnd = Vector2.Distance(new Vector2(insideLeftEnd.X, insideLeftEnd.Y), new Vector2(insideRightEnd.X, insideRightEnd.Y)); 241 | float lengthDifferenceEnd = (rayLength * 4) - (outsideLengthEnd + insideLengthEnd); 242 | 243 | float totalValidAreaEnd = outsideCheckEnd + insideCheckEnd + lengthDifferenceEnd; 244 | 245 | flatnessValues.Add(new PixelFlat() 246 | { 247 | Point = evalPointStart, 248 | FlatnessValue = totalValidAreaStart + totalValidAreaEnd 249 | }); 250 | 251 | if(i == -1) 252 | { 253 | VMFDebug.CreateDebugImage("EvalOption" + j, onDraw: (g) => 254 | { 255 | for (int i = 0; i < grid.GetLength(0); i++) 256 | { 257 | for (int j = 0; j < grid.GetLength(1); j++) 258 | { 259 | g.DrawRectangle(grid[i, j] == 0 ? Pens.Gray : Pens.White, new Rectangle(i, j, 1, 1)); 260 | } 261 | } 262 | g.DrawLine(Pens.DarkBlue, evalPointStart, insideRightStart); 263 | g.DrawLine(Pens.DarkBlue, evalPointStart, insideLeftStart); 264 | g.DrawLine(Pens.LightBlue, new Point(evalPointStart.X, evalPointStart.Y + 1), outsideRightStart); 265 | g.DrawLine(Pens.LightBlue, new Point(evalPointStart.X, evalPointStart.Y + 1), outsideLeftStart); 266 | g.DrawEllipse(Pens.DarkBlue, new Rectangle((int)insideMiddleStart.X, (int)insideMiddleStart.Y, 2, 2)); 267 | g.DrawEllipse(Pens.LightBlue, new Rectangle((int)outsideMiddleStart.X, (int)outsideMiddleStart.Y, 2, 2)); 268 | 269 | g.DrawLine(Pens.DarkGreen, new Point(evalPointEnd.X, evalPointEnd.Y + 1), insideRightEnd); 270 | g.DrawLine(Pens.DarkGreen, new Point(evalPointEnd.X, evalPointEnd.Y + 1), insideLeftEnd); 271 | g.DrawLine(Pens.LightGreen, new Point(evalPointEnd.X, evalPointEnd.Y - 1), outsideRightEnd); 272 | g.DrawLine(Pens.LightGreen, new Point(evalPointEnd.X, evalPointEnd.Y - 1), outsideLeftEnd); 273 | g.DrawEllipse(Pens.DarkGreen, new Rectangle((int)insideMiddleEnd.X, (int)insideMiddleEnd.Y, 2, 2)); 274 | g.DrawEllipse(Pens.LightGreen, new Rectangle((int)outsideMiddleEnd.X, (int)outsideMiddleEnd.Y, 2, 2)); 275 | 276 | g.DrawLine(Pens.Red, evalPointStart, evalPointEnd); 277 | 278 | g.DrawRectangle(Pens.Black, new Rectangle((int)evalPointStart.X, (int)evalPointStart.Y, 1, 1)); 279 | g.DrawRectangle(Pens.Black, new Rectangle((int)evalPointEnd.X, (int)evalPointEnd.Y, 1, 1)); 280 | 281 | g.DrawString("Total: " + (totalValidAreaStart + totalValidAreaEnd) + "\n" + 282 | "Top Middle Check: " + (outsideCheckStart + insideCheckStart) + "\n" + 283 | "Top Length Check: " + (lengthDifferenceStart) + "/" + (rayLength * 4) + "\n" + 284 | "Bottom Middle Check: " + (outsideCheckEnd + insideCheckEnd) + "\n" + 285 | "Bottom Length Check: " + (lengthDifferenceEnd) + "/" + (rayLength * 4), 286 | SystemFonts.DefaultFont, Brushes.Black, new PointF(0, 0)); 287 | }, map.Width, map.Height); 288 | } 289 | } 290 | flatnessValues.Sort((x, y) => { return x.FlatnessValue.CompareTo(y.FlatnessValue); }); 291 | 292 | //Not sure if theres a better way to choose out of the top contenders, maybe explore later 293 | Point current = flatnessValues[0].Point; 294 | Point goal = hitPoints[new Point((int)current.X, (int)current.Y)]; 295 | 296 | int from = -1; 297 | int to = -1; 298 | for (int j = 0; j < emptySpots.Count; j++) 299 | { 300 | for (int k = 0; k < emptySpots[j].Count; k++) 301 | { 302 | if (emptySpots[j][k] == current) 303 | { 304 | from = j; 305 | break; 306 | } 307 | } 308 | if (from != -1) 309 | { 310 | break; 311 | } 312 | } 313 | for (int j = 0; j < emptySpots.Count; j++) 314 | { 315 | for (int k = 0; k < emptySpots[j].Count; k++) 316 | { 317 | if (emptySpots[j][k] == goal) 318 | { 319 | to = j; 320 | break; 321 | } 322 | } 323 | if (to != -1) 324 | { 325 | break; 326 | } 327 | } 328 | 329 | bool loops = false; 330 | List visited = new List(); 331 | Queue toCheck = new Queue(); 332 | toCheck.Enqueue(from); 333 | int check = -1; 334 | while (toCheck.Count > 0) 335 | { 336 | check = toCheck.Dequeue(); 337 | 338 | visited.Add(check); 339 | 340 | if (check == to) 341 | { 342 | loops = true; 343 | break; 344 | } 345 | 346 | if (fromConnections[check].Count == 0) 347 | { 348 | continue; 349 | } 350 | 351 | for (int j = 0; j < fromConnections[check].Count; j++) 352 | { 353 | if (!visited.Contains(fromConnections[check][j])) 354 | { 355 | toCheck.Enqueue(fromConnections[check][j]); 356 | } 357 | } 358 | } 359 | 360 | if (!loops) 361 | { 362 | fromConnections[to].Add(from); 363 | 364 | while (true) 365 | { 366 | line.Add(current); 367 | 368 | if (current == goal) 369 | { 370 | break; 371 | } 372 | 373 | current = new Point(current.X, current.Y + 1); 374 | } 375 | 376 | linesToCreate.Add(line); 377 | } 378 | } 379 | 380 | Console.WriteLine("Adding loop points"); 381 | for (int i = 0; i < linesToCreate.Count; i++) 382 | { 383 | for (int j = 0; j < linesToCreate[i].Count; j++) 384 | { 385 | grid[linesToCreate[i][j].X, linesToCreate[i][j].Y] = 0; 386 | } 387 | } 388 | 389 | 390 | VMFDebug.CreateDebugImage("LoopPoints", onDraw: (g) => 391 | { 392 | for (int i = 0; i < grid.GetLength(0); i++) 393 | { 394 | for (int j = 0; j < grid.GetLength(1); j++) 395 | { 396 | g.DrawRectangle(grid[i, j] == 0 ? Pens.Gray : Pens.White, new Rectangle(i, j, 1, 1)); 397 | } 398 | } 399 | for (int i = 0; i < emptySpots.Count; i++) 400 | { 401 | g.DrawString(i.ToString(), SystemFonts.DefaultFont, Brushes.Red, new PointF(emptySpots[i][0].X, emptySpots[i][0].Y)); 402 | } 403 | for (int i = 0; i < linesToCreate.Count; i++) 404 | { 405 | Vector2 pos = Vector2.Lerp(new Vector2(linesToCreate[i][0].X, linesToCreate[i][0].Y), new Vector2(linesToCreate[i][linesToCreate[i].Count - 1].X, linesToCreate[i][linesToCreate[i].Count - 1].Y), 0.5f); 406 | g.DrawString(i.ToString(), SystemFonts.DefaultFont, Brushes.Blue, new PointF(pos.X, pos.Y)); 407 | } 408 | for (int i = 0; i < closestOptions.Count; i++) 409 | { 410 | for (int j = 0; j < closestOptions[i].Count; j++) 411 | { 412 | g.DrawRectangle(Pens.Black, new Rectangle((int)closestOptions[i][j].X, (int)closestOptions[i][j].Y, 1, 1)); 413 | } 414 | } 415 | }, map.Width, map.Height); 416 | } 417 | 418 | 419 | List linedUpPoints = new List(); 420 | 421 | bool[,] boolGrid = new bool[grid.GetLength(0), grid.GetLength(1)]; 422 | for (int x = 0; x < grid.GetLength(0); x++) 423 | { 424 | for (int y = 0; y < grid.GetLength(1); y++) 425 | { 426 | boolGrid[x, y] = grid[x, y] == 1; 427 | } 428 | } 429 | 430 | linedUpPoints = new List( 431 | BitmapHelper.CreateFromBitmap(new ArrayBitmap(boolGrid)) 432 | ); 433 | 434 | List sharedPoints = new List(); 435 | Dictionary toMove = new Dictionary(); 436 | 437 | List badValues = new List(); 438 | 439 | bool swapValueToRemove = true; 440 | for (int i = linedUpPoints.Count - 1; i >= 0; i--) 441 | { 442 | for (int j = linedUpPoints.Count - 1; j >= i; j--) 443 | { 444 | if (i == j) 445 | { 446 | continue; 447 | } 448 | 449 | float dist = Vector2.Distance(linedUpPoints[i], linedUpPoints[j]); 450 | 451 | if (dist <= 2 && !sharedPoints.Contains(linedUpPoints[i]) && !sharedPoints.Contains(linedUpPoints[j])) 452 | { 453 | int diff = j - i; 454 | if (diff < 4) 455 | { 456 | if(swapValueToRemove) 457 | { 458 | badValues.Add(i); 459 | } 460 | else 461 | { 462 | badValues.Add(j); 463 | } 464 | 465 | swapValueToRemove = !swapValueToRemove; 466 | } 467 | else 468 | { 469 | sharedPoints.Add(linedUpPoints[i]); 470 | sharedPoints.Add(linedUpPoints[j]); 471 | toMove.Add(i, linedUpPoints[j]); 472 | } 473 | } 474 | } 475 | } 476 | 477 | foreach (int key in toMove.Keys) 478 | { 479 | linedUpPoints[key] = toMove[key]; 480 | } 481 | 482 | for (int i = linedUpPoints.Count; i >= 0; i--) 483 | { 484 | if(badValues.Contains(i)) 485 | { 486 | linedUpPoints.RemoveAt(i); 487 | } 488 | } 489 | 490 | VMFDebug.CreateDebugImage("ImageProcess", onDraw: (g) => 491 | { 492 | float scale = 0.4f; 493 | 494 | for (int i = 0; i < linedUpPoints.Count; i++) 495 | { 496 | int iN = (i + 1) % (linedUpPoints.Count); 497 | g.DrawRectangle(new Pen(Color.Black), new Rectangle((int)(linedUpPoints[i].X * scale), (int)(linedUpPoints[i].Y * scale), 1, 1)); 498 | g.DrawLine(new Pen(Color.Black, 3), 499 | new Point((int)(linedUpPoints[i].X * scale), (int)(linedUpPoints[i].Y * scale)), 500 | new Point((int)(linedUpPoints[iN].X * scale), (int)(linedUpPoints[iN].Y * scale))); 501 | 502 | if(linedUpPoints[i] == linedUpPoints[iN]) 503 | { 504 | g.DrawEllipse(new Pen(Color.Blue, 3), new Rectangle((int)(linedUpPoints[i].X * scale), (int)(linedUpPoints[i].Y * scale), 1, 1)); 505 | } 506 | 507 | g.DrawString(i.ToString(), SystemFonts.DefaultFont, Brushes.Red, new Point((int)(linedUpPoints[i].X * scale), (int)(linedUpPoints[i].Y * scale))); 508 | } 509 | 510 | for (int i = 0; i < linedUpPoints.Count; i++) 511 | { 512 | g.DrawRectangle(new Pen(Color.FromArgb(i % 255, i % 255, i % 255)), new Rectangle((int)(linedUpPoints[i].X * scale), (int)(linedUpPoints[i].Y * scale), 1, 1)); 513 | } 514 | }); 515 | 516 | 517 | Polygon poly = new Polygon() 518 | { 519 | Visgroup = Visgroups.TAR_LAYOUT, 520 | Position = new Vector3(-map.Width, -map.Height, 0), 521 | Data = new PolygonShapeData() 522 | { 523 | Depth = 64, 524 | Scalar = 2, 525 | PolygonPoints = linedUpPoints 526 | } 527 | }; 528 | 529 | shapes.Add(poly); 530 | 531 | shapes.AddRange(WallGenerator.CreateWalls(poly, new WallData() 532 | { 533 | Height = 256, 534 | Thickness = 16 535 | })); 536 | 537 | return shapes; 538 | } 539 | 540 | public Point PixelRaycast(int[,] grid, Point start, Point direction, int valueToStopOn, int maxDistance = 1000) 541 | { 542 | int distanceIncrease = 0; 543 | Point curr = start; 544 | while (true) 545 | { 546 | int nextPosition = GetNeighbor(grid, curr.X, curr.Y, direction.X, direction.Y); 547 | if (nextPosition == -1) 548 | { 549 | return curr; 550 | } 551 | Point nextPoint = new Point(curr.X + direction.X, curr.Y + direction.Y); 552 | if (nextPosition == valueToStopOn) 553 | { 554 | return nextPoint; 555 | } 556 | 557 | curr = nextPoint; 558 | distanceIncrease++; 559 | if(distanceIncrease >= maxDistance) 560 | { 561 | return curr; 562 | } 563 | } 564 | } 565 | 566 | public List GetEmptySpace(ref int[,] grid, int x, int y) 567 | { 568 | List output = new List(); 569 | int currX = x; 570 | int currY = y; 571 | Queue pointsToCheck = new Queue(); 572 | Point start = new Point(x, y); 573 | pointsToCheck.Enqueue(start); 574 | 575 | int target = 0; 576 | int replaceWith = 1; 577 | 578 | 579 | while(pointsToCheck.Count > 0) 580 | { 581 | Point currPoint = pointsToCheck.Dequeue(); 582 | currX = currPoint.X; 583 | currY = currPoint.Y; 584 | 585 | if (grid[currX, currY] != target) 586 | { 587 | continue; 588 | } 589 | else 590 | { 591 | grid[currX, currY] = replaceWith; 592 | output.Add(new Point(currX, currY)); 593 | } 594 | 595 | List nextPoints = new List() 596 | { 597 | new Point(-1, 0), 598 | new Point(1, 0), 599 | new Point(0, -1), 600 | new Point(0, 1) 601 | }; 602 | 603 | for (int i = 0; i < nextPoints.Count; i++) 604 | { 605 | int xi = nextPoints[i].X; 606 | int yi = nextPoints[i].Y; 607 | 608 | if (currX + xi == -1 || currY + yi == -1 || currX + xi == grid.GetLength(0) || currY + yi == grid.GetLength(1)) 609 | { 610 | continue; 611 | } 612 | 613 | Point nextPoint = new Point(currX + xi, currY + yi); 614 | 615 | pointsToCheck.Enqueue(nextPoint); 616 | } 617 | } 618 | 619 | return output; 620 | } 621 | 622 | 623 | private int GetNeighbor(int[,] grid, int pointX, int pointY, int x, int y) 624 | { 625 | if (pointX == 0 || pointY == 0 || pointX == grid.GetLength(0) - 1 || pointY == grid.GetLength(1) - 1) 626 | { 627 | return -1; 628 | } 629 | 630 | return grid[pointX + x, pointY + y]; 631 | } 632 | } 633 | 634 | public class MiscGenerationMethod : GenerationMethod 635 | { 636 | public override List GetBrushes(out List entities) 637 | { 638 | entities = new List(); 639 | List shapes = new List(); 640 | 641 | //Manaully made convex shape 642 | Polygon floor = new Polygon() 643 | { 644 | Visgroup = Visgroups.TAR_LAYOUT, 645 | Position = new Vector3(-256, -256, 16), 646 | Data = new PolygonShapeData() 647 | { 648 | Depth = 32, 649 | Scalar = 128, 650 | PolygonPoints = new List() 651 | { 652 | new Vector2(-3, -1), 653 | new Vector2(4, -1), 654 | new Vector2(4, 2), 655 | new Vector2(3, 3), 656 | new Vector2(3, 4), 657 | new Vector2(4, 6), 658 | new Vector2(7, 6), 659 | new Vector2(8, 8), 660 | new Vector2(8, 10), 661 | new Vector2(6, 10), 662 | new Vector2(6, 8), 663 | new Vector2(4, 8), 664 | new Vector2(4, 15), 665 | new Vector2(2, 16), 666 | new Vector2(-1, 16), 667 | new Vector2(-1, 18), 668 | new Vector2(4, 18), 669 | new Vector2(6, 16), 670 | new Vector2(6, 10), 671 | new Vector2(8, 10), 672 | new Vector2(8, 12), 673 | new Vector2(10, 12), 674 | new Vector2(11, 13), 675 | new Vector2(11, 15), 676 | new Vector2(10, 16), 677 | new Vector2(8, 16), 678 | new Vector2(6, 18), 679 | new Vector2(6, 20), 680 | new Vector2(4, 22), 681 | new Vector2(0, 22), 682 | new Vector2(-2, 20), 683 | new Vector2(-6, 20), 684 | new Vector2(-8, 18), 685 | new Vector2(-8, 16), 686 | new Vector2(-6, 14), 687 | new Vector2(-4, 14), 688 | new Vector2(-4, 12), 689 | new Vector2(-6, 10), 690 | new Vector2(-6, 6), 691 | new Vector2(-3, 6), 692 | new Vector2(-3, 4), 693 | new Vector2(-1, 4), 694 | new Vector2(-2, 10), 695 | new Vector2(-2, 14), 696 | new Vector2(0, 14), 697 | new Vector2(0, 8), 698 | new Vector2(1, 8), 699 | new Vector2(1, 3), 700 | new Vector2(0, 2), 701 | new Vector2(0, 1), 702 | new Vector2(-1, 1), 703 | new Vector2(-1, 4), 704 | new Vector2(-3, 4) 705 | } 706 | } 707 | }; 708 | shapes.Add(floor); 709 | 710 | 711 | shapes.AddRange(StairsGenerator.Generate(new StairData() 712 | { 713 | Visgroup = Visgroups.TAR_LAYOUT, 714 | RailingThickness = 8, 715 | BlockEntityID = EntityTemplates.BlockEntityID++, 716 | Position = new Vector3(-256, 0, 128), 717 | Run = 12, 718 | Rise = 8, 719 | StairCount = 16, 720 | StairWidth = 128, 721 | Direction = Direction.East 722 | }, 723 | new RotationData() 724 | { 725 | RotationAxis = new Vector3(0, 0, 1), 726 | RotationAngle = 0 727 | } 728 | )); 729 | shapes.AddRange(StairsGenerator.Generate(new StairData() 730 | { 731 | Visgroup = Visgroups.TAR_LAYOUT, 732 | RailingThickness = 8, 733 | BlockEntityID = EntityTemplates.BlockEntityID++, 734 | Position = new Vector3(256, 0, 128), 735 | Run = 12, 736 | Rise = 8, 737 | StairCount = 16, 738 | StairWidth = 128, 739 | Direction = Direction.West 740 | }, 741 | new RotationData() 742 | { 743 | RotationAxis = new Vector3(0, 0, 1), 744 | RotationAngle = 0 745 | } 746 | )); 747 | shapes.AddRange(StairsGenerator.Generate(new StairData() 748 | { 749 | Visgroup = Visgroups.TAR_LAYOUT, 750 | RailingThickness = 8, 751 | BlockEntityID = EntityTemplates.BlockEntityID++, 752 | Position = new Vector3(0, -256, 128), 753 | Run = 12, 754 | Rise = 8, 755 | StairCount = 16, 756 | StairWidth = 128, 757 | Direction = Direction.North 758 | }, 759 | new RotationData() 760 | { 761 | RotationAxis = new Vector3(0, 0, 1), 762 | RotationAngle = 0 763 | } 764 | )); 765 | shapes.AddRange(StairsGenerator.Generate(new StairData() 766 | { 767 | Visgroup = Visgroups.TAR_LAYOUT, 768 | RailingThickness = 8, 769 | BlockEntityID = EntityTemplates.BlockEntityID++, 770 | Position = new Vector3(0, 256, 128), 771 | Run = 12, 772 | Rise = 8, 773 | StairCount = 16, 774 | StairWidth = 128, 775 | Direction = Direction.South 776 | }, 777 | new RotationData() 778 | { 779 | RotationAxis = new Vector3(0, 0, 1), 780 | RotationAngle = 0 781 | } 782 | )); 783 | 784 | shapes.AddRange(WallGenerator.CreateWalls(floor, new WallData() 785 | { 786 | Height = 256, 787 | Thickness = 64, 788 | facesIndicesToSkip = new List() 789 | })); 790 | 791 | return shapes; 792 | } 793 | } 794 | 795 | public class HollowCubeGenerationMethod : GenerationMethod 796 | { 797 | public Vector3 Position = Vector3.Zero; 798 | public Vector3 Size; 799 | public int Scalar = 32; 800 | public float Thickness; 801 | public string Texture = Textures.DEV_MEASUREGENERIC01B; 802 | public override List GetBrushes(out List entities) 803 | { 804 | entities = new List(); 805 | List shapes = new List(); 806 | 807 | //Top 808 | shapes.Add(new Cube() 809 | { 810 | Position = this.Position * Scalar + new Vector3(0, 0, Size.Z) * Scalar, 811 | Texture = this.Texture, 812 | Data = new CubeShapeData() 813 | { 814 | Size = new Vector3(Size.X, Size.Y, Thickness) * Scalar * 2 815 | } 816 | }); 817 | 818 | //Bottom 819 | shapes.Add(new Cube() 820 | { 821 | Position = this.Position * Scalar + new Vector3(0, 0, -Size.Z) * Scalar, 822 | Texture = this.Texture, 823 | Data = new CubeShapeData() 824 | { 825 | Size = new Vector3(Size.X, Size.Y, Thickness) * Scalar * 2 826 | } 827 | }); 828 | 829 | //Front 830 | shapes.Add(new Cube() 831 | { 832 | Position = this.Position * Scalar + new Vector3(0, Size.Y, 0) * Scalar, 833 | Texture = this.Texture, 834 | Data = new CubeShapeData() 835 | { 836 | Size = new Vector3(Size.X, Thickness, Size.Z) * Scalar * 2 837 | } 838 | }); 839 | 840 | //Back 841 | shapes.Add(new Cube() 842 | { 843 | Position = this.Position * Scalar + new Vector3(0, -Size.Y, 0) * Scalar, 844 | Texture = this.Texture, 845 | Data = new CubeShapeData() 846 | { 847 | Size = new Vector3(Size.X, Thickness, Size.Z) * Scalar * 2 848 | } 849 | }); 850 | 851 | //Left 852 | shapes.Add(new Cube() 853 | { 854 | Position = this.Position * Scalar + new Vector3(Size.X, 0, 0) * Scalar, 855 | Texture = this.Texture, 856 | Data = new CubeShapeData() 857 | { 858 | Size = new Vector3(Thickness, Size.Y, Size.Z) * Scalar * 2 859 | } 860 | }); 861 | 862 | //Right 863 | shapes.Add(new Cube() 864 | { 865 | Position = this.Position * Scalar + new Vector3(-Size.X, 0, 0) * Scalar, 866 | Texture = this.Texture, 867 | Data = new CubeShapeData() 868 | { 869 | Size = new Vector3(Thickness, Size.Y, Size.Z) * Scalar * 2 870 | } 871 | }); 872 | 873 | return shapes; 874 | } 875 | } 876 | 877 | public class BasicSpawnsGenerationMethod : GenerationMethod 878 | { 879 | public override List GetBrushes(out List entities) 880 | { 881 | entities = new List(); 882 | entities.Add(EntityTemplates.InfoPlayerTerrorist( 883 | origin: new Vector3(0, -64, 32)) 884 | ); 885 | entities.Add(EntityTemplates.InfoPlayerCounterTerrorist( 886 | origin: new Vector3(128, -64, 32)) 887 | ); 888 | 889 | return new List(); 890 | } 891 | } 892 | 893 | public class SimpleTemplateGenerationMethod : GenerationMethod 894 | { 895 | public override List GetBrushes(out List entities) 896 | { 897 | entities = new List(); 898 | List shapes = new List(); 899 | 900 | shapes.Add(new Polygon() 901 | { 902 | Data = new PolygonShapeData() 903 | { 904 | Depth = 32, 905 | Scalar = 512, 906 | PolygonPoints = new List() 907 | { 908 | new Vector2(-1, -1), 909 | new Vector2(1, -1), 910 | new Vector2(1, 1), 911 | new Vector2(-1, 1), 912 | } 913 | } 914 | }); 915 | 916 | return shapes; 917 | } 918 | } 919 | public class GridGenerationMethod : GenerationMethod 920 | { 921 | public int gridWidth = 16; 922 | public int gridHeight = 16; 923 | public int blockSize = 1024; 924 | public int blockDepth = 64; 925 | public List displacementSidesPerBlock = new List(); 926 | public override List GetBrushes(out List entities) 927 | { 928 | entities = new List(); 929 | List shapes = new List(); 930 | 931 | int size = blockSize * 2; 932 | 933 | for (int i = -gridWidth / 2; i < gridWidth / 2; i++) 934 | { 935 | for (int j = -gridHeight / 2; j < gridHeight / 2; j++) 936 | { 937 | shapes.Add(new Cube() 938 | { 939 | SidesAreDisplacements = displacementSidesPerBlock, 940 | Position = new Vector3(i * size / 2, j * size / 2, 0), 941 | Texture = Textures.DEV_MEASUREGENERIC01B, 942 | Data = new CubeShapeData() 943 | { 944 | Size = new Vector3(size / 2, size / 2, blockDepth) 945 | } 946 | }); 947 | } 948 | } 949 | 950 | return shapes; 951 | } 952 | } 953 | public class AimMapGenerationMethod : GenerationMethod 954 | { 955 | public int MaxEnemies = 10; 956 | public bool PlayerIsTerrorist = true; 957 | public int mapSize = 768; 958 | 959 | public int overrideMinStairsCount = -1; 960 | public int overrideMaxStairsCount = -1; 961 | public int overrideMin56BoxCount = -1; 962 | public int overrideMax56BoxCount = -1; 963 | public int overrideMin64BoxCount = -1; 964 | public int overrideMax64BoxCount = -1; 965 | public override List GetBrushes(out List entities) 966 | { 967 | entities = new List(); 968 | List shapes = new List(); 969 | 970 | Random random = new Random(); 971 | 972 | Polygon floor = new Polygon() 973 | { 974 | Texture = Textures.DUST_STONEWALL02, 975 | Position = new Vector3(0, 0, -16), 976 | Data = new PolygonShapeData() 977 | { 978 | Depth = 32, 979 | Scalar = mapSize, 980 | PolygonPoints = new List() 981 | { 982 | new Vector2(-1, -1), 983 | new Vector2(1, -1), 984 | new Vector2(1, 1), 985 | new Vector2(-1, 1), 986 | } 987 | } 988 | }; 989 | shapes.Add(floor); 990 | 991 | shapes.Add(new Polygon() 992 | { 993 | Position = new Vector3(0, 0, 112), 994 | Texture = Textures.TRIGGER, 995 | BlockEntityID = EntityTemplates.BlockEntityID++, 996 | EntityType = EntityTemplates.BlockEntityType.func_buyzone_all, 997 | Data = new PolygonShapeData() 998 | { 999 | Depth = 256, 1000 | Scalar = mapSize, 1001 | PolygonPoints = new List() 1002 | { 1003 | new Vector2(-1, -1), 1004 | new Vector2(1, -1), 1005 | new Vector2(1, 1), 1006 | new Vector2(-1, 1), 1007 | } 1008 | } 1009 | }); 1010 | 1011 | mapSize -= 32; 1012 | 1013 | float badDistance = mapSize / 8; 1014 | int amountOfPoints = mapSize / 7; 1015 | List randomPoints = new List(); 1016 | for (int i = 0; i < amountOfPoints; i++) 1017 | { 1018 | Vector2 point = new Vector2(random.Next(-mapSize, mapSize + 1), random.Next(-mapSize, mapSize + 1)); 1019 | 1020 | while(true) 1021 | { 1022 | bool goodToContinue = true; 1023 | for (int j = 0; j < randomPoints.Count; j++) 1024 | { 1025 | float dist = Vector2.Distance(point, randomPoints[j]); 1026 | if (dist < badDistance) 1027 | { 1028 | goodToContinue = false; 1029 | } 1030 | } 1031 | 1032 | if(goodToContinue) 1033 | { 1034 | break; 1035 | } 1036 | else 1037 | { 1038 | point = new Vector2(random.Next(-mapSize, mapSize + 1), random.Next(-mapSize, mapSize + 1)); 1039 | } 1040 | } 1041 | randomPoints.Add(point); 1042 | } 1043 | 1044 | Queue pointsToAdd = new Queue(randomPoints); 1045 | 1046 | int stairsToAdd = random.Next(overrideMinStairsCount == -1 ? 1 : overrideMinStairsCount, overrideMaxStairsCount == -1 ? mapSize / 80 : overrideMaxStairsCount); 1047 | for (int i = 0; i < stairsToAdd; i++) 1048 | { 1049 | Vector2 pos = pointsToAdd.Dequeue(); 1050 | shapes.AddRange(StairsGenerator.Generate(new StairData() 1051 | { 1052 | Texture = Textures.DUST_STONEWALL02, 1053 | Visgroup = Visgroups.TAR_LAYOUT, 1054 | RailingThickness = 8, 1055 | BlockEntityID = EntityTemplates.BlockEntityID++, 1056 | Position = new Vector3(pos.X, pos.Y, 0), 1057 | Run = 12, 1058 | Rise = 8, 1059 | StairCount = 4 + (4 * random.Next(0,5)), 1060 | StairWidth = 96 + (32 * random.Next(0, 5)), 1061 | Direction = (Direction)random.Next(0, 4) 1062 | }, 1063 | new RotationData() 1064 | { 1065 | RotationAxis = new Vector3(0, 0, 1), 1066 | RotationAngle = 0 1067 | } 1068 | )); 1069 | } 1070 | 1071 | int coverToAdd56 = random.Next(overrideMin56BoxCount == -1 ? mapSize / 70 : overrideMin56BoxCount, overrideMax56BoxCount == -1 ? mapSize / 40 : overrideMax56BoxCount); 1072 | for (int i = 0; i < coverToAdd56; i++) 1073 | { 1074 | Vector2 pos = pointsToAdd.Dequeue(); 1075 | entities.Add(EntityTemplates.PropStatic(Models.DustCrateStyle264x64x64, origin: new Vector3(pos.X, pos.Y, 0))); 1076 | } 1077 | 1078 | int coverToAdd64 = random.Next(overrideMin64BoxCount == -1 ? mapSize / 60 : overrideMin64BoxCount, overrideMax64BoxCount == -1 ? mapSize / 30 : overrideMax64BoxCount); 1079 | for (int i = 0; i < coverToAdd64; i++) 1080 | { 1081 | Vector2 pos = pointsToAdd.Dequeue(); 1082 | entities.Add(EntityTemplates.PropStatic(Models.DustCrateStyle264x64x64, origin: new Vector3(pos.X, pos.Y, 0))); 1083 | } 1084 | 1085 | Vector2 playerPoint = pointsToAdd.Dequeue(); 1086 | if (PlayerIsTerrorist) 1087 | { 1088 | entities.Add(EntityTemplates.InfoPlayerTerrorist( 1089 | origin: new Vector3(playerPoint.X, playerPoint.Y, 4)) 1090 | ); 1091 | } 1092 | else 1093 | { 1094 | entities.Add(EntityTemplates.InfoPlayerCounterTerrorist( 1095 | origin: new Vector3(playerPoint.X, playerPoint.Y, 4)) 1096 | ); 1097 | } 1098 | 1099 | for (int i = 0; i < pointsToAdd.Count && i < MaxEnemies; i++) 1100 | { 1101 | Vector2 botPoint = pointsToAdd.Dequeue(); 1102 | if(PlayerIsTerrorist) 1103 | { 1104 | entities.Add(EntityTemplates.InfoPlayerCounterTerrorist( 1105 | origin: new Vector3(botPoint.X, botPoint.Y, 4)) 1106 | ); 1107 | } 1108 | else 1109 | { 1110 | entities.Add(EntityTemplates.InfoPlayerTerrorist( 1111 | origin: new Vector3(botPoint.X, botPoint.Y, 4)) 1112 | ); 1113 | } 1114 | } 1115 | 1116 | 1117 | shapes.AddRange(WallGenerator.CreateWalls(floor, new WallData() 1118 | { 1119 | Texture = Textures.DUST_STONEWALL02, 1120 | Height = 256, 1121 | Thickness = 64, 1122 | facesIndicesToSkip = new List() 1123 | })); 1124 | 1125 | return shapes; 1126 | } 1127 | } 1128 | 1129 | 1130 | public class BhopGenerationMethod : GenerationMethod 1131 | { 1132 | public override List GetBrushes(out List entities) 1133 | { 1134 | entities = new List(); 1135 | List shapes = new List(); 1136 | 1137 | int distanceBetween = 384; 1138 | int blockSize = 64; 1139 | int amountOfBlocks = 75; 1140 | 1141 | int rotation = 0; 1142 | int rotationChange = 15; 1143 | 1144 | int pathWidth = 256; 1145 | 1146 | Random random = new Random(); 1147 | 1148 | List positions = new List(); 1149 | 1150 | for (int i = 0; i < amountOfBlocks + 2; i++) 1151 | { 1152 | int dist = blockSize + distanceBetween; 1153 | 1154 | Vector3 finalPos = new Vector3(0, 0, 0); 1155 | int safety = 0; 1156 | 1157 | int currRot = rotation; 1158 | 1159 | while(true) 1160 | { 1161 | rotation = currRot; 1162 | rotation += rotationChange; 1163 | int randomXDegree = rotation; 1164 | int randomYDegree = rotation; 1165 | if (random.Next(0, 7) == 0 || safety > 1000) 1166 | { 1167 | rotationChange *= -1; 1168 | } 1169 | if (random.Next(0, 2) == 0) 1170 | { 1171 | int changeAdjust = random.Next(-5, 6); 1172 | rotationChange += changeAdjust; 1173 | rotationChange = Math.Clamp(rotationChange, safety > 5000 ? -40 : -25, safety > 1000 ? 40 : 25); 1174 | 1175 | if (rotationChange >= 0) 1176 | { 1177 | if (rotationChange < 10) 1178 | { 1179 | rotationChange = 10; 1180 | } 1181 | } 1182 | else 1183 | { 1184 | if (rotationChange > -10) 1185 | { 1186 | rotationChange = -10; 1187 | } 1188 | } 1189 | } 1190 | 1191 | float x = (int)((180 / Math.PI) * Math.Cos(Math.PI * randomXDegree / 180.0)); 1192 | float y = (int)((180 / Math.PI) * Math.Sin(Math.PI * randomYDegree / 180.0)); 1193 | x /= 100; 1194 | y /= 100; 1195 | x *= dist; 1196 | y *= dist; 1197 | 1198 | finalPos = new Vector3(0, 0, 0); 1199 | if (i == 1) 1200 | { 1201 | finalPos += new Vector3(x, y, 0); 1202 | } 1203 | else if (i != 0) 1204 | { 1205 | finalPos += shapes[shapes.Count - 1].Position + new Vector3(x, y, 0); 1206 | } 1207 | 1208 | bool goodPath = true; 1209 | for (int j = 0; j < positions.Count - 4; j++) 1210 | { 1211 | if (Vector3.Distance(positions[j], finalPos) < pathWidth * (safety > 5000 ? 2.5f : 4f)) 1212 | { 1213 | goodPath = false; 1214 | break; 1215 | } 1216 | } 1217 | 1218 | if(goodPath) 1219 | { 1220 | break; 1221 | } 1222 | else 1223 | { 1224 | safety++; 1225 | if(safety > 10000) 1226 | { 1227 | break; 1228 | } 1229 | } 1230 | } 1231 | 1232 | positions.Add(finalPos + new Vector3(0, 0, -blockSize * 2)); 1233 | 1234 | if(i != 0 && i != amountOfBlocks + 1) 1235 | { 1236 | shapes.Add(new Cube() 1237 | { 1238 | BlockEntityID = EntityTemplates.BlockEntityID++, 1239 | EntityType = EntityTemplates.BlockEntityType.func_detail, 1240 | Position = finalPos, 1241 | Texture = Textures.DEV_MEASUREGENERIC01B, 1242 | Data = new CubeShapeData() 1243 | { 1244 | Size = new Vector3(blockSize, blockSize, blockSize * 6) 1245 | } 1246 | }); 1247 | } 1248 | } 1249 | 1250 | List points = new List(); 1251 | 1252 | for (int k = 0; k < 2; k++) 1253 | { 1254 | for (int i = k == 0 ? 0 : 1; i < positions.Count; i++) 1255 | { 1256 | //Okay basically all the shit I'm doing here is getting the vertex normal of the current point 1257 | //and basing how the wall should be made based off that, so all the walls end up as trapazoids. 1258 | //The only exception is if a wall piece is missing, I then get the intersection based on which 1259 | //side of the wall it is, and try to flatten it. It's not perfect but it works? 1260 | int index1 = (i - 1) % positions.Count; 1261 | if (index1 == -1) 1262 | { 1263 | index1 = positions.Count - 1; 1264 | } 1265 | int index2 = i % positions.Count; 1266 | int index3 = (i + 1) % positions.Count; 1267 | int index4 = (i + 2) % positions.Count; 1268 | Vector2 a = new Vector2(positions[index1].X, positions[index1].Y); 1269 | Vector2 b = new Vector2(positions[index2].X, positions[index2].Y); 1270 | Vector2 c = new Vector2(positions[index3].X, positions[index3].Y); 1271 | Vector2 d = new Vector2(positions[index4].X, positions[index4].Y); 1272 | 1273 | Vector2 abNormal = Vector2.Normalize(Shape.GetNormal2D(a, b)); 1274 | Vector2 bcNormal = Vector2.Normalize(Shape.GetNormal2D(b, c)); 1275 | Vector2 vertexNormalabc = Vector2.Normalize((abNormal + bcNormal)) * pathWidth; 1276 | Vector2 cdNormal = Vector2.Normalize(Shape.GetNormal2D(c, d)); 1277 | Vector2 vertexNormalbcd = Vector2.Normalize((bcNormal + cdNormal)) * pathWidth; 1278 | 1279 | Vector2 point = new Vector2(positions[index2].X, positions[index2].Y) + vertexNormalabc; 1280 | Vector2 point2 = new Vector2(positions[index3].X, positions[index3].Y) + vertexNormalbcd; 1281 | if (!points.Contains(point)) 1282 | points.Add(point); 1283 | //if (!points.Contains(point2)) 1284 | // points.Add(point2); 1285 | //points.Add(c); 1286 | //points.Add(b); 1287 | } 1288 | 1289 | positions.Reverse(); 1290 | } 1291 | 1292 | shapes.Add(new Polygon() 1293 | { 1294 | Texture = Textures.SKYBOX, 1295 | Position = new Vector3(0, 0, 512), 1296 | Data = new PolygonShapeData() 1297 | { 1298 | Depth = 32, 1299 | Scalar = 1, 1300 | PolygonPoints = new List(points) 1301 | } 1302 | }); 1303 | Polygon floor = new Polygon() 1304 | { 1305 | Texture = Textures.DUST_CINDERBLOCK_CHECKERED01, 1306 | Position = new Vector3(0, 0, -96), 1307 | Data = new PolygonShapeData() 1308 | { 1309 | Depth = 32, 1310 | Scalar = 1, 1311 | PolygonPoints = new List(points) 1312 | } 1313 | }; 1314 | shapes.Add(floor); 1315 | shapes.Add(new Polygon() 1316 | { 1317 | Texture = Textures.TRIGGER, 1318 | BlockEntityID = EntityTemplates.BlockEntityID++, 1319 | EntityType = EntityTemplates.BlockEntityType.trigger_hurt, 1320 | EntitySettings = new List() 1321 | { 1322 | "damage", "200", 1323 | "damagecap", "200", 1324 | "damagemodel", "0", 1325 | "damagetype", "0", 1326 | "nodmgforce", "0", 1327 | "origin", "", 1328 | "spawnflags", "4097", 1329 | "StartDisabled", "0" 1330 | }, 1331 | Position = new Vector3(0, 0, -96), 1332 | Data = new PolygonShapeData() 1333 | { 1334 | Depth = 64, 1335 | Scalar = 1, 1336 | PolygonPoints = new List(points) 1337 | } 1338 | }); 1339 | 1340 | shapes.AddRange(WallGenerator.CreateWalls(floor, new WallData() 1341 | { 1342 | Texture = Textures.DEV_MEASUREGENERIC01B, 1343 | Height = 1024, 1344 | Thickness = 64, 1345 | facesIndicesToSkip = new List() 1346 | })); 1347 | 1348 | entities.Add(EntityTemplates.InfoPlayerTerrorist( 1349 | origin: new Vector3(positions[1].X, positions[1].Y, blockSize * 3 + 4)) 1350 | ); 1351 | 1352 | return shapes; 1353 | } 1354 | } 1355 | 1356 | public abstract class GenerationMethod 1357 | { 1358 | public abstract List GetBrushes(out List entities); 1359 | } 1360 | } 1361 | -------------------------------------------------------------------------------- /VMFGeneration/Generator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Numerics; 7 | using VMFGenerator; 8 | 9 | namespace VMFGenerator 10 | { 11 | public class Generator 12 | { 13 | #region Constants 14 | private const string versionInfoConstant = 15 | @"versioninfo 16 | { 17 | ""editorversion"" ""400"" 18 | ""editorbuild"" ""8456"" 19 | ""mapversion"" ""1"" 20 | ""formatversion"" ""100"" 21 | ""prefab"" ""0"" 22 | }"; 23 | 24 | private const string viewSettingsConstant = 25 | @"viewsettings 26 | { 27 | ""bSnapToGrid"" ""1"" 28 | ""bShowGrid"" ""1"" 29 | ""bShowLogicalGrid"" ""0"" 30 | ""nGridSpacing"" ""64"" 31 | ""bShow3DGrid"" ""0"" 32 | }"; 33 | 34 | private const string worldSetupConstant = 35 | @" ""id"" ""1"" 36 | ""mapversion"" ""797"" 37 | ""classname"" ""worldspawn"" 38 | ""comment"" ""Created with 7ark's VMF Generator, found at https://github.com/7ark/CSGO-VMF-Generator"" 39 | ""detailmaterial"" ""detail/detailsprites"" 40 | ""detailvbsp"" ""detail.vbsp"" 41 | ""maxpropscreenwidth"" ""-1"" 42 | ""skyname"" ""sky_cs15_daylight02_hdr"" 43 | "; 44 | 45 | private const string displacementFaceConstant = @" dispinfo 46 | { 47 | ""power"" ""3"" 48 | ""startposition"" ""[-512 0 16]"" 49 | ""flags"" ""0"" 50 | ""elevation"" ""0"" 51 | ""subdiv"" ""0"" 52 | normals 53 | { 54 | ""row0"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 55 | ""row1"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 56 | ""row2"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 57 | ""row3"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 58 | ""row4"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 59 | ""row5"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 60 | ""row6"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 61 | ""row7"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 62 | ""row8"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 63 | } 64 | distances 65 | { 66 | ""row0"" ""0 0 0 0 0 0 0 0 0"" 67 | ""row1"" ""0 0 0 0 0 0 0 0 0"" 68 | ""row2"" ""0 0 0 0 0 0 0 0 0"" 69 | ""row3"" ""0 0 0 0 0 0 0 0 0"" 70 | ""row4"" ""0 0 0 0 0 0 0 0 0"" 71 | ""row5"" ""0 0 0 0 0 0 0 0 0"" 72 | ""row6"" ""0 0 0 0 0 0 0 0 0"" 73 | ""row7"" ""0 0 0 0 0 0 0 0 0"" 74 | ""row8"" ""0 0 0 0 0 0 0 0 0"" 75 | } 76 | offsets 77 | { 78 | ""row0"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 79 | ""row1"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 80 | ""row2"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 81 | ""row3"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 82 | ""row4"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 83 | ""row5"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 84 | ""row6"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 85 | ""row7"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 86 | ""row8"" ""0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"" 87 | } 88 | offset_normals 89 | { 90 | ""row0"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 91 | ""row1"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 92 | ""row2"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 93 | ""row3"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 94 | ""row4"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 95 | ""row5"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 96 | ""row6"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 97 | ""row7"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 98 | ""row8"" ""0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1"" 99 | } 100 | alphas 101 | { 102 | ""row0"" ""0 0 0 0 0 0 0 0 0"" 103 | ""row1"" ""0 0 0 0 0 0 0 0 0"" 104 | ""row2"" ""0 0 0 0 0 0 0 0 0"" 105 | ""row3"" ""0 0 0 0 0 0 0 0 0"" 106 | ""row4"" ""0 0 0 0 0 0 0 0 0"" 107 | ""row5"" ""0 0 0 0 0 0 0 0 0"" 108 | ""row6"" ""0 0 0 0 0 0 0 0 0"" 109 | ""row7"" ""0 0 0 0 0 0 0 0 0"" 110 | ""row8"" ""0 0 0 0 0 0 0 0 0"" 111 | } 112 | triangle_tags 113 | { 114 | ""row0"" ""9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9"" 115 | ""row1"" ""9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9"" 116 | ""row2"" ""9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9"" 117 | ""row3"" ""9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9"" 118 | ""row4"" ""9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9"" 119 | ""row5"" ""9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9"" 120 | ""row6"" ""9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9"" 121 | ""row7"" ""9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9"" 122 | } 123 | allowed_verts 124 | { 125 | ""10"" ""-1 -1 -1 -1 -1 -1 -1 -1 -1 -1"" 126 | } 127 | }"; 128 | #endregion 129 | 130 | private NumberFormatInfo nfi = new NumberFormatInfo(); 131 | public string Generate(string path) 132 | { 133 | nfi.NumberDecimalSeparator = "."; 134 | 135 | string uniqueVMF = string.Empty; 136 | 137 | List shapes = new List(); 138 | List entities = new List(); 139 | GenerateData(out shapes, out entities); 140 | 141 | List visgroups = new List(); 142 | 143 | for (int i = 0; i < shapes.Count; i++) 144 | { 145 | if(!visgroups.Contains(shapes[i].Visgroup)) 146 | { 147 | visgroups.Add(shapes[i].Visgroup); 148 | } 149 | } 150 | 151 | Random rand = new Random(); 152 | for (int i = 0; i < visgroups.Count; i++) 153 | { 154 | Visgroups.VisgroupNameToID.Add(visgroups[i], i); 155 | Visgroups.VisgroupNameToColor.Add(visgroups[i], Color.FromArgb(rand.Next(0, 255), rand.Next(0, 255), rand.Next(0, 255))); 156 | } 157 | 158 | List brushes = new List(); 159 | Dictionary>> blockEntities = new Dictionary>>(); 160 | 161 | //Seperate the brushes from the func details 162 | for (int i = 0; i < shapes.Count; i++) 163 | { 164 | if (shapes[i].BlockEntityID == -1) 165 | { 166 | brushes.Add(shapes[i]); 167 | } 168 | else 169 | { 170 | if (!blockEntities.ContainsKey(shapes[i].BlockEntityID)) 171 | { 172 | string values = shapes[i].EntityType.ToValue(); 173 | 174 | if(shapes[i].EntitySettings.Count > 0) 175 | { 176 | values += "\"" + Environment.NewLine; 177 | } 178 | 179 | for (int j = 0; j < shapes[i].EntitySettings.Count; j += 2) 180 | { 181 | values += "\t\"" + shapes[i].EntitySettings[j] + "\" \"" + shapes[i].EntitySettings[j + 1] + (j + 1 == shapes[i].EntitySettings.Count - 1 ? string.Empty : "\"" + Environment.NewLine); 182 | } 183 | blockEntities.Add(shapes[i].BlockEntityID, new Tuple>(values, new List())); 184 | } 185 | 186 | blockEntities[shapes[i].BlockEntityID].Item2.Add(shapes[i]); 187 | } 188 | } 189 | 190 | uniqueVMF += versionInfoConstant + Environment.NewLine; 191 | uniqueVMF += "visgroups" + Environment.NewLine; 192 | uniqueVMF += "{" + Environment.NewLine; 193 | for (int i = 0; i < visgroups.Count; i++) 194 | { 195 | uniqueVMF += "\tvisgroup" + Environment.NewLine; 196 | uniqueVMF += "\t{" + Environment.NewLine; 197 | uniqueVMF += "\t\t\"name\" \"" + visgroups[i] + "\"" + Environment.NewLine; 198 | uniqueVMF += "\t\t\"visgroupid\" \"" + Visgroups.VisgroupNameToID[visgroups[i]] + "\"" + Environment.NewLine; 199 | Color col = Visgroups.VisgroupNameToColor[visgroups[i]]; 200 | uniqueVMF += "\t\t\"color\" \"" + col.R.ToString(nfi) + " " + col.G.ToString(nfi) + " " + col.B.ToString(nfi) + "\"" + Environment.NewLine; 201 | uniqueVMF += "\t}" + Environment.NewLine; 202 | } 203 | uniqueVMF += "}" + Environment.NewLine; 204 | uniqueVMF += viewSettingsConstant + Environment.NewLine; 205 | uniqueVMF += "world" + Environment.NewLine; 206 | uniqueVMF += "{" + Environment.NewLine; 207 | uniqueVMF += worldSetupConstant + Environment.NewLine; 208 | 209 | WriteShapes(ref uniqueVMF, brushes, false); 210 | 211 | uniqueVMF += "}" + Environment.NewLine; 212 | 213 | //Generic entities 214 | for (int i = 0; i < entities.Count; i++) 215 | { 216 | uniqueVMF += entities[i] + Environment.NewLine; 217 | } 218 | 219 | //Func details 220 | foreach (int id in blockEntities.Keys) 221 | { 222 | uniqueVMF += "entity" + Environment.NewLine; 223 | uniqueVMF += "{" + Environment.NewLine; 224 | uniqueVMF += "\t\"id\" \"" + (++EntityTemplates.LastID) + "\"" + Environment.NewLine; 225 | uniqueVMF += "\t\"classname\" \"" + blockEntities[id].Item1 + "\"" + Environment.NewLine; 226 | WriteShapes(ref uniqueVMF, blockEntities[id].Item2, true); 227 | uniqueVMF += "}" + Environment.NewLine; 228 | } 229 | Console.WriteLine("Generation Completed"); 230 | 231 | return uniqueVMF; 232 | } 233 | 234 | private void WriteShapes(ref string uniqueVMF, List shapes, bool writeVisgroupsAfter) 235 | { 236 | //Brushes 237 | for (int i = 0; i < shapes.Count; i++) 238 | { 239 | uniqueVMF += "\tsolid" + Environment.NewLine; 240 | uniqueVMF += "\t{" + Environment.NewLine; 241 | uniqueVMF += "\t\t\"id\" \"" + shapes[i].ID + "\"" + Environment.NewLine; 242 | SolidSide[] sides = shapes[i].Sides; 243 | for (int j = 0; j < sides.Length; j++) 244 | { 245 | uniqueVMF += "\t\tside" + Environment.NewLine; 246 | uniqueVMF += "\t\t{" + Environment.NewLine; 247 | uniqueVMF += "\t\t\t\"id\" \"" + sides[j].ID + "\"" + Environment.NewLine + 248 | "\t\t\t\"plane\" \"(" + sides[j].Plane[0].X.ToString(nfi) + " " + sides[j].Plane[0].Y.ToString(nfi) + " " + sides[j].Plane[0].Z.ToString(nfi) + ") (" + sides[j].Plane[1].X.ToString(nfi) + " " + sides[j].Plane[1].Y.ToString(nfi) + " " + sides[j].Plane[1].Z.ToString(nfi) + ") (" + sides[j].Plane[2].X.ToString(nfi) + " " + sides[j].Plane[2].Y.ToString(nfi) + " " + sides[j].Plane[2].Z.ToString(nfi) + ")\"" + Environment.NewLine + 249 | "\t\t\t\"material\" \"" + shapes[i].Texture + "\"" + Environment.NewLine + 250 | "\t\t\t\"uaxis\" \"[" + sides[j].UV[0].X.ToString(nfi) + " " + sides[j].UV[0].Y.ToString(nfi) + " " + sides[j].UV[0].Z.ToString(nfi) + " " + sides[j].UV[0].W.ToString(nfi) + "] 0.25\"" + Environment.NewLine + 251 | "\t\t\t\"vaxis\" \"[" + sides[j].UV[1].X.ToString(nfi) + " " + sides[j].UV[1].Y.ToString(nfi) + " " + sides[j].UV[1].Z.ToString(nfi) + " " + sides[j].UV[1].W.ToString(nfi) + "] 0.25\"" + Environment.NewLine + 252 | "\t\t\t\"rotation\" \"0\"" + Environment.NewLine + 253 | "\t\t\t\"lightmapscale\" \"16\"" + Environment.NewLine + 254 | "\t\t\t\"smoothing_groups\" \"0\"" + Environment.NewLine; 255 | if (sides[j].Displacement) 256 | { 257 | uniqueVMF += displacementFaceConstant; 258 | } 259 | uniqueVMF += "\t\t}" + Environment.NewLine; 260 | } 261 | if(!writeVisgroupsAfter) 262 | { 263 | WriteVisgroups(ref uniqueVMF, shapes[i].Visgroup); 264 | } 265 | uniqueVMF += "\t}" + Environment.NewLine; 266 | } 267 | 268 | if(shapes.Count > 0) 269 | { 270 | //Not too concerned about setting up multiple visgroups for now, can be done later if needed 271 | string visgroup = shapes[0].Visgroup; 272 | 273 | if (writeVisgroupsAfter) 274 | { 275 | WriteVisgroups(ref uniqueVMF, visgroup, 2); 276 | } 277 | } 278 | } 279 | 280 | private void WriteVisgroups(ref string uniqueVMF, string visgroup, int indents = 3) 281 | { 282 | uniqueVMF += new string('\t', indents - 1) + "editor" + Environment.NewLine; 283 | uniqueVMF += new string('\t', indents - 1) + "{" + Environment.NewLine; 284 | 285 | string visgroupName = visgroup; 286 | int visgroupId = -1; 287 | Color col = Color.White; 288 | 289 | if (visgroupName != string.Empty) 290 | { 291 | visgroupId = Visgroups.VisgroupNameToID[visgroupName]; 292 | col = Visgroups.VisgroupNameToColor[visgroupName]; 293 | } 294 | 295 | uniqueVMF += new string('\t', indents) + "\"color\" \"" + col.R + " " + col.G + " " + col.B + "\"" + Environment.NewLine; 296 | if (visgroupId != -1) 297 | { 298 | uniqueVMF += new string('\t', indents) + "\"visgroupid\" \"" + visgroupId + "\"" + Environment.NewLine; 299 | } 300 | uniqueVMF += new string('\t', indents) + "\"visgroupshown\" \"1\"" + Environment.NewLine; 301 | uniqueVMF += new string('\t', indents) + "\"visgroupautoshown\" \"1\"" + Environment.NewLine; 302 | uniqueVMF += new string('\t', indents - 1) + "}" + Environment.NewLine; 303 | } 304 | 305 | private void GenerateData(out List shapesResult, out List entities) 306 | { 307 | List generationMethods = new List(); 308 | entities = new List(); 309 | EasyInputLayer.GetInput(out generationMethods, out entities); 310 | 311 | //Start shape construction 312 | List shapes = new List(); 313 | for (int i = 0; i < generationMethods.Count; i++) 314 | { 315 | List ents; 316 | shapes.AddRange(generationMethods[i].GetBrushes(out ents)); 317 | if(ents.Count > 0) 318 | { 319 | entities.AddRange(ents); 320 | } 321 | } 322 | 323 | List finalShapeList = new List(); 324 | for (int i = shapes.Count - 1; i >= 0; i--) 325 | { 326 | if (shapes[i] is Polygon) 327 | { 328 | (shapes[i] as Polygon).CalculatePreGenerateData(); 329 | 330 | if (!IsShapeConvex(shapes[i] as Polygon)) 331 | { 332 | VMFDebug.CreateDebugImage("PreTriangulation" + (i), onDraw: (g) => 333 | { 334 | Pen greyPen = new Pen(Color.Gray, 3); 335 | Pen redPen = new Pen(Color.Red, 3); 336 | for (int j = 0; j < shapes.Count; j++) 337 | { 338 | if (shapes[j] is Polygon) 339 | { 340 | VMFDebug.AddShapeToGraphics(g, shapes[j] as Polygon, greyPen, positionAdjustment: new Vector2(250, 50), scale: 0.14f); 341 | } 342 | } 343 | VMFDebug.AddShapeToGraphics(g, shapes[i] as Polygon, redPen, positionAdjustment: new Vector2(250, 50), scale: 0.14f); 344 | }); 345 | 346 | 347 | Console.WriteLine("Found a concave shape. Attempting triangulation"); 348 | List replacements = new List(); 349 | List temp = ConvertToConvex(shapes[i] as Polygon); 350 | 351 | for (int j = 0; j < temp.Count; j++) 352 | { 353 | replacements.Add(temp[j] as Polygon); 354 | } 355 | 356 | Console.WriteLine("Single shape converted into " + replacements.Count + " new shapes"); 357 | 358 | List combinedShapes = new List(); 359 | 360 | for (int j = replacements.Count - 1; j >= 0; j--) 361 | { 362 | if (!IsShapeConvex(replacements[j] as Polygon)) 363 | { 364 | replacements[j] = RemoveRedundantPoints(replacements[j] as Polygon); 365 | Console.WriteLine("An invalid shape was found in the replacement batch. Attempting to fix..."); 366 | 367 | if (((PolygonShapeData)replacements[j].Data).PolygonPoints.Count < 3 || !IsShapeConvex(replacements[j] as Polygon)) 368 | { 369 | Console.WriteLine("Could not fix invalid shape. Deleting."); 370 | replacements.RemoveAt(j); 371 | } 372 | else 373 | { 374 | Console.WriteLine("Invalid shape fixed!"); 375 | } 376 | } 377 | } 378 | 379 | combinedShapes = replacements; 380 | CombineShapes(replacements, out combinedShapes); 381 | 382 | PolygonShapeData shapeData = shapes[i].Data as PolygonShapeData; 383 | for (int j = 0; j < combinedShapes.Count; j++) 384 | { 385 | finalShapeList.Add(combinedShapes[j]); 386 | } 387 | } 388 | else 389 | { 390 | finalShapeList.Add(shapes[i] as Polygon); 391 | } 392 | } 393 | else 394 | { 395 | finalShapeList.Add(shapes[i]); 396 | } 397 | } 398 | 399 | int currId = 0; 400 | for (int i = 0; i < finalShapeList.Count; i++) 401 | { 402 | finalShapeList[i].ID = i; 403 | finalShapeList[i].GenerateSides(currId); 404 | currId += finalShapeList[i].Sides.Length; 405 | } 406 | //End shape construction 407 | 408 | shapesResult = finalShapeList; 409 | } 410 | 411 | public static Polygon RemoveRedundantPoints(Polygon polygon, bool smallDetail = false) 412 | { 413 | List points = ((PolygonShapeData)polygon.Data).PolygonPoints; 414 | Vector2 one = points[0]; 415 | Vector2 two = points[1]; 416 | Vector2 three = points[2]; 417 | 418 | List indicesToRemove = new List(); 419 | 420 | for (int i = 0; i < points.Count + 2; i++) 421 | { 422 | int index = i % points.Count; 423 | int index2 = (i + 1) % points.Count; 424 | int index3 = (i + 2) % points.Count; 425 | 426 | one = points[index]; 427 | two = points[index2]; 428 | three = points[index3]; 429 | 430 | Vector2 oneThreeDirection = Vector2.Normalize(three - one); 431 | Vector2 oneTwoDirection = Vector2.Normalize(two - one); 432 | Vector2 twoThreeDirection = Vector2.Normalize(two - one); 433 | 434 | float dot1 = Vector2.Dot(oneThreeDirection, oneTwoDirection); 435 | 436 | bool same = (oneThreeDirection == oneTwoDirection && oneThreeDirection == twoThreeDirection) || (smallDetail && dot1 > 0.5f); 437 | if (same && !indicesToRemove.Contains(i)) 438 | { 439 | indicesToRemove.Add(index2); 440 | } 441 | } 442 | 443 | for (int i = points.Count; i >= 0; i--) 444 | { 445 | if (indicesToRemove.Contains(i)) 446 | { 447 | points.RemoveAt(i); 448 | } 449 | } 450 | 451 | Polygon final = new Polygon(polygon); 452 | ((PolygonShapeData)final.Data).PolygonPoints = points; 453 | return final; 454 | } 455 | 456 | 457 | public static void CombineShapes(List shapes, out List resultingShapes, int depth = 0) 458 | { 459 | List applicants = new List(); 460 | 461 | Polygon currentPolygonToEval = null; 462 | List options = new List(shapes); 463 | int save = 0; 464 | 465 | while (true) 466 | { 467 | if (currentPolygonToEval == null) 468 | { 469 | if (options.Count <= 0) 470 | { 471 | break; 472 | } 473 | currentPolygonToEval = options[0]; 474 | options.RemoveAt(0); 475 | } 476 | 477 | Polygon prev = null; 478 | Polygon found = null; 479 | bool anyCombination = false; 480 | for (int i = 0; i < options.Count; i++) 481 | { 482 | Polygon v = new Polygon(currentPolygonToEval); 483 | v = v.Combine(options[i]); 484 | 485 | if (v != null) 486 | { 487 | v = RemoveRedundantPoints(v); 488 | if (IsShapeConvex(v)) 489 | { 490 | found = options[i]; 491 | prev = new Polygon(currentPolygonToEval); 492 | currentPolygonToEval = v; 493 | options.RemoveAt(i); 494 | anyCombination = true; 495 | break; 496 | } 497 | } 498 | } 499 | 500 | VMFDebug.CreateDebugImage("CombiningStep" + (save++), onDraw: (g) => 501 | { 502 | Pen greyPen = new Pen(Color.Gray, 3); 503 | Pen blackPen = new Pen(Color.Black, 3); 504 | Pen redPen = new Pen(Color.Red, 3); 505 | Pen bluePen = new Pen(Color.Blue, 3); 506 | Pen greenPen = new Pen(Color.Green, 3); 507 | 508 | Vector2 posAdjust = new Vector2(0, 50); 509 | float scale = 0.14f; 510 | 511 | for (int i = 0; i < options.Count; i++) 512 | { 513 | VMFDebug.AddShapeToGraphics(g, options[i], greyPen, positionAdjustment: posAdjust, scale: scale); 514 | } 515 | 516 | for (int i = 0; i < applicants.Count; i++) 517 | { 518 | VMFDebug.AddShapeToGraphics(g, applicants[i], blackPen, positionAdjustment: posAdjust, scale: scale); 519 | } 520 | 521 | if (found != null) 522 | { 523 | VMFDebug.AddShapeToGraphics(g, found, bluePen, positionAdjustment: posAdjust, scale: scale); 524 | } 525 | 526 | if (prev != null) 527 | { 528 | VMFDebug.AddShapeToGraphics(g, prev, greenPen, positionAdjustment: posAdjust, scale: scale); 529 | } 530 | else 531 | { 532 | VMFDebug.AddShapeToGraphics(g, currentPolygonToEval, redPen, positionAdjustment: posAdjust, scale: scale); 533 | } 534 | }); 535 | 536 | if (!anyCombination) 537 | { 538 | applicants.Add(new Polygon(currentPolygonToEval)); 539 | currentPolygonToEval = null; 540 | } 541 | } 542 | 543 | List result = new List(); 544 | for (int i = 0; i < applicants.Count; i++) 545 | { 546 | bool canAdd = true; 547 | for (int j = 0; j < result.Count; j++) 548 | { 549 | PolygonShapeData rSD = result[j].Data as PolygonShapeData; 550 | PolygonShapeData aSD = applicants[i].Data as PolygonShapeData; 551 | if (rSD.PolygonPoints.SequenceEqual(aSD.PolygonPoints)) 552 | { 553 | canAdd = false; 554 | break; 555 | } 556 | } 557 | if (canAdd) 558 | { 559 | result.Add(applicants[i]); 560 | } 561 | } 562 | 563 | resultingShapes = result; 564 | } 565 | 566 | private List ConvertToConvex(Polygon shape) 567 | { 568 | PolygonShapeData shapeData = shape.Data as PolygonShapeData; 569 | 570 | List> result = PolygonTriangulator.Triangulate(shapeData.PolygonPoints); 571 | List newShapeList = new List(); 572 | 573 | for (int i = 0; i < result.Count; i++) 574 | { 575 | Polygon newShape = new Polygon(shape); 576 | List points = result[i]; 577 | //points.Reverse(); 578 | newShape.Data = new PolygonShapeData() 579 | { 580 | Depth = shapeData.Depth, 581 | Scalar = shapeData.Scalar, 582 | PolygonPoints = points 583 | }; 584 | newShapeList.Add(newShape); 585 | } 586 | 587 | return newShapeList; 588 | } 589 | 590 | public static bool IsShapeConvex(Polygon shape) 591 | { 592 | PolygonShapeData shapeData = shape.Data as PolygonShapeData; 593 | 594 | for (int i = 0; i < shapeData.PolygonPoints.Count; i++) 595 | { 596 | Vector2 first = shapeData.PolygonPoints[i]; 597 | int sI = i == shapeData.PolygonPoints.Count - 1 ? 0 : i + 1; 598 | Vector2 second = shapeData.PolygonPoints[sI]; 599 | for (int j = 0; j < shapeData.PolygonPoints.Count; j++) 600 | { 601 | Vector2 pointToCheck = shapeData.PolygonPoints[j]; 602 | if (pointToCheck != first && pointToCheck != second) 603 | { 604 | bool isConvex = ((second.X - first.X) * (pointToCheck.Y - first.Y) - (second.Y - first.Y) * (pointToCheck.X - first.X)) > 0; 605 | 606 | if (!isConvex) 607 | { 608 | return false; 609 | } 610 | } 611 | } 612 | } 613 | 614 | return true; 615 | } 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /VMFGeneration/LineEquation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Numerics; 5 | using System.Text; 6 | 7 | namespace VMFGenerator 8 | { 9 | public class LineEquation 10 | { 11 | public LineEquation(Vector2 start, Vector2 end) 12 | { 13 | Start = start; 14 | End = end; 15 | 16 | IsVertical = Math.Abs(End.X - start.X) < 0.00001f; 17 | M = (End.Y - Start.Y) / (End.X - Start.X); 18 | A = -M; 19 | B = 1; 20 | C = Start.Y - M * Start.X; 21 | } 22 | 23 | public bool IsVertical { get; private set; } 24 | 25 | public float M { get; private set; } 26 | 27 | public Vector2 Start { get; private set; } 28 | public Vector2 End { get; private set; } 29 | 30 | public float A { get; private set; } 31 | public float B { get; private set; } 32 | public float C { get; private set; } 33 | private struct Line 34 | { 35 | public Vector2 p1, p2; 36 | }; 37 | 38 | private bool OnLine(Line line1, Vector2 p) 39 | { //check whether p is on the line or not 40 | if (p.X <= MathF.Max(line1.p1.X, line1.p2.X) && p.X <= MathF.Min(line1.p1.X, line1.p2.X) && 41 | (p.Y <= MathF.Max(line1.p1.Y, line1.p2.Y) && p.Y <= MathF.Min(line1.p1.Y, line1.p2.Y))) 42 | { 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | 49 | private int Direction(Vector2 a, Vector2 b, Vector2 c) 50 | { 51 | int val = (int)((b.Y - a.Y) * (c.X - b.X) - (b.X - a.X) * (c.Y - b.Y)); 52 | if (val == 0) 53 | { 54 | return 0; 55 | } 56 | else if (val < 0) 57 | { 58 | //CCW direction 59 | return 2; 60 | } 61 | else 62 | { 63 | //CW direction 64 | return 1; 65 | } 66 | } 67 | 68 | private bool IsIntersect(Line line1, Line line2) 69 | { 70 | int dir1 = Direction(line1.p1, line1.p2, line2.p1); 71 | int dir2 = Direction(line1.p1, line1.p2, line2.p2); 72 | int dir3 = Direction(line2.p1, line2.p2, line1.p1); 73 | int dir4 = Direction(line2.p1, line2.p2, line1.p2); 74 | 75 | if (dir1 != dir2 && dir3 != dir4) 76 | return true; //Intersecting 77 | 78 | if (dir1 == 0 && OnLine(line1, line2.p1)) //When p2 of line2 are on the line1 79 | return true; 80 | 81 | if (dir2 == 0 && OnLine(line1, line2.p2)) //When p1 of line2 are on the line1 82 | return true; 83 | 84 | if (dir3 == 0 && OnLine(line2, line1.p1)) //When p2 of line1 are on the line2 85 | return true; 86 | 87 | if (dir4 == 0 && OnLine(line2, line1.p2)) //When p1 of line1 are on the line2 88 | return true; 89 | 90 | return false; 91 | } 92 | 93 | public bool Intersects(LineEquation otherLine) 94 | { 95 | return IsIntersect(new Line() { p1 = Start, p2 = End }, new Line() { p1 = otherLine.Start, p2 = otherLine.End }); 96 | } 97 | 98 | 99 | public bool IntersectsWithLine(LineEquation otherLine, out Vector2 intersectionPoint) 100 | { 101 | intersectionPoint = new Vector2(0, 0); 102 | 103 | if(!IsIntersect(new Line() { p1 = Start, p2 = End }, new Line() { p1 = otherLine.Start, p2 = otherLine.End })) 104 | { 105 | return false; 106 | } 107 | 108 | if (IsVertical && otherLine.IsVertical) 109 | { 110 | return false; 111 | } 112 | if (IsVertical || otherLine.IsVertical) 113 | { 114 | intersectionPoint = GetIntersectionPointIfOneIsVertical(otherLine, this); 115 | return true; 116 | } 117 | float delta = A * otherLine.B - otherLine.A * B; 118 | bool hasIntersection = Math.Abs(delta - 0) > 0.0001f; 119 | if (hasIntersection) 120 | { 121 | float x = (otherLine.B * C - B * otherLine.C) / delta; 122 | float y = (A * otherLine.C - otherLine.A * C) / delta; 123 | intersectionPoint = new Vector2(x, y); 124 | } 125 | return hasIntersection; 126 | } 127 | 128 | private static Vector2 GetIntersectionPointIfOneIsVertical(LineEquation line1, LineEquation line2) 129 | { 130 | LineEquation verticalLine = line2.IsVertical ? line2 : line1; 131 | LineEquation nonVerticalLine = line2.IsVertical ? line1 : line2; 132 | 133 | float y = (verticalLine.Start.X - nonVerticalLine.Start.X) * 134 | (nonVerticalLine.End.Y - nonVerticalLine.Start.Y) / 135 | ((nonVerticalLine.End.X - nonVerticalLine.Start.X)) + 136 | nonVerticalLine.Start.Y; 137 | float x = line1.IsVertical ? line1.Start.X : line2.Start.X; 138 | return new Vector2(x, y); 139 | } 140 | 141 | public override string ToString() 142 | { 143 | return "[" + Start + "], [" + End + "]"; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /VMFGeneration/Models.cs: -------------------------------------------------------------------------------- 1 | namespace VMFGenerator 2 | { 3 | public static class Models 4 | { 5 | public const string DustCrateStyle264x64x57 = "models/props/de_dust/hr_dust/dust_crates/dust_crate_style_02_64x64x57"; 6 | public const string DustCrateStyle264x64x64 = "models/props/de_dust/hr_dust/dust_crates/dust_crate_style_02_64x64x64"; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /VMFGeneration/PolygonTriangulator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Numerics; 5 | using System.Linq; 6 | using VMFGenerator; 7 | 8 | namespace VMFGenerator 9 | { 10 | public class PolygonTriangulator 11 | { 12 | /// 13 | /// My triangulation function. I used this https://www.gamedev.net/tutorials/programming/graphics/polygon-triangulation-r3334/ 14 | /// as a guide and it was incredibly helpful. I also made my own modifications for my own usecases and attempts at fixes. 15 | /// 16 | /// 17 | /// 18 | public static List> Triangulate(List Polygon) 19 | { 20 | //These are purely dummy values used for debug drawing 21 | int c2 = 0; 22 | int c = 0; 23 | 24 | List sharedValues = Polygon.GroupBy(x => x).Where(g => g.Count() > 1).Select(x => x.Key).ToList(); 25 | 26 | float amountToRaise = 0.1f; 27 | List higherIndices = new List(); 28 | 29 | Dictionary highestIndex = new Dictionary(); 30 | Dictionary currentHighest = new Dictionary(); 31 | List onesToReset = new List(); 32 | 33 | for (int i = 0; i < sharedValues.Count; i++) 34 | { 35 | highestIndex.Add(sharedValues[i], -1); 36 | currentHighest.Add(sharedValues[i], 0); 37 | } 38 | 39 | for (int i = 0; i < Polygon.Count; i++) 40 | { 41 | if (!sharedValues.Contains(Polygon[i])) 42 | { 43 | continue; 44 | } 45 | 46 | int indexA = (i - 1) % Polygon.Count; 47 | if (indexA < 0) 48 | { 49 | indexA = Polygon.Count - 1; 50 | } 51 | int indexB = (i) % Polygon.Count; 52 | int indexC = (i + 1) % Polygon.Count; 53 | 54 | Vector2 prev = Polygon[indexA]; 55 | Vector2 curr = Polygon[indexB]; 56 | Vector2 next = Polygon[indexC]; 57 | 58 | if ((prev.Y + next.Y) > currentHighest[Polygon[i]]) 59 | { 60 | currentHighest[Polygon[i]] = (prev.Y + next.Y); 61 | highestIndex[Polygon[i]] = i; 62 | } 63 | } 64 | 65 | //So this is all my attempt to bypass issues with connected polygons. When I have two edges that are right next to each other, and share points 66 | //I detect those, and one set I move *slightly* higher. That way the algorithm will treat them separately and doesnt get confused. 67 | //Then when its done all it need to do, I simply move them back down. It works okay enough? Some work arounds were needed but its working. 68 | foreach (Vector2 pos in highestIndex.Keys) 69 | { 70 | Polygon[highestIndex[pos]] = new Vector2(Polygon[highestIndex[pos]].X, Polygon[highestIndex[pos]].Y + amountToRaise); 71 | onesToReset.Add(Polygon[highestIndex[pos]]); 72 | } 73 | 74 | List remainingValues = new List(Polygon); 75 | List> result = new List>(); 76 | 77 | int index = -1; 78 | bool triangleMade = true; 79 | while (triangleMade) 80 | { 81 | triangleMade = false; 82 | for (int v = Polygon.Count + 2; v >= 0; v--) 83 | { 84 | index = v; 85 | 86 | int indexA = (index - 1) % Polygon.Count; 87 | if (indexA < 0) 88 | { 89 | indexA = Polygon.Count - 1; 90 | } 91 | int indexB = (index) % Polygon.Count; 92 | int indexC = (index + 1) % Polygon.Count; 93 | Vector2 prev = Polygon[indexA]; 94 | Vector2 curr = Polygon[indexB]; 95 | Vector2 next = Polygon[indexC]; 96 | 97 | VMFDebug.CreateDebugImage("TriangulationStepAttempt" + c2, onDraw: (g) => 98 | { 99 | float scale = 0.14f; 100 | Point positionAdjustment = new Point(250, 50); 101 | Pen whitePen = new Pen(Color.White, 3); 102 | Pen greyPen = new Pen(Color.Gray, 3); 103 | Pen blackPen = new Pen(Color.Black, 3); 104 | Pen redPen = new Pen(Color.Red, 3); 105 | Pen bluePen = new Pen(Color.Blue, 3); 106 | Pen greenPen = new Pen(Color.Green, 3); 107 | Pen otherPen = new Pen(Color.LightBlue, 3); 108 | 109 | for (int i = 0; i < Polygon.Count; i++) 110 | { 111 | int iN = (i + 1) % Polygon.Count; 112 | Point p1 = new Point((int)(Polygon[i].X * scale + positionAdjustment.X), (int)(Polygon[i].Y * scale + positionAdjustment.Y)); 113 | Point p2 = new Point((int)(Polygon[iN].X * scale + positionAdjustment.X), (int)(Polygon[iN].Y * scale + positionAdjustment.Y)); 114 | 115 | g.DrawLine(greyPen, p1, p2); 116 | } 117 | 118 | for (int i = 0; i < result.Count; i++) 119 | { 120 | for (int j = 0; j < result[i].Count; j++) 121 | { 122 | int iN = (j + 1) % result[i].Count; 123 | Point p1 = new Point((int)(result[i][j].X * scale + positionAdjustment.X), (int)(result[i][j].Y * scale + positionAdjustment.Y)); 124 | Point p2 = new Point((int)(result[i][iN].X * scale + positionAdjustment.X), (int)(result[i][iN].Y * scale + positionAdjustment.Y)); 125 | 126 | g.DrawLine(blackPen, p1, p2); 127 | } 128 | } 129 | 130 | Point prevP = new Point((int)(prev.X * scale + positionAdjustment.X), (int)(prev.Y * scale + positionAdjustment.Y)); 131 | Point currP = new Point((int)(curr.X * scale + positionAdjustment.X), (int)(curr.Y * scale + positionAdjustment.Y)); 132 | Point nextP = new Point((int)(next.X * scale + positionAdjustment.X), (int)(next.Y * scale + positionAdjustment.Y)); 133 | g.DrawLine(otherPen, prevP, currP); 134 | g.DrawLine(otherPen, currP, nextP); 135 | g.DrawLine(otherPen, nextP, prevP); 136 | 137 | g.DrawEllipse(redPen, prev.X * scale + positionAdjustment.X, prev.Y * scale + positionAdjustment.Y, 10, 10); 138 | g.DrawEllipse(bluePen, curr.X * scale + positionAdjustment.X, curr.Y * scale + positionAdjustment.Y, 10, 10); 139 | g.DrawEllipse(greenPen, next.X * scale + positionAdjustment.X, next.Y * scale + positionAdjustment.Y, 10, 10); 140 | 141 | }); 142 | c2++; 143 | 144 | //2D cross product or wedge product or whatever you wanna call it 145 | Vector2 v1 = next - curr; 146 | Vector2 v2 = prev - curr; 147 | float val = (v1.X * v2.Y) - (v1.Y * v2.X); 148 | 149 | if(val <= 0) 150 | { 151 | continue; 152 | } 153 | 154 | bool noGood = false; 155 | for (int i = 0; i < remainingValues.Count; i++) 156 | { 157 | Vector2 point = remainingValues[i]; 158 | 159 | Vector2 pointButShort = new Vector2(point.X, MathF.Round(point.Y - amountToRaise, 1)); 160 | if (sharedValues.Contains(pointButShort)) 161 | { 162 | point = pointButShort; 163 | } 164 | 165 | Vector2 currPoint = point; 166 | Vector2 altCurrPoint = new Vector2(currPoint.X, MathF.Round(currPoint.Y + amountToRaise, 1)); 167 | if (currPoint == prev || currPoint == curr || currPoint == next || 168 | altCurrPoint == prev || altCurrPoint == curr || altCurrPoint == next) 169 | { 170 | continue; 171 | } 172 | if (PointInTriangle(remainingValues[i], prev, curr, next)) 173 | { 174 | noGood = true; 175 | break; 176 | } 177 | } 178 | if (noGood) 179 | { 180 | continue; 181 | } 182 | 183 | result.Add(new List() 184 | { 185 | prev, 186 | curr, 187 | next 188 | }); 189 | Polygon.Remove(curr); 190 | 191 | triangleMade = true; 192 | 193 | if(Polygon.Count > 2) 194 | { 195 | VMFDebug.CreateDebugImage("TriangulationStep" + c, onDraw: (g) => 196 | { 197 | float scale = 0.13f; 198 | Point positionAdjustment = new Point(200, 100); 199 | Pen whitePen = new Pen(Color.White, 3); 200 | Pen greyPen = new Pen(Color.Gray, 3); 201 | Pen blackPen = new Pen(Color.Black, 3); 202 | Pen redPen = new Pen(Color.Red, 3); 203 | Pen bluePen = new Pen(Color.Blue, 3); 204 | Pen greenPen = new Pen(Color.Green, 3); 205 | 206 | for (int i = 0; i < Polygon.Count; i++) 207 | { 208 | int iN = (i + 1) % Polygon.Count; 209 | Point p1 = new Point((int)(Polygon[i].X * scale + positionAdjustment.X), (int)(Polygon[i].Y * scale + positionAdjustment.Y)); 210 | Point p2 = new Point((int)(Polygon[iN].X * scale + positionAdjustment.X), (int)(Polygon[iN].Y * scale + positionAdjustment.Y)); 211 | 212 | g.DrawLine(greyPen, p1, p2); 213 | } 214 | 215 | for (int i = 0; i < result.Count; i++) 216 | { 217 | for (int j = 0; j < result[i].Count; j++) 218 | { 219 | int iN = (j + 1) % result[i].Count; 220 | Point p1 = new Point((int)(result[i][j].X * scale + positionAdjustment.X), (int)(result[i][j].Y * scale + positionAdjustment.Y)); 221 | Point p2 = new Point((int)(result[i][iN].X * scale + positionAdjustment.X), (int)(result[i][iN].Y * scale + positionAdjustment.Y)); 222 | 223 | g.DrawLine(blackPen, p1, p2); 224 | } 225 | } 226 | 227 | g.DrawEllipse(redPen, prev.X * scale + positionAdjustment.X, prev.Y * scale + positionAdjustment.Y, 10, 10); 228 | g.DrawEllipse(bluePen, curr.X * scale + positionAdjustment.X, curr.Y * scale + positionAdjustment.Y, 10, 10); 229 | g.DrawEllipse(greenPen, next.X * scale + positionAdjustment.X, next.Y * scale + positionAdjustment.Y, 10, 10); 230 | 231 | Font font = new Font(FontFamily.GenericSansSerif, 20, SystemFonts.DefaultFont.Style); 232 | for (int i = 0; i < remainingValues.Count; i++) 233 | { 234 | Point p1 = new Point((int)(remainingValues[i].X * scale + positionAdjustment.X), (int)(remainingValues[i].Y * scale + positionAdjustment.Y)); 235 | bool contains = false; 236 | for (int j = 0; j < result.Count; j++) 237 | { 238 | contains = result[j].Contains(new Vector2(remainingValues[i].X, remainingValues[i].Y)); 239 | if(contains) 240 | { 241 | break; 242 | } 243 | } 244 | if (remainingValues[i] != prev && remainingValues[i] != curr && remainingValues[i] != next && !contains) 245 | { 246 | g.DrawString(i.ToString(), font, Brushes.DarkGreen, new PointF(p1.X, p1.Y)); 247 | } 248 | } 249 | for (int i = 0; i < remainingValues.Count; i++) 250 | { 251 | Point p1 = new Point((int)(remainingValues[i].X * scale + positionAdjustment.X), (int)(remainingValues[i].Y * scale + positionAdjustment.Y)); 252 | if (remainingValues[i] == prev || remainingValues[i] == curr || remainingValues[i] == next) 253 | { 254 | g.DrawString(i.ToString(), font, Brushes.DarkViolet, new PointF(p1.X + 40, p1.Y)); 255 | } 256 | } 257 | 258 | }); 259 | } 260 | 261 | c++; 262 | } 263 | } 264 | 265 | //Resetting the shared points values 266 | for (int i = 0; i < result.Count; i++) 267 | { 268 | for (int j = 0; j < result[i].Count; j++) 269 | { 270 | if (onesToReset.Contains(result[i][j])) 271 | { 272 | result[i][j] = new Vector2(result[i][j].X, result[i][j].Y - amountToRaise); 273 | } 274 | } 275 | } 276 | 277 | for (int r = 0; r < result.Count; r++) 278 | { 279 | VMFDebug.CreateDebugImage("FinalTriangulation" + r, onDraw: (g) => 280 | { 281 | float scale = 0.12f; 282 | Pen blackPen = new Pen(Color.Black, 3); 283 | Pen greyPen = new Pen(Color.Gray, 3); 284 | 285 | for (int i = 0; i < result.Count; i++) 286 | { 287 | for (int j = 0; j < result[i].Count; j++) 288 | { 289 | int iN = (j + 1) % result[i].Count; 290 | Point p1 = new Point((int)(result[i][j].X * scale), (int)(result[i][j].Y * scale)); 291 | Point p2 = new Point((int)(result[i][iN].X * scale), (int)(result[i][iN].Y * scale)); 292 | 293 | g.DrawLine(greyPen, p1, p2); 294 | } 295 | } 296 | 297 | for (int i = 0; i < result[r].Count; i++) 298 | { 299 | int iN = (i + 1) % result[r].Count; 300 | Point p1 = new Point((int)(result[r][i].X * scale), (int)(result[r][i].Y * scale)); 301 | Point p2 = new Point((int)(result[r][iN].X * scale), (int)(result[r][iN].Y * scale)); 302 | 303 | g.DrawLine(blackPen, p1, p2); 304 | } 305 | }); 306 | } 307 | 308 | 309 | return result; 310 | } 311 | 312 | private static float Sign(Vector2 p1, Vector2 p2, Vector2 p3) 313 | { 314 | return (p1.X - p3.X) * (p2.Y - p3.Y) - (p2.X - p3.X) * (p1.Y - p3.Y); 315 | } 316 | 317 | private static bool PointInTriangle(Vector2 pt, Vector2 v1, Vector2 v2, Vector2 v3) 318 | { 319 | float d1, d2, d3; 320 | bool has_neg, has_pos; 321 | 322 | d1 = Sign(pt, v1, v2); 323 | d2 = Sign(pt, v2, v3); 324 | d3 = Sign(pt, v3, v1); 325 | 326 | has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); 327 | has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); 328 | 329 | return !(has_neg && has_pos); 330 | } 331 | } 332 | } -------------------------------------------------------------------------------- /VMFGeneration/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace VMFGenerator 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | string directory = @"C:\Program Files (x86)\Steam\steamapps\common\Counter-Strike Global Offensive\sdk_content\maps"; 11 | if(args.Length > 0) 12 | { 13 | if(Directory.Exists(args[0])) 14 | { 15 | directory = args[0]; 16 | } 17 | } 18 | string vmfPath = directory + @"\GeneratedMap.vmf"; 19 | 20 | Generator gen = new Generator(); 21 | File.WriteAllText(vmfPath, gen.Generate(vmfPath)); 22 | 23 | //Thanks to Pikapi for reminding me to add this 24 | Console.WriteLine("Press any key to continue"); 25 | Console.ReadKey(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /VMFGeneration/Shape.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Numerics; 6 | using VMFGenerator; 7 | 8 | namespace VMFGenerator 9 | { 10 | #region Shapes 11 | public enum Direction { North, South, West, East } 12 | public enum SideFacing { Top, Bottom, Back, Front, Left, Right } 13 | public struct SolidSide 14 | { 15 | public int ID; 16 | public Vector3[] Plane; 17 | public Vector4[] UV; 18 | public bool Displacement; 19 | } 20 | 21 | public class PolygonShapeData : ShapeData 22 | { 23 | public int Depth; 24 | public int Scalar = 1; 25 | public List PolygonPoints; 26 | 27 | public PolygonShapeData() { } 28 | 29 | public PolygonShapeData(PolygonShapeData self) 30 | { 31 | Depth = self.Depth; 32 | Scalar = self.Scalar; 33 | PolygonPoints = new List(); 34 | for (int i = 0; i < self.PolygonPoints.Count; i++) 35 | { 36 | PolygonPoints.Add(self.PolygonPoints[i]); 37 | } 38 | } 39 | } 40 | 41 | /// 42 | /// Polygon class, one side is composed of a set of points representing a 2D polygon. This shape can then be rotated or otherwise to create slopes, etc. 43 | /// 44 | public class Polygon : Shape 45 | { 46 | public Polygon() { } 47 | public Polygon(Polygon self) : base(self) 48 | { 49 | ID = self.ID; 50 | Position = self.Position; 51 | if (self.Sides != null) 52 | { 53 | Sides = new SolidSide[self.Sides.Length]; 54 | } 55 | if (Sides != null) 56 | { 57 | for (int i = 0; i < self.Sides.Length; i++) 58 | { 59 | Sides[i] = self.Sides[i]; 60 | } 61 | } 62 | Data = new PolygonShapeData(self.Data as PolygonShapeData); 63 | } 64 | 65 | public Polygon Combine(Polygon other) 66 | { 67 | PolygonShapeData myShapeData = Data as PolygonShapeData; 68 | PolygonShapeData shapeData = other.Data as PolygonShapeData; 69 | 70 | List sharedPoints = new List(); 71 | int lowestIndexMe = -1; 72 | int lowestIndexOther = -1; 73 | Vector2 lowestMe = new Vector2(99999999, 99999999); 74 | Vector2 lowestOther = new Vector2(99999999, 99999999); 75 | Vector2 lowest = new Vector2(99999999, 99999999); 76 | 77 | for (int i = 0; i < myShapeData.PolygonPoints.Count; i++) 78 | { 79 | if (shapeData.PolygonPoints.Contains(myShapeData.PolygonPoints[i])) 80 | { 81 | sharedPoints.Add(myShapeData.PolygonPoints[i]); 82 | } 83 | 84 | if (myShapeData.PolygonPoints[i].X < lowest.X || (myShapeData.PolygonPoints[i].X == lowest.X && myShapeData.PolygonPoints[i].Y < lowest.Y)) 85 | { 86 | lowest = myShapeData.PolygonPoints[i]; 87 | } 88 | 89 | if (myShapeData.PolygonPoints[i].X < lowestMe.X || (myShapeData.PolygonPoints[i].X == lowestMe.X && myShapeData.PolygonPoints[i].Y < lowestMe.Y)) 90 | { 91 | lowestMe = myShapeData.PolygonPoints[i]; 92 | lowestIndexMe = i; 93 | } 94 | } 95 | 96 | if (sharedPoints.Count <= 1) 97 | { 98 | return null; 99 | } 100 | 101 | for (int i = 0; i < shapeData.PolygonPoints.Count; i++) 102 | { 103 | if (shapeData.PolygonPoints[i].X < lowest.X || (shapeData.PolygonPoints[i].X == lowest.X && shapeData.PolygonPoints[i].Y < lowest.Y)) 104 | { 105 | lowest = shapeData.PolygonPoints[i]; 106 | } 107 | 108 | if (shapeData.PolygonPoints[i].X < lowestOther.X || (shapeData.PolygonPoints[i].X == lowestOther.X && shapeData.PolygonPoints[i].Y < lowestOther.Y)) 109 | { 110 | lowestOther = shapeData.PolygonPoints[i]; 111 | lowestIndexOther = i; 112 | } 113 | } 114 | 115 | bool startOnMe = false; 116 | int currIndex = -1; 117 | if (lowestMe != lowestOther) 118 | { 119 | if (lowestMe.X == lowestOther.X) 120 | { 121 | startOnMe = lowestMe.Y < lowestOther.Y; 122 | } 123 | else 124 | { 125 | startOnMe = lowestMe.X < lowestOther.X; 126 | } 127 | } 128 | else 129 | { 130 | int lowestMeNext = lowestIndexMe + 1; 131 | if (lowestMeNext >= myShapeData.PolygonPoints.Count) 132 | { 133 | lowestMeNext = 0; 134 | } 135 | int lowestOtherNext = lowestIndexOther + 1; 136 | if (lowestOtherNext >= shapeData.PolygonPoints.Count) 137 | { 138 | lowestOtherNext = 0; 139 | } 140 | Vector2 mePointOption = myShapeData.PolygonPoints[lowestMeNext]; 141 | Vector2 otherPointOption = shapeData.PolygonPoints[lowestOtherNext]; 142 | 143 | float constantAngle = MathF.Atan2(0.5f, 1) * 180 / MathF.PI; 144 | Vector2 meDifference = Vector2.Normalize(mePointOption - lowest); 145 | float meAngle = MathF.Atan2(meDifference.X, meDifference.Y) * 180 / MathF.PI; 146 | Vector2 otherDifference = Vector2.Normalize(otherPointOption - lowest); 147 | float otherAngle = MathF.Atan2(otherDifference.X, otherDifference.Y) * 180 / MathF.PI; 148 | 149 | startOnMe = meAngle > otherAngle; 150 | } 151 | 152 | currIndex = startOnMe ? lowestIndexMe : lowestIndexOther; 153 | 154 | List startingList = startOnMe ? myShapeData.PolygonPoints : shapeData.PolygonPoints; 155 | Vector2 firstPosition = startingList[currIndex]; 156 | List newShapeList = new List(); 157 | 158 | int safety = 0; 159 | while (true) 160 | { 161 | Vector2 pos = startingList[currIndex]; 162 | 163 | if (pos == firstPosition && newShapeList.Count > 0) 164 | { 165 | break; 166 | } 167 | 168 | newShapeList.Add(pos); 169 | 170 | if (sharedPoints.Contains(pos) && newShapeList.Count > 1) 171 | { 172 | startingList = startOnMe ? shapeData.PolygonPoints : myShapeData.PolygonPoints; 173 | 174 | for (int i = 0; i < startingList.Count; i++) 175 | { 176 | if (startingList[i] == pos) 177 | { 178 | currIndex = i; 179 | break; 180 | } 181 | } 182 | 183 | startOnMe = !startOnMe; 184 | } 185 | 186 | currIndex++; 187 | if (currIndex >= startingList.Count) 188 | { 189 | currIndex = 0; 190 | } 191 | 192 | safety++; 193 | if (safety > 1000) 194 | { 195 | break; 196 | } 197 | } 198 | 199 | PolygonShapeData finalData = new PolygonShapeData(myShapeData); 200 | 201 | finalData.PolygonPoints = newShapeList; 202 | 203 | Polygon topVertex = new Polygon(other) 204 | { 205 | ID = other.ID, 206 | Position = other.Position, 207 | Sides = other.Sides, 208 | Data = finalData 209 | }; 210 | 211 | return topVertex; 212 | } 213 | 214 | public Vector2 GetCenter() 215 | { 216 | PolygonShapeData shapeData = Data as PolygonShapeData; 217 | Vector2 center = new Vector2(0, 0); 218 | for (int i = 0; i < shapeData.PolygonPoints.Count; i++) 219 | { 220 | center += shapeData.PolygonPoints[i] * shapeData.Scalar; 221 | } 222 | 223 | center /= shapeData.PolygonPoints.Count; 224 | 225 | return center; 226 | } 227 | 228 | public override void GenerateSides(int startingID) 229 | { 230 | PolygonShapeData shapeData = Data as PolygonShapeData; 231 | 232 | //For now we only do cubes 233 | Sides = new SolidSide[shapeData.PolygonPoints.Count + 2]; 234 | 235 | for (int i = 0; i < Sides.Length; i++) 236 | { 237 | Sides[i].ID = startingID + i; 238 | } 239 | 240 | //Top 241 | Sides[0].Plane = new Vector3[] 242 | { 243 | Position + new Vector3(-shapeData.Scalar, -shapeData.Scalar, (shapeData.Depth * 0.5f)), 244 | Position + new Vector3(-shapeData.Scalar, shapeData.Scalar, (shapeData.Depth * 0.5f)), 245 | Position + new Vector3( shapeData.Scalar, shapeData.Scalar, (shapeData.Depth * 0.5f)) 246 | }; 247 | Sides[0].UV = new Vector4[] 248 | { 249 | new Vector4(1, 0, 0, 0), 250 | new Vector4(0, 1, 0, 0) 251 | }; 252 | 253 | //Bottom 254 | Sides[1].Plane = new Vector3[] 255 | { 256 | Position + new Vector3( shapeData.Scalar, -shapeData.Scalar, -(shapeData.Depth * 0.5f)), 257 | Position + new Vector3( shapeData.Scalar, shapeData.Scalar, -(shapeData.Depth * 0.5f)), 258 | Position + new Vector3(-shapeData.Scalar, shapeData.Scalar, -(shapeData.Depth * 0.5f)) 259 | }; 260 | Sides[1].UV = new Vector4[] 261 | { 262 | new Vector4(1, 0, 0, 0), 263 | new Vector4(0, 1, 0, 0) 264 | }; 265 | 266 | for (int i = 0; i < shapeData.PolygonPoints.Count; i++) 267 | { 268 | int sidesIndex = 2 + i; 269 | Vector2 first = shapeData.PolygonPoints[i]; 270 | int sI = i == shapeData.PolygonPoints.Count - 1 ? 0 : i + 1; 271 | Vector2 second = shapeData.PolygonPoints[sI]; 272 | 273 | float angle = MathF.Atan2(second.Y - first.Y, second.X - first.X) * (180 / MathF.PI); 274 | if (angle < 0) 275 | { 276 | angle += 360; 277 | } 278 | 279 | Sides[sidesIndex].Plane = new Vector3[] 280 | { 281 | Position + new Vector3(first.X, first.Y, -(shapeData.Depth * 0.5f)), 282 | Position + new Vector3(first.X, first.Y, (shapeData.Depth * 0.5f)), 283 | Position + new Vector3(second.X, second.Y, (shapeData.Depth * 0.5f)) 284 | }; 285 | 286 | bool useFirstMethod = (angle > 0 && angle <= 90) || (angle > 180 && angle <= 270); 287 | if (useFirstMethod) 288 | { 289 | Sides[sidesIndex].UV = new Vector4[] 290 | { 291 | new Vector4(0, 1, 0, 0), 292 | new Vector4(0, 0, -1, 0) 293 | }; 294 | } 295 | else 296 | { 297 | Sides[sidesIndex].UV = new Vector4[] 298 | { 299 | new Vector4(1, 0, 0, 0), 300 | new Vector4(0, 0, -1, 0) 301 | }; 302 | 303 | } 304 | } 305 | 306 | base.GenerateSides(startingID); 307 | } 308 | 309 | public void CalculatePreGenerateData() 310 | { 311 | PolygonShapeData shapeData = Data as PolygonShapeData; 312 | 313 | for (int i = 0; i < shapeData.PolygonPoints.Count; i++) 314 | { 315 | int pointX = (int)MathF.Round(shapeData.PolygonPoints[i].X * shapeData.Scalar, 0, MidpointRounding.ToEven); 316 | int pointY = (int)MathF.Round(shapeData.PolygonPoints[i].Y * shapeData.Scalar, 0, MidpointRounding.ToEven); 317 | 318 | shapeData.PolygonPoints[i] = new Vector2(pointX, pointY); 319 | } 320 | } 321 | } 322 | 323 | 324 | public class CubeShapeData : ShapeData 325 | { 326 | public Vector3 Size; 327 | 328 | public CubeShapeData() { } 329 | public CubeShapeData(CubeShapeData self) : base(self) 330 | { 331 | Size = self.Size; 332 | } 333 | } 334 | 335 | public class Cube : Shape 336 | { 337 | public Cube() { } 338 | public Cube(Cube self) : base(self) 339 | { 340 | Data = new CubeShapeData((CubeShapeData)self.Data); 341 | } 342 | public override void GenerateSides(int startingID) 343 | { 344 | CubeShapeData shapeData = Data as CubeShapeData; 345 | 346 | //For now we only do cubes 347 | Sides = new SolidSide[6]; 348 | 349 | for (int i = 0; i < Sides.Length; i++) 350 | { 351 | Sides[i].ID = startingID + i; 352 | } 353 | 354 | //Top 355 | Sides[0].Plane = new Vector3[] 356 | { 357 | Position + new Vector3(-(shapeData.Size.X * 0.5f), -(shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f), 358 | Position + new Vector3(-(shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f), 359 | Position + new Vector3((shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f) 360 | }; 361 | Sides[0].UV = new Vector4[] 362 | { 363 | new Vector4(1, 0, 0, 0), 364 | new Vector4(0, 1, 0, 0) 365 | }; 366 | 367 | //Bottom 368 | Sides[1].Plane = new Vector3[] 369 | { 370 | Position + new Vector3((shapeData.Size.X * 0.5f), -(shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f), 371 | Position + new Vector3((shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f), 372 | Position + new Vector3(-(shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f) 373 | }; 374 | Sides[1].UV = new Vector4[] 375 | { 376 | new Vector4(1, 0, 0, 0), 377 | new Vector4(0, 1, 0, 0) 378 | }; 379 | 380 | //Front 381 | Sides[2].Plane = new Vector3[] 382 | { 383 | Position + new Vector3(-(shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f), 384 | Position + new Vector3(-(shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f), 385 | Position + new Vector3((shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f) 386 | }; 387 | Sides[2].UV = new Vector4[] 388 | { 389 | new Vector4(1, 0, 0, 0), 390 | new Vector4(0, 0, 1, 0) 391 | }; 392 | 393 | //Back 394 | Sides[3].Plane = new Vector3[] 395 | { 396 | Position + new Vector3(-(shapeData.Size.X * 0.5f), -(shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f), 397 | Position + new Vector3(-(shapeData.Size.X * 0.5f), -(shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f), 398 | Position + new Vector3((shapeData.Size.X * 0.5f), -(shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f) 399 | }; 400 | Sides[3].UV = new Vector4[] 401 | { 402 | new Vector4(1, 0, 0, 0), 403 | new Vector4(0, 0, 1, 0) 404 | }; 405 | 406 | //Left 407 | Sides[4].Plane = new Vector3[] 408 | { 409 | Position + new Vector3(-(shapeData.Size.X * 0.5f), -(shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f), 410 | Position + new Vector3(-(shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f), 411 | Position + new Vector3(-(shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f) 412 | }; 413 | Sides[4].UV = new Vector4[] 414 | { 415 | new Vector4(0, 0, 1, 0), 416 | new Vector4(0, 1, 0, 0) 417 | }; 418 | 419 | //Right 420 | Sides[5].Plane = new Vector3[] 421 | { 422 | Position + new Vector3((shapeData.Size.X * 0.5f), -(shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f), 423 | Position + new Vector3((shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), shapeData.Size.Z * 0.5f), 424 | Position + new Vector3((shapeData.Size.X * 0.5f), (shapeData.Size.Y * 0.5f), -shapeData.Size.Z * 0.5f) 425 | }; 426 | Sides[5].UV = new Vector4[] 427 | { 428 | new Vector4(0, 0, 1, 0), 429 | new Vector4(0, 1, 0, 0) 430 | }; 431 | 432 | base.GenerateSides(startingID); 433 | } 434 | } 435 | 436 | public class ShapeData 437 | { 438 | public List Rotation = new List(); 439 | 440 | public ShapeData() { } 441 | public ShapeData(ShapeData self) 442 | { 443 | Rotation.Clear(); 444 | for (int i = 0; i < self.Rotation.Count; i++) 445 | { 446 | Rotation.Add(new RotationData(self.Rotation[i])); 447 | } 448 | } 449 | } 450 | 451 | public class RotationData 452 | { 453 | public Vector3 RotationAxis = new Vector3(0, 0, 0); 454 | public float RotationAngle = 0; 455 | public Vector3? RotationPoint = null; 456 | public bool UsePositionAsCenterPoint = false; 457 | 458 | public RotationData() { } 459 | public RotationData(RotationData self) 460 | { 461 | RotationAxis = self.RotationAxis; 462 | RotationAngle = self.RotationAngle; 463 | RotationPoint = self.RotationPoint; 464 | UsePositionAsCenterPoint = self.UsePositionAsCenterPoint; 465 | } 466 | } 467 | 468 | public abstract class Shape 469 | { 470 | public Shape() { } 471 | public Shape(Shape self) 472 | { 473 | BlockEntityID = self.BlockEntityID; 474 | EntityType = self.EntityType; 475 | EntitySettings = self.EntitySettings; 476 | ID = self.ID; 477 | Position = self.Position; 478 | Sides = self.Sides; 479 | Data = new ShapeData(self.Data); 480 | Texture = self.Texture; 481 | Visgroup = self.Visgroup; 482 | } 483 | 484 | public string Visgroup = string.Empty; 485 | public int BlockEntityID = -1; 486 | public EntityTemplates.BlockEntityType EntityType = EntityTemplates.BlockEntityType.func_detail; 487 | public List EntitySettings = new List(); 488 | public int ID; 489 | public Vector3 Position; 490 | public SolidSide[] Sides; 491 | public List SidesAreDisplacements = new List(); 492 | public ShapeData Data; 493 | public string Texture = Textures.DEV_MEASUREGENERIC01B; 494 | 495 | public virtual void GenerateSides(int startingID) 496 | { 497 | Vector3 center = new Vector3(0, 0, 0); 498 | for (int i = 0; i < Sides.Length; i++) 499 | { 500 | Vector3 sideCenter = new Vector3(0, 0, 0); 501 | for (int j = 0; j < Sides[i].Plane.Length; j++) 502 | { 503 | sideCenter += Sides[i].Plane[j]; 504 | } 505 | 506 | sideCenter /= Sides[i].Plane.Length; 507 | 508 | center += sideCenter; 509 | 510 | if(SidesAreDisplacements.Contains((SideFacing)i)) 511 | { 512 | Sides[i].Displacement = true; 513 | } 514 | } 515 | center /= Sides.Length; 516 | 517 | while (Data.Rotation.Count > 0) 518 | { 519 | RotationData rotData = Data.Rotation[0]; 520 | Data.Rotation.RemoveAt(0); 521 | if (rotData.UsePositionAsCenterPoint) 522 | { 523 | center = Position; 524 | } 525 | if (rotData.RotationPoint != null) 526 | { 527 | center = (Vector3)rotData.RotationPoint; 528 | } 529 | 530 | Rotate(rotData.RotationAxis, rotData.RotationAngle, center); 531 | } 532 | 533 | CalculateUVs(); 534 | } 535 | 536 | public virtual void Rotate(Vector3 axis, float angle, Vector3 pointToRotateAround) 537 | { 538 | for (int i = 0; i < Sides.Length; i++) 539 | { 540 | for (int j = 0; j < Sides[i].Plane.Length; j++) 541 | { 542 | Sides[i].Plane[j] = Vector3.Transform(Sides[i].Plane[j] - pointToRotateAround, Quaternion.CreateFromAxisAngle(axis, angle * (MathF.PI / 180f))) + pointToRotateAround; 543 | } 544 | } 545 | } 546 | 547 | protected virtual void CalculateUVs() 548 | { 549 | //I don't think I'm actually doing this exactly right for Source - unsure how to fix perfectly, and it works good enough for now? 550 | for (int i = 0; i < Sides.Length; i++) 551 | { 552 | Vector3 U = Sides[i].Plane[1] - Sides[i].Plane[0]; 553 | Vector3 V = Sides[i].Plane[2] - Sides[i].Plane[0]; 554 | 555 | Vector3 normal = Vector3.Normalize(new Vector3( 556 | (U.Y * V.Z) - (U.Z * V.Y), 557 | (U.Z * V.X) - (U.X * V.X), 558 | (U.X * V.Y) - (U.Y * V.X) 559 | )); 560 | 561 | var uAxis = Vector3.Normalize(new Vector3(normal.Z != 0 ? -normal.Z : -normal.Y, 0, normal.X)); 562 | var vAxis = Vector3.Cross(uAxis, normal); 563 | 564 | Sides[i].UV = new Vector4[] 565 | { 566 | new Vector4(uAxis.X, uAxis.Y, uAxis.Z, 0), 567 | new Vector4(vAxis.X, vAxis.Y, vAxis.Z, 0) 568 | }; 569 | } 570 | } 571 | 572 | public static Vector2 GetNormal2D(Vector2 A, Vector2 B) 573 | { 574 | float dx = B.X - A.X; 575 | float dy = B.Y - A.Y; 576 | 577 | return new Vector2(dy, -dx); 578 | } 579 | } 580 | 581 | #endregion 582 | 583 | #region Generators 584 | 585 | public class StairData 586 | { 587 | public string Visgroup; 588 | public int BlockEntityID = -1; 589 | public EntityTemplates.BlockEntityType EntityType = EntityTemplates.BlockEntityType.func_detail; 590 | public Vector3 Position; 591 | public int StairCount; 592 | public int StairWidth; 593 | public int Rise; 594 | public int Run; 595 | public string Texture = Textures.DEV_MEASUREGENERIC01B; 596 | public Direction Direction; 597 | public int RailingThickness = 0; 598 | } 599 | 600 | public static class StairsGenerator 601 | { 602 | public static List Generate(StairData data, RotationData rotationData = null) 603 | { 604 | List shapes = new List(); 605 | Vector3 posChangeDepth = new Vector3(); 606 | Vector3 posChangeWidth = new Vector3(); 607 | float rotationAngle = 0; 608 | 609 | switch (data.Direction) 610 | { 611 | case Direction.North: 612 | rotationAngle = 90; 613 | posChangeDepth = new Vector3(0, -1, -1); 614 | posChangeWidth = new Vector3(-1, 0, -1); 615 | break; 616 | case Direction.South: 617 | rotationAngle = 270; 618 | posChangeDepth = new Vector3(0, 1, -1); 619 | posChangeWidth = new Vector3(1, 0, -1); 620 | break; 621 | case Direction.West: 622 | rotationAngle = 180; 623 | posChangeDepth = new Vector3(1, 0, -1); 624 | posChangeWidth = new Vector3(0, 1, -1); 625 | break; 626 | case Direction.East: 627 | rotationAngle = 0; 628 | posChangeDepth = new Vector3(-1, 0, -1); 629 | posChangeWidth = new Vector3(0, -1, -1); 630 | break; 631 | } 632 | 633 | //Each individual step 634 | for (int i = 0; i < data.StairCount; i++) 635 | { 636 | int run = i * data.Run; 637 | int rise = i * data.Rise; 638 | 639 | switch (data.Direction) 640 | { 641 | case Direction.North: 642 | shapes.Add(new Cube() 643 | { 644 | Texture = data.Texture, 645 | BlockEntityID = data.BlockEntityID, 646 | Position = data.Position + new Vector3(0, run, rise + data.Rise * 0.5f), 647 | Data = new CubeShapeData() 648 | { 649 | Size = new Vector3(data.StairWidth, data.Run, data.Rise) 650 | } 651 | }); 652 | break; 653 | case Direction.South: 654 | shapes.Add(new Cube() 655 | { 656 | Texture = data.Texture, 657 | BlockEntityID = data.BlockEntityID, 658 | Position = data.Position + new Vector3(0, -run, rise + data.Rise * 0.5f), 659 | Data = new CubeShapeData() 660 | { 661 | Size = new Vector3(data.StairWidth, data.Run, data.Rise) 662 | } 663 | }); 664 | break; 665 | case Direction.West: 666 | shapes.Add(new Cube() 667 | { 668 | Texture = data.Texture, 669 | BlockEntityID = data.BlockEntityID, 670 | Position = data.Position + new Vector3(-run, 0, rise + data.Rise * 0.5f), 671 | Data = new CubeShapeData() 672 | { 673 | Size = new Vector3(data.Run, data.StairWidth, data.Rise) 674 | } 675 | }); 676 | break; 677 | case Direction.East: 678 | shapes.Add(new Cube() 679 | { 680 | Texture = data.Texture, 681 | BlockEntityID = data.BlockEntityID, 682 | Position = data.Position + new Vector3(run, 0, rise + data.Rise * 0.5f), 683 | Data = new CubeShapeData() 684 | { 685 | Size = new Vector3(data.Run, data.StairWidth, data.Rise) 686 | } 687 | }); 688 | break; 689 | } 690 | } 691 | 692 | Cube lastStair = (Cube)shapes[shapes.Count - 1]; 693 | Cube frontPiece = new Cube(lastStair); 694 | frontPiece.Position.Z = Lerp(shapes[0].Position.Z, lastStair.Position.Z, 0.5f) - data.Rise * 0.5f; 695 | ((CubeShapeData)frontPiece.Data).Size.Z = data.Rise * (data.StairCount - 1); 696 | 697 | shapes.Add(frontPiece); 698 | 699 | Vector3 perfectlyCenteredPosition = new Vector3(posChangeDepth.X * (data.Run * 1.5f), posChangeDepth.Y * (data.Run * 1.5f), (posChangeDepth.Z * (data.Rise * 0.5f)) + data.Rise * 0.5f); 700 | 701 | //Clip Brush 702 | shapes.Add(new Polygon() 703 | { 704 | BlockEntityID = data.BlockEntityID, 705 | Texture = Textures.CLIP, 706 | Position = data.Position + perfectlyCenteredPosition, 707 | Data = new PolygonShapeData() 708 | { 709 | Rotation = new List() 710 | { 711 | new RotationData() 712 | { 713 | RotationAxis = new Vector3(1, 0, 0), 714 | RotationAngle = 90, 715 | UsePositionAsCenterPoint = true, 716 | }, 717 | new RotationData() 718 | { 719 | RotationAxis = new Vector3(0, 0, 1), 720 | RotationAngle = rotationAngle, 721 | UsePositionAsCenterPoint = true 722 | } 723 | }, 724 | Depth = data.StairWidth, 725 | Scalar = 1, 726 | PolygonPoints = new List() 727 | { 728 | new Vector2(0, 0), 729 | new Vector2(data.Run * data.StairCount, 0), 730 | new Vector2(data.Run * data.StairCount, data.Rise * data.StairCount) 731 | } 732 | } 733 | }); 734 | 735 | if (data.RailingThickness != 0) 736 | { 737 | //Railing Left 738 | shapes.Add(new Polygon() 739 | { 740 | BlockEntityID = data.BlockEntityID, 741 | Texture = data.Texture, 742 | Position = data.Position + perfectlyCenteredPosition + posChangeWidth * new Vector3(data.StairWidth * 0.5f + data.RailingThickness * 0.5f, data.StairWidth * 0.5f + data.RailingThickness * 0.5f, 0), 743 | Data = new PolygonShapeData() 744 | { 745 | Rotation = new List() 746 | { 747 | new RotationData() 748 | { 749 | RotationAxis = new Vector3(1, 0, 0), 750 | RotationAngle = 90, 751 | UsePositionAsCenterPoint = true, 752 | }, 753 | new RotationData() 754 | { 755 | RotationAxis = new Vector3(0, 0, 1), 756 | RotationAngle = rotationAngle, 757 | UsePositionAsCenterPoint = true 758 | } 759 | }, 760 | Depth = data.RailingThickness, 761 | Scalar = 1, 762 | PolygonPoints = new List() 763 | { 764 | new Vector2(0, 0), 765 | new Vector2(data.Run * data.StairCount + data.Run, 0), 766 | new Vector2(data.Run * data.StairCount + data.Run, data.Rise * data.StairCount), 767 | new Vector2(data.Run * data.StairCount, data.Rise * data.StairCount), 768 | } 769 | } 770 | }); 771 | 772 | //Railing Right 773 | shapes.Add(new Polygon() 774 | { 775 | BlockEntityID = data.BlockEntityID, 776 | Texture = data.Texture, 777 | Position = data.Position + perfectlyCenteredPosition + posChangeWidth * new Vector3(-data.StairWidth * 0.5f - data.RailingThickness * 0.5f, -data.StairWidth * 0.5f - data.RailingThickness * 0.5f, 0), 778 | Data = new PolygonShapeData() 779 | { 780 | Rotation = new List() 781 | { 782 | new RotationData() 783 | { 784 | RotationAxis = new Vector3(1, 0, 0), 785 | RotationAngle = 90, 786 | UsePositionAsCenterPoint = true, 787 | }, 788 | new RotationData() 789 | { 790 | RotationAxis = new Vector3(0, 0, 1), 791 | RotationAngle = rotationAngle, 792 | UsePositionAsCenterPoint = true 793 | } 794 | }, 795 | Depth = data.RailingThickness, 796 | Scalar = 1, 797 | PolygonPoints = new List() 798 | { 799 | new Vector2(0, 0), 800 | new Vector2(data.Run * data.StairCount + data.Run, 0), 801 | new Vector2(data.Run * data.StairCount + data.Run, data.Rise * data.StairCount), 802 | new Vector2(data.Run * data.StairCount, data.Rise * data.StairCount), 803 | } 804 | } 805 | }); 806 | } 807 | 808 | rotationData.RotationPoint = data.Position; 809 | 810 | if (rotationData != null) 811 | { 812 | for (int i = 0; i < shapes.Count; i++) 813 | { 814 | shapes[i].Data.Rotation.Add(rotationData); 815 | } 816 | } 817 | 818 | for (int i = 0; i < shapes.Count; i++) 819 | { 820 | shapes[i].Visgroup = data.Visgroup; 821 | } 822 | 823 | 824 | return shapes; 825 | } 826 | private static float Lerp(float firstFloat, float secondFloat, float by) 827 | { 828 | return firstFloat * (1 - by) + secondFloat * by; 829 | } 830 | } 831 | 832 | public class WallData 833 | { 834 | public string Texture = Textures.DEV_MEASUREGENERIC01B; 835 | public int Height; 836 | public int Thickness; 837 | public bool CapEnd = true; 838 | public List facesIndicesToSkip = new List(); 839 | } 840 | 841 | public static class WallGenerator 842 | { 843 | public static List CreateWalls(Polygon polygon, WallData wallData) 844 | { 845 | List> allPoints = new List>(); 846 | List points = new List(); 847 | List lastFace = new List(); 848 | List> normals = new List>(); 849 | 850 | PolygonShapeData polyData = polygon.Data as PolygonShapeData; 851 | 852 | List sharedValues = polyData.PolygonPoints.GroupBy(x => x).Where(g => g.Count() > 1).Select(x => x.Key).ToList(); 853 | for (int i = 0; i < polyData.PolygonPoints.Count; i++) 854 | { 855 | if(sharedValues.Contains(polyData.PolygonPoints[i])) 856 | { 857 | wallData.facesIndicesToSkip.Add(i); 858 | i++; 859 | } 860 | } 861 | 862 | for (int i = 0; i < polyData.PolygonPoints.Count; i++) 863 | { 864 | if (wallData.facesIndicesToSkip.Contains(i)) 865 | { 866 | continue; 867 | } 868 | 869 | //Okay basically all the shit I'm doing here is getting the vertex normal of the current point 870 | //and basing how the wall should be made based off that, so all the walls end up as trapazoids. 871 | //The only exception is if a wall piece is missing, I then get the intersection based on which 872 | //side of the wall it is, and try to flatten it. It's not perfect but it works? 873 | int index1 = (i - 1) % polyData.PolygonPoints.Count; 874 | if (index1 == -1) 875 | { 876 | index1 = polyData.PolygonPoints.Count - 1; 877 | } 878 | int index2 = i % polyData.PolygonPoints.Count; 879 | int index3 = (i + 1) % polyData.PolygonPoints.Count; 880 | int index4 = (i + 2) % polyData.PolygonPoints.Count; 881 | Vector2 a = polyData.PolygonPoints[index1] * polyData.Scalar; 882 | Vector2 b = polyData.PolygonPoints[index2] * polyData.Scalar; 883 | Vector2 c = polyData.PolygonPoints[index3] * polyData.Scalar; 884 | Vector2 d = polyData.PolygonPoints[index4] * polyData.Scalar; 885 | 886 | Vector2 abNormal = Vector2.Normalize(Shape.GetNormal2D(a, b)); 887 | Vector2 bcNormal = Vector2.Normalize(Shape.GetNormal2D(b, c)); 888 | Vector2 vertexNormalabc = Vector2.Normalize((abNormal + bcNormal)) * wallData.Thickness; 889 | Vector2 cdNormal = Vector2.Normalize(Shape.GetNormal2D(c, d)); 890 | Vector2 vertexNormalbcd = Vector2.Normalize((bcNormal + cdNormal)) * wallData.Thickness; 891 | 892 | Vector2 point = polyData.PolygonPoints[index2] * polyData.Scalar + vertexNormalabc; 893 | Vector2 point2 = polyData.PolygonPoints[index3] * polyData.Scalar + vertexNormalbcd; 894 | points.Add(point); 895 | points.Add(point2); 896 | points.Add(c); 897 | points.Add(b); 898 | 899 | if (wallData.facesIndicesToSkip.Contains(i + 1)) 900 | { 901 | LineEquation first = new LineEquation(points[2], points[2] - bcNormal); 902 | LineEquation second = new LineEquation(points[0], points[1]); 903 | Vector2 intersectPoint; 904 | if(first.IntersectsWithLine(second, out intersectPoint)) 905 | { 906 | points[1] = intersectPoint; 907 | } 908 | } 909 | if(wallData.facesIndicesToSkip.Contains(i - 1)) 910 | { 911 | LineEquation first = new LineEquation(points[3], points[3] - bcNormal); 912 | LineEquation second = new LineEquation(points[0], points[1]); 913 | Vector2 intersectPoint; 914 | if (first.IntersectsWithLine(second, out intersectPoint)) 915 | { 916 | points[0] = intersectPoint; 917 | } 918 | } 919 | 920 | allPoints.Add(points); 921 | points = new List(); 922 | } 923 | 924 | //Idk there were edge cases I had NAN values and they didnt seem to affect anything sooooooo 925 | for (int i = allPoints.Count - 1; i >= 0; i--) 926 | { 927 | bool hasNan = false; 928 | for (int j = 0; j < allPoints[i].Count; j++) 929 | { 930 | if(allPoints[i][j] != allPoints[i][j]) 931 | { 932 | hasNan = true; 933 | break; 934 | } 935 | } 936 | 937 | if(hasNan) 938 | { 939 | allPoints.RemoveAt(i); 940 | } 941 | } 942 | 943 | VMFDebug.CreateDebugImage("WallOutput", onDraw: (g) => 944 | { 945 | float scale = 0.1f; 946 | for (int i = 0; i < allPoints.Count; i++) 947 | { 948 | for (int j = 0; j < allPoints[i].Count; j++) 949 | { 950 | int iN = (j + 1) % allPoints[i].Count; 951 | Point p1 = new Point((int)(allPoints[i][j].X * scale + 100), (int)(allPoints[i][j].Y * scale + 100)); 952 | Point p2 = new Point((int)(allPoints[i][iN].X * scale + 100), (int)(allPoints[i][iN].Y * scale + 100)); 953 | 954 | g.DrawLine(new Pen(Color.Black, 3), p1, p2); 955 | } 956 | } 957 | }); 958 | 959 | List finalPolygons = new List(); 960 | for (int i = 0; i < allPoints.Count; i++) 961 | { 962 | finalPolygons.Add( 963 | new Polygon() 964 | { 965 | Texture = wallData.Texture, 966 | Position = polygon.Position + new Vector3(0, 0, wallData.Height * 0.5f), 967 | Data = new PolygonShapeData() 968 | { 969 | Depth = wallData.Height + ((PolygonShapeData)polygon.Data).Depth, 970 | Scalar = 1, 971 | PolygonPoints = allPoints[i] 972 | } 973 | }); 974 | } 975 | 976 | return finalPolygons; 977 | } 978 | } 979 | 980 | #endregion 981 | } 982 | -------------------------------------------------------------------------------- /VMFGeneration/Textures.cs: -------------------------------------------------------------------------------- 1 | namespace VMFGenerator 2 | { 3 | public static class Textures 4 | { 5 | public const string DEV_MEASUREGENERIC01B = "DEV/DEV_MEASUREGENERIC01B"; 6 | public const string CLIP = "TOOLS/TOOLSCLIP"; 7 | public const string TRIGGER = "TOOLS/TOOLSTRIGGER"; 8 | public const string SKYBOX = "TOOLS/TOOLSSKYBOX"; 9 | public const string DUST_STONEWALL02 = "DE_DUST/STONEWALL02"; 10 | public const string DUST_CINDERBLOCK_CHECKERED01 = "DUST_MASSIVE/DUST_CINDERBLOCK_CHECKERED_01"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /VMFGeneration/Tool2D.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace VMFGenerator 6 | { 7 | #region MIT License 8 | /* 9 | * Copyright (c) 2005-2008 Jonathan Mark Porter. http://physics2d.googlepages.com/ 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights to 14 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 15 | * the Software, and to permit persons to whom the Software is furnished to do so, 16 | * subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be 19 | * included in all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 22 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 23 | * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | * OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | #endregion 29 | 30 | 31 | 32 | 33 | #if UseDouble 34 | using Scalar = System.Double; 35 | #else 36 | using Scalar = System.Single; 37 | #endif 38 | 39 | using System; 40 | using System.Collections.Generic; 41 | using System.Numerics; 42 | 43 | public interface IBitmap 44 | { 45 | int Width { get; } 46 | int Height { get; } 47 | bool this[int x, int y] { get; } 48 | } 49 | 50 | public sealed class ArrayBitmap : IBitmap 51 | { 52 | private bool[,] bitmap; 53 | 54 | public ArrayBitmap(bool[,] bitmap) 55 | { 56 | if (bitmap == null) { throw new ArgumentNullException("bitmap"); } 57 | this.bitmap = bitmap; 58 | } 59 | 60 | public int Width 61 | { 62 | get { return bitmap.GetLength(0); } 63 | } 64 | 65 | public int Height 66 | { 67 | get { return bitmap.GetLength(1); } 68 | } 69 | 70 | public bool this[int x, int y] 71 | { 72 | get 73 | { 74 | return 75 | x >= 0 && x < Width && 76 | y >= 0 && y < Height && 77 | bitmap[x, y]; 78 | } 79 | } 80 | 81 | } 82 | 83 | public static class BitmapHelper 84 | { 85 | class BitMapSkipper 86 | { 87 | float xMin; 88 | float xMax; 89 | List[] scans; 90 | 91 | 92 | public BitMapSkipper(IBitmap bitmap, List points) 93 | { 94 | FromVectors(points); 95 | CreateScans(); 96 | FillScans(points); 97 | FormatScans(bitmap); 98 | } 99 | 100 | void FromVectors(List points) 101 | { 102 | if (points == null) { throw new ArgumentNullException("vectors"); } 103 | if (points.Count == 0) { throw new ArgumentOutOfRangeException("points"); } 104 | xMin = points[0].X; 105 | xMax = xMin; 106 | for (int index = 1; index < points.Count; ++index) 107 | { 108 | Vector2 current = points[index]; 109 | if (current.X > xMax) 110 | { 111 | xMax = current.X; 112 | } 113 | else if (current.X < xMin) 114 | { 115 | xMin = current.X; 116 | } 117 | } 118 | } 119 | void CreateScans() 120 | { 121 | scans = new List[(int)(xMax - xMin) + 1]; 122 | for (int index = 0; index < scans.Length; ++index) 123 | { 124 | scans[index] = new List(); 125 | } 126 | } 127 | void FillScans(List points) 128 | { 129 | for (int index = 0; index < points.Count; ++index) 130 | { 131 | Vector2 point = points[index]; 132 | int scanIndex = (int)(point.X - xMin); 133 | scans[scanIndex].Add(point.Y); 134 | } 135 | } 136 | 137 | void FormatScans(IBitmap bitmap) 138 | { 139 | for (int index = 0; index < scans.Length; ++index) 140 | { 141 | scans[index].Sort(); 142 | FormatScan(bitmap, index); 143 | } 144 | } 145 | void FormatScan(IBitmap bitmap, int x) 146 | { 147 | List scan = scans[x]; 148 | List newScan = new List(); 149 | bool inPoly = false; 150 | for (int index = 0; index < scan.Count; ++index) 151 | { 152 | float y = scan[index]; 153 | if (!inPoly) 154 | { 155 | newScan.Add(y); 156 | } 157 | bool value = bitmap[(int)(x + xMin), (int)y + 1]; 158 | if (value) 159 | { 160 | inPoly = true; 161 | } 162 | else 163 | { 164 | newScan.Add(y); 165 | inPoly = false; 166 | } 167 | } 168 | //if (newScan.Count % 2 != 0) { throw new Exception(); } 169 | scans[x] = newScan; 170 | } 171 | 172 | public bool TryGetSkip(Vector2 point, out float nextY) 173 | { 174 | int scanIndex = (int)(point.Y - xMin); 175 | if (scanIndex < 0 || scanIndex >= scans.Length) 176 | { 177 | nextY = 0; 178 | return false; 179 | } 180 | List scan = scans[scanIndex]; 181 | for (int index = 1; index < scan.Count; index += 2) 182 | { 183 | if (point.Y >= scan[index - 1] && 184 | point.Y <= scan[index]) 185 | { 186 | nextY = scan[index]; 187 | return true; 188 | } 189 | } 190 | nextY = 0; 191 | return false; 192 | } 193 | } 194 | static readonly Vector2[] bitmapPoints = new Vector2[]{ 195 | new Vector2 (1,1), 196 | new Vector2 (0,1), 197 | new Vector2 (-1,1), 198 | new Vector2 (-1,0), 199 | new Vector2 (-1,-1), 200 | new Vector2 (0,-1), 201 | new Vector2 (1,-1), 202 | new Vector2 (1,0), 203 | }; 204 | 205 | public static List CreateFromBitmap(IBitmap bitmap) 206 | { 207 | return Reduce(CreateFromBitmap(bitmap, GetFirst(bitmap))); 208 | } 209 | 210 | public static List> CreateManyFromBitmap(IBitmap bitmap) 211 | { 212 | List skippers = new List(); 213 | List> result = new List>(); 214 | foreach (Vector2 first in GetFirsts(bitmap, skippers)) 215 | { 216 | List points = CreateFromBitmap(bitmap, first); 217 | BitMapSkipper skipper = new BitMapSkipper(bitmap, points); 218 | skippers.Add(skipper); 219 | result.Add(Reduce(points)); 220 | } 221 | return result; 222 | } 223 | 224 | private static List CreateFromBitmap(IBitmap bitmap, Vector2 first) 225 | { 226 | Vector2 current = first; 227 | Vector2 last = first - new Vector2(0, 1); 228 | List result = new List(); 229 | 230 | do 231 | { 232 | result.Add(current); 233 | current = GetNextVertex(bitmap, current, last); 234 | last = result[result.Count - 1]; 235 | } while (current != first); 236 | 237 | if (result.Count < 3) 238 | { 239 | throw new ArgumentException("The image has an area with less then 3 pixels (possibly an artifact).", "bitmap"); 240 | } 241 | 242 | return result; 243 | } 244 | 245 | private static Vector2 GetFirst(IBitmap bitmap) 246 | { 247 | for (int x = bitmap.Width - 1; x > -1; --x) 248 | { 249 | for (int y = 0; y < bitmap.Height; ++y) 250 | { 251 | if (bitmap[x, y]) 252 | { 253 | return new Vector2(x, y); 254 | } 255 | } 256 | } 257 | throw new ArgumentException("TODO", "bitmap"); 258 | } 259 | 260 | private static IEnumerable GetFirsts(IBitmap bitmap, List skippers) 261 | { 262 | for (int x = bitmap.Width - 1; x > -1; --x) 263 | { 264 | for (int y = 0; y < bitmap.Height; ++y) 265 | { 266 | if (bitmap[x, y]) 267 | { 268 | bool contains = false; 269 | Vector2 result = new Vector2(x, y); 270 | for (int index = 0; index < skippers.Count; ++index) 271 | { 272 | float nextY; 273 | if (skippers[index].TryGetSkip(result, out nextY)) 274 | { 275 | contains = true; 276 | y = (int)nextY; 277 | break; 278 | } 279 | } 280 | if (!contains) 281 | { 282 | yield return result; 283 | } 284 | } 285 | } 286 | } 287 | } 288 | 289 | private static Vector2 GetNextVertex(IBitmap bitmap, Vector2 current, Vector2 last) 290 | { 291 | int offset = 0; 292 | Vector2 point; 293 | 294 | for (int index = 0; index < bitmapPoints.Length; ++index) 295 | { 296 | point = current + bitmapPoints[index]; 297 | if (point == last) 298 | { 299 | offset = index + 1; 300 | break; 301 | } 302 | } 303 | 304 | for (int index = 0; index < bitmapPoints.Length; ++index) 305 | { 306 | point = current + bitmapPoints[(index + offset) % bitmapPoints.Length]; 307 | if (point.X >= 0 && point.X < bitmap.Width && 308 | point.Y >= 0 && point.Y < bitmap.Height && 309 | bitmap[(int)point.X, (int)point.Y]) 310 | { 311 | return point; 312 | } 313 | } 314 | 315 | throw new ArgumentException("The image has an area with less then 3 pixels (possibly an artifact).", "bitmap"); 316 | } 317 | 318 | private static List Reduce(List list) 319 | { 320 | List result = new List(list.Count); 321 | Vector2 p1 = list[list.Count - 2]; 322 | Vector2 p2 = list[list.Count - 1]; 323 | Vector2 p3; 324 | for (int index = 0; index < list.Count; ++index, p2 = p3) 325 | { 326 | if (index == list.Count - 1) 327 | { 328 | if (result.Count == 0) { throw new ArgumentException("Bad Polygon"); } 329 | p3.X = (int)result[0].X; 330 | p3.Y = (int)result[0].Y; 331 | } 332 | else { p3 = list[index]; } 333 | if (!IsInLine(p1, p2, p3, 20)) 334 | { 335 | result.Add(new Vector2(p2.X, p2.Y)); 336 | p1 = p2; 337 | } 338 | } 339 | return result; 340 | } 341 | 342 | private static bool IsInLine(Vector2 p1, Vector2 p2, Vector2 p3, float delta) 343 | { 344 | float a1 = MathF.Atan2(p1.Y - p2.Y, p1.X - p2.X) * 180 / MathF.PI; 345 | float a2 = MathF.Atan2(p2.Y - p3.Y, p2.X - p3.X) * 180 / MathF.PI; 346 | float diff = a1 - a2; 347 | return diff > -delta && diff < delta; 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /VMFGeneration/VMFDebug.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Numerics; 6 | using System.Text; 7 | using VMFGenerator; 8 | 9 | namespace VMFGenerator 10 | { 11 | public static class VMFDebug 12 | { 13 | public static bool DebugMode = false; 14 | 15 | public static void CreateDebugImage(string fileName, System.Action onDraw, int width = 500, int height = 500) 16 | { 17 | if(!DebugMode) 18 | { 19 | return; 20 | } 21 | 22 | using (Bitmap canvas = new Bitmap(width, height)) 23 | { 24 | using (Graphics g = Graphics.FromImage(canvas)) 25 | { 26 | g.FillRectangle(Brushes.White, new Rectangle(0, 0, width, height)); 27 | 28 | onDraw?.Invoke(g); 29 | } 30 | 31 | if(!Directory.Exists(Directory.GetCurrentDirectory() + @"\DebugOutput")) 32 | { 33 | Directory.CreateDirectory(Directory.GetCurrentDirectory() + @"\DebugOutput"); 34 | } 35 | 36 | canvas.Save(Directory.GetCurrentDirectory() + @"\DebugOutput\" + fileName + ".png"); 37 | } 38 | } 39 | public static void AddShapeToGraphics(Graphics g, Polygon polgon, Pen pen, Vector2 positionAdjustment = new Vector2(), float scale = 0.1f) 40 | { 41 | List points = ((PolygonShapeData)polgon.Data).PolygonPoints; 42 | for (int i = 0; i < points.Count; i++) 43 | { 44 | int iN = (i + 1) % points.Count; 45 | Point p1 = new Point((int)(points[i].X * scale + positionAdjustment.X), (int)(points[i].Y * scale + positionAdjustment.Y)); 46 | Point p2 = new Point((int)(points[iN].X * scale + positionAdjustment.X), (int)(points[iN].Y * scale + positionAdjustment.Y)); 47 | 48 | g.DrawLine(pen, p1, p2); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /VMFGeneration/VMFGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /VMFGeneration/Visgroups.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Text; 5 | 6 | namespace VMFGenerator 7 | { 8 | public static class Visgroups 9 | { 10 | public static Dictionary VisgroupNameToID = new Dictionary(); 11 | public static Dictionary VisgroupNameToColor = new Dictionary(); 12 | public const string TAR_LAYOUT = "tar_layout"; 13 | public const string TAR_MASK = "tar_mask"; 14 | public const string TAR_COVER = "tar_cover"; 15 | } 16 | } 17 | --------------------------------------------------------------------------------