├── .gitignore ├── .gitmodules ├── Examples ├── export-example.bat └── host-example.bat ├── LICENSE ├── README.md ├── SourceUtils.FileExport ├── App.config ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── SourceUtils.FileExport.csproj └── packages.config ├── SourceUtils.Test ├── KeyValsTest.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── SourceUtils.Test.csproj ├── entities.txt └── packages.config ├── SourceUtils.WebExport ├── App.config ├── Bsp │ ├── AmbientCubes.cs │ ├── BrushModel.cs │ ├── Entities.cs │ ├── Geometry.cs │ ├── Index.cs │ ├── Lightmap.cs │ ├── Materials.cs │ └── Visibility.cs ├── Export.cs ├── ImageMagick.cs ├── LZString.cs ├── Material.cs ├── ModelPatch.cs ├── OpenTK.dll.config ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── ResourceController.cs ├── ResourceDictionary.cs ├── Resources │ ├── index.template.html │ ├── js │ │ ├── facepunch.webgame.d.ts │ │ ├── facepunch.webgame.js │ │ ├── sourceutils.d.ts │ │ └── sourceutils.js │ ├── src │ │ ├── AmbientLoader.ts │ │ ├── BspModel.ts │ │ ├── DispGeometryLoader.ts │ │ ├── Entities │ │ │ ├── BrushEntity.ts │ │ │ ├── Camera.ts │ │ │ ├── Displacement.ts │ │ │ ├── MoveRope.ts │ │ │ ├── PvsEntity.ts │ │ │ ├── StaticProp.ts │ │ │ └── Worldspawn.ts │ │ ├── LeafGeometryLoader.ts │ │ ├── Map.ts │ │ ├── MapMaterialLoader.ts │ │ ├── MapViewer.ts │ │ ├── PagedLoader.ts │ │ ├── Shaders │ │ │ ├── BaseShaderProgram.ts │ │ │ ├── Lightmapped2WayBlend.ts │ │ │ ├── LightmappedBase.ts │ │ │ ├── LightmappedGeneric.ts │ │ │ ├── ModelBase.ts │ │ │ ├── Sky.ts │ │ │ ├── SplineRope.ts │ │ │ ├── UnlitGeneric.ts │ │ │ ├── VertexLitGeneric.ts │ │ │ ├── Water.ts │ │ │ └── WorldTwoTextureBlend.ts │ │ ├── SkyCube.ts │ │ ├── StudioModel.ts │ │ ├── Utils.ts │ │ └── VisLoader.ts │ ├── styles │ │ └── mapviewer.css │ └── tsconfig.json ├── SourceUtils.WebExport.csproj ├── StaticFiles.cs ├── StudioModelDictionary.cs ├── Texture.Convert.cs ├── Texture.cs ├── Utils.cs └── packages.config ├── SourceUtils.sln ├── SourceUtils ├── BitBuffer.cs ├── Color32.cs ├── DisposingEventTarget.cs ├── IntRect.cs ├── IntVector2.cs ├── KeyValues.cs ├── KeyValues.txt ├── LumpReader.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── RectanglePacker.cs ├── ResourceLoader.cs ├── SourceUtils.csproj ├── StudioModelFile.cs ├── SubStream.cs ├── ValveBsp │ ├── ArrayLump.cs │ ├── BspTree.cs │ ├── Displacement.cs │ ├── DisplacementManager.cs │ ├── EntityLump.cs │ ├── GameLump.cs │ ├── LightmapLayout.cs │ ├── LumpInfo.cs │ ├── LumpType.cs │ ├── PakFileLump.cs │ ├── Reflection.cs │ ├── StaticProps.cs │ ├── Structures.cs │ ├── ValveBspFile.cs │ └── VisibilityLump.cs ├── ValveMaterialFile.cs ├── ValvePackage.cs ├── ValveTextureFile.cs ├── ValveTriangleFile.cs ├── ValveVertexFile.cs ├── ValveVertexLightingFile.cs ├── Vector2.cs ├── Vector3.cs ├── Vector4.cs └── packages.config ├── build.sh ├── export-pages-kz.bat ├── export-pages.bat └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | .settings/ 14 | .vscode/ 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | artifacts/ 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | *.VC.VC.opendb 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | *.sap 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding add-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | nCrunchTemp_* 119 | 120 | # MightyMoose 121 | *.mm.* 122 | AutoTest.Net/ 123 | 124 | # Web workbench (sass) 125 | .sass-cache/ 126 | 127 | # Installshield output folder 128 | [Ee]xpress/ 129 | 130 | # DocProject is a documentation generator add-in 131 | DocProject/buildhelp/ 132 | DocProject/Help/*.HxT 133 | DocProject/Help/*.HxC 134 | DocProject/Help/*.hhc 135 | DocProject/Help/*.hhk 136 | DocProject/Help/*.hhp 137 | DocProject/Help/Html2 138 | DocProject/Help/html 139 | 140 | # Click-Once directory 141 | publish/ 142 | 143 | # Publish Web Output 144 | *.[Pp]ublish.xml 145 | *.azurePubxml 146 | # TODO: Comment the next line if you want to checkin your web deploy settings 147 | # but database connection strings (with potential passwords) will be unencrypted 148 | *.pubxml 149 | *.publishproj 150 | 151 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 152 | # checkin your Azure Web App publish settings, but sensitive information contained 153 | # in these scripts will be unencrypted 154 | PublishScripts/ 155 | 156 | # NuGet Packages 157 | *.nupkg 158 | # The packages folder can be ignored because of Package Restore 159 | **/packages/* 160 | # except build/, which is used as an MSBuild target. 161 | !**/packages/build/ 162 | # Uncomment if necessary however generally it will be regenerated when needed 163 | #!**/packages/repositories.config 164 | # NuGet v3's project.json files produces more ignoreable files 165 | *.nuget.props 166 | *.nuget.targets 167 | 168 | # Microsoft Azure Build Output 169 | csx/ 170 | *.build.csdef 171 | 172 | # Microsoft Azure Emulator 173 | ecf/ 174 | rcf/ 175 | 176 | # Windows Store app package directories and files 177 | AppPackages/ 178 | BundleArtifacts/ 179 | Package.StoreAssociation.xml 180 | _pkginfo.txt 181 | 182 | # Visual Studio cache files 183 | # files ending in .cache can be ignored 184 | *.[Cc]ache 185 | # but keep track of directories ending in .cache 186 | !*.[Cc]ache/ 187 | 188 | # Others 189 | ClientBin/ 190 | ~$* 191 | *~ 192 | *.dbmdl 193 | *.dbproj.schemaview 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | MapViewServer/Scripts/typings/ 258 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Metapyziks/SourceUtils/0f15906b4d1f2a6afb4e781aa648c4e5ede68663/.gitmodules -------------------------------------------------------------------------------- /Examples/export-example.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: !IMPORTANT! Which maps to export. Can use "*" as a wildcard, for example "de_*,cs_*". 4 | SET MAPS="de_overpass,de_cache,de_cbble" 5 | 6 | :: !IMPORTANT! Directory to export to. 7 | SET OUTPUT_DIR="exported" 8 | 9 | :: !IMPORTANT! Should be the root URL of where the exported files will be on your web server. 10 | :: For example, if the output directory will be at "http://your-website.com/foo/exported", this 11 | :: should be either "/foo/exported" or "http://your-website.com/foo/exported" 12 | SET URL_PREFIX="/" 13 | 14 | :: This can be set to "true" if you wish to pause at the end of the script (eg. debugging purposes) 15 | SET SHOULD_PAUSE="false" 16 | 17 | :: Game install folder (should work for other Source games) 18 | SET GAME_DIR="C:\Program Files (x86)\Steam\steamapps\common\Counter-Strike Global Offensive\csgo" 19 | 20 | :: Where to look for the specified maps, relative to the game install folder 21 | SET MAPS_DIR="maps" 22 | 23 | :: Extra options: --overwrite, --verbose, --untextured 24 | SET OPTIONS="--overwrite --verbose" 25 | 26 | "bin\SourceUtils.WebExport.exe" ^ 27 | export ^ 28 | --maps %MAPS% ^ 29 | --outdir %OUTPUT_DIR% ^ 30 | --gamedir %GAME_DIR% ^ 31 | --mapsdir %MAPS_DIR% ^ 32 | --url-prefix %URL_PREFIX% ^ 33 | %OPTIONS:"=% 34 | 35 | if %SHOULD_PAUSE% == "true" pause 36 | -------------------------------------------------------------------------------- /Examples/host-example.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: Map to open in your web browser 4 | SET INITIAL_MAP="de_mirage" 5 | 6 | :: This can be set to "true" if you wish to pause at the end of the script (eg. debugging purposes) 7 | SET SHOULD_PAUSE="false" 8 | 9 | :: Game install folder (should work for other Source games) 10 | SET GAME_DIR="C:\Program Files (x86)\Steam\steamapps\common\Counter-Strike Global Offensive\csgo" 11 | 12 | :: Where to look for maps, relative to the game install folder 13 | SET MAPS_DIR="maps" 14 | 15 | :: Launch a browser window 16 | start "" "http://localhost:8080/maps/%INITIAL_MAP%/index.html" 17 | 18 | :: Extra options: --overwrite, --verbose, --untextured 19 | SET OPTIONS="--untextured" 20 | 21 | echo Launching web server... 22 | "bin\SourceUtils.WebExport.exe" ^ 23 | host ^ 24 | --gamedir %GAME_DIR% ^ 25 | --mapsdir %MAPS_DIR% ^ 26 | %OPTIONS:"=% 27 | 28 | if %SHOULD_PAUSE% == "true" pause -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 James King 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 | # SourceUtils 2 | Tools for reading and exporting source engine file formats. Also includes a web server to browse files / view maps. 3 | 4 | ![](http://files.facepunch.com/ziks/2017/March/06/chrome_2017-03-06_23-33-21.jpg) 5 | 6 | ## Untextured Demos 7 | 8 | * [Mirage](https://metapyziks.github.io/SourceUtils/maps/de_mirage/) 9 | * [Cobble](https://metapyziks.github.io/SourceUtils/maps/de_cbble/) 10 | * [Overpass](https://metapyziks.github.io/SourceUtils/maps/de_overpass/) 11 | * [Nuke](https://metapyziks.github.io/SourceUtils/maps/de_nuke/) 12 | * [Inferno](https://metapyziks.github.io/SourceUtils/maps/de_inferno/) 13 | -------------------------------------------------------------------------------- /SourceUtils.FileExport/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SourceUtils.FileExport/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandLine; 3 | 4 | namespace SourceUtils.FileExport 5 | { 6 | class Program 7 | { 8 | class Options 9 | { 10 | [Option( 'g', "gamedir", HelpText = "Game directory to export from.", Required = true )] 11 | public string GameDir { get; set; } 12 | 13 | [Option( 'v', "verbose", HelpText = "Write every action to standard output." )] 14 | public bool Verbose { get; set; } 15 | 16 | [Option( "untextured", HelpText = "Only export a single colour for each texture." )] 17 | public bool Untextured { get; set; } 18 | 19 | [Option('o', "outdir", HelpText = "Output directory.", Required = true)] 20 | public string OutDir { get; set; } 21 | 22 | [Option('m', "maps", HelpText = "Specific semi-colon separated map names to export (e.g. 'de_dust2;de_mirage').", Required = true)] 23 | public string Maps { get; set; } 24 | } 25 | 26 | static int Main( Options args ) 27 | { 28 | var maps = args.Maps.Split( new [] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries ); 29 | 30 | foreach ( var map in maps ) 31 | { 32 | 33 | } 34 | 35 | return 0; 36 | } 37 | 38 | static int Main(string[] args) 39 | { 40 | var result = Parser.Default.ParseArguments( args ); 41 | return result.MapResult( Main, _ => 1 ); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SourceUtils.FileExport/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SourceUtils.FileExport")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SourceUtils.FileExport")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1263be7b-1e7f-415a-9cf9-917cbdd3572f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SourceUtils.FileExport/SourceUtils.FileExport.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1263BE7B-1E7F-415A-9CF9-917CBDD3572F} 8 | Exe 9 | Properties 10 | SourceUtils.FileExport 11 | SourceUtils.FileExport 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\CommandLineParser.2.1.1-beta\lib\net45\CommandLine.dll 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {2c5fefa1-39ba-458e-b95c-2d8414958820} 60 | SourceUtils 61 | 62 | 63 | 64 | 71 | -------------------------------------------------------------------------------- /SourceUtils.FileExport/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /SourceUtils.Test/KeyValsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace SourceUtils.Test 5 | { 6 | [TestClass] 7 | public class KeyValsTest 8 | { 9 | [TestMethod] 10 | public void MultiRoot1() 11 | { 12 | const string src = @" 13 | { 14 | ""classname"" ""func_target"" 15 | ""origin"" ""0 1 2"" 16 | } 17 | { 18 | ""classname"" ""info_player_start"" 19 | ""origin"" ""7 -3.2 8"" 20 | } 21 | "; 22 | 23 | var keyVals = KeyValues.ParseList( src ); 24 | 25 | foreach ( var keyVal in keyVals ) 26 | { 27 | Console.WriteLine( (string) keyVal["classname"] ); 28 | } 29 | } 30 | 31 | [TestMethod] 32 | public void MultiRoot2() 33 | { 34 | var keyVals = KeyValues.ParseList( Properties.Resources.entities ); 35 | 36 | foreach ( var keyVal in keyVals ) 37 | { 38 | Console.WriteLine( (string) keyVal["classname"] ); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SourceUtils.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("SourceUtils.Test")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("SourceUtils.Test")] 10 | [assembly: AssemblyCopyright("Copyright © 2018")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("b17851ad-7b29-464b-b92a-73816d6d8d7c")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /SourceUtils.Test/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SourceUtils.Test.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SourceUtils.Test.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to { 65 | ///"world_maxs" "4232 3712 1756" 66 | ///"world_mins" "-5880 -4264 -20" 67 | ///"skyname" "sky_cs15_daylight01_hdr" 68 | ///"maxpropscreenwidth" "-1" 69 | ///"detailvbsp" "detail.vbsp" 70 | ///"detailmaterial" "detail/detailsprites" 71 | ///"classname" "worldspawn" 72 | ///"mapversion" "17816" 73 | ///"hammerid" "1" 74 | ///} 75 | ///{ 76 | ///"model" "*1" 77 | ///"vrad_brush_cast_shadows" "0" 78 | ///"StartDisabled" "0" 79 | ///"spawnflags" "2" 80 | ///"Solidity" "2" 81 | ///"solidbsp" "0" 82 | ///"shadowdepthnocache" "0" 83 | ///"rendermode" "0" 84 | ///"renderfx" "0" 85 | ///"rendercolor" "255 255 255" 86 | ///"renderamt" "255" 87 | ///"origin" "-3274 -38 [rest of string was truncated]";. 88 | /// 89 | internal static string entities { 90 | get { 91 | return ResourceManager.GetString("entities", resourceCulture); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /SourceUtils.Test/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\entities.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 123 | 124 | -------------------------------------------------------------------------------- /SourceUtils.Test/SourceUtils.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {B17851AD-7B29-464B-B92A-73816D6D8D7C} 8 | Library 9 | Properties 10 | SourceUtils.Test 11 | SourceUtils.Test 12 | v4.6.1 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | 42 | ..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 43 | 44 | 45 | ..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | True 55 | True 56 | Resources.resx 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {2c5fefa1-39ba-458e-b95c-2d8414958820} 65 | SourceUtils 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ResXFileCodeGenerator 74 | Resources.Designer.cs 75 | 76 | 77 | 78 | 79 | 80 | 81 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /SourceUtils.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Bsp/AmbientCubes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using Ziks.WebServer; 6 | 7 | namespace SourceUtils.WebExport.Bsp 8 | { 9 | public class AmbientCube 10 | { 11 | [JsonProperty("position")] 12 | public Vector3 Position { get; set; } 13 | 14 | [JsonProperty("samples")] 15 | public int[] Samples { get; set; } 16 | } 17 | 18 | public class AmbientPage 19 | { 20 | public const int LeavesPerPage = 4096; 21 | 22 | [JsonProperty("values")] 23 | public IEnumerable> Values { get; set; } 24 | } 25 | 26 | [Prefix("/maps/{map}/geom")] 27 | class AmbientController : ResourceController 28 | { 29 | [Get("/ambientpage{page}.json")] 30 | public AmbientPage Get( [Url] string map, [Url] int page ) 31 | { 32 | if ( Skip ) return null; 33 | 34 | var bsp = Program.GetMap(map); 35 | var first = page * AmbientPage.LeavesPerPage; 36 | var count = Math.Min( first + AmbientPage.LeavesPerPage, bsp.Leaves.Length ) - first; 37 | 38 | if ( count < 0 ) 39 | { 40 | first = bsp.Leaves.Length; 41 | count = 0; 42 | } 43 | 44 | var hdr = bsp.LeafAmbientLightingHdr.Length > bsp.LeafAmbientLighting.Length; 45 | var indices = hdr ? bsp.LeafAmbientIndicesHdr : bsp.LeafAmbientIndices; 46 | var ambients = hdr ? bsp.LeafAmbientLightingHdr : bsp.LeafAmbientLighting; 47 | 48 | return new AmbientPage 49 | { 50 | Values = Enumerable.Range( first, count ).Select( x => 51 | { 52 | var leaf = bsp.Leaves[x]; 53 | var index = indices[x]; 54 | var list = new List(index.AmbientSampleCount); 55 | 56 | var min = new SourceUtils.Vector3(leaf.Min.X, leaf.Min.Y, leaf.Min.Z); 57 | var max = new SourceUtils.Vector3(leaf.Max.X, leaf.Max.Y, leaf.Max.Z); 58 | 59 | for (var i = index.FirstAmbientSample; i < index.FirstAmbientSample + index.AmbientSampleCount; ++i) 60 | { 61 | var ambient = ambients[i]; 62 | var samples = new int[6]; 63 | var relPos = new SourceUtils.Vector3(ambient.X, ambient.Y, ambient.Z) * (1f / 255f); 64 | 65 | for (var j = 0; j < 6; ++j) 66 | { 67 | samples[j] = ambient.Cube[j]; 68 | } 69 | 70 | list.Add(new AmbientCube 71 | { 72 | Position = (min + relPos * (max - min)).Rounded, 73 | Samples = samples 74 | }); 75 | } 76 | 77 | return list; 78 | } ) 79 | }; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Bsp/BrushModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Newtonsoft.Json; 4 | using SourceUtils.ValveBsp; 5 | using Ziks.WebServer; 6 | 7 | namespace SourceUtils.WebExport.Bsp 8 | { 9 | public struct Plane 10 | { 11 | public static implicit operator Plane(ValveBsp.Plane plane) 12 | { 13 | return new Plane(plane); 14 | } 15 | 16 | [JsonProperty("norm")] 17 | public Vector3 Normal { get; set; } 18 | 19 | [JsonProperty("dist")] 20 | public float Dist { get; set; } 21 | 22 | public Plane(ValveBsp.Plane plane) 23 | { 24 | Normal = plane.Normal; 25 | Dist = plane.Dist; 26 | } 27 | } 28 | 29 | public struct Vector3 30 | { 31 | public static implicit operator Vector3(SourceUtils.Vector3 vec) 32 | { 33 | return new Vector3(vec); 34 | } 35 | 36 | public static implicit operator SourceUtils.Vector3(Vector3 vec) 37 | { 38 | return new SourceUtils.Vector3(vec.X, vec.Y, vec.Z); 39 | } 40 | 41 | public static implicit operator Vector3(Vector3S vec) 42 | { 43 | return new Vector3(vec); 44 | } 45 | 46 | [JsonProperty("x")] 47 | public float X; 48 | 49 | [JsonProperty("y")] 50 | public float Y; 51 | 52 | [JsonProperty("z")] 53 | public float Z; 54 | 55 | public Vector3(float x, float y, float z) 56 | { 57 | X = x; 58 | Y = y; 59 | Z = z; 60 | } 61 | 62 | public Vector3(SourceUtils.Vector3 vec) 63 | { 64 | X = vec.X; 65 | Y = vec.Y; 66 | Z = vec.Z; 67 | } 68 | 69 | public Vector3(Vector3S vec) 70 | { 71 | X = vec.X; 72 | Y = vec.Y; 73 | Z = vec.Z; 74 | } 75 | } 76 | 77 | public class BspElement 78 | { 79 | [JsonProperty("min")] 80 | public Vector3 Min { get; set; } 81 | 82 | [JsonProperty("max")] 83 | public Vector3 Max { get; set; } 84 | } 85 | 86 | public class BspNode : BspElement 87 | { 88 | [JsonProperty("plane")] 89 | public Plane Plane { get; set; } 90 | 91 | [JsonProperty("children")] 92 | public BspElement[] Children { get; } = new BspElement[2]; 93 | } 94 | 95 | public class BspLeaf : BspElement 96 | { 97 | [JsonProperty("index")] 98 | public int Index { get; set; } 99 | 100 | [JsonProperty("flags")] 101 | public LeafFlags Flags { get; set; } 102 | 103 | [JsonProperty("hasFaces")] 104 | public bool HasFaces { get; set; } 105 | 106 | [JsonProperty("cluster")] 107 | public int? Cluster { get; set; } 108 | } 109 | 110 | public class BspModel 111 | { 112 | [JsonProperty("index")] 113 | public int Index { get; set; } 114 | 115 | [JsonProperty("min")] 116 | public Vector3 Min { get; set; } 117 | 118 | [JsonProperty("max")] 119 | public Vector3 Max { get; set; } 120 | 121 | [JsonProperty("origin")] 122 | public Vector3 Origin { get; set; } 123 | 124 | [JsonProperty("headNode")] 125 | public BspNode HeadNode { get; set; } 126 | } 127 | 128 | public class BspModelPage 129 | { 130 | public const int FacesPerPage = 8192; 131 | 132 | [JsonProperty("models")] 133 | public List Models { get; } = new List(); 134 | } 135 | 136 | [Prefix( "/maps/{map}/geom" )] 137 | class ModelController : ResourceController 138 | { 139 | private BspElement ConvertElement( ValveBspFile bsp, BspChild child ) 140 | { 141 | return child.IsLeaf ? (BspElement) ConvertLeaf( bsp, child.Index ) : ConvertNode( bsp, child.Index ); 142 | } 143 | 144 | private BspNode ConvertNode( ValveBspFile bsp, int index ) 145 | { 146 | var node = bsp.Nodes[index]; 147 | var response = new BspNode 148 | { 149 | Min = node.Min, 150 | Max = node.Max, 151 | Plane = bsp.Planes[node.PlaneNum] 152 | }; 153 | 154 | response.Children[0] = ConvertElement( bsp, node.ChildA ); 155 | response.Children[1] = ConvertElement( bsp, node.ChildB ); 156 | 157 | return response; 158 | } 159 | 160 | private BspLeaf ConvertLeaf( ValveBspFile bsp, int index ) 161 | { 162 | var leaf = bsp.Leaves[index]; 163 | var response = new BspLeaf 164 | { 165 | Index = index, 166 | Min = leaf.Min, 167 | Max = leaf.Max, 168 | Flags = leaf.AreaFlags.Flags, 169 | HasFaces = leaf.NumLeafFaces > 0 170 | }; 171 | 172 | if ( leaf.Cluster != -1 ) response.Cluster = leaf.Cluster; 173 | 174 | return response; 175 | } 176 | 177 | protected override bool ForceNoFormatting => true; 178 | 179 | [Get( "/bsppage{index}.json" )] 180 | public BspModelPage GetPage( [Url] string map, [Url] int index ) 181 | { 182 | var bsp = Program.GetMap( map ); 183 | 184 | var info = IndexController.GetPageLayout( bsp, bsp.Models.Length, 185 | BspModelPage.FacesPerPage, null, i => bsp.Models[i].NumFaces ).Skip( index ).FirstOrDefault(); 186 | 187 | var first = info?.First ?? StudioModelDictionary.GetResourceCount( bsp ); 188 | var count = info?.Count ?? 0; 189 | 190 | var page = new BspModelPage(); 191 | 192 | for ( var i = first; i < first + count; ++i ) 193 | { 194 | var model = bsp.Models[i]; 195 | 196 | page.Models.Add( new BspModel 197 | { 198 | Index = index, 199 | Min = model.Min, 200 | Max = model.Max, 201 | Origin = model.Origin, 202 | HeadNode = ConvertNode( bsp, model.HeadNode ) 203 | } ); 204 | } 205 | 206 | return page; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Bsp/Lightmap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using ImageMagick; 4 | using MimeTypes; 5 | using OpenTK.Graphics.ES20; 6 | using SourceUtils.ValveBsp; 7 | using Ziks.WebServer; 8 | 9 | namespace SourceUtils.WebExport.Bsp 10 | { 11 | [Prefix("/maps/{map}")] 12 | class LightmapController : ResourceController 13 | { 14 | [Get("/lightmap.json")] 15 | public Texture GetInfo( [Url] string map ) 16 | { 17 | var bsp = Program.GetMap(map); 18 | 19 | return new Texture 20 | { 21 | Target = TextureTarget.Texture2D, 22 | Width = bsp.LightmapLayout.TextureSize.X, 23 | Height = bsp.LightmapLayout.TextureSize.Y, 24 | Params = 25 | { 26 | WrapS = TextureWrapMode.ClampToEdge, 27 | WrapT = TextureWrapMode.ClampToEdge, 28 | Filter = TextureMinFilter.Nearest, 29 | MipMap = false 30 | }, 31 | Elements = 32 | { 33 | new TextureElement 34 | { 35 | Level = 0, 36 | Url = $"/maps/{map}/lightmap.png" 37 | } 38 | } 39 | }; 40 | } 41 | 42 | [Get("/lightmap.png")] 43 | public void Get( [Url] string map ) 44 | { 45 | if ( Skip ) 46 | { 47 | Response.Close(); 48 | return; 49 | } 50 | 51 | Response.ContentType = MimeTypeMap.GetMimeType(".png"); 52 | 53 | var bsp = Program.GetMap( map ); 54 | 55 | var lightingLump = bsp.LightingHdr.Length > 0 ? bsp.LightingHdr.LumpType : bsp.Lighting.LumpType; 56 | using (var sampleStream = bsp.GetLumpStream(lightingLump)) 57 | { 58 | var lightmap = bsp.LightmapLayout; 59 | var width = lightmap.TextureSize.X; 60 | var height = lightmap.TextureSize.Y; 61 | 62 | var pixels = new byte[width * height * 4]; 63 | 64 | var sampleBuffer = new ColorRGBExp32[256 * 256]; 65 | var faces = bsp.FacesHdr.Length > 0 ? bsp.FacesHdr : bsp.Faces; 66 | 67 | for (int i = 0, iEnd = faces.Length; i < iEnd; ++i) 68 | { 69 | var face = faces[i]; 70 | if (face.LightOffset == -1) continue; 71 | 72 | var rect = lightmap.GetLightmapRegion(i); 73 | var sampleCount = rect.Width * rect.Height; 74 | 75 | sampleStream.Seek(face.LightOffset, SeekOrigin.Begin); 76 | 77 | LumpReader.ReadLumpFromStream(sampleStream, sampleCount, sampleBuffer); 78 | 79 | for (var y = 0; y < rect.Height; ++y) 80 | for (var x = 0; x < rect.Width; ++x) 81 | { 82 | var s = Math.Max(0, Math.Min(x, rect.Width - 1)); 83 | var t = Math.Max(0, Math.Min(y, rect.Height - 1)); 84 | 85 | var index = (rect.X + x + width * (rect.Y + y)) * 4; 86 | var sampleIndex = s + t * rect.Width; 87 | var sample = sampleBuffer[sampleIndex]; 88 | 89 | pixels[index + 0] = sample.B; 90 | pixels[index + 1] = sample.G; 91 | pixels[index + 2] = sample.R; 92 | pixels[index + 3] = (byte)(sample.Exponent + 128); 93 | } 94 | } 95 | 96 | using ( var img = new MagickImage( pixels, new MagickReadSettings 97 | { 98 | Width = width, 99 | Height = height, 100 | PixelStorage = new PixelStorageSettings( StorageType.Char, "BGRA" ) 101 | } ) ) 102 | { 103 | img.Write( Response.OutputStream, MagickFormat.Png ); 104 | } 105 | 106 | Response.OutputStream.Close(); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Bsp/Materials.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using SourceUtils.ValveBsp.Entities; 5 | using Ziks.WebServer; 6 | 7 | namespace SourceUtils.WebExport.Bsp 8 | { 9 | public class MaterialPage 10 | { 11 | public const int MaterialsPerPage = 8192; 12 | 13 | [JsonProperty("textures")] 14 | public List Textures { get; } = new List(); 15 | 16 | [JsonProperty("materials")] 17 | public List Materials { get; } = new List(); 18 | } 19 | 20 | [Prefix("/maps/{map}/materials")] 21 | class MaterialController : ResourceController 22 | { 23 | [Get("/matpage{index}.json")] 24 | public MaterialPage GetPage( [Url] string map, [Url] int index ) 25 | { 26 | var bsp = Program.GetMap(map); 27 | var first = index * MaterialPage.MaterialsPerPage; 28 | var count = Math.Min(first + MaterialPage.MaterialsPerPage, MaterialDictionary.GetResourceCount( bsp )) - first; 29 | 30 | if (count < 0) 31 | { 32 | first = MaterialDictionary.GetResourceCount( bsp); 33 | count = 0; 34 | } 35 | 36 | var texDict = new Dictionary(); 37 | 38 | var page = new MaterialPage(); 39 | 40 | for ( var i = 0; i < count; ++i ) 41 | { 42 | var path = MaterialDictionary.GetResourcePath( bsp, first + i ); 43 | var mat = Material.Get(bsp, path); 44 | page.Materials.Add(mat); 45 | 46 | if ( mat == null ) 47 | { 48 | Console.ForegroundColor = ConsoleColor.Yellow; 49 | Console.WriteLine($"Missing material '{path}'!"); 50 | Console.ResetColor(); 51 | continue; 52 | } 53 | 54 | foreach ( var prop in mat.Properties ) 55 | { 56 | if ( prop.Type != MaterialPropertyType.TextureUrl ) continue; 57 | 58 | prop.Type = MaterialPropertyType.TextureIndex; 59 | 60 | var texUrl = (Url) prop.Value; 61 | int texIndex; 62 | if ( texDict.TryGetValue( texUrl, out texIndex ) ) 63 | { 64 | prop.Value = texIndex; 65 | continue; 66 | } 67 | 68 | prop.Value = texIndex = page.Textures.Count; 69 | 70 | var texPath = TextureController.GetTexturePath( texUrl ); 71 | var tex = Texture.Get( bsp, texPath ); 72 | 73 | if ( tex == null ) 74 | { 75 | Console.ForegroundColor = ConsoleColor.Yellow; 76 | Console.WriteLine($"Missing texture '{texPath}'!"); 77 | Console.ResetColor(); 78 | } 79 | 80 | texDict.Add( texUrl, texIndex ); 81 | page.Textures.Add( tex ); 82 | } 83 | } 84 | 85 | return page; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Bsp/Visibility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using Ziks.WebServer; 6 | 7 | namespace SourceUtils.WebExport.Bsp 8 | { 9 | public class VisPage 10 | { 11 | public const int ClustersPerPage = 8192; 12 | 13 | [JsonProperty("values")] 14 | public IEnumerable> Values { get; set; } 15 | } 16 | 17 | [Prefix("/maps/{map}/geom")] 18 | class VisController : ResourceController 19 | { 20 | [Get("/vispage{page}.json")] 21 | public VisPage Get( [Url] string map, [Url] int page ) 22 | { 23 | if ( Skip ) return null; 24 | 25 | var bsp = Program.GetMap(map); 26 | var first = page * VisPage.ClustersPerPage; 27 | var count = Math.Min( first + VisPage.ClustersPerPage, bsp.Visibility.NumClusters ) - first; 28 | 29 | if ( count < 0 ) 30 | { 31 | first = bsp.Visibility.NumClusters; 32 | count = 0; 33 | } 34 | 35 | return new VisPage 36 | { 37 | Values = Enumerable.Range( first, count ).Select( x => new CompressedList( bsp.Visibility[x] ) ) 38 | }; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/ModelPatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text.RegularExpressions; 4 | using CommandLine; 5 | 6 | namespace SourceUtils.WebExport 7 | { 8 | enum ReplacementType 9 | { 10 | Name, 11 | Directory 12 | } 13 | 14 | struct ReplacementCommand 15 | { 16 | private static readonly Regex _sCommandRegex = new Regex(@"^\s*(?n(ame)?|d(ir(ectory)?)?)\s*\[\s*(?[0-9]+|\*)\s*\]\s*(?\+?[=:])\s*(?.+)\s*$", RegexOptions.IgnoreCase); 17 | private static readonly Regex _sReplaceRegex = new Regex(@"\$\{\s*(?[a-zA-Z0-9_-]+)\s*\}"); 18 | 19 | public static bool TryParse(string value, out ReplacementCommand cmd) 20 | { 21 | cmd = default(ReplacementCommand); 22 | 23 | var match = _sCommandRegex.Match(value); 24 | if (!match.Success) return false; 25 | 26 | var type = match.Groups["type"].Value[0] == 'n' 27 | ? ReplacementType.Name 28 | : ReplacementType.Directory; 29 | 30 | var index = match.Groups["index"].Value == "*" ? -1 : int.Parse(match.Groups["index"].Value); 31 | 32 | cmd = new ReplacementCommand(type, index, match.Groups["value"].Value); 33 | 34 | return true; 35 | } 36 | 37 | public readonly ReplacementType Type; 38 | public readonly int Index; 39 | public readonly string Value; 40 | 41 | public bool Wildcard => Index == -1; 42 | 43 | public ReplacementCommand(ReplacementType type, int index, string value) 44 | { 45 | Type = type; 46 | Index = index; 47 | Value = value; 48 | } 49 | 50 | public string GetFormattedValue(int index, string original) 51 | { 52 | return _sReplaceRegex.Replace(Value, match => 53 | { 54 | var name = match.Groups["name"].Value; 55 | 56 | switch (name.ToLower()) 57 | { 58 | case "index": 59 | return index.ToString(); 60 | case "dir": 61 | return Path.GetDirectoryName(original); 62 | case "name": 63 | return Path.GetFileName(original); 64 | case "path": 65 | return original; 66 | default: 67 | return ""; 68 | } 69 | }); 70 | } 71 | } 72 | 73 | class ModelBaseOptions : BaseOptions 74 | { 75 | [Option('i', "input", HelpText = "Input model path.", Required = true)] 76 | public string InputPath { get; set; } 77 | 78 | [Option('o', "output", HelpText = "Output path to write to.")] 79 | public string OutputPath { get; set; } 80 | } 81 | 82 | [Verb("model-patch", HelpText = "Replace one or more textures in a Studio Model.")] 83 | class ModelPatchOptions : ModelBaseOptions 84 | { 85 | [Option('r', "replace", Separator = ';', HelpText = "Replacement commands.")] 86 | public IEnumerable Replace { get; set; } 87 | 88 | [Option('f', "flags", HelpText = "Replacement Studio Model flags.")] 89 | public string Flags { get; set; } 90 | } 91 | 92 | [Verb("material-extract", HelpText = "Dump materials used by a Studio Model.")] 93 | class MaterialExtractOptions : ModelBaseOptions 94 | { 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SourceUtils.WebExport")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SourceUtils.WebExport")] 13 | [assembly: AssemblyCopyright("Copyright © James King 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("171622ad-547b-496c-a230-16fca08a0112")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.1.*")] 36 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/ResourceDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace SourceUtils.WebExport 6 | { 7 | public abstract class ResourceDictionary 8 | where TDictionary : ResourceDictionary, new() 9 | { 10 | private static readonly Dictionary _sDicts = 11 | new Dictionary(); 12 | 13 | protected static TDictionary GetDictionary( ValveBspFile bsp ) 14 | { 15 | TDictionary dict; 16 | if ( _sDicts.TryGetValue( bsp, out dict ) ) return dict; 17 | 18 | dict = new TDictionary(); 19 | dict.FindResourcePaths( bsp ); 20 | 21 | bsp.Disposing += _ => _sDicts.Remove( bsp ); 22 | 23 | _sDicts.Add( bsp, dict ); 24 | 25 | return dict; 26 | } 27 | 28 | public static int GetResourceCount( ValveBspFile bsp ) 29 | { 30 | return GetDictionary( bsp ).ResourceCount; 31 | } 32 | 33 | public static int GetResourceIndex( ValveBspFile bsp, string path ) 34 | { 35 | return GetDictionary( bsp ).GetResourceIndex( path ); 36 | } 37 | 38 | public static string GetResourcePath( ValveBspFile bsp, int index ) 39 | { 40 | return GetDictionary( bsp ).GetResourcePath( index ); 41 | } 42 | 43 | private readonly List _resources = new List(); 44 | 45 | private readonly Dictionary _indices = 46 | new Dictionary( StringComparer.InvariantCultureIgnoreCase ); 47 | 48 | public int ResourceCount => _resources.Count; 49 | 50 | private void FindResourcePaths( ValveBspFile bsp ) 51 | { 52 | foreach ( var path in OnFindResourcePaths( bsp ) ) 53 | { 54 | Add( path ); 55 | } 56 | } 57 | 58 | protected abstract IEnumerable OnFindResourcePaths( ValveBspFile bsp ); 59 | 60 | protected virtual string NormalizePath( string path ) 61 | { 62 | return path.ToLower().Replace( '\\', '/' ).Replace( "//", "/" ); 63 | } 64 | 65 | private void Add( string path ) 66 | { 67 | path = NormalizePath( path ); 68 | 69 | if ( _indices.ContainsKey( path ) ) return; 70 | 71 | _indices.Add( path, _resources.Count ); 72 | _resources.Add( path ); 73 | } 74 | 75 | public string GetResourcePath( int index ) 76 | { 77 | return _resources[index]; 78 | } 79 | 80 | public int GetResourceIndex( string path ) 81 | { 82 | path = NormalizePath( path ); 83 | 84 | int index; 85 | if ( _indices.TryGetValue( path, out index ) ) return index; 86 | return -1; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/index.template.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Map Viewer - ${mapName} 7 | 8 | 9 | 10 | 11 | 12 | 22 | 23 | 24 |

Source Map Viewer Demo - ${mapName}

25 |
26 | 37 |
38 | 39 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/AmbientLoader.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | export interface IAmbientPage { 5 | values: IAmbientSample[][]; 6 | } 7 | 8 | export interface IAmbientSample { 9 | position: Facepunch.IVector3; 10 | samples: number[]; 11 | } 12 | 13 | export class AmbientPage extends ResourcePage { 14 | protected onGetValue(index: number): IAmbientSample[] { 15 | return this.page.values[index]; 16 | } 17 | } 18 | 19 | export class AmbientLoader extends PagedLoader { 20 | protected onCreatePage(page: IPageInfo): AmbientPage { 21 | return new AmbientPage(page); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/DispGeometryLoader.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export interface IDispGeometryPage { 7 | displacements: IFace[]; 8 | materials: IMaterialGroup[]; 9 | } 10 | 11 | export class DispGeometryPage extends ResourcePage { 12 | private readonly viewer: MapViewer; 13 | 14 | private matGroups: WebGame.MeshHandle[][]; 15 | private dispFaces: IFace[]; 16 | 17 | constructor(viewer: MapViewer, page: IPageInfo) { 18 | super(page); 19 | 20 | this.viewer = viewer; 21 | } 22 | 23 | onLoadValues(page: IDispGeometryPage): void { 24 | this.matGroups = new Array(page.materials.length); 25 | this.dispFaces = page.displacements; 26 | 27 | for (let i = 0, iEnd = page.materials.length; i < iEnd; ++i) { 28 | const matGroup = page.materials[i]; 29 | const mat = this.viewer.mapMaterialLoader.loadMaterial(matGroup.material); 30 | const data = WebGame.MeshManager.decompress(matGroup.meshData); 31 | this.matGroups[i] = this.viewer.meshes.addMeshData(data, index => mat); 32 | } 33 | 34 | super.onLoadValues(page); 35 | } 36 | 37 | protected onGetValue(index: number): Facepunch.WebGame.MeshHandle { 38 | const dispFace = this.dispFaces[index]; 39 | if (dispFace.element === -1 || dispFace.material === -1) return null; 40 | return this.matGroups[dispFace.material][dispFace.element]; 41 | } 42 | } 43 | 44 | export class DispGeometryLoader extends PagedLoader { 45 | readonly viewer: MapViewer; 46 | 47 | constructor(viewer: MapViewer) { 48 | super(); 49 | 50 | this.viewer = viewer; 51 | } 52 | 53 | protected onCreatePage(page: IPageInfo): DispGeometryPage { 54 | return new DispGeometryPage(this.viewer, page); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Entities/BrushEntity.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export namespace Entities { 7 | export interface IBrushEntity extends IPvsEntity { 8 | model: number; 9 | } 10 | 11 | export class BrushEntity extends PvsEntity { 12 | readonly model: BspModel; 13 | readonly isWorldSpawn: boolean; 14 | 15 | constructor(map: Map, info: IBrushEntity) { 16 | super(map, info); 17 | 18 | this.isWorldSpawn = info.model === 0; 19 | 20 | this.model = map.viewer.bspModelLoader.loadModel(info.model); 21 | this.model.addUsage(this); 22 | this.model.addOnLoadCallback(model => { 23 | const leaves = model.getLeaves(); 24 | for (let i = 0, iEnd = leaves.length; i < iEnd; ++i) { 25 | leaves[i].entity = this; 26 | } 27 | }); 28 | } 29 | 30 | onAddToDrawList(list: Facepunch.WebGame.DrawList): void { 31 | super.onAddToDrawList(list); 32 | 33 | if (this.isWorldSpawn) return; 34 | 35 | const leaves = this.model.getLeaves(); 36 | if (leaves != null) list.addItems(leaves); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Entities/Displacement.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | import WebGame = Facepunch.WebGame; 3 | 4 | export namespace Entities { 5 | export interface IDisplacement extends IPvsEntity { 6 | index: number; 7 | } 8 | 9 | export class Displacement extends PvsEntity { 10 | private readonly index: number; 11 | private isLoaded = false; 12 | 13 | constructor(map: Map, info: IDisplacement) { 14 | super(map, info); 15 | 16 | this.index = info.index; 17 | } 18 | 19 | onAddToDrawList(list: Facepunch.WebGame.DrawList): void { 20 | if (!this.isLoaded) { 21 | this.isLoaded = true; 22 | this.map.viewer.dispGeometryLoader.load(this.index, handle => { 23 | if (handle != null) this.drawable.addMeshHandles([handle]); 24 | }); 25 | } 26 | 27 | super.onAddToDrawList(list); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Entities/PvsEntity.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | import WebGame = Facepunch.WebGame; 3 | 4 | export namespace Entities { 5 | export interface IEntity { 6 | classname: string; 7 | targetname?: string; 8 | origin?: Facepunch.IVector3; 9 | angles?: Facepunch.IVector3; 10 | scale?: number; 11 | } 12 | 13 | export interface IColor { 14 | r: number; 15 | g: number; 16 | b: number; 17 | } 18 | 19 | export interface IEnvFogController extends IEntity { 20 | fogEnabled: boolean; 21 | fogStart: number; 22 | fogEnd: number; 23 | fogMaxDensity: number; 24 | farZ: number; 25 | fogColor: IColor; 26 | } 27 | 28 | export class Entity extends WebGame.DrawableEntity { 29 | readonly map: Map; 30 | readonly targetname: string; 31 | 32 | constructor(map: Map, info: IEntity) { 33 | super(true); 34 | 35 | this.map = map; 36 | this.targetname = info.targetname; 37 | 38 | if (this.targetname != null) { 39 | this.map.addNamedEntity(this.targetname, this); 40 | } 41 | 42 | if (info.origin !== undefined) { 43 | this.setPosition(info.origin); 44 | } 45 | 46 | if (info.angles !== undefined) { 47 | const mul = Math.PI / 180; 48 | this.setAngles(info.angles.x * mul, info.angles.y * mul, info.angles.z * mul); 49 | } 50 | 51 | if (info.scale !== undefined) { 52 | this.setScale(info.scale); 53 | } 54 | } 55 | } 56 | 57 | export interface IPvsEntity extends IEntity { 58 | clusters: number[]; 59 | } 60 | 61 | export class PvsEntity extends Entity { 62 | private readonly clusters: number[]; 63 | 64 | constructor(map: Map, info: IPvsEntity) { 65 | super(map, info); 66 | 67 | this.clusters = info.clusters; 68 | } 69 | 70 | isInCluster(cluster: number): boolean { 71 | const clusters = this.clusters; 72 | if (clusters == null) return true; 73 | for (let i = 0, iEnd = clusters.length; i < iEnd; ++i) { 74 | if (clusters[i] === cluster) return true; 75 | } 76 | return false; 77 | } 78 | 79 | isInAnyCluster(clusters: number[]): boolean { 80 | if (clusters == null) return true; 81 | for (let i = 0, iEnd = clusters.length; i < iEnd; ++i) { 82 | if (this.isInCluster(clusters[i])) return true; 83 | } 84 | return false; 85 | } 86 | 87 | populateDrawList(drawList: WebGame.DrawList, clusters: number[]): void { 88 | drawList.addItem(this); 89 | this.onPopulateDrawList(drawList, clusters); 90 | } 91 | 92 | protected onPopulateDrawList(drawList: WebGame.DrawList, clusters: number[]): void {} 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Entities/StaticProp.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export namespace Entities { 7 | export interface IStaticProp extends IPvsEntity { 8 | model: number; 9 | vertLighting?: number; 10 | albedoModulation?: number; 11 | } 12 | 13 | export class StaticProp extends PvsEntity { 14 | readonly model: StudioModel; 15 | 16 | private readonly info: IStaticProp; 17 | 18 | private lighting: (number[][] | BspLeaf); 19 | private albedoModulation?: number; 20 | 21 | constructor(map: Map, info: IStaticProp) { 22 | super(map, info); 23 | 24 | this.info = info; 25 | this.albedoModulation = info.albedoModulation; 26 | 27 | if (this.info.vertLighting !== undefined) { 28 | this.map.viewer.vertLightingLoader.load(this.info.vertLighting, value => { 29 | this.lighting = value; 30 | this.checkLoaded(); 31 | }); 32 | } else { 33 | // TODO: lighting offset 34 | this.map.getLeafAt(this.info.origin, leaf => { 35 | if (leaf == null) { 36 | this.lighting = null; 37 | this.checkLoaded(); 38 | } else { 39 | leaf.getAmbientCube(null, null, success => { 40 | this.lighting = leaf; 41 | this.checkLoaded(); 42 | }); 43 | } 44 | }); 45 | } 46 | 47 | this.model = map.viewer.studioModelLoader.loadModel(info.model); 48 | this.model.addUsage(this); 49 | this.model.addOnLoadCallback(model => { 50 | this.checkLoaded(); 51 | }); 52 | } 53 | 54 | private checkLoaded(): void { 55 | if (!this.model.isLoaded()) return; 56 | if (this.lighting === undefined) return; 57 | 58 | this.drawable.addMeshHandles(this.model.createMeshHandles(0, this.getMatrix(), this.lighting, this.albedoModulation)); 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Entities/Worldspawn.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | import WebGame = Facepunch.WebGame; 3 | 4 | export namespace Entities { 5 | export interface IWorldspawn extends IBrushEntity { 6 | skyMaterial: WebGame.IMaterialInfo; 7 | } 8 | 9 | export class Worldspawn extends BrushEntity { 10 | private readonly clusterLeaves: {[cluster: number]: BspLeaf[]} = {}; 11 | 12 | constructor(map: Map, info: IWorldspawn) { 13 | super(map, info); 14 | 15 | this.model.addOnLoadCallback(model => this.onModelLoad()); 16 | } 17 | 18 | private onModelLoad(): void { 19 | const leaves = this.model.getLeaves(); 20 | 21 | for (let i = 0, iEnd = leaves.length; i < iEnd; ++i) { 22 | const leaf = leaves[i]; 23 | if (leaf.cluster === undefined) continue; 24 | 25 | let clusterLeaves = this.clusterLeaves[leaf.cluster]; 26 | if (clusterLeaves == null) { 27 | this.clusterLeaves[leaf.cluster] = clusterLeaves = []; 28 | } 29 | 30 | clusterLeaves.push(leaf); 31 | } 32 | 33 | this.map.viewer.forceDrawListInvalidation(true); 34 | } 35 | 36 | isInAnyCluster(clusters: number[]): boolean { 37 | return true; 38 | } 39 | 40 | isInCluster(cluster: number): boolean { 41 | return true; 42 | } 43 | 44 | protected onPopulateDrawList(drawList: Facepunch.WebGame.DrawList, clusters: number[]): void { 45 | if (clusters == null) { 46 | const leaves = this.model.getLeaves(); 47 | if (leaves != null) drawList.addItems(leaves); 48 | return; 49 | } 50 | 51 | for (let i = 0, iEnd = clusters.length; i < iEnd; ++i) { 52 | const cluster = clusters[i]; 53 | const clusterLeaves = this.clusterLeaves[cluster]; 54 | if (clusterLeaves != null) drawList.addItems(clusterLeaves); 55 | } 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/LeafGeometryLoader.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export interface IFace { 7 | material: number; 8 | element: number; 9 | } 10 | 11 | export interface IMaterialGroup { 12 | material: number; 13 | meshData: WebGame.ICompressedMeshData; 14 | } 15 | 16 | export interface ILeafGeometryPage { 17 | leaves: IFace[][]; 18 | materials: IMaterialGroup[]; 19 | } 20 | 21 | export class LeafGeometryPage extends ResourcePage { 22 | private readonly viewer: MapViewer; 23 | 24 | private matGroups: WebGame.MeshHandle[][]; 25 | private leafFaces: IFace[][]; 26 | 27 | constructor(viewer: MapViewer, page: IPageInfo) { 28 | super(page); 29 | 30 | this.viewer = viewer; 31 | } 32 | 33 | onLoadValues(page: ILeafGeometryPage): void { 34 | this.matGroups = new Array(page.materials.length); 35 | this.leafFaces = page.leaves; 36 | 37 | for (let i = 0, iEnd = page.materials.length; i < iEnd; ++i) { 38 | const matGroup = page.materials[i]; 39 | const mat = this.viewer.mapMaterialLoader.loadMaterial(matGroup.material); 40 | const data = WebGame.MeshManager.decompress(matGroup.meshData); 41 | this.matGroups[i] = this.viewer.meshes.addMeshData(data, index => mat); 42 | } 43 | 44 | super.onLoadValues(page); 45 | } 46 | 47 | protected onGetValue(index: number): Facepunch.WebGame.MeshHandle[] { 48 | const leafFaces = this.leafFaces[index]; 49 | 50 | const handles = new Array(leafFaces.length); 51 | for (let i = 0, iEnd = leafFaces.length; i < iEnd; ++i) { 52 | const leafFace = leafFaces[i]; 53 | handles[i] = this.matGroups[leafFace.material][leafFace.element]; 54 | } 55 | 56 | return handles; 57 | } 58 | } 59 | 60 | export class LeafGeometryLoader extends PagedLoader { 61 | readonly viewer: MapViewer; 62 | 63 | constructor(viewer: MapViewer) { 64 | super(); 65 | 66 | this.viewer = viewer; 67 | } 68 | 69 | protected onCreatePage(page: IPageInfo): LeafGeometryPage { 70 | return new LeafGeometryPage(this.viewer, page); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/MapMaterialLoader.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export interface IMapMaterialPage { 7 | textures: WebGame.ITextureInfo[]; 8 | materials: WebGame.IMaterialInfo[]; 9 | } 10 | 11 | export class MapMaterialPage extends ResourcePage { 12 | private readonly viewer: MapViewer; 13 | 14 | private materials: WebGame.IMaterialInfo[]; 15 | 16 | constructor(viewer: MapViewer, page: IPageInfo) { 17 | super(page); 18 | 19 | this.viewer = viewer; 20 | } 21 | 22 | onLoadValues(page: IMapMaterialPage): void { 23 | this.materials = page.materials; 24 | 25 | const textures = page.textures; 26 | for (let i = 0, iEnd = this.materials.length; i < iEnd; ++i) { 27 | const mat = this.materials[i]; 28 | if (mat == null) continue; 29 | const props = mat.properties; 30 | for (let j = 0, jEnd = props.length; j < jEnd; ++j) { 31 | const prop = props[j]; 32 | if (prop.type !== WebGame.MaterialPropertyType.TextureIndex) continue; 33 | prop.type = WebGame.MaterialPropertyType.TextureInfo; 34 | prop.value = textures[prop.value as number]; 35 | } 36 | } 37 | 38 | super.onLoadValues(page); 39 | } 40 | 41 | protected onGetValue(index: number): WebGame.IMaterialInfo { 42 | return this.materials[index]; 43 | } 44 | } 45 | 46 | export class MapMaterialLoader extends PagedLoader { 47 | readonly viewer: MapViewer; 48 | 49 | private readonly materials: {[index: number]: WebGame.MaterialLoadable} = {}; 50 | 51 | constructor(viewer: MapViewer) { 52 | super(); 53 | this.viewer = viewer; 54 | } 55 | 56 | loadMaterial(index: number): WebGame.Material { 57 | let material = this.materials[index]; 58 | if (material !== undefined) return material; 59 | this.materials[index] = material = new WebGame.MaterialLoadable(this.viewer); 60 | this.load(index, info => info == null ? null : material.loadFromInfo(info)); 61 | return material; 62 | } 63 | 64 | protected onCreatePage(page: IPageInfo): MapMaterialPage { 65 | return new MapMaterialPage(this.viewer, page); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/PagedLoader.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | export interface IPageRequest { 3 | index: number; 4 | callback: (payload: TValue, page: TPage) => void; 5 | } 6 | 7 | export abstract class ResourcePage { 8 | readonly first: number; 9 | readonly count: number; 10 | readonly url: string; 11 | 12 | private readonly values: TValue[]; 13 | 14 | private toLoad: IPageRequest>[] = []; 15 | 16 | protected page: TPayload; 17 | 18 | constructor(info: IPageInfo) { 19 | this.first = info.first; 20 | this.count = info.count; 21 | this.url = info.url; 22 | this.values = new Array(info.count); 23 | } 24 | 25 | getLoadPriority(): number { 26 | return this.toLoad.length; 27 | } 28 | 29 | getValue(index: number): TValue { 30 | index -= this.first; 31 | let value = this.values[index]; 32 | if (value === undefined) { 33 | this.values[index] = value = this.onGetValue(index); 34 | } 35 | 36 | return value; 37 | } 38 | 39 | protected abstract onGetValue(index: number): TValue; 40 | 41 | load(index: number, callback: (payload: TValue, page: ResourcePage) => void): TValue { 42 | if (this.page != null) { 43 | const value = this.getValue(index); 44 | callback(value, this); 45 | return value; 46 | } 47 | 48 | this.toLoad.push({ index: index, callback: callback }); 49 | } 50 | 51 | onLoadValues(page: TPayload): void { 52 | this.page = page; 53 | 54 | for (let i = 0, iEnd = this.toLoad.length; i < iEnd; ++i) { 55 | const request = this.toLoad[i]; 56 | request.callback(this.getValue(request.index), this); 57 | } 58 | 59 | this.toLoad = null; 60 | } 61 | } 62 | 63 | export abstract class PagedLoader> implements Facepunch.ILoader { 64 | 65 | private pages: TPage[]; 66 | 67 | private readonly toLoad: TPage[] = []; 68 | 69 | private active = 0; 70 | private loadProgress = 0; 71 | 72 | protected abstract onCreatePage(page: IPageInfo): TPage; 73 | 74 | throwIfNotFound = true; 75 | 76 | getLoadProgress(): number { 77 | return this.pages == null ? 0 : this.loadProgress / this.pages.length; 78 | } 79 | 80 | load(index: number, callback: (payload: TValue, page: TPage) => void): TValue { 81 | if (this.pages == null) { 82 | throw new Error("Page layout not loaded."); 83 | } 84 | 85 | for (let i = 0, iEnd = this.pages.length; i < iEnd; ++i) { 86 | const page = this.pages[i]; 87 | if (index >= page.first && index < page.first + page.count) { 88 | return page.load(index, callback); 89 | } 90 | } 91 | 92 | if (this.throwIfNotFound) { 93 | throw new Error(`Unable to find page for index ${index}.`); 94 | } 95 | } 96 | 97 | setPageLayout(pages: IPageInfo[]): void { 98 | if (this.pages != null) { 99 | throw new Error("Changing page layout not implemented."); 100 | } 101 | 102 | this.pages = new Array(pages.length); 103 | 104 | for (let i = 0, iEnd = pages.length; i < iEnd; ++i) { 105 | this.pages[i] = this.onCreatePage(pages[i]); 106 | this.toLoad.push(this.pages[i]); 107 | } 108 | } 109 | 110 | private getNextToLoad(): TPage { 111 | let bestScore = 0; 112 | let bestIndex = -1; 113 | 114 | for (let i = 0; i < this.toLoad.length; ++i) { 115 | const page = this.toLoad[i]; 116 | const score = page.getLoadPriority(); 117 | if (score > bestScore) { 118 | bestIndex = i; 119 | bestScore = score; 120 | } 121 | } 122 | 123 | if (bestIndex === -1) return null; 124 | 125 | return this.toLoad.splice(bestIndex, 1)[0]; 126 | } 127 | 128 | update(requestQuota: number): number { 129 | while (this.active < requestQuota) { 130 | const next = this.getNextToLoad(); 131 | if (next == null) break; 132 | 133 | let lastProgress = 0; 134 | 135 | ++this.active; 136 | Facepunch.Http.getJson(next.url, page => { 137 | --this.active; 138 | this.loadProgress += 1 - lastProgress; 139 | lastProgress = 1; 140 | next.onLoadValues(page); 141 | }, error => { 142 | --this.active; 143 | console.warn(error); 144 | }, (loaded, total) => { 145 | if (total !== undefined) { 146 | const progress = loaded / total; 147 | this.loadProgress += (progress - lastProgress); 148 | lastProgress = progress; 149 | } 150 | }); 151 | } 152 | 153 | return this.active; 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/BaseShaderProgram.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | import WebGame = Facepunch.WebGame; 3 | 4 | export namespace Shaders { 5 | export class BaseMaterial { 6 | cullFace = true; 7 | } 8 | 9 | export class BaseShaderProgram extends WebGame.ShaderProgram { 10 | private readonly materialCtor: { new(): TMaterial }; 11 | 12 | constructor(context: WebGLRenderingContext, ctor: { new(): TMaterial }) { 13 | super(context); 14 | 15 | this.materialCtor = ctor; 16 | } 17 | 18 | createMaterialProperties(): any { 19 | return new this.materialCtor(); 20 | } 21 | 22 | bufferMaterial(buf: WebGame.CommandBuffer, material: WebGame.Material): void { 23 | this.bufferMaterialProps(buf, material.properties as TMaterial); 24 | } 25 | 26 | bufferMaterialProps(buf: WebGame.CommandBuffer, props: TMaterial): void { 27 | const gl = this.context; 28 | 29 | if (props.cullFace) { 30 | buf.enable(gl.CULL_FACE); 31 | } else { 32 | buf.disable(gl.CULL_FACE); 33 | } 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/Lightmapped2WayBlend.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export namespace Shaders { 7 | export class Lightmapped2WayBlendMaterial extends LightmappedBaseMaterial { 8 | basetexture2: WebGame.Texture = null; 9 | blendModulateTexture: WebGame.Texture = null; 10 | } 11 | 12 | export class Lightmapped2WayBlend extends LightmappedBase { 13 | readonly uBaseTexture2 = this.addUniform("uBaseTexture2", WebGame.UniformSampler); 14 | readonly uBlendModulateTexture = this.addUniform("uBlendModulateTexture", WebGame.UniformSampler); 15 | 16 | readonly uBlendModulate = this.addUniform("uBlendModulate", WebGame.Uniform1I); 17 | 18 | constructor(context: WebGLRenderingContext) { 19 | super(context, Lightmapped2WayBlendMaterial); 20 | 21 | const gl = context; 22 | 23 | this.includeShaderSource(gl.VERTEX_SHADER, ` 24 | attribute float aAlpha; 25 | 26 | varying float vAlpha; 27 | 28 | void main() 29 | { 30 | LightmappedBase_main(); 31 | 32 | vAlpha = aAlpha; 33 | }`); 34 | 35 | this.includeShaderSource(gl.FRAGMENT_SHADER, ` 36 | precision mediump float; 37 | 38 | varying float vAlpha; 39 | 40 | uniform sampler2D uBaseTexture2; 41 | uniform sampler2D uBlendModulateTexture; 42 | 43 | uniform int uBlendModulate; // [0, 1] 44 | 45 | void main() 46 | { 47 | vec3 sample0 = texture2D(uBaseTexture, vTextureCoord).rgb; 48 | vec3 sample1 = texture2D(uBaseTexture2, vTextureCoord).rgb; 49 | 50 | float blend; 51 | if (uBlendModulate != 0) { 52 | vec3 blendSample = texture2D(uBlendModulateTexture, vTextureCoord).rga; 53 | 54 | float blendMin = max(0.0, blendSample.y - blendSample.x * 0.5); 55 | float blendMax = min(1.0, blendSample.y + blendSample.x * 0.5); 56 | 57 | blend = max(0.0, min(1.0, (vAlpha - blendMin) / max(0.0, blendMax - blendMin))); 58 | } else { 59 | blend = max(0.0, min(1.0, vAlpha)); 60 | } 61 | 62 | vec3 blendedSample = mix(sample0, sample1, blend); 63 | vec3 lightmapped = ApplyLightmap(blendedSample); 64 | 65 | gl_FragColor = vec4(ApplyFog(lightmapped), 1.0); 66 | }`); 67 | 68 | this.addAttribute("aAlpha", WebGame.VertexAttribute.alpha); 69 | 70 | this.uBaseTexture2.setDefault(WebGame.TextureUtils.getErrorTexture(gl)); 71 | this.uBlendModulateTexture.setDefault(WebGame.TextureUtils.getTranslucentTexture(gl)); 72 | 73 | this.compile(); 74 | } 75 | 76 | bufferMaterialProps(buf: Facepunch.WebGame.CommandBuffer, props: Lightmapped2WayBlendMaterial): void { 77 | super.bufferMaterialProps(buf, props); 78 | 79 | this.uBaseTexture2.bufferValue(buf, props.basetexture2); 80 | this.uBlendModulateTexture.bufferValue(buf, props.blendModulateTexture); 81 | 82 | this.uBlendModulate.bufferValue(buf, props.blendModulateTexture != null ? 1 : 0); 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/LightmappedBase.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export namespace Shaders { 7 | export class LightmappedBaseMaterial extends ModelBaseMaterial { 8 | 9 | } 10 | 11 | export abstract class LightmappedBase extends ModelBase { 12 | readonly uLightmap = this.addUniform("uLightmap", WebGame.UniformSampler); 13 | 14 | constructor(context: WebGLRenderingContext, ctor: { new(): TMaterial }) { 15 | super(context, ctor); 16 | 17 | const gl = context; 18 | 19 | this.includeShaderSource(gl.VERTEX_SHADER, ` 20 | attribute vec2 aLightmapCoord; 21 | 22 | varying vec2 vLightmapCoord; 23 | 24 | void LightmappedBase_main() 25 | { 26 | ModelBase_main(); 27 | 28 | vLightmapCoord = aLightmapCoord; 29 | }`); 30 | 31 | this.includeShaderSource(gl.FRAGMENT_SHADER, ` 32 | precision mediump float; 33 | 34 | varying vec2 vLightmapCoord; 35 | 36 | uniform sampler2D ${this.uLightmap}; 37 | uniform vec4 ${this.uLightmap.getSizeUniform()}; 38 | 39 | vec3 DecompressLightmapSample(vec4 sample) 40 | { 41 | float exp = sample.a * 255.0 - 128.0; 42 | return sample.rgb * pow(2.0, exp); 43 | } 44 | 45 | vec3 ApplyLightmap(vec3 inColor) 46 | { 47 | const float gamma = 1.0 / 2.2; 48 | 49 | vec2 size = ${this.uLightmap.getSizeUniform()}.xy; 50 | vec2 invSize = ${this.uLightmap.getSizeUniform()}.zw; 51 | vec2 scaledCoord = vLightmapCoord * size - vec2(0.5, 0.5); 52 | vec2 minCoord = floor(scaledCoord) + vec2(0.5, 0.5); 53 | vec2 maxCoord = minCoord + vec2(1.0, 1.0); 54 | vec2 delta = scaledCoord - floor(scaledCoord); 55 | 56 | minCoord *= invSize; 57 | maxCoord *= invSize; 58 | 59 | vec3 sampleA = DecompressLightmapSample(texture2D(${this.uLightmap}, vec2(minCoord.x, minCoord.y))); 60 | vec3 sampleB = DecompressLightmapSample(texture2D(${this.uLightmap}, vec2(maxCoord.x, minCoord.y))); 61 | vec3 sampleC = DecompressLightmapSample(texture2D(${this.uLightmap}, vec2(minCoord.x, maxCoord.y))); 62 | vec3 sampleD = DecompressLightmapSample(texture2D(${this.uLightmap}, vec2(maxCoord.x, maxCoord.y))); 63 | 64 | vec3 sample = mix(mix(sampleA, sampleB, delta.x), mix(sampleC, sampleD, delta.x), delta.y); 65 | 66 | return inColor * pow(sample, vec3(gamma, gamma, gamma)); 67 | }`); 68 | 69 | this.addAttribute("aLightmapCoord", WebGame.VertexAttribute.uv2); 70 | 71 | this.uLightmap.setDefault(WebGame.TextureUtils.getWhiteTexture(context)); 72 | } 73 | 74 | bufferSetup(buf: Facepunch.WebGame.CommandBuffer): void { 75 | super.bufferSetup(buf); 76 | 77 | this.uLightmap.bufferParameter(buf, Map.lightmapParam); 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/LightmappedGeneric.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | export namespace Shaders { 5 | export class LightmappedGenericMaterial extends LightmappedBaseMaterial { 6 | 7 | } 8 | 9 | export class LightmappedGeneric extends LightmappedBase { 10 | constructor(context: WebGLRenderingContext) { 11 | super(context, LightmappedGenericMaterial); 12 | 13 | const gl = context; 14 | 15 | this.includeShaderSource(gl.VERTEX_SHADER, ` 16 | void main() 17 | { 18 | LightmappedBase_main(); 19 | }`); 20 | 21 | this.includeShaderSource(gl.FRAGMENT_SHADER, ` 22 | precision mediump float; 23 | 24 | void main() 25 | { 26 | vec4 modelBase = ModelBase_main(); 27 | vec3 lightmapped = ${this.uEmission} != 0 ? modelBase.rgb : ApplyLightmap(modelBase.rgb); 28 | 29 | gl_FragColor = vec4(ApplyFog(lightmapped), modelBase.a); 30 | }`); 31 | 32 | this.compile(); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/ModelBase.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | import WebGame = Facepunch.WebGame; 3 | 4 | export namespace Shaders { 5 | export class ModelBaseMaterial extends BaseMaterial { 6 | basetexture: WebGame.Texture = null; 7 | alphaTest = false; 8 | translucent = false; 9 | alpha = 1; 10 | fogEnabled = true; 11 | emission = false; 12 | emissionTint = new Facepunch.Vector3(0, 0, 0); 13 | } 14 | 15 | export abstract class ModelBase extends BaseShaderProgram { 16 | readonly uProjection = this.addUniform("uProjection", WebGame.UniformMatrix4); 17 | readonly uView = this.addUniform("uView", WebGame.UniformMatrix4); 18 | readonly uModel = this.addUniform("uModel", WebGame.UniformMatrix4); 19 | 20 | readonly uBaseTexture = this.addUniform("uBaseTexture", WebGame.UniformSampler); 21 | 22 | readonly uAlphaTest = this.addUniform("uAlphaTest", WebGame.Uniform1F); 23 | readonly uTranslucent = this.addUniform("uTranslucent", WebGame.Uniform1F); 24 | readonly uAlpha = this.addUniform("uAlpha", WebGame.Uniform1F); 25 | 26 | readonly uFogParams = this.addUniform("uFogParams", WebGame.Uniform4F); 27 | readonly uFogColor = this.addUniform("uFogColor", WebGame.Uniform3F); 28 | readonly uFogEnabled = this.addUniform("uFogEnabled", WebGame.Uniform1I); 29 | 30 | readonly uEmission = this.addUniform("uEmission", WebGame.Uniform1I); 31 | readonly uEmissionTint = this.addUniform("uEmissionTint", WebGame.Uniform3F); 32 | 33 | constructor(context: WebGLRenderingContext, ctor: { new(): TMaterial }) { 34 | super(context, ctor); 35 | 36 | const gl = context; 37 | 38 | this.includeShaderSource(gl.VERTEX_SHADER, ` 39 | attribute vec3 aPosition; 40 | attribute vec2 aTextureCoord; 41 | 42 | varying vec2 vTextureCoord; 43 | varying float vDepth; 44 | 45 | uniform mat4 ${this.uProjection}; 46 | uniform mat4 ${this.uView}; 47 | uniform mat4 ${this.uModel}; 48 | 49 | uniform mediump int ${this.uEmission}; 50 | 51 | void ModelBase_main() 52 | { 53 | vec4 viewPos = ${this.uView} * ${this.uModel} * vec4(aPosition, 1.0); 54 | 55 | gl_Position = ${this.uProjection} * viewPos; 56 | 57 | vTextureCoord = aTextureCoord; 58 | vDepth = -viewPos.z; 59 | }`); 60 | 61 | this.includeShaderSource(gl.FRAGMENT_SHADER, ` 62 | precision mediump float; 63 | 64 | varying vec2 vTextureCoord; 65 | varying float vDepth; 66 | 67 | uniform sampler2D ${this.uBaseTexture}; 68 | 69 | uniform float ${this.uAlphaTest}; // [0, 1] 70 | uniform float ${this.uTranslucent}; // [0, 1] 71 | uniform float ${this.uAlpha}; // [0..1] 72 | 73 | uniform vec4 ${this.uFogParams}; 74 | uniform vec3 ${this.uFogColor}; 75 | uniform int ${this.uFogEnabled}; 76 | 77 | uniform int ${this.uEmission}; 78 | uniform vec3 ${this.uEmissionTint}; 79 | 80 | vec3 ApplyFog(vec3 inColor) 81 | { 82 | if (${this.uFogEnabled} == 0) return inColor; 83 | 84 | float fogDensity = ${this.uFogParams}.x + ${this.uFogParams}.y * vDepth; 85 | fogDensity = min(max(fogDensity, ${this.uFogParams}.z), ${this.uFogParams}.w); 86 | return mix(inColor, ${this.uFogColor}, fogDensity); 87 | } 88 | 89 | vec4 ModelBase_main() 90 | { 91 | vec4 sample = texture2D(${this.uBaseTexture}, vTextureCoord); 92 | if (sample.a <= ${this.uAlphaTest} - 0.5) discard; 93 | 94 | float alpha = mix(1.0, ${this.uAlpha} * sample.a, ${this.uTranslucent}); 95 | 96 | if (${this.uEmission} != 0) 97 | { 98 | sample.rgb += ${this.uEmissionTint}; 99 | } 100 | 101 | return vec4(sample.rgb, alpha); 102 | }`); 103 | 104 | this.addAttribute("aPosition", WebGame.VertexAttribute.position); 105 | this.addAttribute("aTextureCoord", WebGame.VertexAttribute.uv); 106 | 107 | this.uBaseTexture.setDefault(WebGame.TextureUtils.getErrorTexture(context)); 108 | } 109 | 110 | bufferSetup(buf: Facepunch.WebGame.CommandBuffer): void { 111 | super.bufferSetup(buf); 112 | 113 | this.uProjection.bufferParameter(buf, WebGame.Camera.projectionMatrixParam); 114 | this.uView.bufferParameter(buf, WebGame.Camera.viewMatrixParam); 115 | 116 | this.uFogParams.bufferParameter(buf, WebGame.Fog.fogInfoParam); 117 | this.uFogColor.bufferParameter(buf, WebGame.Fog.fogColorParam); 118 | } 119 | 120 | bufferModelMatrix(buf: Facepunch.WebGame.CommandBuffer, value: Float32Array): void { 121 | super.bufferModelMatrix(buf, value); 122 | 123 | this.uModel.bufferValue(buf, false, value); 124 | } 125 | 126 | bufferMaterialProps(buf: Facepunch.WebGame.CommandBuffer, props: TMaterial): void { 127 | super.bufferMaterialProps(buf, props); 128 | 129 | this.uBaseTexture.bufferValue(buf, props.basetexture); 130 | 131 | this.uAlphaTest.bufferValue(buf, props.alphaTest ? 1 : 0); 132 | this.uTranslucent.bufferValue(buf, props.translucent ? 1 : 0); 133 | this.uAlpha.bufferValue(buf, props.alpha); 134 | 135 | this.uFogEnabled.bufferValue(buf, props.fogEnabled ? 1 : 0); 136 | 137 | this.uEmission.bufferValue(buf, props.emission ? 1 : 0); 138 | if (props.emission) { 139 | this.uEmissionTint.bufferValue(buf, props.emissionTint.x, props.emissionTint.y, props.emissionTint.z); 140 | } 141 | 142 | const gl = this.context; 143 | 144 | buf.enable(gl.DEPTH_TEST); 145 | 146 | if (props.translucent) { 147 | buf.depthMask(false); 148 | buf.enable(gl.BLEND); 149 | buf.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 150 | } else { 151 | buf.depthMask(true); 152 | buf.disable(gl.BLEND); 153 | } 154 | } 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/SplineRope.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | import WebGame = Facepunch.WebGame; 3 | 4 | export namespace Shaders { 5 | export class SplineRopeMaterial extends ModelBaseMaterial { 6 | ambient: Facepunch.Vector3[]; 7 | } 8 | 9 | export class SplineRope extends ModelBase { 10 | private uAmbient0 = this.addUniform("uAmbient[0]", WebGame.Uniform3F); 11 | private uAmbient1 = this.addUniform("uAmbient[1]", WebGame.Uniform3F); 12 | private uAmbient2 = this.addUniform("uAmbient[2]", WebGame.Uniform3F); 13 | private uAmbient3 = this.addUniform("uAmbient[3]", WebGame.Uniform3F); 14 | private uAmbient4 = this.addUniform("uAmbient[4]", WebGame.Uniform3F); 15 | private uAmbient5 = this.addUniform("uAmbient[5]", WebGame.Uniform3F); 16 | 17 | uAmbient: WebGame.Uniform3F[]; 18 | 19 | constructor(context: WebGLRenderingContext) { 20 | super(context, SplineRopeMaterial); 21 | 22 | this.uAmbient = [this.uAmbient0, this.uAmbient1, this.uAmbient2, this.uAmbient3, this.uAmbient4, this.uAmbient5]; 23 | 24 | const gl = context; 25 | 26 | this.includeShaderSource(gl.VERTEX_SHADER, ` 27 | attribute vec3 aTangent; 28 | attribute vec2 aSplineParams; 29 | 30 | uniform vec3 uAmbient[6]; 31 | 32 | varying vec3 vAmbient; 33 | 34 | vec3 SampleAmbient(vec3 normal, vec3 axis, int index) 35 | { 36 | return pow(max(0.0, dot(normal, axis)), 2.0) * pow(uAmbient[index], vec3(0.5, 0.5, 0.5)); 37 | } 38 | 39 | void main() 40 | { 41 | vec4 viewPos = ${this.uView} * ${this.uModel} * vec4(aPosition, 1.0); 42 | vec3 viewTangent = normalize((${this.uView} * ${this.uModel} * vec4(aTangent, 0.0)).xyz); 43 | vec3 viewNormalA = normalize(cross(viewPos.xyz, viewTangent)); 44 | vec3 viewNormalB = normalize(cross(viewNormalA, viewTangent)); 45 | 46 | vec3 viewUnitX = normalize((${this.uView} * vec4(1.0, 0.0, 0.0, 0.0)).xyz); 47 | vec3 viewUnitY = normalize((${this.uView} * vec4(0.0, 1.0, 0.0, 0.0)).xyz); 48 | vec3 viewUnitZ = normalize((${this.uView} * vec4(0.0, 0.0, 1.0, 0.0)).xyz); 49 | 50 | vec3 viewNormal = normalize(viewNormalA * (aTextureCoord.x - 0.5) 51 | + viewNormalB * sqrt(1.0 - pow(1.0 - aTextureCoord.x * 2.0, 2.0)) * 0.5); 52 | 53 | vAmbient = SampleAmbient(viewNormal, viewUnitX, 0) 54 | + SampleAmbient(viewNormal, -viewUnitX, 1) 55 | + SampleAmbient(viewNormal, viewUnitY, 2) 56 | + SampleAmbient(viewNormal, -viewUnitY, 3) 57 | + SampleAmbient(viewNormal, viewUnitZ, 4) 58 | + SampleAmbient(viewNormal, -viewUnitZ, 5); 59 | 60 | viewPos.xyz += viewNormal * aSplineParams.x; 61 | viewPos.xyz += viewUnitZ * aSplineParams.y; 62 | 63 | gl_Position = ${this.uProjection} * viewPos; 64 | 65 | vTextureCoord = aTextureCoord; 66 | vDepth = -viewPos.z; 67 | }`); 68 | 69 | this.includeShaderSource(gl.FRAGMENT_SHADER, ` 70 | precision mediump float; 71 | 72 | varying vec3 vAmbient; 73 | 74 | void main() 75 | { 76 | vec4 mainSample = ModelBase_main(); 77 | gl_FragColor = vec4(ApplyFog(mainSample.rgb * vAmbient), mainSample.a); 78 | }`); 79 | 80 | this.addAttribute("aTangent", WebGame.VertexAttribute.normal); 81 | this.addAttribute("aSplineParams", WebGame.VertexAttribute.uv2); 82 | 83 | this.compile(); 84 | } 85 | 86 | bufferMaterialProps(buf: Facepunch.WebGame.CommandBuffer, props: SplineRopeMaterial): void { 87 | super.bufferMaterialProps(buf, props); 88 | 89 | if (props.ambient != null) { 90 | const values = props.ambient; 91 | const uniforms = this.uAmbient; 92 | for (let i = 0; i < 6; ++i) { 93 | const value = values[i]; 94 | if (value == null) continue; 95 | uniforms[i].bufferValue(buf, value.x, value.y, value.z); 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/UnlitGeneric.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export namespace Shaders { 7 | export class UnlitGenericMaterial extends ModelBaseMaterial { 8 | 9 | } 10 | 11 | export class UnlitGeneric extends ModelBase { 12 | constructor(context: WebGLRenderingContext) { 13 | super(context, UnlitGenericMaterial); 14 | 15 | const gl = context; 16 | 17 | this.includeShaderSource(gl.VERTEX_SHADER, ` 18 | void main() 19 | { 20 | ModelBase_main(); 21 | }`); 22 | 23 | this.includeShaderSource(gl.FRAGMENT_SHADER, ` 24 | precision mediump float; 25 | 26 | void main() 27 | { 28 | vec4 mainSample = ModelBase_main(); 29 | gl_FragColor = vec4(ApplyFog(mainSample.rgb), mainSample.a); 30 | }`); 31 | 32 | this.compile(); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/VertexLitGeneric.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export namespace Shaders { 7 | export class VertexLitGenericMaterial extends ModelBaseMaterial { 8 | 9 | } 10 | 11 | export class VertexLitGeneric extends ModelBase { 12 | constructor(context: WebGLRenderingContext) { 13 | super(context, VertexLitGenericMaterial); 14 | 15 | const gl = context; 16 | 17 | this.includeShaderSource(gl.VERTEX_SHADER, ` 18 | attribute vec3 aEncodedColors; 19 | 20 | varying vec3 vVertexLighting; 21 | varying vec3 vAlbedoModulation; 22 | 23 | void main() 24 | { 25 | vVertexLighting = ${this.uEmission} != 0 ? vec3(1.0, 1.0, 1.0) : floor(aEncodedColors) * (2.0 / 255.0); 26 | vAlbedoModulation = fract(aEncodedColors) * (256.0 / 255.0); 27 | 28 | ModelBase_main(); 29 | }`); 30 | 31 | this.includeShaderSource(gl.FRAGMENT_SHADER, ` 32 | precision mediump float; 33 | 34 | varying vec3 vVertexLighting; 35 | varying vec3 vAlbedoModulation; 36 | 37 | void main() 38 | { 39 | vec4 mainSample = ModelBase_main(); 40 | gl_FragColor = vec4(ApplyFog(mainSample.rgb * vVertexLighting * vAlbedoModulation), mainSample.a); 41 | }`); 42 | 43 | this.addAttribute("aEncodedColors", WebGame.VertexAttribute.rgb); 44 | 45 | this.compile(); 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Shaders/WorldTwoTextureBlend.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace SourceUtils { 4 | import WebGame = Facepunch.WebGame; 5 | 6 | export namespace Shaders { 7 | export class WorldTwoTextureBlendMaterial extends LightmappedBaseMaterial { 8 | detail: WebGame.Texture = null; 9 | detailScale: number = 1; 10 | } 11 | 12 | export class WorldTwoTextureBlend extends LightmappedBase { 13 | readonly uDetail = this.addUniform("uDetail", WebGame.UniformSampler); 14 | readonly uDetailScale = this.addUniform("uDetailScale", WebGame.Uniform1F); 15 | 16 | constructor(context: WebGLRenderingContext) { 17 | super(context, WorldTwoTextureBlendMaterial); 18 | 19 | const gl = context; 20 | 21 | this.includeShaderSource(gl.VERTEX_SHADER, ` 22 | void main() 23 | { 24 | LightmappedBase_main(); 25 | }`); 26 | 27 | this.includeShaderSource(gl.FRAGMENT_SHADER, ` 28 | precision mediump float; 29 | 30 | uniform sampler2D uDetail; 31 | uniform float uDetailScale; 32 | 33 | void main() 34 | { 35 | vec3 base = texture2D(uBaseTexture, vTextureCoord).rgb; 36 | vec4 detail = texture2D(uDetail, vTextureCoord * uDetailScale); 37 | 38 | vec3 blendedSample = mix(base.rgb, detail.rgb, detail.a); 39 | vec3 lightmapped = ApplyLightmap(blendedSample); 40 | 41 | gl_FragColor = vec4(ApplyFog(lightmapped), 1.0); 42 | }`); 43 | 44 | this.uDetail.setDefault(WebGame.TextureUtils.getTranslucentTexture(gl)); 45 | 46 | this.compile(); 47 | } 48 | 49 | bufferMaterialProps(buf: Facepunch.WebGame.CommandBuffer, props: WorldTwoTextureBlendMaterial): void { 50 | super.bufferMaterialProps(buf, props); 51 | 52 | this.uDetail.bufferValue(buf, props.detail); 53 | this.uDetailScale.bufferValue(buf, props.detailScale); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/SkyCube.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | import WebGame = Facepunch.WebGame; 3 | 4 | export class SkyCube extends WebGame.DrawListItem { 5 | constructor(viewer: MapViewer, material: WebGame.Material) { 6 | super(); 7 | 8 | const meshData: WebGame.IMeshData = { 9 | attributes: [WebGame.VertexAttribute.uv, WebGame.VertexAttribute.alpha], 10 | elements: [ 11 | { 12 | mode: WebGame.DrawMode.Triangles, 13 | material: material, 14 | indexOffset: 0, 15 | indexCount: 36 16 | } 17 | ], 18 | vertices: [], 19 | indices: [] 20 | }; 21 | 22 | for (let face = 0; face < 6; ++face) { 23 | meshData.vertices.push(0, 0, face); 24 | meshData.vertices.push(1, 0, face); 25 | meshData.vertices.push(1, 1, face); 26 | meshData.vertices.push(0, 1, face); 27 | 28 | const index = face * 4; 29 | meshData.indices.push(index + 0, index + 1, index + 2); 30 | meshData.indices.push(index + 0, index + 2, index + 3); 31 | } 32 | 33 | this.addMeshHandles(viewer.meshes.addMeshData(meshData)); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/Utils.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | export class ColorConversion { 3 | private static lastScreenGamma: number; 4 | private static linearToScreenGammaTable: number[]; 5 | 6 | private static exponentTable: number[]; 7 | 8 | public static initialize(screenGamma: number): void { 9 | if (this.exponentTable == null) { 10 | const table = this.exponentTable = new Array(256); 11 | for (let i = 0; i < 256; ++i) { 12 | table[i] = Math.pow(2.0, i - 128); 13 | } 14 | } 15 | 16 | if (this.lastScreenGamma !== screenGamma) { 17 | this.lastScreenGamma = screenGamma; 18 | 19 | const g = 1.0 / screenGamma; 20 | 21 | const table = this.linearToScreenGammaTable = new Array(1024); 22 | for (let i = 0; i < 1024; ++i) { 23 | const f = i / 1023; 24 | const inf = Math.floor(255 * Math.pow(f, g)); 25 | table[i] = inf < 0 ? 0 : inf > 255 ? 255 : inf; 26 | } 27 | } 28 | } 29 | 30 | public static rgbExp32ToVector3(rgbExp: number, out: Facepunch.IVector3): Facepunch.IVector3 { 31 | if (out == null) out = new Facepunch.Vector3(); 32 | 33 | const r = (rgbExp >> 0) & 0xff; 34 | const g = (rgbExp >> 8) & 0xff; 35 | const b = (rgbExp >> 16) & 0xff; 36 | const exp = (rgbExp >> 24) & 0xff; 37 | 38 | const mul = this.exponentTable[exp]; 39 | 40 | out.x = r * mul; 41 | out.y = g * mul; 42 | out.z = b * mul; 43 | 44 | return out; 45 | } 46 | 47 | public static linearToScreenGamma(f: number): number { 48 | let index = Math.floor(f * 1023); 49 | if (index < 0) index = 0; 50 | else if (index > 1023) index = 1023; 51 | 52 | return this.linearToScreenGammaTable[index]; 53 | } 54 | } 55 | 56 | ColorConversion.initialize(2.2); 57 | } 58 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/src/VisLoader.ts: -------------------------------------------------------------------------------- 1 | namespace SourceUtils { 2 | export interface IVisPage { 3 | values: (number[] | string)[]; 4 | } 5 | 6 | export class VisPage extends ResourcePage { 7 | protected onGetValue(index: number): number[] { 8 | if (typeof (this.page.values[index]) === "string") { 9 | this.page.values[index] = Facepunch.Utils.decompress(this.page.values[index]); 10 | } 11 | 12 | return this.page.values[index] as number[]; 13 | } 14 | } 15 | 16 | export class VisLoader extends PagedLoader { 17 | constructor() { 18 | super(); 19 | this.throwIfNotFound = false; 20 | } 21 | 22 | protected onCreatePage(page: IPageInfo): VisPage { 23 | return new VisPage(page); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/styles/mapviewer.css: -------------------------------------------------------------------------------- 1 | .map-viewer canvas { 2 | position: absolute; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .map-viewer:-webkit-full-screen { 8 | position: absolute !important; 9 | top: 0 !important; 10 | left: 0 !important; 11 | bottom: 0 !important; 12 | right: 0 !important; 13 | } 14 | 15 | .map-viewer .side-panel { 16 | position: relative; 17 | float: right; 18 | clear: right; 19 | z-index: 32; 20 | width: 256px; 21 | background-color: rgba(0, 0, 0, 0.25); 22 | color: white; 23 | font-family: sans-serif; 24 | padding: 16px; 25 | margin: 8px; 26 | user-select: none; 27 | } 28 | 29 | .map-viewer .side-panel .slider { 30 | width: 240px; 31 | margin: 8px; 32 | } 33 | 34 | .map-viewer .side-panel .label { 35 | display: inline-block; 36 | font-size: 10pt; 37 | padding-right: 8px; 38 | color: #cccccc; 39 | } 40 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Resources/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noEmitOnError": true, 5 | "removeComments": false, 6 | "sourceMap": false, 7 | "declaration": true, 8 | "outFile": "js/sourceutils.js", 9 | "target": "es5" 10 | }, 11 | "compileOnSave": true, 12 | "include": ["src/**/*.ts"], 13 | "exclude": [ 14 | "node_modules", 15 | "wwwroot" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/StaticFiles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using JetBrains.Annotations; 6 | using MimeTypes; 7 | using Ziks.WebServer; 8 | 9 | namespace SourceUtils.WebExport 10 | { 11 | partial class Program 12 | { 13 | private static readonly Dictionary StaticFiles = 14 | new Dictionary( StringComparer.InvariantCultureIgnoreCase ) 15 | { 16 | {"/js/facepunch.webgame.js", Properties.Resources.facepunch_webgame}, 17 | {"/js/sourceutils.js", Properties.Resources.sourceutils}, 18 | {"/styles/mapviewer.css", Properties.Resources.mapviewer} 19 | }; 20 | 21 | class StaticController : Controller 22 | { 23 | protected override void OnServiceText( string text ) 24 | { 25 | using ( var writer = new StreamWriter( Response.OutputStream ) ) 26 | { 27 | writer.Write( text ); 28 | } 29 | } 30 | 31 | [Get( MatchAllUrl = false ), UsedImplicitly] 32 | public string GetFile() 33 | { 34 | var path = Request.Url.AbsolutePath; 35 | 36 | if ( BaseOptions.ResourcesDir != null ) 37 | { 38 | var filePath = Path.Combine(BaseOptions.ResourcesDir, path.Substring(1)); 39 | 40 | if (File.Exists(filePath)) 41 | { 42 | Response.ContentType = MimeTypeMap.GetMimeType(Path.GetExtension(path)); 43 | return File.ReadAllText(filePath); 44 | } 45 | } 46 | 47 | string value; 48 | if ( StaticFiles.TryGetValue( path, out value ) ) 49 | { 50 | Response.ContentType = MimeTypeMap.GetMimeType( Path.GetExtension( path ) ); 51 | return value; 52 | } 53 | 54 | Response.StatusCode = 404; 55 | return "File not found."; 56 | } 57 | } 58 | 59 | private static void AddStaticFileControllers( Server server ) 60 | { 61 | server.Controllers.Add("/"); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/StudioModelDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SourceUtils.WebExport 8 | { 9 | internal class StudioModelDictionary : ResourceDictionary 10 | { 11 | public static int GetVertexCount( ValveBspFile bsp, int index ) 12 | { 13 | return GetDictionary( bsp ).GetVertexCount( index ); 14 | } 15 | 16 | private readonly List _vertexCounts = new List(); 17 | 18 | protected override IEnumerable OnFindResourcePaths( ValveBspFile bsp ) 19 | { 20 | var items = Enumerable.Range( 0, bsp.StaticProps.ModelCount ) 21 | .Select( x => bsp.StaticProps.GetModelName( x ) ) 22 | .Select( x => 23 | { 24 | var mdl = StudioModelFile.FromProvider( x, bsp.PakFile, Program.Resources ); 25 | if ( mdl == null ) return null; 26 | return new 27 | { 28 | Path = x, 29 | VertexCount = mdl.TotalVertices, 30 | FirstMaterialIndex = MaterialDictionary.GetResourceIndex( bsp, mdl.GetMaterialName( 0, bsp.PakFile, Program.Resources ) ) 31 | }; 32 | } ) 33 | .Where( x => x != null ) 34 | .GroupBy( x => x.FirstMaterialIndex ) 35 | .OrderByDescending( x => x.Count() ) 36 | .SelectMany( x => x ) 37 | .ToArray(); 38 | 39 | foreach ( var item in items ) 40 | { 41 | yield return item.Path; 42 | 43 | var index = GetResourceIndex( item.Path ); 44 | if ( index == _vertexCounts.Count ) 45 | { 46 | _vertexCounts.Add( item.VertexCount ); 47 | } 48 | } 49 | } 50 | 51 | public int GetVertexCount( int index ) 52 | { 53 | return _vertexCounts[index]; 54 | } 55 | 56 | protected override string NormalizePath( string path ) 57 | { 58 | path = base.NormalizePath( path ); 59 | 60 | if ( !path.StartsWith( "models/" ) ) path = $"models/{path}"; 61 | if ( !path.EndsWith( ".mdl" ) ) path = $"{path}.mdl"; 62 | 63 | return path; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SourceUtils.WebExport 6 | { 7 | static class Utils 8 | { 9 | [ThreadStatic] 10 | private static StringBuilder _sArrayBuilder; 11 | 12 | public static string CompressArray(IEnumerable enumerable) 13 | { 14 | return CompressArray(enumerable, x => x.ToString()); 15 | } 16 | 17 | public static string CompressArray(IEnumerable enumerable, Func serializer) 18 | { 19 | if (_sArrayBuilder == null) _sArrayBuilder = new StringBuilder(); 20 | else _sArrayBuilder.Remove(0, _sArrayBuilder.Length); 21 | 22 | _sArrayBuilder.Append("["); 23 | foreach (var item in enumerable) 24 | { 25 | _sArrayBuilder.Append(serializer(item)); 26 | _sArrayBuilder.Append(","); 27 | } 28 | 29 | if (_sArrayBuilder.Length > 1) _sArrayBuilder.Remove(_sArrayBuilder.Length - 1, 1); 30 | _sArrayBuilder.Append("]"); 31 | 32 | return LZString.compressToBase64( _sArrayBuilder.ToString() ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SourceUtils.WebExport/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /SourceUtils.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2036 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceUtils", "SourceUtils\SourceUtils.csproj", "{2C5FEFA1-39BA-458E-B95C-2D8414958820}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceUtils.WebExport", "SourceUtils.WebExport\SourceUtils.WebExport.csproj", "{171622AD-547B-496C-A230-16FCA08A0112}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceUtils.FileExport", "SourceUtils.FileExport\SourceUtils.FileExport.csproj", "{1263BE7B-1E7F-415A-9CF9-917CBDD3572F}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceUtils.Test", "SourceUtils.Test\SourceUtils.Test.csproj", "{B17851AD-7B29-464B-B92A-73816D6D8D7C}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {2C5FEFA1-39BA-458E-B95C-2D8414958820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {2C5FEFA1-39BA-458E-B95C-2D8414958820}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {2C5FEFA1-39BA-458E-B95C-2D8414958820}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {2C5FEFA1-39BA-458E-B95C-2D8414958820}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {171622AD-547B-496C-A230-16FCA08A0112}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {171622AD-547B-496C-A230-16FCA08A0112}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {171622AD-547B-496C-A230-16FCA08A0112}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {171622AD-547B-496C-A230-16FCA08A0112}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {1263BE7B-1E7F-415A-9CF9-917CBDD3572F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {1263BE7B-1E7F-415A-9CF9-917CBDD3572F}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {1263BE7B-1E7F-415A-9CF9-917CBDD3572F}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {1263BE7B-1E7F-415A-9CF9-917CBDD3572F}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {B17851AD-7B29-464B-B92A-73816D6D8D7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {B17851AD-7B29-464B-B92A-73816D6D8D7C}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {B17851AD-7B29-464B-B92A-73816D6D8D7C}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {B17851AD-7B29-464B-B92A-73816D6D8D7C}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {BE348FED-0C4E-4470-BCC5-59D97B802B8C} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /SourceUtils/Color32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SourceUtils 4 | { 5 | public struct Color32 : IEquatable 6 | { 7 | public static Color32 FromRgb( int rgb ) 8 | { 9 | return new Color32( (byte) ((rgb >> 16) & 0xff), (byte) ((rgb >> 8) & 0xff), (byte) (rgb & 0xff) ); 10 | } 11 | public static Color32 FromBgr( int bgr ) 12 | { 13 | return new Color32( (byte) (bgr & 0xff), (byte) ((bgr >> 8) & 0xff), (byte) ((bgr >> 16) & 0xff) ); 14 | } 15 | 16 | public static Color32 FromArgb( int argb ) 17 | { 18 | return new Color32( (byte) ((argb >> 16) & 0xff), (byte) ((argb >> 8) & 0xff), (byte) (argb & 0xff), (byte) ((argb >> 24) & 0xff) ); 19 | } 20 | 21 | public byte R; 22 | public byte G; 23 | public byte B; 24 | public byte A; 25 | 26 | public Color32( byte r, byte g, byte b, byte a = 255 ) 27 | { 28 | R = r; 29 | G = g; 30 | B = b; 31 | A = a; 32 | } 33 | 34 | public int ToRgb() 35 | { 36 | return (R << 16) | (G << 8) | B; 37 | } 38 | 39 | public int ToArgb() 40 | { 41 | return (A << 24) | ToRgb(); 42 | } 43 | 44 | public override int GetHashCode() 45 | { 46 | return ToArgb(); 47 | } 48 | 49 | public bool Equals( Color32 other ) 50 | { 51 | return R == other.R && G == other.G && B == other.B && A == other.A; 52 | } 53 | 54 | public override bool Equals( object obj ) 55 | { 56 | return obj is Color32 && Equals( (Color32) obj ); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SourceUtils/DisposingEventTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SourceUtils 8 | { 9 | public class DisposingEventTarget : IDisposable 10 | where T : DisposingEventTarget 11 | { 12 | private readonly List> _disposingHandlers 13 | = new List>(); 14 | 15 | public event Action Disposing 16 | { 17 | add 18 | { 19 | lock ( _disposingHandlers ) 20 | { 21 | _disposingHandlers.Add( value ); 22 | } 23 | } 24 | remove 25 | { 26 | lock ( _disposingHandlers ) 27 | { 28 | _disposingHandlers.Remove( value ); 29 | } 30 | } 31 | } 32 | 33 | public void Dispose() 34 | { 35 | lock ( _disposingHandlers ) 36 | { 37 | foreach ( var disposingHandler in _disposingHandlers ) 38 | { 39 | disposingHandler( (T) this ); 40 | } 41 | 42 | _disposingHandlers.Clear(); 43 | } 44 | 45 | OnDispose(); 46 | } 47 | 48 | protected virtual void OnDispose() 49 | { 50 | 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SourceUtils/IntRect.cs: -------------------------------------------------------------------------------- 1 | namespace SourceUtils 2 | { 3 | public struct IntRect 4 | { 5 | public int X; 6 | public int Y; 7 | public int Width; 8 | public int Height; 9 | 10 | public IntVector2 Min => new IntVector2( X, Y ); 11 | public IntVector2 Max => new IntVector2( X, Y ); 12 | public IntVector2 Size => new IntVector2( Width, Height ); 13 | 14 | public IntRect( int x, int y, int width, int height ) 15 | { 16 | X = x; 17 | Y = y; 18 | Width = width; 19 | Height = height; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SourceUtils/IntVector2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SourceUtils 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct IntVector2 : IEquatable 8 | { 9 | public static readonly IntVector2 Zero = new IntVector2( 0, 0 ); 10 | 11 | public static implicit operator Vector2( IntVector2 vector ) 12 | { 13 | return new Vector2( vector.X, vector.Y ); 14 | } 15 | 16 | public static IntVector2 operator+(IntVector2 a, IntVector2 b) 17 | { 18 | return new IntVector2( a.X + b.X, a.Y + b.Y ); 19 | } 20 | 21 | public int X; 22 | public int Y; 23 | 24 | public IntVector2( int x, int y ) 25 | { 26 | X = x; 27 | Y = y; 28 | } 29 | 30 | public bool Equals( IntVector2 other ) 31 | { 32 | return X == other.X && Y == other.Y; 33 | } 34 | 35 | public override bool Equals( object obj ) 36 | { 37 | if ( ReferenceEquals( null, obj ) ) return false; 38 | return obj is IntVector2 && Equals( (IntVector2) obj ); 39 | } 40 | 41 | public override int GetHashCode() 42 | { 43 | unchecked 44 | { 45 | return (X * 397) ^ Y; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SourceUtils/KeyValues.txt: -------------------------------------------------------------------------------- 1 | Skip = /(\s+|\/\/[^\n]*(\n|$))+/; 2 | 3 | Escaped 4 | { 5 | Document = ( Definition.BlockList* | Definition.List ) /$/; 6 | 7 | String = '"' Quoted '"' | '“' Quoted '”' | Unquoted 8 | { 9 | Quoted = /([^"\n\\]|\\[\\"nt])*/; 10 | Unquoted = /([^\s\/"{}\\]|\/(?!\/)|\\[\\"nt{}])+/; 11 | } 12 | 13 | ignore Skip 14 | { 15 | Definition = String ( "{" List "}" | String ) 16 | { 17 | List = Definition*; 18 | 19 | collapse 20 | { 21 | BlockList = "{" Definition.List "}"; 22 | } 23 | } 24 | } 25 | } 26 | 27 | Unescaped 28 | { 29 | Document = ( Definition.BlockList* | Definition.List ) /$/; 30 | 31 | String = '"' Quoted '"' | '“' Quoted '”' | Unquoted 32 | { 33 | Quoted = /[^"\n]*/; 34 | Unquoted = /([^\s\/"{}]|\/(?!\/))+/; 35 | } 36 | 37 | ignore Skip 38 | { 39 | Definition = String ( "{" List "}" | String ) 40 | { 41 | List = Definition*; 42 | 43 | collapse 44 | { 45 | BlockList = "{" Definition.List "}"; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SourceUtils/LumpReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace SourceUtils 7 | { 8 | public class LumpReader 9 | where TLump : struct 10 | { 11 | public static TLump[] ReadLump(byte[] src, int offset, int length) 12 | { 13 | var size = Marshal.SizeOf(typeof(TLump)); 14 | var count = length/size; 15 | var array = new TLump[count]; 16 | 17 | if (typeof (TLump) == typeof (byte)) 18 | { 19 | Array.Copy(src, array, array.Length); 20 | return array; 21 | } 22 | 23 | var tempPtr = Marshal.AllocHGlobal(size); 24 | 25 | for (var i = 0; i < count; ++i) 26 | { 27 | Marshal.Copy(src, offset + i * size, tempPtr, size); 28 | array[i] = (TLump) Marshal.PtrToStructure(tempPtr, typeof (TLump)); 29 | } 30 | 31 | Marshal.FreeHGlobal(tempPtr); 32 | 33 | return array; 34 | } 35 | 36 | public static void ReadLumpToList(byte[] src, int offset, int length, List dstList) 37 | { 38 | var size = Marshal.SizeOf(typeof(TLump)); 39 | var count = length/size; 40 | 41 | if (typeof(TLump) == typeof(byte)) 42 | { 43 | ((List) (object) dstList).AddRange(src); 44 | } 45 | 46 | var tempPtr = Marshal.AllocHGlobal(size); 47 | 48 | for (var i = 0; i < count; ++i) 49 | { 50 | Marshal.Copy(src, offset + i * size, tempPtr, size); 51 | dstList.Add((TLump) Marshal.PtrToStructure(tempPtr, typeof(TLump))); 52 | } 53 | 54 | Marshal.FreeHGlobal(tempPtr); 55 | } 56 | 57 | [ThreadStatic] 58 | private static byte[] _sReadLumpBuffer; 59 | 60 | [ThreadStatic] 61 | private static List _sReadLumpList; 62 | 63 | public static TLump ReadSingleFromStream(Stream stream) 64 | { 65 | if (_sReadLumpList == null) _sReadLumpList = new List(); 66 | else _sReadLumpList.Clear(); 67 | 68 | ReadLumpFromStream(stream, 1, _sReadLumpList); 69 | 70 | return _sReadLumpList[0]; 71 | } 72 | 73 | public static void ReadLumpFromStream(Stream stream, int count, Action handler, bool reseekPerItem = true) 74 | { 75 | ReadLumpFromStream( stream, count, ( index, lump ) => handler( lump ), reseekPerItem ); 76 | } 77 | 78 | public static void ReadLumpFromStream(Stream stream, int count, Action handler, bool reseekPerItem = true) 79 | { 80 | if (_sReadLumpList == null) _sReadLumpList = new List(); 81 | else _sReadLumpList.Clear(); 82 | 83 | var size = Marshal.SizeOf(typeof (TLump)); 84 | var start = stream.Position; 85 | 86 | ReadLumpFromStream(stream, count, _sReadLumpList); 87 | var end = stream.Position; 88 | 89 | for (var i = 0; i < count; ++i) 90 | { 91 | if (reseekPerItem) stream.Seek(start + i*size, SeekOrigin.Begin); 92 | handler(i, _sReadLumpList[i]); 93 | } 94 | 95 | if (reseekPerItem) stream.Seek( end, SeekOrigin.Begin ); 96 | } 97 | 98 | public static void ReadLumpFromStream( Stream stream, int count, TLump[] dst, int dstOffset = 0 ) 99 | { 100 | ReadLumpFromStream( stream, count, ( index, item ) => dst[dstOffset + index] = item, false ); 101 | } 102 | 103 | public static TLump[] ReadLumpFromStream(Stream stream, int count) 104 | { 105 | var array = new TLump[count]; 106 | ReadLumpFromStream( stream, count, array ); 107 | return array; 108 | } 109 | 110 | public static TValue[] ReadLumpFromStream(Stream stream, int count, Func selectFunc) 111 | { 112 | if (_sReadLumpList == null) _sReadLumpList = new List(); 113 | else _sReadLumpList.Clear(); 114 | 115 | ReadLumpFromStream(stream, count, _sReadLumpList); 116 | 117 | var output = new TValue[count]; 118 | for (var i = 0; i < count; ++i) 119 | { 120 | output[i] = selectFunc( _sReadLumpList[i] ); 121 | } 122 | 123 | return output; 124 | } 125 | 126 | public static void ReadLumpFromStream(Stream stream, int count, List dstList) 127 | { 128 | var size = Marshal.SizeOf(typeof (TLump)); 129 | var length = count*size; 130 | 131 | if (_sReadLumpBuffer == null || _sReadLumpBuffer.Length < length) 132 | { 133 | _sReadLumpBuffer = new byte[length]; 134 | } 135 | 136 | stream.Read(_sReadLumpBuffer, 0, length); 137 | ReadLumpToList(_sReadLumpBuffer, 0, length, dstList); 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /SourceUtils/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | // Information about this assembly is defined by the following attributes. 4 | // Change them to the values specific to your project. 5 | 6 | [assembly: AssemblyTitle ("SourceUtils")] 7 | [assembly: AssemblyDescription ("")] 8 | [assembly: AssemblyConfiguration ("")] 9 | [assembly: AssemblyCompany ("")] 10 | [assembly: AssemblyProduct ("")] 11 | [assembly: AssemblyCopyright ("ziks")] 12 | [assembly: AssemblyTrademark ("")] 13 | [assembly: AssemblyCulture ("")] 14 | 15 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 16 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 17 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 18 | 19 | [assembly: AssemblyVersion ("1.0.*")] 20 | 21 | // The following attributes are used to specify the signing key for the assembly, 22 | // if desired. See the Mono documentation for more information about signing. 23 | 24 | //[assembly: AssemblyDelaySign(false)] 25 | //[assembly: AssemblyKeyFile("")] 26 | 27 | -------------------------------------------------------------------------------- /SourceUtils/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SourceUtils.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SourceUtils.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to EndOfInput = /$/; 65 | /// 66 | ///Escaped 67 | ///{ 68 | /// Document = Definition* EndOfInput; 69 | /// 70 | /// String = '"' Quoted '"' | Unquoted 71 | /// { 72 | /// Quoted = /([^"\n\\]|\\[\\"nt])+/; 73 | /// Unquoted = /([^\s\/"{}\\]|\/(?!\/)|\\[\\"nt{}])+/; 74 | /// } 75 | /// 76 | /// ignore /(\s+|\/\/[^\n]*(\n|$)/ 77 | /// { 78 | /// Definition = Key ( Value | SubKeys ); 79 | /// 80 | /// Key = String; 81 | /// Value = String; 82 | /// 83 | /// SubKeys = "{" Definition* "}"; 84 | /// } 85 | ///} 86 | /// 87 | ///Unescaped 88 | ///{ 89 | /// Document = Definition* EndOfInput; 90 | /// 91 | /// String = '"' Quoted '"' [rest of string was truncated]";. 92 | /// 93 | internal static string KeyValues { 94 | get { 95 | return ResourceManager.GetString("KeyValues", resourceCulture); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /SourceUtils/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\KeyValues.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 123 | 124 | -------------------------------------------------------------------------------- /SourceUtils/RectanglePacker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SourceUtils 5 | { 6 | /// 7 | /// Adapted from https://github.com/ChevyRay/RectanglePacker 8 | /// 9 | public class RectanglePacker 10 | { 11 | public int Width { get; private set; } 12 | public int Height { get; private set; } 13 | 14 | public int MaxWidth { get; } 15 | public int MaxHeight { get; } 16 | 17 | private readonly List _nodes = new List(); 18 | 19 | public RectanglePacker( int initialWidth = 1, int initialHeight = 1, 20 | int maxWidth = int.MaxValue, int maxHeight = int.MaxValue ) 21 | { 22 | Width = initialWidth; 23 | Height = initialHeight; 24 | 25 | MaxWidth = maxWidth; 26 | MaxHeight = maxHeight; 27 | 28 | AddNode( 0, 0, int.MaxValue, int.MaxValue ); 29 | } 30 | 31 | private int FindInsertionIndex( Node node, int first, int last ) 32 | { 33 | while ( true ) 34 | { 35 | if ( first == last ) return first; 36 | 37 | var mid = (first + last) >> 1; 38 | var comparison = node.CompareTo( _nodes[mid] ); 39 | if ( comparison == 0 ) return mid; 40 | if ( comparison > 0 ) last = mid; 41 | else first = mid + 1; 42 | } 43 | } 44 | 45 | private void AddNode( int x, int y, int w, int h ) 46 | { 47 | if ( w <= 0 || h <= 0 ) return; 48 | var node = new Node( x, y, w, h ); 49 | 50 | var index = FindInsertionIndex( node, 0, _nodes.Count ); 51 | _nodes.Insert( index, node ); 52 | } 53 | 54 | private bool CanFitInNode( int w, int h, Node node ) 55 | { 56 | return w <= node.W && h <= node.H && node.X + w <= Width && node.Y + h <= Height; 57 | } 58 | 59 | private bool TryExpand() 60 | { 61 | if ( Width >= MaxWidth && Height >= MaxHeight ) return false; 62 | 63 | if ( Width <= Height && Width < MaxWidth ) Width = Math.Min( MaxWidth, Width * 2 ); 64 | else Height = Math.Min( MaxHeight, Height * 2 ); 65 | 66 | return true; 67 | } 68 | 69 | public bool Pack( int w, int h, out int x, out int y ) 70 | { 71 | do 72 | { 73 | for ( var i = _nodes.Count - 1; i >= 0; --i ) 74 | { 75 | if ( !CanFitInNode( w, h, _nodes[i] ) ) continue; 76 | 77 | var node = _nodes[i]; 78 | _nodes.RemoveAt( i ); 79 | 80 | x = node.X; 81 | y = node.Y; 82 | 83 | var r = x + w; 84 | var b = y + h; 85 | 86 | if ( node.Bottom - b > node.Right - r ) 87 | { 88 | AddNode( r, y, node.Right - r, h ); 89 | AddNode( x, b, node.W, node.Bottom - b ); 90 | } 91 | else 92 | { 93 | AddNode( x, b, w, node.Bottom - b ); 94 | AddNode( r, y, node.Right - r, node.H ); 95 | } 96 | 97 | return true; 98 | } 99 | } while ( TryExpand() ); 100 | 101 | x = 0; 102 | y = 0; 103 | return false; 104 | } 105 | 106 | public struct Node : IComparable 107 | { 108 | public int X; 109 | public int Y; 110 | public int W; 111 | public int H; 112 | 113 | public Node( int x, int y, int w, int h ) 114 | { 115 | X = x; 116 | Y = y; 117 | W = w; 118 | H = h; 119 | } 120 | 121 | public int MaxSide => Math.Max( W, H ); 122 | public int MinSide => Math.Min( W, H ); 123 | 124 | public int Right 125 | { 126 | get { return X + W; } 127 | } 128 | 129 | public int Bottom 130 | { 131 | get { return Y + H; } 132 | } 133 | 134 | public int CompareTo( Node other ) 135 | { 136 | var wComparison = MaxSide.CompareTo( other.MaxSide ); 137 | return wComparison != 0 ? wComparison : MinSide.CompareTo( other.H ); 138 | } 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /SourceUtils/ResourceLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | namespace SourceUtils 8 | { 9 | public interface IResourceProvider 10 | { 11 | IEnumerable GetFiles(string directory = ""); 12 | IEnumerable GetDirectories(string directory = ""); 13 | 14 | bool ContainsFile(string filePath); 15 | Stream OpenFile(string filePath); 16 | } 17 | 18 | [AttributeUsage(AttributeTargets.Class)] 19 | public class PathPrefixAttribute : Attribute 20 | { 21 | public string Value { get; set; } 22 | 23 | public PathPrefixAttribute(string value) 24 | { 25 | Value = value; 26 | } 27 | } 28 | 29 | public class FSLoader : IResourceProvider 30 | { 31 | private string root; 32 | 33 | public FSLoader(string directory = "") 34 | { 35 | root = directory; 36 | } 37 | 38 | public bool ContainsFile(string filePath) 39 | { 40 | return File.Exists(Path.Combine(root, filePath)); 41 | } 42 | 43 | public IEnumerable GetDirectories(string directory = "") 44 | { 45 | return Directory.GetDirectories(Path.Combine(root, directory)); 46 | } 47 | 48 | public IEnumerable GetFiles(string directory = "") 49 | { 50 | return Directory.GetFiles(Path.Combine(root, directory)); 51 | } 52 | 53 | public Stream OpenFile(string filePath) 54 | { 55 | return File.OpenRead(Path.Combine(root, filePath)); 56 | } 57 | } 58 | 59 | public class ResourceLoader : IResourceProvider 60 | { 61 | private readonly List _providers = new List(); 62 | 63 | public void AddResourceProvider(IResourceProvider provider) 64 | { 65 | _providers.Add(provider); 66 | } 67 | 68 | public void RemoveResourceProvider(IResourceProvider provider) 69 | { 70 | _providers.Remove(provider); 71 | } 72 | 73 | public IEnumerable GetFiles(string directory = "") 74 | { 75 | return _providers.SelectMany(x => x.GetFiles(directory)).OrderBy( x => x ); 76 | } 77 | 78 | public IEnumerable GetDirectories(string directory = "") 79 | { 80 | return _providers.SelectMany(x => x.GetDirectories(directory)).OrderBy( x => x ); 81 | } 82 | 83 | public bool ContainsFile(string filePath) 84 | { 85 | for (var i = _providers.Count - 1; i >= 0; --i) 86 | { 87 | if (_providers[i].ContainsFile(filePath)) return true; 88 | } 89 | 90 | return false; 91 | } 92 | 93 | public Stream OpenFile(string filePath) 94 | { 95 | for (var i = _providers.Count - 1; i >= 0; --i) 96 | { 97 | if (_providers[i].ContainsFile(filePath)) return _providers[i].OpenFile(filePath); 98 | } 99 | 100 | return _providers[0].OpenFile(filePath); 101 | } 102 | 103 | private abstract class ResourceCollection 104 | { 105 | private readonly ResourceLoader _loader; 106 | private readonly string _pathPrefix; 107 | 108 | private readonly Dictionary _loaded 109 | = new Dictionary(StringComparer.CurrentCultureIgnoreCase); 110 | 111 | protected ResourceCollection(ResourceLoader loader, string pathPrefix = null) 112 | { 113 | _loader = loader; 114 | _pathPrefix = pathPrefix; 115 | } 116 | 117 | public object Load(string filePath) 118 | { 119 | var fullPath = filePath; 120 | 121 | object loaded; 122 | if (TryGetLoaded(filePath, out loaded)) return loaded; 123 | 124 | if (_pathPrefix != null) 125 | { 126 | fullPath = $"{_pathPrefix}/{filePath}"; 127 | if (!_loader.ContainsFile(fullPath)) fullPath = filePath; 128 | } 129 | 130 | using (var stream = _loader.OpenFile(fullPath)) 131 | { 132 | loaded = OnLoad(stream); 133 | _loaded.Add(filePath, loaded); 134 | } 135 | 136 | return loaded; 137 | } 138 | 139 | private bool TryGetLoaded(string filePath, out object resource) 140 | { 141 | return _loaded.TryGetValue(filePath, out resource); 142 | } 143 | 144 | protected abstract object OnLoad(Stream stream); 145 | } 146 | 147 | private class ResourceCollection : ResourceCollection 148 | { 149 | private static string FindPathPrefix(Type type) 150 | { 151 | var attrib = type.GetCustomAttributes(false).FirstOrDefault(); 152 | return attrib?.Value; 153 | } 154 | 155 | private static Func FindFromStreamDelegate(Type type) 156 | { 157 | const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Static; 158 | var types = new[] { typeof(Stream) }; 159 | var method = type.GetMethod("FromStream", bindingFlags, null, types, null); 160 | 161 | if (method == null) throw new Exception($"Unable to find method {type.FullName}.FromStream({typeof(Stream).Name})"); 162 | 163 | return (Func) Delegate.CreateDelegate(typeof(Func), null, method, true); 164 | } 165 | 166 | private readonly Func _fromStreamDelegate; 167 | 168 | public ResourceCollection(ResourceLoader loader) 169 | : base(loader, FindPathPrefix(typeof(T))) 170 | { 171 | _fromStreamDelegate = FindFromStreamDelegate(typeof(T)); 172 | } 173 | 174 | protected override object OnLoad(Stream stream) 175 | { 176 | return _fromStreamDelegate(stream); 177 | } 178 | } 179 | 180 | private readonly Dictionary _resourceCollections 181 | = new Dictionary(); 182 | 183 | public T Load(string filePath) 184 | { 185 | ResourceCollection collection; 186 | if (_resourceCollections.TryGetValue(typeof(T), out collection)) 187 | { 188 | return (T) collection.Load(filePath); 189 | } 190 | 191 | collection = new ResourceCollection(this); 192 | _resourceCollections.Add(typeof(T), collection); 193 | 194 | return (T) collection.Load(filePath); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /SourceUtils/SourceUtils.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2C5FEFA1-39BA-458E-B95C-2D8414958820} 8 | Library 9 | Properties 10 | SourceUtils 11 | SourceUtils 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | true 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | true 33 | 34 | 35 | 36 | ..\packages\Facepunch.Parse.0.1.0-CI00040\lib\net35\Facepunch.Parse.dll 37 | 38 | 39 | ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll 40 | True 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | True 62 | True 63 | Resources.resx 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | ResXFileCodeGenerator 104 | Resources.Designer.cs 105 | 106 | 107 | 108 | 115 | -------------------------------------------------------------------------------- /SourceUtils/SubStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SourceUtils 5 | { 6 | public class SubStream : Stream 7 | { 8 | public Stream BaseStream { get; } 9 | 10 | public override bool CanRead => true; 11 | public override bool CanSeek => BaseStream.CanSeek; 12 | public override bool CanWrite => false; 13 | public override long Length { get; } 14 | 15 | public override long Position 16 | { 17 | get { return BaseStream.Position - _offset; } 18 | set { Seek( value, SeekOrigin.Begin ); } 19 | } 20 | 21 | private readonly long _offset; 22 | private readonly bool _ownsBaseStream; 23 | 24 | public SubStream( Stream baseStream, long offset, long length, bool ownsBaseStream ) 25 | { 26 | BaseStream = baseStream; 27 | 28 | _offset = offset; 29 | Length = length; 30 | 31 | _ownsBaseStream = ownsBaseStream; 32 | } 33 | 34 | public override void Flush() 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | 39 | public override long Seek( long offset, SeekOrigin origin ) 40 | { 41 | switch ( origin ) 42 | { 43 | case SeekOrigin.Begin: 44 | if ( offset < 0 || offset > Length ) throw new ArgumentOutOfRangeException(); 45 | return BaseStream.Seek( _offset + offset, SeekOrigin.Begin ) - _offset; 46 | case SeekOrigin.Current: 47 | var curPos = Position; 48 | if ( curPos < 0 || curPos > Length ) throw new InvalidOperationException(); 49 | if ( curPos + offset < 0 || curPos + offset > Length ) throw new ArgumentOutOfRangeException(); 50 | return BaseStream.Seek( offset, SeekOrigin.Current ) - _offset; 51 | case SeekOrigin.End: 52 | if ( offset > 0 || offset < -Length ) throw new ArgumentOutOfRangeException(); 53 | return BaseStream.Seek( _offset + Length + offset, SeekOrigin.Begin ) - _offset; 54 | default: 55 | return BaseStream.Seek( offset, origin ); 56 | } 57 | } 58 | 59 | public override void SetLength( long value ) 60 | { 61 | throw new NotImplementedException(); 62 | } 63 | 64 | public override int Read( byte[] buffer, int offset, int count ) 65 | { 66 | var curPos = Position; 67 | if ( curPos < 0 || curPos > Length ) throw new InvalidOperationException(); 68 | 69 | count = Math.Min( count, (int) (Length - curPos) ); 70 | return BaseStream.Read( buffer, offset, count ); 71 | } 72 | 73 | public override void Write( byte[] buffer, int offset, int count ) 74 | { 75 | throw new NotImplementedException(); 76 | } 77 | 78 | protected override void Dispose( bool disposing ) 79 | { 80 | base.Dispose( disposing ); 81 | 82 | if ( _ownsBaseStream && disposing ) BaseStream.Dispose(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /SourceUtils/ValveBsp/BspTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SourceUtils.ValveBsp 8 | { 9 | public class BspTree 10 | { 11 | internal interface IElem 12 | { 13 | void GetIntersectingLeaves( Vector3[] corners, List outLeaves ); 14 | } 15 | 16 | private class Node : IElem 17 | { 18 | public readonly int Index; 19 | public readonly BspNode Info; 20 | 21 | public readonly Plane Plane; 22 | 23 | public readonly IElem ChildA; 24 | public readonly IElem ChildB; 25 | 26 | public IElem this[ int index ] 27 | { 28 | get 29 | { 30 | switch ( index ) 31 | { 32 | case 0: return ChildA; 33 | case 1: return ChildB; 34 | default: throw new IndexOutOfRangeException(); 35 | } 36 | } 37 | } 38 | 39 | public Node( ValveBspFile bsp, int index ) 40 | { 41 | Index = index; 42 | Info = bsp.Nodes[index]; 43 | 44 | Plane = bsp.Planes[Info.PlaneNum]; 45 | 46 | ChildA = Info.ChildA.IsLeaf ? (IElem) new Leaf( bsp, Info.ChildA.Index ) : new Node( bsp, Info.ChildA.Index ); 47 | ChildB = Info.ChildB.IsLeaf ? (IElem) new Leaf( bsp, Info.ChildB.Index ) : new Node( bsp, Info.ChildB.Index ); 48 | } 49 | 50 | void IElem.GetIntersectingLeaves( Vector3[] corners, List outLeaves ) 51 | { 52 | bool front = false, back = false; 53 | 54 | for ( int i = 0, count = corners.Length; i < count; ++i ) 55 | { 56 | if ( Plane.IsInFront( corners[i] ) ) 57 | { 58 | front = true; 59 | if ( back ) break; 60 | } 61 | else 62 | { 63 | back = true; 64 | if ( front ) break; 65 | } 66 | } 67 | 68 | if ( front ) ChildA.GetIntersectingLeaves( corners, outLeaves ); 69 | if ( back ) ChildB.GetIntersectingLeaves( corners, outLeaves ); 70 | } 71 | } 72 | 73 | public class Leaf : IElem 74 | { 75 | public readonly int Index; 76 | public readonly IBspLeaf Info; 77 | 78 | public Leaf( ValveBspFile bsp, int index ) 79 | { 80 | Index = index; 81 | Info = bsp.Leaves[index]; 82 | } 83 | 84 | void IElem.GetIntersectingLeaves( Vector3[] corners, List outLeaves ) 85 | { 86 | if ( Info.Cluster != -1 ) outLeaves.Add( this ); 87 | } 88 | } 89 | 90 | private readonly ValveBspFile _bsp; 91 | private readonly Node _headNode; 92 | 93 | public readonly BspModel Info; 94 | 95 | public BspTree( ValveBspFile bsp, int modelIndex ) 96 | { 97 | _bsp = bsp; 98 | Info = bsp.Models[modelIndex]; 99 | 100 | _headNode = new Node( bsp, Info.HeadNode ); 101 | } 102 | 103 | [ThreadStatic] 104 | private static Vector3[] _sCorners; 105 | 106 | public IEnumerable GetIntersectingLeaves( Vector3 min, Vector3 max ) 107 | { 108 | var list = new List(); 109 | GetIntersectingLeaves( min, max, list ); 110 | return list; 111 | } 112 | 113 | public void GetIntersectingLeaves( Vector3 min, Vector3 max, List outLeaves ) 114 | { 115 | if ( _sCorners == null ) _sCorners = new Vector3[8]; 116 | 117 | _sCorners[0] = new Vector3( min.X, min.Y, min.Z ); 118 | _sCorners[1] = new Vector3( max.X, min.Y, min.Z ); 119 | _sCorners[2] = new Vector3( min.X, max.Y, min.Z ); 120 | _sCorners[3] = new Vector3( max.X, max.Y, min.Z ); 121 | _sCorners[4] = new Vector3( min.X, min.Y, max.Z ); 122 | _sCorners[5] = new Vector3( max.X, min.Y, max.Z ); 123 | _sCorners[6] = new Vector3( min.X, max.Y, max.Z ); 124 | _sCorners[7] = new Vector3( max.X, max.Y, max.Z ); 125 | 126 | ((IElem) _headNode).GetIntersectingLeaves( _sCorners, outLeaves ); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /SourceUtils/ValveBsp/DisplacementManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SourceUtils.ValveBsp 4 | { 5 | public class DisplacementManager 6 | { 7 | private readonly ValveBspFile _bsp; 8 | private readonly Dictionary _displacements = new Dictionary(); 9 | 10 | internal DisplacementManager( ValveBspFile bsp ) 11 | { 12 | _bsp = bsp; 13 | } 14 | 15 | public Displacement this[ int index ] 16 | { 17 | get 18 | { 19 | lock ( this ) 20 | { 21 | Displacement existing; 22 | if ( _displacements.TryGetValue( index, out existing ) ) return existing; 23 | 24 | existing = new Displacement( _bsp, index ); 25 | _displacements.Add( index, existing ); 26 | return existing; 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SourceUtils/ValveBsp/GameLump.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SourceUtils 10 | { 11 | partial class ValveBspFile 12 | { 13 | public class GameLump : ILump 14 | { 15 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 16 | private struct Item 17 | { 18 | public readonly int Id; 19 | public readonly ushort Flags; 20 | public readonly ushort Version; 21 | public readonly int FileOffset; 22 | public readonly int FileLength; 23 | } 24 | 25 | private readonly ValveBspFile _bspFile; 26 | private Dictionary _items; 27 | 28 | public LumpType LumpType { get; } 29 | 30 | public GameLump( ValveBspFile bspFile, LumpType type ) 31 | { 32 | _bspFile = bspFile; 33 | LumpType = type; 34 | } 35 | 36 | private string GetIdString( int id ) 37 | { 38 | var str = ""; 39 | 40 | for ( var i = 0; i < 4 && id >> (i << 3) > 0; ++i ) 41 | { 42 | str = (char) ((id >> (i << 3)) & 0x7f) + str; 43 | } 44 | 45 | return str; 46 | } 47 | 48 | public ushort GetItemFlags( string id ) 49 | { 50 | EnsureLoaded(); 51 | 52 | return _items[id].Flags; 53 | } 54 | 55 | public ushort GetItemVersion( string id ) 56 | { 57 | EnsureLoaded(); 58 | 59 | return _items[id].Version; 60 | } 61 | 62 | public Stream OpenItem( string id ) 63 | { 64 | EnsureLoaded(); 65 | 66 | var item = _items[id]; 67 | return _bspFile.GetSubStream( item.FileOffset, item.FileLength ); 68 | } 69 | 70 | private void EnsureLoaded() 71 | { 72 | lock ( this ) 73 | { 74 | if ( _items != null ) return; 75 | 76 | _items = new Dictionary(); 77 | 78 | using ( var reader = new BinaryReader( _bspFile.GetLumpStream( LumpType ) ) ) 79 | { 80 | var count = reader.ReadInt32(); 81 | LumpReader.ReadLumpFromStream( reader.BaseStream, count, item => _items.Add( GetIdString( item.Id ), item ) ); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /SourceUtils/ValveBsp/LumpInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace SourceUtils 4 | { 5 | partial class ValveBspFile 6 | { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public struct LumpInfo 9 | { 10 | public int Offset; 11 | public int Length; 12 | public int Version; 13 | public LumpType IdentCode; 14 | 15 | public override string ToString() 16 | { 17 | return $"{{ Type: {IdentCode}, Length: {Length:N0}, Version: {Version} }}"; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SourceUtils/ValveBsp/LumpType.cs: -------------------------------------------------------------------------------- 1 | namespace SourceUtils 2 | { 3 | partial class ValveBspFile 4 | { 5 | public interface ILump 6 | { 7 | LumpType LumpType { get; } 8 | } 9 | 10 | public enum LumpType : int 11 | { 12 | ENTITIES, 13 | PLANES, 14 | TEXDATA, 15 | VERTEXES, 16 | VISIBILITY, 17 | NODES, 18 | TEXINFO, 19 | FACES, 20 | LIGHTING, 21 | OCCLUSION, 22 | LEAFS, 23 | FACEIDS, 24 | EDGES, 25 | SURFEDGES, 26 | MODELS, 27 | WORLDLIGHTS, 28 | LEAFFACES, 29 | LEAFBRUSHES, 30 | BRUSHES, 31 | BRUSHSIDES, 32 | AREAS, 33 | AREAPORTALS, 34 | PROPCOLLISION, 35 | PROPHULLS, 36 | PROPHULLVERTS, 37 | PROPTRIS, 38 | DISPINFO, 39 | ORIGINALFACES, 40 | PHYSDISP, 41 | PHYSCOLLIDE, 42 | VERTNORMALS, 43 | VERTNORMALINDICES, 44 | DISP_LIGHTMAP_ALPHAS, 45 | DISP_VERTS, 46 | DISP_LIGHTMAP_SAMPLE_POSITIONS, 47 | GAME_LUMP, 48 | LEAFWATERDATA, 49 | PRIMITIVES, 50 | PRIMVERTS, 51 | PRIMINDICES, 52 | PAKFILE, 53 | CLIPPORTALVERTS, 54 | CUBEMAPS, 55 | TEXDATA_STRING_DATA, 56 | TEXDATA_STRING_TABLE, 57 | OVERLAYS, 58 | LEAFMINDISTTOWATER, 59 | FACE_MACRO_TEXTURE_INFO, 60 | DISP_TRIS, 61 | PROP_BLOB, 62 | WATEROVERLAYS, 63 | LEAF_AMBIENT_INDEX_HDR, 64 | LEAF_AMBIENT_INDEX, 65 | LIGHTING_HDR, 66 | WORLDLIGHTS_HDR, 67 | LEAF_AMBIENT_LIGHTING_HDR, 68 | LEAF_AMBIENT_LIGHTING, 69 | XZIPPAKFILE, 70 | FACES_HDR, 71 | MAP_FLAGS, 72 | OVERLAY_FADES, 73 | OVERLAY_SYSTEM_LEVELS, 74 | PHYSLEVEL, 75 | DISP_MULTIBLEND 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SourceUtils/ValveBsp/PakFileLump.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using ICSharpCode.SharpZipLib.Zip; 6 | 7 | namespace SourceUtils 8 | { 9 | partial class ValveBspFile 10 | { 11 | public class PakFileLump : DisposingEventTarget, ILump, IResourceProvider 12 | { 13 | /// 14 | /// If true, will write to a file to help debug the contents. 15 | /// 16 | public static bool DebugContents { get; set; } 17 | 18 | [ThreadStatic] 19 | private static Dictionary _sArchivePool; 20 | 21 | private static ZipFile GetZipArchive( PakFileLump pak ) 22 | { 23 | var pool = _sArchivePool ?? (_sArchivePool = new Dictionary()); 24 | 25 | ZipFile archive; 26 | if ( pool.TryGetValue( pak, out archive ) ) return archive; 27 | 28 | archive = new ZipFile( pak._bspFile.GetLumpStream( pak.LumpType ) ); 29 | 30 | pak.Disposing += _ => 31 | { 32 | pool.Remove( pak ); 33 | archive.Close(); 34 | }; 35 | 36 | pool.Add( pak, archive ); 37 | 38 | return archive; 39 | } 40 | 41 | public LumpType LumpType { get; } 42 | 43 | private readonly ValveBspFile _bspFile; 44 | private bool _loaded; 45 | 46 | private readonly Dictionary _entryDict = 47 | new Dictionary( StringComparer.InvariantCultureIgnoreCase ); 48 | 49 | public PakFileLump( ValveBspFile bspFile, LumpType type ) 50 | { 51 | _bspFile = bspFile; 52 | _loaded = false; 53 | LumpType = type; 54 | } 55 | 56 | private void EnsureLoaded() 57 | { 58 | lock ( this ) 59 | { 60 | if ( _loaded ) return; 61 | _loaded = true; 62 | 63 | _bspFile.Disposing += _ => Dispose(); 64 | 65 | if ( DebugContents ) 66 | { 67 | using ( var stream = _bspFile.GetLumpStream( LumpType ) ) 68 | { 69 | var bytes = new byte[stream.Length]; 70 | stream.Read( bytes, 0, bytes.Length ); 71 | File.WriteAllBytes( $"{_bspFile.Name}.pakfile.zip", bytes ); 72 | } 73 | } 74 | 75 | using ( var archive = new ZipFile( _bspFile.GetLumpStream( LumpType ) ) ) 76 | { 77 | _entryDict.Clear(); 78 | for ( var i = 0; i < archive.Count; ++i ) 79 | { 80 | var entry = archive[i]; 81 | var path = $"/{entry.Name.Replace( '\\', '/' )}"; 82 | if ( !entry.IsFile || _entryDict.ContainsKey( path ) ) continue; 83 | _entryDict.Add( path, i ); 84 | } 85 | } 86 | } 87 | } 88 | 89 | public IEnumerable GetFiles( string directory = "" ) 90 | { 91 | var prefix = $"{directory}/"; 92 | 93 | EnsureLoaded(); 94 | return _entryDict.Keys 95 | .Where( x => x.StartsWith( prefix, StringComparison.InvariantCultureIgnoreCase ) ) 96 | .Select( x => x.Substring( prefix.Length ) ); 97 | } 98 | 99 | public IEnumerable GetDirectories( string directory = "" ) 100 | { 101 | var prefix = $"{directory}/"; 102 | 103 | EnsureLoaded(); 104 | return _entryDict.Keys 105 | .Where( x => x.StartsWith( prefix, StringComparison.InvariantCultureIgnoreCase ) ) 106 | .Select( x => x.Substring( prefix.Length ) ) 107 | .Where( x => x.Contains( '/' ) ) 108 | .Select( x => x.Substring( 0, x.IndexOf( '/' ) ) ) 109 | .Distinct( StringComparer.InvariantCultureIgnoreCase ); 110 | } 111 | 112 | public bool ContainsFile( string filePath ) 113 | { 114 | EnsureLoaded(); 115 | return _entryDict.ContainsKey( $"/{filePath}" ); 116 | } 117 | 118 | public Stream OpenFile( string filePath ) 119 | { 120 | EnsureLoaded(); 121 | var archive = GetZipArchive( this ); 122 | return archive.GetInputStream( _entryDict[$"/{filePath}"] ); 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /SourceUtils/ValveBsp/Reflection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace SourceUtils 5 | { 6 | [AttributeUsage(AttributeTargets.Struct)] 7 | public class StructVersionAttribute : Attribute 8 | { 9 | public int MinVersion { get; set; } = 0; 10 | public int MaxVersion { get; set; } = int.MaxValue; 11 | 12 | public StructVersionAttribute() { } 13 | 14 | public StructVersionAttribute( int minVersion, int maxVersion ) 15 | { 16 | MinVersion = minVersion; 17 | MaxVersion = maxVersion; 18 | } 19 | } 20 | 21 | partial class ValveBspFile 22 | { 23 | [AttributeUsage(AttributeTargets.Property)] 24 | private class BspLumpAttribute : Attribute 25 | { 26 | public LumpType Type { get; set; } 27 | 28 | public BspLumpAttribute(LumpType type) 29 | { 30 | Type = type; 31 | } 32 | } 33 | 34 | private void InitializeLumps() 35 | { 36 | foreach (var prop in GetType().GetProperties() ) 37 | { 38 | var attrib = prop.GetCustomAttribute(); 39 | if (attrib == null) continue; 40 | if (!typeof(ILump).IsAssignableFrom(prop.PropertyType)) continue; 41 | 42 | prop.SetValue(this, Activator.CreateInstance(prop.PropertyType, this, attrib.Type)); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /SourceUtils/ValveBsp/VisibilityLump.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SourceUtils 11 | { 12 | partial class ValveBspFile 13 | { 14 | public class VisibilityLump : ILump, IEnumerable> 15 | { 16 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 17 | private struct ByteOffset 18 | { 19 | public int Pvs; 20 | public int Pas; 21 | } 22 | 23 | private int _numClusters = -1; 24 | 25 | private bool Loaded => _numClusters != -1; 26 | 27 | public LumpType LumpType { get; } 28 | 29 | public int NumClusters 30 | { 31 | get 32 | { 33 | EnsureLoaded(); 34 | return _numClusters; 35 | } 36 | } 37 | 38 | private readonly ValveBspFile _bspFile; 39 | private ByteOffset[] _offsets; 40 | private HashSet[] _vpsList; 41 | 42 | public VisibilityLump( ValveBspFile bspFile, LumpType type ) 43 | { 44 | _bspFile = bspFile; 45 | LumpType = type; 46 | } 47 | 48 | public HashSet this[ int clusterIndex ] 49 | { 50 | get 51 | { 52 | EnsureLoaded(); 53 | var set = _vpsList[clusterIndex]; 54 | return set ?? (_vpsList[clusterIndex] = ReadSet( _offsets[clusterIndex].Pvs )); 55 | } 56 | } 57 | 58 | private HashSet ReadSet( int byteOffset ) 59 | { 60 | using ( var stream = _bspFile.GetLumpStream( LumpType ) ) 61 | { 62 | stream.Seek( byteOffset, SeekOrigin.Begin ); 63 | 64 | var set = new HashSet(); 65 | 66 | var clusters = NumClusters; 67 | var offset = 0; 68 | while ( offset < clusters ) 69 | { 70 | var bits = stream.ReadByte(); 71 | if ( bits == 0 ) 72 | { 73 | offset += stream.ReadByte() * 8; 74 | continue; 75 | } 76 | 77 | for ( var i = 0; i < 8 && offset + i < clusters; ++i ) 78 | { 79 | if ( (bits & (1 << i)) != 0 ) set.Add( offset + i ); 80 | } 81 | 82 | offset += 8; 83 | } 84 | 85 | return set; 86 | } 87 | } 88 | 89 | private void EnsureLoaded() 90 | { 91 | lock ( this ) 92 | { 93 | if ( Loaded ) return; 94 | 95 | using ( var reader = new BinaryReader( _bspFile.GetLumpStream( LumpType ) ) ) 96 | { 97 | if ( reader.BaseStream.Length == 0 ) 98 | { 99 | _numClusters = 0; 100 | return; 101 | } 102 | 103 | _numClusters = reader.ReadInt32(); 104 | _vpsList = new HashSet[_numClusters]; 105 | _offsets = LumpReader.ReadLumpFromStream( reader.BaseStream, _numClusters ); 106 | } 107 | } 108 | } 109 | 110 | private struct Enumerator : IEnumerator> 111 | { 112 | private readonly VisibilityLump _lump; 113 | private int _currentIndex; 114 | 115 | public Enumerator( VisibilityLump lump ) 116 | { 117 | _lump = lump; 118 | _currentIndex = -1; 119 | } 120 | 121 | public void Dispose() { } 122 | 123 | public bool MoveNext() 124 | { 125 | return ++_currentIndex < _lump.NumClusters; 126 | } 127 | 128 | public void Reset() 129 | { 130 | _currentIndex = -1; 131 | } 132 | 133 | public HashSet Current => _lump[_currentIndex]; 134 | object IEnumerator.Current => _lump[_currentIndex]; 135 | } 136 | 137 | IEnumerator IEnumerable.GetEnumerator() 138 | { 139 | return GetEnumerator(); 140 | } 141 | 142 | public IEnumerator> GetEnumerator() 143 | { 144 | EnsureLoaded(); 145 | return new Enumerator(this); 146 | } 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /SourceUtils/ValveMaterialFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using Facepunch.Parse; 8 | 9 | namespace SourceUtils 10 | { 11 | public class VmtParserException : Exception 12 | { 13 | public VmtParserException(string expected, int line, string lineValue) 14 | : base( $"Error while parsing material: expected {expected} on line {line}.{Environment.NewLine}{lineValue}" ) { } 15 | } 16 | 17 | [PathPrefix("materials")] 18 | public class ValveMaterialFile 19 | { 20 | public static ValveMaterialFile FromStream(Stream stream) 21 | { 22 | return new ValveMaterialFile( stream ); 23 | } 24 | 25 | public static ValveMaterialFile FromProvider( string path, params IResourceProvider[] providers ) 26 | { 27 | var provider = providers.FirstOrDefault( x => x.ContainsFile( path ) ); 28 | if ( provider == null ) return null; 29 | 30 | ValveMaterialFile vmt; 31 | using ( var stream = provider.OpenFile( path ) ) 32 | { 33 | vmt = new ValveMaterialFile( stream ); 34 | } 35 | 36 | if ( !vmt.Shaders.Any() ) return null; 37 | 38 | var shader = vmt.Shaders.First(); 39 | var props = vmt[shader]; 40 | 41 | if ( !shader.Equals( "patch", StringComparison.InvariantCultureIgnoreCase ) ) return vmt; 42 | 43 | var includePath = ((string) props["include"]).Replace( '\\', '/' ); 44 | var includeVmt = FromProvider( includePath, providers ); 45 | 46 | if (includeVmt == null) 47 | { 48 | Console.ForegroundColor = ConsoleColor.Yellow; 49 | Console.WriteLine($"Missing material '{includePath}' included by '{path}'!"); 50 | Console.ResetColor(); 51 | 52 | return null; 53 | } 54 | 55 | var includeShader = includeVmt.Shaders.First(); 56 | 57 | includeVmt[includeShader].MergeFrom( props["insert"], false ); 58 | includeVmt[includeShader].MergeFrom( props["replace"], true ); 59 | 60 | return includeVmt; 61 | } 62 | 63 | private readonly KeyValues _keyValues; 64 | 65 | private ValveMaterialFile( Stream stream ) 66 | { 67 | _keyValues = KeyValues.FromStream( stream ); 68 | } 69 | 70 | public IEnumerable Shaders => _keyValues.Keys; 71 | 72 | public bool ContainsShader(string shader) 73 | { 74 | return _keyValues.ContainsKey(shader); 75 | } 76 | 77 | public KeyValues.Entry this[string shader] => _keyValues[shader]; 78 | } 79 | } -------------------------------------------------------------------------------- /SourceUtils/ValveVertexFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace SourceUtils 9 | { 10 | [StructLayout( LayoutKind.Sequential, Pack = 1 )] 11 | public struct StudioVertex 12 | { 13 | public StudioBoneWeight BoneWeights; 14 | public Vector3 Position; 15 | public Vector3 Normal; 16 | public float TexCoordX; 17 | public float TexCoordY; 18 | 19 | public override string ToString() 20 | { 21 | return $"{Position}, {Normal}, ({TexCoordX}, {TexCoordY})"; 22 | } 23 | } 24 | 25 | [StructLayout( LayoutKind.Sequential, Pack = 1 )] 26 | public struct StudioBoneWeight 27 | { 28 | public float Weight0; 29 | public float Weight1; 30 | public float Weight2; 31 | public byte Bone0; 32 | public byte Bone1; 33 | public byte Bone2; 34 | public byte NumBones; 35 | } 36 | 37 | public class ValveVertexFile 38 | { 39 | [StructLayout( LayoutKind.Sequential, Pack = 1 )] 40 | public struct VertexFixup 41 | { 42 | public int Lod; 43 | public int SourceVertexId; 44 | public int NumVertices; 45 | } 46 | 47 | public static ValveVertexFile FromProvider( string path, params IResourceProvider[] providers ) 48 | { 49 | var provider = providers.FirstOrDefault( x => x.ContainsFile( path ) ); 50 | if ( provider == null ) return null; 51 | 52 | using ( var stream = provider.OpenFile( path ) ) 53 | { 54 | return new ValveVertexFile( stream ); 55 | } 56 | } 57 | 58 | public static ValveVertexFile FromStream(Stream stream) 59 | { 60 | return new ValveVertexFile(stream); 61 | } 62 | 63 | private readonly int[] _numVerts; 64 | private readonly StudioVertex[] _vertices; 65 | private readonly Vector4[] _tangents; 66 | private readonly VertexFixup[] _fixups; 67 | 68 | public int NumLods { get; } 69 | public bool HasTangents { get; } 70 | 71 | public ValveVertexFile( Stream stream ) 72 | { 73 | using ( var reader = new BinaryReader( stream ) ) 74 | { 75 | var id = reader.ReadInt32(); 76 | var version = reader.ReadInt32(); 77 | 78 | Debug.Assert( id == 0x56534449 ); 79 | Debug.Assert( version == 4 ); 80 | 81 | reader.ReadInt32(); 82 | 83 | NumLods = reader.ReadInt32(); 84 | _numVerts = new int[NumLods]; 85 | 86 | for ( var i = 0; i < 8; ++i ) 87 | { 88 | var count = reader.ReadInt32(); 89 | if ( i < NumLods ) _numVerts[i] = count; 90 | } 91 | 92 | var numFixups = reader.ReadInt32(); 93 | var fixupTableStart = reader.ReadInt32(); 94 | var vertexDataStart = reader.ReadInt32(); 95 | var tangentDataStart = reader.ReadInt32(); 96 | 97 | if ( numFixups > 0 ) 98 | { 99 | reader.BaseStream.Seek( fixupTableStart, SeekOrigin.Begin ); 100 | _fixups = LumpReader.ReadLumpFromStream( reader.BaseStream, numFixups ); 101 | } 102 | 103 | HasTangents = tangentDataStart != 0; 104 | 105 | var vertLength = (int) (HasTangents ? tangentDataStart - vertexDataStart : stream.Length - vertexDataStart); 106 | 107 | reader.BaseStream.Seek( vertexDataStart, SeekOrigin.Begin ); 108 | _vertices = LumpReader.ReadLumpFromStream( reader.BaseStream, vertLength / Marshal.SizeOf() ); 109 | 110 | if ( HasTangents ) 111 | { 112 | var tangLength = (int) (stream.Length - tangentDataStart); 113 | reader.BaseStream.Seek( tangentDataStart, SeekOrigin.Begin ); 114 | _tangents = LumpReader.ReadLumpFromStream( reader.BaseStream, tangLength / Marshal.SizeOf() ); 115 | } 116 | } 117 | } 118 | 119 | public int GetVertexCount( int lod ) 120 | { 121 | return GetVertices( lod, null ); 122 | } 123 | 124 | public int GetVertices( int lod, StudioVertex[] dest, int offset = 0 ) 125 | { 126 | if ( _fixups == null ) 127 | { 128 | if (dest != null) Array.Copy( _vertices, 0, dest, offset, _numVerts[lod] ); 129 | return _numVerts[lod]; 130 | } 131 | 132 | var startOffset = offset; 133 | 134 | foreach ( var vertexFixup in _fixups ) 135 | { 136 | if ( vertexFixup.Lod < lod ) continue; 137 | 138 | if ( dest != null ) Array.Copy( _vertices, vertexFixup.SourceVertexId, dest, offset, vertexFixup.NumVertices ); 139 | offset += vertexFixup.NumVertices; 140 | } 141 | 142 | return offset - startOffset; 143 | } 144 | 145 | public Vector4[] GetTangents( int lod ) 146 | { 147 | throw new NotImplementedException(); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /SourceUtils/ValveVertexLightingFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace SourceUtils 9 | { 10 | public class ValveVertexLightingFile 11 | { 12 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 13 | private struct VhvMeshHeader 14 | { 15 | public int Lod; 16 | public int VertCount; 17 | public int VertOffset; 18 | public int Unused0; 19 | public int Unused1; 20 | public int Unused2; 21 | public int Unused3; 22 | } 23 | 24 | private interface IVertexData 25 | { 26 | VertexData4 GetVertexColor(); 27 | } 28 | 29 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 30 | public struct VertexData4 : IVertexData 31 | { 32 | public byte B; 33 | public byte G; 34 | public byte R; 35 | public byte A; 36 | 37 | public VertexData4 GetVertexColor() 38 | { 39 | return this; 40 | } 41 | 42 | public override string ToString() 43 | { 44 | return $"{{ {B:x2}{G:x2}{R:x2}{A:x2} }}"; 45 | } 46 | } 47 | 48 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 49 | private struct VertexData2 : IVertexData 50 | { 51 | public VertexData4 Color0; 52 | public VertexData4 Color1; 53 | public VertexData4 Color2; 54 | 55 | public VertexData4 GetVertexColor() 56 | { 57 | return new VertexData4 58 | { 59 | B = (byte) ((Color0.B + Color1.B + Color2.B) / 3), 60 | G = (byte) ((Color0.G + Color1.G + Color2.G) / 3), 61 | R = (byte) ((Color0.R + Color1.R + Color2.R) / 3), 62 | A = (byte) ((Color0.A + Color1.A + Color2.A) / 3) 63 | }; 64 | } 65 | } 66 | 67 | public static ValveVertexLightingFile FromStream( Stream stream ) 68 | { 69 | return new ValveVertexLightingFile( stream ); 70 | } 71 | 72 | public static ValveVertexLightingFile FromProvider( string path, params IResourceProvider[] providers ) 73 | { 74 | var provider = providers.FirstOrDefault( x => x.ContainsFile( path ) ); 75 | if ( provider == null ) return null; 76 | 77 | using ( var stream = provider.OpenFile( path ) ) 78 | { 79 | try 80 | { 81 | return new ValveVertexLightingFile( stream ); 82 | } 83 | catch ( NotSupportedException ) 84 | { 85 | return null; 86 | } 87 | } 88 | } 89 | 90 | private readonly VertexData4[][][] _samples; 91 | 92 | public ValveVertexLightingFile( Stream stream ) 93 | { 94 | using ( var reader = new BinaryReader( stream ) ) 95 | { 96 | var version = (int) reader.ReadByte(); 97 | var baseOffset = 0; 98 | 99 | if ( version == 0 ) 100 | { 101 | // First 3 bytes are missing :( 102 | version = 2; 103 | baseOffset = -3; 104 | } 105 | else 106 | { 107 | version |= reader.ReadByte() << 8; 108 | version |= reader.ReadUInt16() << 16; 109 | } 110 | 111 | if ( version != 2 ) 112 | { 113 | throw new NotSupportedException( $"Vertex lighting file version {version:x} is not supported."); 114 | } 115 | 116 | var checksum = reader.ReadInt32(); 117 | var vertFlags = reader.ReadUInt32(); 118 | var vertSize = reader.ReadUInt32(); 119 | var vertCount = reader.ReadUInt32(); 120 | var meshCount = reader.ReadInt32(); 121 | 122 | var unused0 = reader.ReadInt64(); // Unused 123 | var unused1 = reader.ReadInt64(); // Unused 124 | 125 | var meshHeaders = new List(); 126 | LumpReader.ReadLumpFromStream(stream, meshCount, meshHeaders); 127 | 128 | _samples = new VertexData4[meshHeaders.Max( x => x.Lod ) + 1][][]; 129 | 130 | for ( var i = 0; i < _samples.Length; ++i ) 131 | { 132 | _samples[i] = new VertexData4[meshHeaders.Count( x => x.Lod == i )][]; 133 | } 134 | 135 | foreach ( var meshHeader in meshHeaders ) 136 | { 137 | stream.Seek( meshHeader.VertOffset + baseOffset, SeekOrigin.Begin ); 138 | 139 | var samples = _samples[meshHeader.Lod]; 140 | var meshIndex = Array.IndexOf( samples, null ); 141 | 142 | if ( vertFlags == 2 ) 143 | { 144 | samples[meshIndex] = LumpReader.ReadLumpFromStream( stream, meshHeader.VertCount, x => x.GetVertexColor() ); 145 | } 146 | else 147 | { 148 | samples[meshIndex] = LumpReader.ReadLumpFromStream( stream, meshHeader.VertCount, x => x.GetVertexColor() ); 149 | } 150 | } 151 | } 152 | } 153 | 154 | public int GetMeshCount( int lod ) 155 | { 156 | return lod < _samples.Length ? _samples[lod].Length : 0; 157 | } 158 | 159 | public VertexData4[] GetSamples( int lod, int mesh ) 160 | { 161 | return _samples[lod][mesh]; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /SourceUtils/Vector2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SourceUtils 5 | { 6 | [StructLayout( LayoutKind.Sequential )] 7 | public struct Vector2 : IEquatable 8 | { 9 | public static readonly Vector2 Zero = new Vector2( 0f, 0f ); 10 | 11 | public static Vector2 operator -( Vector2 vector ) 12 | { 13 | return new Vector2( -vector.X, -vector.Y ); 14 | } 15 | 16 | public static Vector2 operator +( Vector2 a, Vector2 b ) 17 | { 18 | return new Vector2( a.X + b.X, a.Y + b.Y ); 19 | } 20 | 21 | public static Vector2 operator -( Vector2 a, Vector2 b ) 22 | { 23 | return new Vector2( a.X - b.X, a.Y - b.Y ); 24 | } 25 | 26 | public static Vector2 operator *( Vector2 vector, float scalar ) 27 | { 28 | return new Vector2( vector.X * scalar, vector.Y * scalar ); 29 | } 30 | 31 | public static Vector2 operator *( Vector2 a, Vector2 b ) 32 | { 33 | return new Vector2( a.X * b.X, a.Y * b.Y ); 34 | } 35 | 36 | public static Vector2 operator /( Vector2 a, Vector2 b ) 37 | { 38 | return new Vector2( a.X / b.X, a.Y / b.Y ); 39 | } 40 | 41 | public float X; 42 | public float Y; 43 | 44 | public Vector2( float x, float y ) 45 | { 46 | X = x; 47 | Y = y; 48 | } 49 | 50 | public bool Equals( Vector2 other ) 51 | { 52 | return X == other.X && Y == other.Y; 53 | } 54 | 55 | public bool Equals( Vector2 other, float epsilon ) 56 | { 57 | return Math.Abs( X - other.X ) < epsilon && Math.Abs( Y - other.Y ) < epsilon; 58 | } 59 | 60 | public override bool Equals( object obj ) 61 | { 62 | return obj is Vector2 && Equals( (Vector2) obj ); 63 | } 64 | 65 | public override int GetHashCode() 66 | { 67 | unchecked 68 | { 69 | var hashCode = X.GetHashCode(); 70 | hashCode = (hashCode * 397) ^ Y.GetHashCode(); 71 | return hashCode; 72 | } 73 | } 74 | 75 | public override string ToString() 76 | { 77 | return $"({X:F2}, {Y:F2})"; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SourceUtils/Vector3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SourceUtils 5 | { 6 | [StructLayout( LayoutKind.Sequential )] 7 | public struct Vector3 : IEquatable 8 | { 9 | public static readonly Vector3 Zero = new Vector3(0f, 0f, 0f); 10 | public static readonly Vector3 NaN = new Vector3( float.NaN, float.NaN, float.NaN ); 11 | 12 | public static Vector3 operator -( Vector3 vector ) 13 | { 14 | return new Vector3( -vector.X, -vector.Y, -vector.Z ); 15 | } 16 | 17 | public static Vector3 operator +( Vector3 a, Vector3 b ) 18 | { 19 | return new Vector3( a.X + b.X, a.Y + b.Y, a.Z + b.Z ); 20 | } 21 | 22 | public static Vector3 operator -( Vector3 a, Vector3 b ) 23 | { 24 | return new Vector3( a.X - b.X, a.Y - b.Y, a.Z - b.Z ); 25 | } 26 | 27 | public static Vector3 operator *( Vector3 a, Vector3 b ) 28 | { 29 | return new Vector3( a.X * b.X, a.Y * b.Y, a.Z * b.Z ); 30 | } 31 | 32 | public static Vector3 operator *( Vector3 vec, float scalar ) 33 | { 34 | return new Vector3( vec.X * scalar, vec.Y * scalar, vec.Z * scalar ); 35 | } 36 | 37 | public static Vector3 operator *( float scalar, Vector3 vec ) 38 | { 39 | return new Vector3( vec.X * scalar, vec.Y * scalar, vec.Z * scalar ); 40 | } 41 | 42 | public static Vector3 Min( Vector3 a, Vector3 b ) 43 | { 44 | return new Vector3( Math.Min( a.X, b.X ), Math.Min( a.Y, b.Y ), Math.Min( a.Z, b.Z ) ); 45 | } 46 | 47 | public static Vector3 Max( Vector3 a, Vector3 b ) 48 | { 49 | return new Vector3( Math.Max( a.X, b.X ), Math.Max( a.Y, b.Y ), Math.Max( a.Z, b.Z ) ); 50 | } 51 | 52 | public float X; 53 | public float Y; 54 | public float Z; 55 | 56 | public float Length => (float) Math.Sqrt( LengthSquared ); 57 | public float LengthSquared => X * X + Y * Y + Z * Z; 58 | 59 | public Vector3 Normalized => this * (1f / Length); 60 | public Vector3 Rounded => new Vector3((float) Math.Round(X), (float) Math.Round(Y), (float) Math.Round(Z)); 61 | 62 | public bool IsNaN => float.IsNaN( X ) || float.IsNaN( Y ) || float.IsNaN( Z ); 63 | 64 | public Vector3( float x, float y, float z ) 65 | { 66 | X = x; 67 | Y = y; 68 | Z = z; 69 | } 70 | 71 | public float Dot( Vector3 other ) 72 | { 73 | return X * other.X + Y * other.Y + Z * other.Z; 74 | } 75 | 76 | public Vector3 Cross( Vector3 other ) 77 | { 78 | return new Vector3( Y * other.Z - Z * other.Y, Z * other.X - X * other.Z, X * other.Y - Y * other.X ); 79 | } 80 | 81 | public bool Equals( Vector3 other ) 82 | { 83 | return X == other.X && Y == other.Y && Z == other.Z; 84 | } 85 | 86 | public bool Equals( Vector3 other, float epsilon ) 87 | { 88 | return Math.Abs( X - other.X ) < epsilon && Math.Abs( Y - other.Y ) < epsilon && 89 | Math.Abs( Z - other.Z ) < epsilon; 90 | } 91 | 92 | public override bool Equals( object obj ) 93 | { 94 | return obj is Vector3 && Equals( (Vector3) obj ); 95 | } 96 | 97 | public override int GetHashCode() 98 | { 99 | unchecked 100 | { 101 | var hashCode = X.GetHashCode(); 102 | hashCode = (hashCode * 397) ^ Y.GetHashCode(); 103 | hashCode = (hashCode * 397) ^ Z.GetHashCode(); 104 | return hashCode; 105 | } 106 | } 107 | 108 | public override string ToString() 109 | { 110 | return $"({X:F2}, {Y:F2}, {Z:F2})"; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /SourceUtils/Vector4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SourceUtils 5 | { 6 | [StructLayout(LayoutKind.Sequential)] 7 | public struct Vector4 : IEquatable 8 | { 9 | public static Vector4 operator -(Vector4 vector) 10 | { 11 | return new Vector4(-vector.X, -vector.Y, -vector.Z, -vector.W); 12 | } 13 | 14 | public float X; 15 | public float Y; 16 | public float Z; 17 | public float W; 18 | 19 | public Vector4(float x, float y, float z, float w) 20 | { 21 | X = x; 22 | Y = y; 23 | Z = z; 24 | W = w; 25 | } 26 | 27 | public bool Equals(Vector4 other) 28 | { 29 | return X == other.X && Y == other.Y && Z == other.Z && W == other.W; 30 | } 31 | 32 | public bool Equals(Vector4 other, float epsilon) 33 | { 34 | return Math.Abs( X - other.X ) < epsilon && Math.Abs( Y - other.Y ) < epsilon && Math.Abs( Z - other.Z ) < epsilon && Math.Abs( W - other.W ) < epsilon; 35 | } 36 | 37 | public override bool Equals(object obj) 38 | { 39 | return obj is Vector4 && Equals((Vector4) obj); 40 | } 41 | 42 | public override int GetHashCode() 43 | { 44 | unchecked 45 | { 46 | var hashCode = X.GetHashCode(); 47 | hashCode = (hashCode * 397) ^ Y.GetHashCode(); 48 | hashCode = (hashCode * 397) ^ Z.GetHashCode(); 49 | hashCode = (hashCode * 397) ^ W.GetHashCode(); 50 | return hashCode; 51 | } 52 | } 53 | 54 | public override string ToString() 55 | { 56 | return $"({X:F2}, {Y:F2}, {Z:F2}, {W:F2})"; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SourceUtils/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | nuget restore 4 | xbuild /p:Configuration=Debug /p:DefineConstants=LINUX 5 | tsc -p "SourceUtils.WebExport/Resources/" 6 | -------------------------------------------------------------------------------- /export-pages-kz.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | "SourceUtils.WebExport\bin\Debug\SourceUtils.WebExport.exe" export ^ 4 | --maps "kz_reach_v2,kz_colors_v2,kz_exps_cursedjourney" ^ 5 | --outdir "..\GOKZReplayViewer-pages\resources" ^ 6 | --gamedir "C:\Program Files (x86)\Steam\steamapps\common\Counter-Strike Global Offensive\csgo" ^ 7 | --mapsdir "maps" ^ 8 | --overwrite --verbose --url-prefix "/GOKZReplayViewer/resources" 9 | -------------------------------------------------------------------------------- /export-pages.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | "SourceUtils.WebExport\bin\Debug\SourceUtils.WebExport.exe" export ^ 4 | --maps "de_*" ^ 5 | --outdir "..\SourceUtils-pages" ^ 6 | --gamedir "C:\Program Files (x86)\Steam\steamapps\common\Counter-Strike Global Offensive\csgo" ^ 7 | --mapsdir "maps" ^ 8 | --untextured --overwrite --verbose --url-prefix "https://metapyziks.github.io/SourceUtils" 9 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mono SourceUtils.WebExport/bin/Debug/SourceUtils.WebExport.exe host -g "/home/ziks/.steam/root/steamapps/common/Counter-Strike Global Offensive/csgo" --untextured 4 | --------------------------------------------------------------------------------