├── .gitattributes ├── .gitignore ├── .idea ├── .gitignore ├── BSPParser.iml ├── codeStyles │ └── Project.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── BSPParser.cpp ├── BSPParser.h ├── CMakeLists.txt ├── CMakePresets.json ├── Displacements ├── Displacements.cpp ├── Displacements.h ├── NormalBlending.cpp ├── SubEdgeIterator.cpp ├── SubEdgeIterator.h ├── TBNGen.cpp └── UVGen.cpp ├── Errors ├── ParseError.cpp ├── ParseError.hpp ├── TriangulationError.cpp └── TriangulationError.hpp ├── FileFormat ├── Enums.h ├── Limits.h ├── Parser.cpp ├── Parser.h ├── Structs.h ├── Vector.cpp └── Vector2D.cpp ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | premakeout/ 366 | .vscode/ 367 | 368 | # idea user configs 369 | .idea/discord.xml 370 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/BSPParser.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /BSPParser.cpp: -------------------------------------------------------------------------------- 1 | #include "BSPParser.h" 2 | 3 | #include "FileFormat/Structs.h" 4 | #include "Displacements/Displacements.h" 5 | #include "Errors/ParseError.hpp" 6 | #include "Errors/TriangulationError.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace BSPStructs; 14 | using namespace BSPEnums; 15 | using namespace BSPErrors; 16 | 17 | void CalcNormal( 18 | const Vector* p0, const Vector* p1, const Vector* p2, 19 | Vector* n 20 | ) 21 | { 22 | Vector edge0 = { p1->x - p0->x, p1->y - p0->y, p1->z - p0->z }; 23 | Vector edge1 = { p2->x - p0->x, p2->y - p0->y, p2->z - p0->z }; 24 | 25 | *n = edge1.Cross(edge0); 26 | n->Normalise(); 27 | } 28 | 29 | void CalcTangentBinormal( 30 | const TexInfo* pTexInfo, const Plane* pPlane, 31 | const Vector* n, Vector* t, Vector* b 32 | ) 33 | { 34 | Vector sAxis{ pTexInfo->textureVecs[0][0], pTexInfo->textureVecs[0][1], pTexInfo->textureVecs[0][2] }; 35 | Vector tAxis{ pTexInfo->textureVecs[1][0], pTexInfo->textureVecs[1][1], pTexInfo->textureVecs[1][2] }; 36 | 37 | *b = tAxis; 38 | b->Normalise(); 39 | *t = n->Cross(*b); 40 | t->Normalise(); 41 | *b = t->Cross(*n); 42 | b->Normalise(); 43 | 44 | if (pPlane->normal.Dot(sAxis.Cross(tAxis)) > 0.0f) { 45 | *t *= -1; 46 | } 47 | } 48 | 49 | void BSPMap::ParseGameLumps() 50 | { 51 | const int32_t* pGameLumpHeader; 52 | BSPParser::GetLumpPtr( 53 | mpData, mDataSize, 54 | mpHeader, LUMP::GAME_LUMP, 55 | reinterpret_cast(&pGameLumpHeader) 56 | ); 57 | 58 | if ( 59 | *pGameLumpHeader < 0 || 60 | *pGameLumpHeader * sizeof(GameLump) + sizeof(int32_t) > mpHeader->lumps[static_cast(LUMP::GAME_LUMP)].length 61 | ) { 62 | throw ParseError("Number of game lumps exceeds the total size of the lump", LUMP::GAME_LUMP); 63 | } 64 | 65 | mNumGameLumps = *pGameLumpHeader; 66 | mpGameLumps = reinterpret_cast(pGameLumpHeader + 1); 67 | 68 | for (int i = 0; i < mNumGameLumps; i++) { 69 | switch (mpGameLumps[i].id) { 70 | case GameLumpID::DETAIL_PROPS: 71 | break; 72 | case GameLumpID::STATIC_PROPS: { 73 | switch (mpGameLumps[i].version) { 74 | case 4: 75 | ParseStaticPropLump(mpGameLumps[i], &mpStaticPropsV4); 76 | break; 77 | case 5: 78 | ParseStaticPropLump(mpGameLumps[i], &mpStaticPropsV5); 79 | break; 80 | case 6: 81 | ParseStaticPropLump(mpGameLumps[i], &mpStaticPropsV6); 82 | break; 83 | // A non-standard version 7 static prop lump exists in the 2013 multiplayer SDK exclusively 84 | // This may appear as either version 7 or 10, but is not compatible with other engine versions' v7 or v10 (thank you Valve) 85 | case 7: 86 | case 10: 87 | ParseStaticPropLump(mpGameLumps[i], &mpStaticPropsV7Multiplayer2013); 88 | break; 89 | default: 90 | throw ParseError((std::string("Unsupported static prop lump version ") + std::to_string(mpGameLumps[i].version)).c_str(), LUMP::GAME_LUMP); 91 | } 92 | 93 | break; 94 | } default: 95 | break; 96 | } 97 | } 98 | } 99 | 100 | bool BSPMap::IsFaceNodraw(const Face* pFace) const 101 | { 102 | return ( 103 | pFace->texInfo < 0 || 104 | ( 105 | mpTexInfos[pFace->texInfo].flags & 106 | ( 107 | SURF::NODRAW | 108 | SURF::SKIP | 109 | SURF::TRIGGER 110 | ) 111 | ) != SURF::NONE 112 | ); 113 | } 114 | 115 | void BSPMap::FreeAll() 116 | { 117 | if (mpData != nullptr) { 118 | free(mpData); 119 | mpData = nullptr; 120 | } 121 | if (mpPositions != nullptr) { 122 | free(mpPositions); 123 | mpPositions = nullptr; 124 | } 125 | if (mpNormals != nullptr) { 126 | free(mpNormals); 127 | mpNormals = nullptr; 128 | } 129 | if (mpTangents != nullptr) { 130 | free(mpTangents); 131 | mpTangents = nullptr; 132 | } 133 | if (mpBinormals != nullptr) { 134 | free(mpBinormals); 135 | mpBinormals = nullptr; 136 | } 137 | if (mpUVs != nullptr) { 138 | free(mpUVs); 139 | mpUVs = nullptr; 140 | } 141 | if (mpAlphas != nullptr) { 142 | free(mpAlphas); 143 | mpAlphas = nullptr; 144 | } 145 | if (mpTexIndices != nullptr) { 146 | free(mpTexIndices); 147 | mpTexIndices = nullptr; 148 | } 149 | } 150 | 151 | bool BSPMap::CalcUVs( 152 | const int16_t texInfoIdx, 153 | const Vector* const pos, 154 | float* const pUVs 155 | ) const 156 | { 157 | if (texInfoIdx < 0 || texInfoIdx > mNumTexInfos) return false; 158 | const TexInfo* pTexInfo = mpTexInfos + texInfoIdx; 159 | 160 | if (pTexInfo->texData < 0 || pTexInfo->texData >= mNumTexDatas) return false; 161 | const TexData* pTexData = mpTexDatas + pTexInfo->texData; 162 | 163 | const float* s = pTexInfo->textureVecs[0]; 164 | const float* t = pTexInfo->textureVecs[1]; 165 | 166 | pUVs[0] = s[0] * pos->x + s[1] * pos->y + s[2] * pos->z + s[3]; 167 | pUVs[1] = t[0] * pos->x + t[1] * pos->y + t[2] * pos->z + t[3]; 168 | 169 | pUVs[0] /= static_cast(pTexData->width); 170 | pUVs[1] /= static_cast(pTexData->height); 171 | 172 | return true; 173 | } 174 | 175 | bool BSPMap::GetSurfEdgeVerts(const int32_t index, Vector* pVertA, Vector* pVertB) const 176 | { 177 | if (index < 0 || index >= mNumSurfEdges) return false; 178 | 179 | int32_t edgeIdx = mpSurfEdges[index]; 180 | if (abs(edgeIdx) > mNumEdges) return false; 181 | 182 | uint16_t iA = mpEdges[abs(edgeIdx)].vertices[0]; 183 | uint16_t iB = mpEdges[abs(edgeIdx)].vertices[1]; 184 | 185 | if (iA >= mNumVertices || iB >= mNumVertices) return false; 186 | 187 | if (edgeIdx < 0) std::swap(iA, iB); 188 | 189 | memcpy(pVertA, mpVertices + iA, sizeof(Vector)); 190 | if (pVertB != nullptr) 191 | memcpy(pVertB, mpVertices + iB, sizeof(Vector)); 192 | 193 | return true; 194 | } 195 | 196 | void BSPMap::Triangulate() 197 | { 198 | using Displacement = Displacements::Displacement; 199 | 200 | // Get worldspawn faces 201 | const Face* firstFace = mpFaces + mpModels[0].firstFace; 202 | int32_t numFaces = mpModels[0].numFaces; 203 | 204 | // Calculate number of tris in the map 205 | mNumTris = 0U; 206 | for (const Face* pFace = firstFace; pFace < firstFace + numFaces; pFace++) { 207 | if (IsFaceNodraw(pFace) || pFace->numEdges < 3) continue; 208 | 209 | int16_t texIdx = pFace->texInfo; 210 | if (texIdx < 0 || texIdx >= mNumTexInfos) continue; 211 | 212 | int16_t dispIdx = pFace->dispInfo; 213 | if (dispIdx < 0) { // Not a displacement 214 | mNumTris += pFace->numEdges - 2; 215 | } else { 216 | if (dispIdx >= mNumDispInfos) { 217 | throw TriangulationError("Displacement index is greater than the number of displacements"); 218 | } 219 | 220 | int32_t size = 1 << mpDispInfos[dispIdx].power; 221 | mNumTris += size * size * 2; 222 | } 223 | } 224 | if (mNumTris == 0U) { 225 | throw TriangulationError("Map has no triangles"); 226 | } 227 | 228 | // malloc buffers 229 | mpPositions = reinterpret_cast(malloc(sizeof(Vector) * 3U * mNumTris)); 230 | mpNormals = reinterpret_cast(malloc(sizeof(Vector) * 3U * mNumTris)); 231 | mpTangents = reinterpret_cast(malloc(sizeof(Vector) * 3U * mNumTris)); 232 | mpBinormals = reinterpret_cast(malloc(sizeof(Vector) * 3U * mNumTris)); 233 | mpUVs = reinterpret_cast(malloc(sizeof(float) * 2U * 3U * mNumTris)); 234 | mpAlphas = reinterpret_cast(malloc(sizeof(float) * 3U * mNumTris)); 235 | mpTexIndices = reinterpret_cast(malloc(sizeof(int16_t) * mNumTris)); 236 | 237 | if ( 238 | mpPositions == nullptr || 239 | mpNormals == nullptr || 240 | mpTangents == nullptr || 241 | mpBinormals == nullptr || 242 | mpUVs == nullptr || 243 | mpAlphas == nullptr || 244 | mpTexIndices == nullptr 245 | ) { 246 | FreeAll(); 247 | throw TriangulationError("Failed to allocate memory for triangle data"); 248 | } 249 | 250 | // Generate vertices from displacements and smooth normals 251 | std::vector displacements(mNumDispInfos); 252 | for (int dispIdx = 0; dispIdx < mNumDispInfos; dispIdx++) { 253 | const DispInfo* pDispInfo = mpDispInfos + dispIdx; 254 | const Face* pFace = mpFaces + pDispInfo->mapFace; 255 | 256 | Vector corners[4]; 257 | int32_t firstCorner = 0; 258 | float firstCornerDist2 = std::numeric_limits::max(); 259 | 260 | for ( 261 | int32_t surfEdgeIdx = pFace->firstEdge; 262 | surfEdgeIdx < pFace->firstEdge + pFace->numEdges; 263 | surfEdgeIdx++ 264 | ) { 265 | Vector vert; 266 | if (!GetSurfEdgeVerts(surfEdgeIdx, &vert)) { 267 | FreeAll(); 268 | throw TriangulationError("Failed to get surface edge vertices"); 269 | } 270 | corners[surfEdgeIdx - pFace->firstEdge] = vert; 271 | 272 | Vector distVec = pDispInfo->startPosition - vert; 273 | float dist2 = distVec.Dot(distVec); 274 | if (dist2 < firstCornerDist2) { 275 | firstCorner = surfEdgeIdx - pFace->firstEdge; 276 | firstCornerDist2 = dist2; 277 | } 278 | } 279 | 280 | // Reorder corners 281 | { 282 | Vector tmpPoints[4]; 283 | for (int i = 0; i < 4; i++) { 284 | tmpPoints[i] = corners[i]; 285 | } 286 | 287 | for (int i = 0; i < 4; i++) { 288 | corners[i] = tmpPoints[(i + firstCorner) % 4]; 289 | } 290 | } 291 | 292 | Displacement& disp = displacements[dispIdx]; 293 | disp.Init(pDispInfo); 294 | 295 | Displacements::GenerateDispSurf(pDispInfo, mpDispVerts + pDispInfo->dispVertStart, corners, disp); 296 | Displacements::GenerateDispSurfNormals(pDispInfo, disp); 297 | Displacements::GenerateDispSurfTangentSpaces( 298 | pDispInfo, mpPlanes + pFace->planeNum, 299 | mpTexInfos + pFace->texInfo, 300 | disp 301 | ); 302 | 303 | float faceUVs[4][2]; 304 | for (int i = 0; i < 4; i++) { 305 | CalcUVs(pFace->texInfo, corners + i, faceUVs[i]); 306 | } 307 | 308 | Displacements::GenerateDispSurfUVs(pDispInfo, faceUVs, disp); 309 | } 310 | 311 | try { 312 | Displacements::SmoothNeighbouringDispSurfNormals(displacements); 313 | } catch (const std::out_of_range& e) { 314 | FreeAll(); 315 | throw TriangulationError(e.what()); 316 | } 317 | 318 | // Offsets 319 | size_t triIdx = 0U; 320 | 321 | Vector* p0 = mpPositions; 322 | Vector* p1 = mpPositions + 1U; 323 | Vector* p2 = mpPositions + 2U; 324 | 325 | Vector* n0 = mpNormals; 326 | Vector* n1 = mpNormals + 1U; 327 | Vector* n2 = mpNormals + 2U; 328 | 329 | Vector* t0 = mpTangents; 330 | Vector* t1 = mpTangents + 1U; 331 | Vector* t2 = mpTangents + 2U; 332 | 333 | Vector* b0 = mpBinormals; 334 | Vector* b1 = mpBinormals + 1U; 335 | Vector* b2 = mpBinormals + 2U; 336 | 337 | float* uv0 = mpUVs; 338 | float* uv1 = mpUVs + 2U; 339 | float* uv2 = mpUVs + 4U; 340 | 341 | float* a0 = mpAlphas; 342 | float* a1 = mpAlphas + 1U; 343 | float* a2 = mpAlphas + 2U; 344 | 345 | // Read data into buffers 346 | for (const Face* pFace = firstFace; pFace < firstFace + numFaces; pFace++) { 347 | if (IsFaceNodraw(pFace) || pFace->numEdges < 3) continue; 348 | 349 | // Get texture index 350 | int16_t texIdx = pFace->texInfo; 351 | if (texIdx < 0 || texIdx >= mNumTexInfos) continue; 352 | 353 | // Get displacement index 354 | int16_t dispIdx = pFace->dispInfo; 355 | 356 | if (dispIdx < 0) { // Triangulate face 357 | // Get root vertex 358 | Vector root; 359 | float rootUV[2]; 360 | GetSurfEdgeVerts(pFace->firstEdge, &root); 361 | if (!CalcUVs(pFace->texInfo, &root, rootUV)) { 362 | FreeAll(); 363 | throw TriangulationError("Failed to calculate root face UVs"); 364 | } 365 | 366 | // For each edge (ignoring first and last) 367 | for ( 368 | int32_t surfEdgeIdx = pFace->firstEdge + 1; 369 | surfEdgeIdx < pFace->firstEdge + pFace->numEdges - 1; 370 | surfEdgeIdx++ 371 | ) { 372 | // Add vertices to positions 373 | *p0 = root; 374 | if ( 375 | (mClockwise && !GetSurfEdgeVerts(surfEdgeIdx, p1, p2)) || 376 | (!mClockwise && !GetSurfEdgeVerts(surfEdgeIdx, p2, p1)) 377 | ) { 378 | FreeAll(); 379 | throw TriangulationError("Failed to get surface edge vertices"); 380 | } 381 | 382 | // Calculate UVs 383 | memcpy(uv0, rootUV, 2U * sizeof(float)); 384 | if ( 385 | !CalcUVs(texIdx, p1, uv1) || 386 | !CalcUVs(texIdx, p2, uv2) 387 | ) { 388 | FreeAll(); 389 | throw TriangulationError("Failed to calculate face vertex UVs"); 390 | } 391 | 392 | // Compute normal/tangent/bitangent 393 | CalcNormal(p0, p1, p2, n0); 394 | CalcTangentBinormal( 395 | mpTexInfos + texIdx, mpPlanes + pFace->planeNum, 396 | n0, t0, b0 397 | ); 398 | *n1 = *n0; 399 | *n2 = *n0; 400 | *t1 = *t0; 401 | *t2 = *t0; 402 | *b1 = *b0; 403 | *b2 = *b0; 404 | 405 | *a0 = 1.f; 406 | *a1 = 1.f; 407 | *a2 = 1.f; 408 | 409 | // Add texture index 410 | mpTexIndices[triIdx] = texIdx; 411 | 412 | // Increment 413 | triIdx++; 414 | 415 | p0 += 3U; 416 | p1 += 3U; 417 | p2 += 3U; 418 | 419 | n0 += 3U; 420 | n1 += 3U; 421 | n2 += 3U; 422 | 423 | t0 += 3U; 424 | t1 += 3U; 425 | t2 += 3U; 426 | 427 | b0 += 3U; 428 | b1 += 3U; 429 | b2 += 3U; 430 | 431 | uv0 += 3U * 2U; 432 | uv1 += 3U * 2U; 433 | uv2 += 3U * 2U; 434 | 435 | a0 += 3U; 436 | a1 += 3U; 437 | a2 += 3U; 438 | } 439 | } else { // Triangulate displacement 440 | const Displacement& disp = displacements[dispIdx]; 441 | int32_t size = 1 << disp.pInfo->power; 442 | 443 | // Write tris 444 | for (int32_t x = 0; x < size; x++) { 445 | for (int32_t y = 0; y < size; y++) { 446 | int32_t a = y * (size + 1) + x; 447 | int32_t b = (y + 1) * (size + 1) + x; 448 | int32_t c = (y + 1) * (size + 1) + (x + 1); 449 | int32_t d = y * (size + 1) + (x + 1); 450 | 451 | for (int tri = 0; tri < 2; tri++) { 452 | int32_t i0 = a, i1 = tri == 0 ? b : c, i2 = tri == 0 ? c : d; 453 | if (!mClockwise) std::swap(i1, i2); 454 | 455 | *p0 = disp.verts[i0]; 456 | *p1 = disp.verts[i1]; 457 | *p2 = disp.verts[i2]; 458 | 459 | *n0 = disp.normals[i0]; 460 | *n1 = disp.normals[i1]; 461 | *n2 = disp.normals[i2]; 462 | 463 | *t0 = disp.tangents[i0]; 464 | *t1 = disp.tangents[i1]; 465 | *t2 = disp.tangents[i2]; 466 | 467 | *b0 = disp.binormals[i0]; 468 | *b1 = disp.binormals[i1]; 469 | *b2 = disp.binormals[i2]; 470 | 471 | memcpy(uv0, disp.uvs + i0 * 2, sizeof(float) * 2); 472 | memcpy(uv1, disp.uvs + i1 * 2, sizeof(float) * 2); 473 | memcpy(uv2, disp.uvs + i2 * 2, sizeof(float) * 2); 474 | 475 | *a0 = disp.alphas[i0]; 476 | *a1 = disp.alphas[i1]; 477 | *a2 = disp.alphas[i2]; 478 | 479 | // Add texture index 480 | mpTexIndices[triIdx] = texIdx; 481 | 482 | // Increment 483 | triIdx++; 484 | 485 | p0 += 3U; 486 | p1 += 3U; 487 | p2 += 3U; 488 | 489 | n0 += 3U; 490 | n1 += 3U; 491 | n2 += 3U; 492 | 493 | t0 += 3U; 494 | t1 += 3U; 495 | t2 += 3U; 496 | 497 | b0 += 3U; 498 | b1 += 3U; 499 | b2 += 3U; 500 | 501 | uv0 += 3U * 2U; 502 | uv1 += 3U * 2U; 503 | uv2 += 3U * 2U; 504 | 505 | a0 += 3U; 506 | a1 += 3U; 507 | a2 += 3U; 508 | } 509 | } 510 | } 511 | } 512 | } 513 | } 514 | 515 | BSPMap::BSPMap( 516 | const uint8_t* const pFileData, const size_t dataSize, const bool clockwise 517 | ) : mDataSize(dataSize), mClockwise(clockwise) 518 | { 519 | try { 520 | if (pFileData == nullptr || dataSize == 0U) { 521 | throw ParseError("File data is null or empty", BSPEnums::LUMP::NONE); 522 | } 523 | 524 | mpData = reinterpret_cast(malloc(dataSize)); 525 | if (mpData == nullptr) { 526 | throw ParseError("Failed to allocate memory for file data copy", BSPEnums::LUMP::NONE); 527 | } 528 | 529 | memcpy(mpData, pFileData, dataSize); 530 | 531 | BSPParser::ParseHeader(mpData, dataSize, &mpHeader); 532 | if (mpHeader->version < 19 || mpHeader->version > 21) { 533 | throw ParseError("Unsupported BSP version", BSPEnums::LUMP::NONE); 534 | } 535 | 536 | BSPParser::ParseArray( 537 | mpData, dataSize, 538 | mpHeader, 539 | &mpVertices, &mNumVertices, 540 | LUMP::VERTICES, MAX_MAP_VERTS 541 | ); 542 | ParseLump(&mpPlanes, &mNumPlanes); 543 | ParseLump(&mpEdges, &mNumEdges); 544 | BSPParser::ParseArray( 545 | mpData, dataSize, 546 | mpHeader, 547 | &mpSurfEdges, &mNumSurfEdges, 548 | LUMP::SURFEDGES, MAX_MAP_SURFEDGES 549 | ); 550 | ParseLump(&mpFaces, &mNumFaces); 551 | ParseLump(&mpTexInfos, &mNumTexInfos); 552 | ParseLump(&mpTexDatas, &mNumTexDatas); 553 | BSPParser::ParseArray( 554 | mpData, dataSize, 555 | mpHeader, 556 | &mpTexDataStringTable, &mNumTexDataStringTableEntries, 557 | LUMP::TEXDATA_STRING_TABLE, MAX_MAP_TEXDATA_STRING_TABLE 558 | ); 559 | BSPParser::ParseArray( 560 | mpData, dataSize, 561 | mpHeader, 562 | &mpTexDataStringData, &mNumTexDataStringDatas, 563 | LUMP::TEXDATA_STRING_DATA, MAX_MAP_TEXDATA_STRING_DATA 564 | ); 565 | ParseLump(&mpModels, &mNumModels); 566 | ParseLump(&mpDispInfos, &mNumDispInfos); 567 | ParseLump(&mpDispVerts, &mNumDispVerts); 568 | ParseGameLumps(); 569 | } catch (const ParseError& error) { 570 | errorReason = error.what(); 571 | errorLump = error.lump; 572 | 573 | free(mpData); 574 | mpData = nullptr; 575 | return; 576 | } 577 | 578 | try { 579 | Triangulate(); 580 | } catch (const TriangulationError& error) { 581 | errorReason = error.what(); 582 | return; 583 | } 584 | 585 | mIsValid = true; 586 | } 587 | 588 | BSPMap::~BSPMap() 589 | { 590 | FreeAll(); 591 | } 592 | 593 | bool BSPMap::IsValid() const { return mIsValid; } 594 | 595 | size_t BSPMap::GetNumTextures() const { 596 | return mNumTexInfos; 597 | } 598 | 599 | BSPTexture BSPMap::GetTexture(const int16_t index) const 600 | { 601 | if (index < 0 || index >= mNumTexInfos) 602 | throw std::out_of_range("Texture index out of bounds"); 603 | 604 | const TexInfo* pTexInfo = mpTexInfos + index; 605 | 606 | if (pTexInfo->texData < 0 || pTexInfo->texData >= mNumTexDatas) 607 | throw std::out_of_range("TexData index out of bounds"); 608 | const TexData* pTexData = mpTexDatas + pTexInfo->texData; 609 | 610 | if ( 611 | pTexData->nameStringTableId < 0 || 612 | pTexData->nameStringTableId >= mNumTexDataStringTableEntries 613 | ) throw std::out_of_range("TexData string table index out of bounds"); 614 | 615 | BSPTexture ret{}; 616 | ret.flags = pTexInfo->flags; 617 | ret.reflectivity = pTexData->reflectivity; 618 | ret.path = mpTexDataStringData + mpTexDataStringTable[pTexData->nameStringTableId]; 619 | ret.width = pTexData->width; 620 | ret.height = pTexData->height; 621 | 622 | return ret; 623 | } 624 | 625 | size_t BSPMap::GetNumTris() const { return mNumTris; } 626 | size_t BSPMap::GetNumVertices() const { return mNumTris * 3; } 627 | const Vector* BSPMap::GetVertices() const { return mpPositions; } 628 | const Vector* BSPMap::GetNormals() const { return mpNormals; } 629 | const Vector* BSPMap::GetTangents() const { return mpTangents; } 630 | const Vector* BSPMap::GetBinormals() const { return mpBinormals; } 631 | const float* BSPMap::GetUVs() const { return mpUVs; } 632 | const float* BSPMap::GetAlphas() const { return mpAlphas; } 633 | const int16_t* BSPMap::GetTriTextures() const { return mpTexIndices; } 634 | 635 | int32_t BSPMap::GetNumStaticProps() const { return mNumStaticProps; } 636 | BSPStaticProp BSPMap::GetStaticProp(const int32_t index) const 637 | { 638 | switch (mStaticPropsVersion) { 639 | case 4: 640 | return GetStaticPropInternal(index, mpStaticPropsV4); 641 | case 5: 642 | return GetStaticPropInternal(index, mpStaticPropsV5); 643 | case 6: 644 | return GetStaticPropInternal(index, mpStaticPropsV6); 645 | case 7: 646 | return GetStaticPropInternal(index, mpStaticPropsV7Multiplayer2013); 647 | default: 648 | throw std::runtime_error("Unsupported static prop version"); 649 | } 650 | } 651 | -------------------------------------------------------------------------------- /BSPParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "FileFormat/Parser.h" 7 | #include "Errors/ParseError.hpp" 8 | 9 | struct BSPTexture 10 | { 11 | BSPEnums::SURF flags = BSPEnums::SURF::NONE; 12 | BSPStructs::Vector reflectivity{0, 0, 0}; 13 | const char* path = nullptr; 14 | int32_t width = 0, height = 0; 15 | }; 16 | 17 | struct BSPStaticProp 18 | { 19 | BSPStructs::Vector pos{}; 20 | BSPStructs::QAngle ang{}; 21 | const char* model = nullptr; 22 | int32_t skin = 0; 23 | }; 24 | 25 | class BSPMap 26 | { 27 | private: 28 | bool mIsValid = false; 29 | 30 | // Raw data 31 | uint8_t* mpData; 32 | size_t mDataSize = 0U; 33 | 34 | // Raw BSP structs 35 | const BSPStructs::Header* mpHeader; 36 | 37 | const BSPStructs::GameLump* mpGameLumps; 38 | size_t mNumGameLumps = 0U; 39 | 40 | const BSPStructs::Vector* mpVertices; 41 | size_t mNumVertices = 0U; 42 | 43 | const BSPStructs::Plane* mpPlanes; 44 | size_t mNumPlanes = 0U; 45 | 46 | const BSPStructs::Edge* mpEdges; 47 | size_t mNumEdges = 0U; 48 | 49 | const int32_t* mpSurfEdges; 50 | size_t mNumSurfEdges = 0U; 51 | 52 | const BSPStructs::Face* mpFaces; 53 | size_t mNumFaces = 0U; 54 | 55 | const BSPStructs::TexInfo* mpTexInfos; 56 | size_t mNumTexInfos = 0U; 57 | 58 | const BSPStructs::TexData* mpTexDatas; 59 | size_t mNumTexDatas = 0U; 60 | 61 | const int32_t* mpTexDataStringTable; 62 | size_t mNumTexDataStringTableEntries = 0U; 63 | 64 | const char* mpTexDataStringData; 65 | size_t mNumTexDataStringDatas = 0U; 66 | 67 | const BSPStructs::Model* mpModels; 68 | size_t mNumModels = 0U; 69 | 70 | const BSPStructs::DispInfo* mpDispInfos; 71 | size_t mNumDispInfos = 0U; 72 | 73 | const BSPStructs::DispVert* mpDispVerts; 74 | size_t mNumDispVerts = 0U; 75 | 76 | const BSPStructs::DetailObjectDict* mpDetailObjectDict; 77 | size_t mNumDetailObjectDictEntries = 0U; 78 | 79 | const BSPStructs::DetailObject* mpDetailObjects; 80 | size_t mNumDetailObjects = 0U; 81 | 82 | const BSPStructs::StaticPropDict* mpStaticPropDict; 83 | size_t mNumStaticPropDictEntries = 0U; 84 | 85 | const BSPStructs::StaticPropLeaf* mpStaticPropLeaves; 86 | size_t mNumStaticPropLeaves = 0U; 87 | 88 | uint16_t mStaticPropsVersion; 89 | const BSPStructs::StaticPropV4* mpStaticPropsV4; 90 | const BSPStructs::StaticPropV5* mpStaticPropsV5; 91 | const BSPStructs::StaticPropV6* mpStaticPropsV6; 92 | const BSPStructs::StaticPropV7Multiplayer2013* mpStaticPropsV7Multiplayer2013; 93 | int32_t mNumStaticProps = 0U; 94 | 95 | // Triangulation 96 | 97 | // Whether to create CW tris or CCW tris 98 | bool mClockwise = true; 99 | 100 | size_t mNumTris = 0U; 101 | 102 | BSPStructs::Vector* mpPositions = nullptr; 103 | BSPStructs::Vector* mpNormals = nullptr, * mpTangents = nullptr, * mpBinormals = nullptr; 104 | float* mpUVs = nullptr, * mpAlphas = nullptr; 105 | int16_t* mpTexIndices = nullptr; 106 | 107 | template 108 | void ParseLump(const LumpDatatype** pArray, size_t* pLength) 109 | { 110 | BSPParser::ParseLump( 111 | mpData, mDataSize, 112 | mpHeader, 113 | pArray, pLength 114 | ); 115 | } 116 | 117 | void ParseGameLumps(); 118 | 119 | template 120 | void ParseStaticPropLump(const BSPStructs::GameLump& gameLump, const StaticProp** pPtrOut) 121 | { 122 | using BSPErrors::ParseError; 123 | using BSPEnums::LUMP; 124 | 125 | if (gameLump.offset < 0) { 126 | throw ParseError("Lump offset is before the start of the data", LUMP::GAME_LUMP); 127 | } 128 | if (gameLump.offset + gameLump.length > mDataSize) { 129 | throw ParseError("Lump offset plus length overruns the data", LUMP::GAME_LUMP); 130 | } 131 | 132 | // Initialise to 3 * int32_t, as this is the number of counts in the game lump 133 | size_t totalLumpSize = sizeof(int32_t) * 3; 134 | if (totalLumpSize > gameLump.length) { 135 | throw ParseError("Static prop game lump length is less than minimum", LUMP::GAME_LUMP); 136 | } 137 | 138 | // Get ptr to number of static prop dict entries 139 | // no idea why valve decided to put the counts for all of these before each segment of the sprop lump, 140 | // cause it's a pain to parse 141 | const uint8_t* pHeader = mpData + gameLump.offset; 142 | mNumStaticPropDictEntries = *reinterpret_cast(pHeader); 143 | totalLumpSize += mNumStaticPropDictEntries * sizeof(BSPStructs::StaticPropDict); 144 | if (mNumStaticPropDictEntries < 0 || totalLumpSize > gameLump.length) { 145 | mNumStaticPropDictEntries = 0; 146 | throw ParseError("Number of static prop dictionary entries is less than 0 or exceeds game lump length", LUMP::GAME_LUMP); 147 | } 148 | 149 | // Move ptr over the int and save offset ptr 150 | pHeader += sizeof(int32_t); 151 | mpStaticPropDict = reinterpret_cast(pHeader); 152 | 153 | // Move ptr over the static prop dictionary entries, and increase lump size by the size of the leaf lump 154 | pHeader += mNumStaticPropDictEntries * sizeof(BSPStructs::StaticPropDict); 155 | mNumStaticPropLeaves = *reinterpret_cast(pHeader); 156 | totalLumpSize += mNumStaticPropLeaves * sizeof(BSPStructs::StaticPropLeaf); 157 | if (mNumStaticPropLeaves < 0 || totalLumpSize > gameLump.length) { 158 | mNumStaticPropLeaves = 0; 159 | throw ParseError("Number of static prop leaves is less than 0 or exceeds game lump length", LUMP::GAME_LUMP); 160 | } 161 | 162 | // Move ptr and save 163 | pHeader += sizeof(int32_t); 164 | mpStaticPropLeaves = reinterpret_cast(pHeader); 165 | 166 | // Move ptr over static prop leaves and add static prop lump size 167 | pHeader += mNumStaticPropLeaves * sizeof(BSPStructs::StaticPropLeaf); 168 | mNumStaticProps = *reinterpret_cast(pHeader); 169 | totalLumpSize += mNumStaticProps * sizeof(StaticProp); 170 | if (mNumStaticProps < 0) { 171 | mNumStaticProps = 0; 172 | throw ParseError("Number of static props is less than 0", LUMP::GAME_LUMP); 173 | } 174 | if (totalLumpSize != gameLump.length) { 175 | mNumStaticProps = 0; 176 | throw ParseError("Amount of parsed static prop data does not match the length of the game lump", LUMP::GAME_LUMP); 177 | } 178 | 179 | pHeader += sizeof(int32_t); 180 | *pPtrOut = reinterpret_cast(pHeader); 181 | } 182 | 183 | template 184 | BSPStaticProp GetStaticPropInternal(const int32_t index, const StaticProp* pStaticProps) const 185 | { 186 | if (index < 0 || index >= mNumStaticProps) 187 | throw std::out_of_range("Static prop index out of bounds"); 188 | 189 | uint16_t dictIdx = pStaticProps[index].propType; 190 | if (dictIdx >= mNumStaticPropDictEntries) 191 | throw std::out_of_range("Static prop dictionary index out of bounds"); 192 | 193 | BSPStaticProp prop; 194 | prop.pos = pStaticProps[index].origin; 195 | prop.ang = pStaticProps[index].angles; 196 | prop.model = mpStaticPropDict[dictIdx].modelName; 197 | prop.skin = pStaticProps[index].skin; 198 | 199 | return prop; 200 | } 201 | 202 | bool IsFaceNodraw(const BSPStructs::Face* pFace) const; 203 | 204 | void FreeAll(); 205 | 206 | bool CalcUVs( 207 | int16_t texInfoIdx, 208 | const BSPStructs::Vector* pos, 209 | float* pUVs 210 | ) const; 211 | 212 | bool GetSurfEdgeVerts(int32_t index, BSPStructs::Vector* pVertA, BSPStructs::Vector* pVertB = nullptr) const; 213 | 214 | void Triangulate(); 215 | 216 | public: 217 | std::string errorReason = "Unknown error"; 218 | BSPEnums::LUMP errorLump = BSPEnums::LUMP::NONE; 219 | 220 | // Parses and triangulates a BSP from raw data 221 | // clockwise sets which winding the triangles should have (default true) 222 | BSPMap(const uint8_t* pFileData, size_t dataSize, bool clockwise = true); 223 | ~BSPMap(); 224 | 225 | // Returns whether the BSP was loaded correctly 226 | [[nodiscard]] bool IsValid() const; 227 | 228 | // Gets th enumber of textures defined in the map 229 | [[nodiscard]] size_t GetNumTextures() const; 230 | 231 | // Returns relevant texture information for an index in the TexInfo lump 232 | [[nodiscard]] BSPTexture GetTexture(int16_t index) const; 233 | 234 | // Gets the number of triangles in the triangulated BSP data 235 | [[nodiscard]] size_t GetNumTris() const; 236 | 237 | // Gets the number of vertices in the triangulated BSP data 238 | [[nodiscard]] size_t GetNumVertices() const; 239 | 240 | // Returns a const pointer to the vertex positions as Vector structs (castable to floats) 241 | [[nodiscard]] const BSPStructs::Vector* GetVertices() const; 242 | 243 | // Returns a const pointer to the vertex normals as Vector structs (castable to floats) 244 | [[nodiscard]] const BSPStructs::Vector* GetNormals() const; 245 | 246 | // Returns a const pointer to the vertex tangents as Vector structs (castable to floats) 247 | [[nodiscard]] const BSPStructs::Vector* GetTangents() const; 248 | 249 | // Returns a const pointer to the vertex binormals as Vector structs (castable to floats) 250 | [[nodiscard]] const BSPStructs::Vector* GetBinormals() const; 251 | 252 | // Returns a const pointer to the vertex UVs as raw float data 253 | [[nodiscard]] const float* GetUVs() const; 254 | 255 | // Returns a const pointer to the vertex alphas as floats 256 | [[nodiscard]] const float* GetAlphas() const; 257 | 258 | // Returns a const pointer to the triangle TexInfo indices as an array of int16_t 259 | [[nodiscard]] const int16_t* GetTriTextures() const; 260 | 261 | [[nodiscard]] int32_t GetNumStaticProps() const; 262 | [[nodiscard]] BSPStaticProp GetStaticProp(int32_t index) const; 263 | }; 264 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project("BSPParser" CXX) 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | add_library( 7 | ${PROJECT_NAME} 8 | "BSPParser.cpp" 9 | 10 | "FileFormat/Parser.cpp" 11 | "FileFormat/Vector.cpp" 12 | 13 | "Displacements/Displacements.cpp" 14 | "Displacements/UVGen.cpp" 15 | "Displacements/TBNGen.cpp" 16 | "Displacements/NormalBlending.cpp" 17 | "Displacements/SubEdgeIterator.cpp" 18 | 19 | "Errors/ParseError.cpp" 20 | "Errors/TriangulationError.cpp" 21 | ) 22 | 23 | target_include_directories( 24 | ${PROJECT_NAME} PRIVATE 25 | 26 | ${PROJECT_SOURCE_DIR} 27 | ) 28 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 20, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "windows-x64-debug", 11 | "displayName": "Windows x64 Debug", 12 | "generator": "Ninja", 13 | "binaryDir": "${sourceDir}/out/build/${presetName}", 14 | "architecture": { 15 | "value": "x64", 16 | "strategy": "external" 17 | }, 18 | "cacheVariables": { 19 | "CMAKE_BUILD_TYPE": "Debug", 20 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 21 | }, 22 | "vendor": { 23 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 24 | "hostOS": ["Windows"] 25 | } 26 | } 27 | }, 28 | { 29 | "name": "windows-x64-relwithsymbols", 30 | "displayName": "Windows x64 RelWithSymbols", 31 | "generator": "Ninja", 32 | "binaryDir": "${sourceDir}/out/build/${presetName}", 33 | "architecture": { 34 | "value": "x64", 35 | "strategy": "external" 36 | }, 37 | "cacheVariables": { 38 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 39 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 40 | }, 41 | "vendor": { 42 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 43 | "hostOS": [ "Windows" ] 44 | } 45 | } 46 | }, 47 | { 48 | "name": "windows-x64-release", 49 | "displayName": "Windows x64 Release", 50 | "generator": "Ninja", 51 | "binaryDir": "${sourceDir}/out/build/${presetName}", 52 | "architecture": { 53 | "value": "x64", 54 | "strategy": "external" 55 | }, 56 | "cacheVariables": { 57 | "CMAKE_BUILD_TYPE": "Release", 58 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 59 | }, 60 | "vendor": { 61 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 62 | "hostOS": [ "Windows" ] 63 | } 64 | } 65 | }, 66 | { 67 | "name": "windows-x86-debug", 68 | "displayName": "Windows x86 Debug", 69 | "generator": "Ninja", 70 | "binaryDir": "${sourceDir}/out/build/${presetName}", 71 | "architecture": { 72 | "value": "x86", 73 | "strategy": "external" 74 | }, 75 | "cacheVariables": { 76 | "CMAKE_BUILD_TYPE": "Debug", 77 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 78 | }, 79 | "vendor": { 80 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 81 | "hostOS": [ "Windows" ] 82 | } 83 | } 84 | }, 85 | { 86 | "name": "windows-x86-relwithsymbols", 87 | "displayName": "Windows x86 RelWithSymbols", 88 | "generator": "Ninja", 89 | "binaryDir": "${sourceDir}/out/build/${presetName}", 90 | "architecture": { 91 | "value": "x86", 92 | "strategy": "external" 93 | }, 94 | "cacheVariables": { 95 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 96 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 97 | }, 98 | "vendor": { 99 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 100 | "hostOS": [ "Windows" ] 101 | } 102 | } 103 | }, 104 | { 105 | "name": "windows-x86-release", 106 | "displayName": "Windows x86 Release", 107 | "generator": "Ninja", 108 | "binaryDir": "${sourceDir}/out/build/${presetName}", 109 | "architecture": { 110 | "value": "x86", 111 | "strategy": "external" 112 | }, 113 | "cacheVariables": { 114 | "CMAKE_BUILD_TYPE": "Release", 115 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 116 | }, 117 | "vendor": { 118 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 119 | "hostOS": [ "Windows" ] 120 | } 121 | } 122 | } 123 | ] 124 | } 125 | -------------------------------------------------------------------------------- /Displacements/Displacements.cpp: -------------------------------------------------------------------------------- 1 | #include "Displacements.h" 2 | 3 | #include 4 | 5 | using namespace Displacements; 6 | using namespace BSPStructs; 7 | 8 | void Displacement::Init(const DispInfo* pDispInfo) 9 | { 10 | pInfo = pDispInfo; 11 | size_t numVerts = (1 << pDispInfo->power) + 1; 12 | numVerts *= numVerts; 13 | 14 | verts = new Vector[numVerts]; 15 | normals = new Vector[numVerts]; 16 | tangents = new Vector[numVerts]; 17 | binormals = new Vector[numVerts]; 18 | 19 | uvs = new float[numVerts * 2U]; 20 | alphas = new float[numVerts]; 21 | } 22 | 23 | Displacement::~Displacement() 24 | { 25 | if (verts != nullptr) delete[] verts; 26 | if (normals != nullptr) delete[] normals; 27 | if (tangents != nullptr) delete[] tangents; 28 | if (binormals != nullptr) delete[] binormals; 29 | if (uvs != nullptr) delete[] uvs; 30 | if (alphas != nullptr) delete[] alphas; 31 | } 32 | 33 | void Displacements::GenerateDispSurf( 34 | const DispInfo* pDispInfo, const DispVert* dispVerts, 35 | const Vector corners[4], Displacement& disp 36 | ) 37 | { 38 | int postSpacing = (1 << pDispInfo->power) + 1; 39 | float ooInt = 1.0f / (float)(postSpacing - 1); 40 | 41 | Vector edgeInt[2]; 42 | edgeInt[0] = corners[1] - corners[0]; 43 | edgeInt[0] *= ooInt; 44 | 45 | edgeInt[1] = corners[2] - corners[3]; 46 | edgeInt[1] *= ooInt; 47 | 48 | for (int i = 0; i < postSpacing; i++) { 49 | Vector endPts[2]; 50 | endPts[0] = edgeInt[0] * i + corners[0]; 51 | endPts[1] = edgeInt[1] * i + corners[3]; 52 | 53 | Vector seg, segInt; 54 | seg = endPts[1] - endPts[0]; 55 | segInt = seg * ooInt; 56 | 57 | for (int j = 0; j < postSpacing; j++) { 58 | int ndx = i * postSpacing + j; 59 | 60 | const DispVert* pDispInfoVert = dispVerts + ndx; 61 | disp.verts[ndx] = endPts[0] + segInt * j + pDispInfoVert->vec * pDispInfoVert->dist; 62 | disp.alphas[ndx] = std::clamp(pDispInfoVert->alpha / 255.f, 0.f, 1.f); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Displacements/Displacements.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Implements builddisp.cpp and displacement normal smoothing from the Source SDK 5 | */ 6 | 7 | #include "FileFormat/Structs.h" 8 | 9 | #include 10 | 11 | namespace Displacements 12 | { 13 | constexpr char CORNER_LOWER_LEFT = 0; 14 | constexpr char CORNER_UPPER_LEFT = 1; 15 | constexpr char CORNER_UPPER_RIGHT = 2; 16 | constexpr char CORNER_LOWER_RIGHT = 3; 17 | 18 | constexpr char NEIGHBOREDGE_LEFT = 0; 19 | constexpr char NEIGHBOREDGE_TOP = 1; 20 | constexpr char NEIGHBOREDGE_RIGHT = 2; 21 | constexpr char NEIGHBOREDGE_BOTTOM = 3; 22 | 23 | static int g_EdgeDims[4] = 24 | { 25 | 0, // NEIGHBOREDGE_LEFT = X 26 | 1, // NEIGHBOREDGE_TOP = Y 27 | 0, // NEIGHBOREDGE_RIGHT = X 28 | 1 // NEIGHBOREDGE_BOTTOM = Y 29 | }; 30 | 31 | struct VertIndex 32 | { 33 | short x, y; 34 | inline short& operator[](short i) 35 | { 36 | return reinterpret_cast(this)[i]; 37 | } 38 | inline const short& operator[](short i) const 39 | { 40 | return reinterpret_cast(this)[i]; 41 | } 42 | }; 43 | 44 | struct Displacement 45 | { 46 | const BSPStructs::DispInfo* pInfo = nullptr; 47 | 48 | BSPStructs::Vector* verts = nullptr; 49 | BSPStructs::Vector* normals = nullptr; 50 | BSPStructs::Vector* tangents = nullptr; 51 | BSPStructs::Vector* binormals = nullptr; 52 | 53 | float* uvs = nullptr; 54 | float* alphas = nullptr; 55 | 56 | void Init(const BSPStructs::DispInfo* pDispInfo); 57 | ~Displacement(); 58 | }; 59 | 60 | void GenerateDispSurf( 61 | const BSPStructs::DispInfo* pDispInfo, const BSPStructs::DispVert* dispVerts, 62 | const BSPStructs::Vector corners[4], Displacement& disp 63 | ); 64 | 65 | void GenerateDispSurfNormals(const BSPStructs::DispInfo* pDispInfo, Displacement& disp); 66 | 67 | void GenerateDispSurfTangentSpaces( 68 | const BSPStructs::DispInfo* pDispInfo, const BSPStructs::Plane* pPlane, 69 | const BSPStructs::TexInfo* pTexInfo, 70 | Displacement& disp 71 | ); 72 | 73 | void GenerateDispSurfUVs(const BSPStructs::DispInfo* pDispInfo, float faceUVs[4][2], Displacement& disp); 74 | 75 | void SmoothNeighbouringDispSurfNormals(std::vector& displacements); 76 | } 77 | -------------------------------------------------------------------------------- /Displacements/NormalBlending.cpp: -------------------------------------------------------------------------------- 1 | #include "Displacements.h" 2 | #include "SubEdgeIterator.h" 3 | 4 | #include 5 | #include 6 | 7 | using namespace Displacements; 8 | using namespace BSPStructs; 9 | 10 | inline float RemapVal(float val, float A, float B, float C, float D) 11 | { 12 | if (A == B) 13 | return val >= B ? D : C; 14 | return C + (D - C) * (val - A) / (B - A); 15 | } 16 | 17 | inline void VectorLerp(const Vector& src1, const Vector& src2, float t, Vector& dest) 18 | { 19 | dest.x = src1.x + (src2.x - src1.x) * t; 20 | dest.y = src1.y + (src2.y - src1.y) * t; 21 | dest.z = src1.z + (src2.z - src1.z) * t; 22 | } 23 | 24 | int GetAllNeighbours(const DispInfo* pDisp, int neighbors[512]) 25 | { 26 | int numNeighbours = 0; 27 | 28 | // Check corner neighbors. 29 | for (int iCorner = 0; iCorner < 4; iCorner++) { 30 | const DispCornerNeighbours* pCorner = pDisp->cornerNeighbours + iCorner; 31 | 32 | for (int i = 0; i < pCorner->numNeighbours; i++) { 33 | if (numNeighbours < 512) 34 | neighbors[numNeighbours++] = pCorner->neighbours[i]; 35 | } 36 | } 37 | 38 | for (int iEdge = 0; iEdge < 4; iEdge++) { 39 | const DispNeighbour& edge = pDisp->edgeNeighbours[iEdge]; 40 | 41 | for (int i = 0; i < 2; i++) { 42 | if (edge.subNeighbors[i].IsValid() && numNeighbours < 512) 43 | neighbors[numNeighbours++] = edge.subNeighbors[i].index; 44 | } 45 | } 46 | 47 | return numNeighbours; 48 | } 49 | 50 | int CornerToVertIdx(const DispInfo* pDispInfo, int corner) 51 | { 52 | int sideLength = (1 << pDispInfo->power) + 1; 53 | 54 | int x = 0, y = 0; 55 | switch (corner) { 56 | case CORNER_UPPER_LEFT: 57 | y = sideLength - 1; 58 | break; 59 | case CORNER_UPPER_RIGHT: 60 | x = sideLength - 1; 61 | y = sideLength - 1; 62 | break; 63 | case CORNER_LOWER_RIGHT: 64 | x = sideLength - 1; 65 | break; 66 | } 67 | 68 | return y * sideLength + x; 69 | } 70 | 71 | int GetEdgeMidPoint(const DispInfo* pDispInfo, int edge) 72 | { 73 | int sideLength = (1 << pDispInfo->power) + 1; 74 | 75 | int end = sideLength - 1; 76 | int mid = sideLength / 2; 77 | 78 | int x = 0, y = 0; 79 | switch (edge) { 80 | case NEIGHBOREDGE_LEFT: 81 | y = mid; 82 | break; 83 | case NEIGHBOREDGE_TOP: 84 | x = mid; 85 | y = end; 86 | break; 87 | case NEIGHBOREDGE_RIGHT: 88 | x = end; 89 | y = mid; 90 | break; 91 | case NEIGHBOREDGE_BOTTOM: 92 | x = mid; 93 | break; 94 | } 95 | 96 | return y * sideLength + x; 97 | } 98 | 99 | int FindNeighborCornerVert(const Displacement& disp, const Vector& vTest) 100 | { 101 | int iClosest = 0; 102 | float flClosest = 1e24; 103 | for (int iCorner = 0; iCorner < 4; iCorner++) { 104 | int iCornerVert = CornerToVertIdx(disp.pInfo, iCorner); 105 | 106 | const Vector& vCornerVert = disp.verts[iCornerVert]; 107 | 108 | Vector delta = vCornerVert - vTest; 109 | float flDist = sqrtf(delta.Dot(delta)); 110 | if (flDist < flClosest) { 111 | iClosest = iCorner; 112 | flClosest = flDist; 113 | } 114 | } 115 | 116 | if (flClosest <= 0.1f) 117 | return iClosest; 118 | else 119 | return -1; 120 | } 121 | 122 | void BlendCorners(std::vector& displacements) 123 | { 124 | std::vector cornerVerts; 125 | 126 | for (int iDisp = 0; iDisp < displacements.size(); iDisp++) { 127 | Displacement& disp = displacements[iDisp]; 128 | 129 | int neighbours[512]; 130 | int numNeighbours = GetAllNeighbours(disp.pInfo, neighbours); 131 | 132 | cornerVerts = std::vector(numNeighbours); 133 | 134 | for (int iCorner = 0; iCorner < 4; iCorner++) { 135 | int iCornerVert = CornerToVertIdx(disp.pInfo, iCorner); 136 | const Vector& vCornerVert = disp.verts[iCornerVert]; 137 | 138 | // For each displacement sharing this corner.. 139 | int divisor = 1; 140 | Vector averageT = disp.tangents[iCornerVert]; 141 | Vector averageB = disp.binormals[iCornerVert]; 142 | Vector averageN = disp.normals[iCornerVert]; 143 | 144 | for (int iNeighbour = 0; iNeighbour < numNeighbours; iNeighbour++) { 145 | Displacement& neighbour = displacements[neighbours[iNeighbour]]; 146 | 147 | // Find out which vert it is on the neighbor. 148 | int iNBCorner = FindNeighborCornerVert(neighbour, vCornerVert); 149 | if (iNBCorner == -1) { 150 | cornerVerts[iNeighbour] = -1; // remove this neighbor from the list. 151 | } else { 152 | int iNBVert = CornerToVertIdx(neighbour.pInfo, iNBCorner); 153 | cornerVerts[iNeighbour] = iNBVert; 154 | 155 | averageT += neighbour.tangents[iNBVert]; 156 | averageB += neighbour.binormals[iNBVert]; 157 | averageN += neighbour.normals[iNBVert]; 158 | divisor++; 159 | } 160 | } 161 | 162 | // Blend all the neighbor normals with this one. 163 | averageT /= divisor; 164 | averageB /= divisor; 165 | averageN /= divisor; 166 | 167 | disp.tangents[iCornerVert] = averageT; 168 | disp.binormals[iCornerVert] = averageB; 169 | disp.normals[iCornerVert] = averageN; 170 | 171 | for (int iNeighbour = 0; iNeighbour < numNeighbours; iNeighbour++) { 172 | int iNBListIndex = neighbours[iNeighbour]; 173 | if (cornerVerts[iNeighbour] != -1) { 174 | Displacement& neighbour = displacements[iNBListIndex]; 175 | 176 | neighbour.tangents[cornerVerts[iNeighbour]] = averageT; 177 | neighbour.binormals[cornerVerts[iNeighbour]] = averageB; 178 | neighbour.normals[cornerVerts[iNeighbour]] = averageN; 179 | } 180 | } 181 | } 182 | } 183 | } 184 | 185 | void BlendTJuncs(std::vector& displacements) 186 | { 187 | for (int iDisp = 0; iDisp < displacements.size(); iDisp++) { 188 | Displacement& disp = displacements[iDisp]; 189 | 190 | for (int iEdge = 0; iEdge < 4; iEdge++) { 191 | const DispNeighbour& edge = disp.pInfo->edgeNeighbours[iEdge]; 192 | 193 | int iMidPoint = GetEdgeMidPoint(disp.pInfo, iEdge); 194 | 195 | if (edge.subNeighbors[0].IsValid() && edge.subNeighbors[1].IsValid()) { 196 | const Vector& vMidPoint = disp.verts[iMidPoint]; 197 | 198 | Displacement& neighbour1 = displacements[edge.subNeighbors[0].index]; 199 | Displacement& neighbour2 = displacements[edge.subNeighbors[1].index]; 200 | 201 | int iNBCorners[2]; 202 | iNBCorners[0] = FindNeighborCornerVert(neighbour1, vMidPoint); 203 | iNBCorners[1] = FindNeighborCornerVert(neighbour2, vMidPoint); 204 | 205 | if (iNBCorners[0] != -1 && iNBCorners[1] != -1) { 206 | int viNBCorners[2] = 207 | { 208 | CornerToVertIdx(neighbour1.pInfo, iNBCorners[0]), 209 | CornerToVertIdx(neighbour2.pInfo, iNBCorners[1]) 210 | }; 211 | 212 | Vector averageT = disp.tangents[iMidPoint]; 213 | Vector averageB = disp.binormals[iMidPoint]; 214 | Vector averageN = disp.normals[iMidPoint]; 215 | 216 | averageT += neighbour1.tangents[viNBCorners[0]] + neighbour2.tangents[viNBCorners[1]]; 217 | averageB += neighbour1.binormals[viNBCorners[0]] + neighbour2.binormals[viNBCorners[1]]; 218 | averageN += neighbour1.normals[viNBCorners[0]] + neighbour2.normals[viNBCorners[1]]; 219 | 220 | averageT /= 3; 221 | averageB /= 3; 222 | averageN /= 3; 223 | 224 | disp.tangents[iMidPoint] = neighbour1.tangents[viNBCorners[0]] = neighbour2.tangents[viNBCorners[1]] = averageT; 225 | disp.binormals[iMidPoint] = neighbour1.binormals[viNBCorners[0]] = neighbour2.binormals[viNBCorners[1]] = averageB; 226 | disp.normals[iMidPoint] = neighbour1.normals[viNBCorners[0]] = neighbour2.normals[viNBCorners[1]] = averageN; 227 | } 228 | } 229 | } 230 | } 231 | } 232 | 233 | void BlendEdges(std::vector& displacements) 234 | { 235 | for (int iDisp = 0; iDisp < displacements.size(); iDisp++) { 236 | Displacement& disp = displacements[iDisp]; 237 | int sideLength = (1 << disp.pInfo->power) + 1; 238 | 239 | for (int iEdge = 0; iEdge < 4; iEdge++) { 240 | const DispNeighbour& edge = disp.pInfo->edgeNeighbours[iEdge]; 241 | 242 | for (int iSub = 0; iSub < 2; iSub++) { 243 | const DispSubNeighbour& sub = edge.subNeighbors[iSub]; 244 | if (!sub.IsValid()) continue; 245 | 246 | Displacement& neighbour = displacements[sub.index]; 247 | 248 | int iEdgeDim = g_EdgeDims[iEdge]; 249 | 250 | DispSubEdgeIterator it; 251 | it.Start(displacements, disp, iEdge, iSub, true); 252 | 253 | it.Next(); 254 | VertIndex prevPos = it.GetIndex(); 255 | 256 | while (it.Next()) { 257 | if (!it.IsLastVert()) { 258 | Vector averageT = disp.tangents[it.GetVertIndex()] + neighbour.tangents[it.GetNBVertIndex()]; 259 | Vector averageB = disp.binormals[it.GetVertIndex()] + neighbour.binormals[it.GetNBVertIndex()]; 260 | Vector averageN = disp.normals[it.GetVertIndex()] + neighbour.normals[it.GetNBVertIndex()]; 261 | averageT /= 2; 262 | averageB /= 2; 263 | averageN /= 2; 264 | 265 | disp.tangents[it.GetVertIndex()] = averageT; 266 | disp.binormals[it.GetVertIndex()] = averageB; 267 | disp.normals[it.GetVertIndex()] = averageN; 268 | neighbour.tangents[it.GetNBVertIndex()] = averageT; 269 | neighbour.binormals[it.GetNBVertIndex()] = averageB; 270 | neighbour.normals[it.GetNBVertIndex()] = averageN; 271 | } 272 | 273 | int iPrevPos = prevPos[!iEdgeDim]; 274 | int iCurPos = it.GetIndex()[!iEdgeDim]; 275 | 276 | for (int iTween = iPrevPos + 1; iTween < iCurPos; iTween++) { 277 | float flPercent = RemapVal(iTween, iPrevPos, iCurPos, 0, 1); 278 | 279 | int coord = prevPos.y * sideLength + prevPos.x; 280 | Vector tangent, binormal, normal; 281 | 282 | VectorLerp(disp.tangents[coord], disp.tangents[it.GetVertIndex()], flPercent, tangent); 283 | VectorLerp(disp.binormals[coord], disp.binormals[it.GetVertIndex()], flPercent, binormal); 284 | VectorLerp(disp.normals[coord], disp.normals[it.GetVertIndex()], flPercent, normal); 285 | 286 | tangent.Normalise(); 287 | binormal.Normalise(); 288 | normal.Normalise(); 289 | 290 | VertIndex viTween; 291 | viTween[iEdgeDim] = it.GetIndex()[iEdgeDim]; 292 | viTween[!iEdgeDim] = iTween; 293 | coord = viTween.y * sideLength + viTween.x; 294 | 295 | disp.tangents[coord] = tangent; 296 | disp.binormals[coord] = binormal; 297 | disp.normals[coord] = normal; 298 | } 299 | 300 | prevPos = it.GetIndex(); 301 | } 302 | } 303 | } 304 | } 305 | } 306 | 307 | void Displacements::SmoothNeighbouringDispSurfNormals(std::vector& displacements) 308 | { 309 | BlendTJuncs(displacements); 310 | BlendCorners(displacements); 311 | BlendEdges(displacements); 312 | } 313 | -------------------------------------------------------------------------------- /Displacements/SubEdgeIterator.cpp: -------------------------------------------------------------------------------- 1 | #include "SubEdgeIterator.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace Displacements; 7 | using namespace BSPStructs; 8 | 9 | constexpr unsigned char CORNER_TO_CORNER = 0; 10 | constexpr unsigned char CORNER_TO_MIDPOINT = 1; 11 | constexpr unsigned char MIDPOINT_TO_CORNER = 2; 12 | 13 | constexpr unsigned char ORIENTATION_CCW_0 = 0; 14 | constexpr unsigned char ORIENTATION_CCW_90 = 1; 15 | constexpr unsigned char ORIENTATION_CCW_180 = 2; 16 | constexpr unsigned char ORIENTATION_CCW_270 = 3; 17 | 18 | int g_EdgeSideLenMul[4] = 19 | { 20 | 0, 21 | 1, 22 | 1, 23 | 0 24 | }; 25 | 26 | struct ShiftInfo 27 | { 28 | int midPointScale; 29 | int powerShiftAdd; 30 | bool valid; 31 | }; 32 | ShiftInfo g_ShiftInfos[3][3] = 33 | { 34 | { 35 | {0, 0, true}, // CORNER_TO_CORNER -> CORNER_TO_CORNER 36 | {0, -1, true}, // CORNER_TO_CORNER -> CORNER_TO_MIDPOINT 37 | {2, -1, true} // CORNER_TO_CORNER -> MIDPOINT_TO_CORNER 38 | }, 39 | { 40 | {0, 1, true}, // CORNER_TO_MIDPOINT -> CORNER_TO_CORNER 41 | {0, 0, false}, // CORNER_TO_MIDPOINT -> CORNER_TO_MIDPOINT (invalid) 42 | {0, 0, false} // CORNER_TO_MIDPOINT -> MIDPOINT_TO_CORNER (invalid) 43 | }, 44 | { 45 | {-1, 1, true}, // MIDPOINT_TO_CORNER -> CORNER_TO_CORNER 46 | {0, 0, false}, // MIDPOINT_TO_CORNER -> CORNER_TO_MIDPOINT (invalid) 47 | {0, 0, false} // MIDPOINT_TO_CORNER -> MIDPOINT_TO_CORNER (invalid) 48 | } 49 | }; 50 | 51 | VertIndex GetCornerPointIndex(int power, int corner) 52 | { 53 | int sideLengthM1 = 1 << power; 54 | 55 | VertIndex v{ 0, 0 }; 56 | switch (corner) { 57 | case CORNER_UPPER_LEFT: 58 | v.y = sideLengthM1; 59 | break; 60 | case CORNER_UPPER_RIGHT: 61 | v.x = sideLengthM1; 62 | v.y = sideLengthM1; 63 | break; 64 | case CORNER_LOWER_RIGHT: 65 | v.x = sideLengthM1; 66 | break; 67 | } 68 | 69 | return v; 70 | } 71 | 72 | void SetupSpan(int power, int iEdge, unsigned char span, VertIndex& viStart, VertIndex& viEnd) 73 | { 74 | int iFreeDim = !g_EdgeDims[iEdge]; 75 | 76 | viStart = GetCornerPointIndex(power, iEdge); 77 | viEnd = GetCornerPointIndex(power, (iEdge + 1) & 3); 78 | 79 | if (iEdge == NEIGHBOREDGE_RIGHT || iEdge == NEIGHBOREDGE_BOTTOM) { 80 | if (span == CORNER_TO_MIDPOINT) 81 | viStart[iFreeDim] = ((1 << power) + 1) / 2; 82 | else if (span == MIDPOINT_TO_CORNER) 83 | viEnd[iFreeDim] = ((1 << power) + 1) / 2; 84 | } else { 85 | if (span == CORNER_TO_MIDPOINT) 86 | viEnd[iFreeDim] = ((1 << power) + 1) / 2; 87 | else if (span == MIDPOINT_TO_CORNER) 88 | viStart[iFreeDim] = ((1 << power) + 1) / 2; 89 | } 90 | } 91 | 92 | void TransformIntoSubNeighbor( 93 | const std::vector& displacements, 94 | const Displacement& disp, 95 | int iEdge, 96 | int iSub, 97 | const VertIndex& nodeIndex, 98 | VertIndex& out 99 | ) 100 | { 101 | const DispSubNeighbour& sub = disp.pInfo->edgeNeighbours[iEdge].subNeighbors[iSub]; 102 | 103 | // Find the part of pDisp's edge that this neighbor covers. 104 | VertIndex viSrcStart, viSrcEnd; 105 | SetupSpan(disp.pInfo->power, iEdge, sub.span, viSrcStart, viSrcEnd); 106 | 107 | // Find the corresponding parts on the neighbor. 108 | const Displacement& neighbour = displacements.at(sub.index); 109 | int iNBEdge = (iEdge + 2 + sub.orientation) & 3; 110 | 111 | VertIndex viDestStart, viDestEnd; 112 | SetupSpan(neighbour.pInfo->power, iNBEdge, sub.span, viDestEnd, viDestStart); 113 | 114 | // Now map the one into the other. 115 | int iFreeDim = !g_EdgeDims[iEdge]; 116 | int fixedPercent = ((nodeIndex[iFreeDim] - viSrcStart[iFreeDim]) * (1 << 16)) / (viSrcEnd[iFreeDim] - viSrcStart[iFreeDim]); 117 | if (fixedPercent < 0 || fixedPercent > (1 << 16)) throw std::out_of_range("fixedPercent out of range"); 118 | 119 | int nbDim = g_EdgeDims[iNBEdge]; 120 | out[nbDim] = viDestStart[nbDim]; 121 | out[!nbDim] = viDestStart[!nbDim] + ((viDestEnd[!nbDim] - viDestStart[!nbDim]) * fixedPercent) / (1 << 16); 122 | 123 | int nbSideLen = (1 << neighbour.pInfo->power) + 1; 124 | if (out.x < 0 || out.x >= nbSideLen) throw std::out_of_range("x out of bounds in neighbour"); 125 | if (out.y < 0 || out.y >= nbSideLen) throw std::out_of_range("y out of bounds in neighbour"); 126 | } 127 | 128 | static inline void RotateVertIncrement( 129 | unsigned char neighor, 130 | const VertIndex& in, 131 | VertIndex& out) 132 | { 133 | if (neighor == ORIENTATION_CCW_0) { 134 | out = in; 135 | } else if (neighor == ORIENTATION_CCW_90) { 136 | out.x = in.y; 137 | out.y = -in.x; 138 | } else if (neighor == ORIENTATION_CCW_180) { 139 | out.x = -in.x; 140 | out.y = -in.y; 141 | } else { 142 | out.x = -in.y; 143 | out.y = in.x; 144 | } 145 | } 146 | 147 | const Displacement* SetupEdgeIncrements( 148 | const std::vector& displacements, 149 | const Displacement& disp, 150 | int iEdge, 151 | int iSub, 152 | VertIndex& myIndex, 153 | VertIndex& myInc, 154 | VertIndex& nbIndex, 155 | VertIndex& nbInc, 156 | int& myEnd, 157 | int& iFreeDim 158 | ) 159 | { 160 | int iEdgeDim = g_EdgeDims[iEdge]; 161 | iFreeDim = !iEdgeDim; 162 | 163 | const DispSubNeighbour& sub = disp.pInfo->edgeNeighbours[iEdge].subNeighbors[iSub]; 164 | if (!sub.IsValid()) return nullptr; 165 | 166 | const Displacement& neighbour = displacements.at(sub.index); 167 | 168 | // Using the shift info actually causes indexing out of range into the neighbour's verts 169 | // I have tried figuring out how Source doesn't encounter this issue to no avail 170 | // Removing the shift add below seems to have no effect on the final normals however (in fact they appear smoother) 171 | // So my only conclusion is that Source literally causes heap corruption in the displacement deserialization inside VRAD 172 | // If anyone has a better explanation please raise an issue on GitHub 173 | //const ShiftInfo& shiftInfo = g_ShiftInfos[sub.span][sub.neighbourSpan]; 174 | //if (!shiftInfo.valid) throw std::runtime_error("Shift info invalid"); 175 | 176 | VertIndex tempInc; 177 | 178 | int sideLength = (1 << disp.pInfo->power) + 1; 179 | myIndex[iEdgeDim] = g_EdgeSideLenMul[iEdge] * (sideLength - 1); 180 | myIndex[iFreeDim] = (sideLength / 2) * iSub; 181 | TransformIntoSubNeighbor(displacements, disp, iEdge, iSub, myIndex, nbIndex); 182 | 183 | int myPower = disp.pInfo->power; 184 | int nbPower = neighbour.pInfo->power;// + shiftInfo.powerShiftAdd; 185 | 186 | myInc[iEdgeDim] = tempInc[iEdgeDim] = 0; 187 | if (nbPower > myPower) { 188 | myInc[iFreeDim] = 1; 189 | tempInc[iFreeDim] = 1 << (nbPower - myPower); 190 | } else { 191 | myInc[iFreeDim] = 1 << (myPower - nbPower); 192 | tempInc[iFreeDim] = 1; 193 | } 194 | RotateVertIncrement(sub.orientation, tempInc, nbInc); 195 | 196 | if (sub.span == CORNER_TO_MIDPOINT) 197 | myEnd = sideLength >> 1; 198 | else 199 | myEnd = sideLength - 1; 200 | 201 | return &neighbour; 202 | } 203 | 204 | DispSubEdgeIterator::DispSubEdgeIterator() 205 | { 206 | mpDisp = nullptr; 207 | mpNeighbor = nullptr; 208 | mFreeDim = mIndex.x = mInc.x = mEnd = 0; 209 | } 210 | 211 | void DispSubEdgeIterator::Start(const std::vector& displacements, const Displacement& disp, int iEdge, int iSub, bool bTouchCorners) 212 | { 213 | mpDisp = &disp; 214 | mpNeighbor = SetupEdgeIncrements(displacements, disp, iEdge, iSub, mIndex, mInc, mNBIndex, mNBInc, mEnd, mFreeDim); 215 | if (mpNeighbor) { 216 | if (bTouchCorners) { 217 | mIndex.x -= mInc.x; 218 | mIndex.y -= mInc.y; 219 | mNBIndex.x -= mNBInc.x; 220 | mNBIndex.y -= mNBInc.y; 221 | 222 | mEnd += mInc[mFreeDim]; 223 | } 224 | } else { 225 | mFreeDim = mIndex.x = mInc.x = mEnd = 0; 226 | } 227 | } 228 | 229 | bool DispSubEdgeIterator::Next() 230 | { 231 | mIndex.x += mInc.x; 232 | mIndex.y += mInc.y; 233 | mNBIndex.x += mNBInc.x; 234 | mNBIndex.y += mNBInc.y; 235 | 236 | return mIndex[mFreeDim] < mEnd; 237 | } 238 | 239 | bool DispSubEdgeIterator::IsLastVert() const 240 | { 241 | return (mIndex[mFreeDim] + mInc[mFreeDim]) >= mEnd; 242 | } 243 | -------------------------------------------------------------------------------- /Displacements/SubEdgeIterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Ripped straight from https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/public/disp_common.h#L48 4 | 5 | #include "Displacements.h" 6 | 7 | namespace Displacements 8 | { 9 | class DispSubEdgeIterator 10 | { 11 | public: 12 | DispSubEdgeIterator(); 13 | 14 | void Start(const std::vector& displacements, const Displacement& pDisp, int iEdge, int iSub, bool bTouchCorners = false); 15 | bool Next(); 16 | 17 | const VertIndex& GetIndex() const 18 | { 19 | return mIndex; 20 | } 21 | const int GetVertIndex() const { 22 | return mIndex.y * ((1 << mpDisp->pInfo->power) + 1) + mIndex.x; 23 | } 24 | const int GetNBVertIndex() const { 25 | return mNBIndex.y * ((1 << mpNeighbor->pInfo->power) + 1) + mNBIndex.x; 26 | } 27 | 28 | bool IsLastVert() const; 29 | 30 | private: 31 | const Displacement* mpDisp; 32 | const Displacement* mpNeighbor; 33 | 34 | VertIndex mIndex; 35 | VertIndex mInc; 36 | 37 | VertIndex mNBIndex; 38 | VertIndex mNBInc; 39 | 40 | int mEnd; 41 | int mFreeDim; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /Displacements/TBNGen.cpp: -------------------------------------------------------------------------------- 1 | #include "Displacements.h" 2 | 3 | using namespace BSPStructs; 4 | 5 | bool DoesEdgeExist(int indexRow, int indexCol, int direction, int postSpacing) 6 | { 7 | switch (direction) { 8 | case 0: 9 | // left edge 10 | if ((indexRow - 1) < 0) 11 | return false; 12 | return true; 13 | case 1: 14 | // top edge 15 | if ((indexCol + 1) > (postSpacing - 1)) 16 | return false; 17 | return true; 18 | case 2: 19 | // right edge 20 | if ((indexRow + 1) > (postSpacing - 1)) 21 | return false; 22 | return true; 23 | case 3: 24 | // bottom edge 25 | if ((indexCol - 1) < 0) 26 | return false; 27 | return true; 28 | default: 29 | return false; 30 | } 31 | } 32 | 33 | Vector CalcNormalFromEdges( 34 | const DispInfo* pDispInfo, const Vector* verts, 35 | int indexRow, int indexCol, bool isEdge[4], int postSpacing 36 | ) 37 | { 38 | Vector accumNormal{}; 39 | int normalCount = 0; 40 | 41 | Vector tmpVec[2]; 42 | Vector tmpNormal; 43 | 44 | if (isEdge[1] && isEdge[2]) { 45 | tmpVec[0] = verts[(indexCol + 1) * postSpacing + indexRow] - verts[indexCol * postSpacing + indexRow]; 46 | tmpVec[1] = verts[indexCol * postSpacing + (indexRow + 1)] - verts[indexCol * postSpacing + indexRow]; 47 | tmpNormal = tmpVec[1].Cross(tmpVec[0]); 48 | tmpNormal.Normalise(); 49 | accumNormal += tmpNormal; 50 | normalCount++; 51 | 52 | tmpVec[0] = verts[(indexCol + 1) * postSpacing + indexRow] - verts[indexCol * postSpacing + (indexRow + 1)]; 53 | tmpVec[1] = verts[(indexCol + 1) * postSpacing + (indexRow + 1)] - verts[indexCol * postSpacing + (indexRow + 1)]; 54 | tmpNormal = tmpVec[1].Cross(tmpVec[0]); 55 | tmpNormal.Normalise(); 56 | accumNormal += tmpNormal; 57 | normalCount++; 58 | } 59 | 60 | if (isEdge[0] && isEdge[1]) { 61 | tmpVec[0] = verts[(indexCol + 1) * postSpacing + (indexRow - 1)] - verts[indexCol * postSpacing + (indexRow - 1)]; 62 | tmpVec[1] = verts[indexCol * postSpacing + indexRow] - verts[indexCol * postSpacing + (indexRow - 1)]; 63 | tmpNormal = tmpVec[1].Cross(tmpVec[0]); 64 | tmpNormal.Normalise(); 65 | accumNormal += tmpNormal; 66 | normalCount++; 67 | 68 | tmpVec[0] = verts[(indexCol + 1) * postSpacing + (indexRow - 1)] - verts[indexCol * postSpacing + indexRow]; 69 | tmpVec[1] = verts[(indexCol + 1) * postSpacing + indexRow] - verts[indexCol * postSpacing + indexRow]; 70 | tmpNormal = tmpVec[1].Cross(tmpVec[0]); 71 | tmpNormal.Normalise(); 72 | accumNormal += tmpNormal; 73 | normalCount++; 74 | } 75 | 76 | if (isEdge[0] && isEdge[3]) { 77 | tmpVec[0] = verts[indexCol * postSpacing + (indexRow - 1)] - verts[(indexCol - 1) * postSpacing + (indexRow - 1)]; 78 | tmpVec[1] = verts[(indexCol - 1) * postSpacing + indexRow] - verts[(indexCol - 1) * postSpacing + (indexRow - 1)]; 79 | tmpNormal = tmpVec[1].Cross(tmpVec[0]); 80 | tmpNormal.Normalise(); 81 | accumNormal += tmpNormal; 82 | normalCount++; 83 | 84 | tmpVec[0] = verts[indexCol * postSpacing + (indexRow - 1)] - verts[(indexCol - 1) * postSpacing + indexRow]; 85 | tmpVec[1] = verts[indexCol * postSpacing + indexRow] - verts[(indexCol - 1) * postSpacing + indexRow]; 86 | tmpNormal = tmpVec[1].Cross(tmpVec[0]); 87 | tmpNormal.Normalise(); 88 | accumNormal += tmpNormal; 89 | normalCount++; 90 | } 91 | 92 | if (isEdge[2] && isEdge[3]) { 93 | tmpVec[0] = verts[indexCol * postSpacing + indexRow] - verts[(indexCol - 1) * postSpacing + indexRow]; 94 | tmpVec[1] = verts[(indexCol - 1) * postSpacing + (indexRow + 1)] - verts[(indexCol - 1) * postSpacing + indexRow]; 95 | tmpNormal = tmpVec[1].Cross(tmpVec[0]); 96 | tmpNormal.Normalise(); 97 | accumNormal += tmpNormal; 98 | normalCount++; 99 | 100 | tmpVec[0] = verts[indexCol * postSpacing + indexRow] - verts[(indexCol - 1) * postSpacing + (indexRow + 1)]; 101 | tmpVec[1] = verts[indexCol * postSpacing + (indexRow + 1)] - verts[(indexCol - 1) * postSpacing + (indexRow + 1)]; 102 | tmpNormal = tmpVec[1].Cross(tmpVec[0]); 103 | tmpNormal.Normalise(); 104 | accumNormal += tmpNormal; 105 | normalCount++; 106 | } 107 | 108 | return accumNormal / normalCount; 109 | } 110 | 111 | void Displacements::GenerateDispSurfNormals(const BSPStructs::DispInfo* pDispInfo, Displacement& disp) 112 | { 113 | int postSpacing = ((1 << pDispInfo->power) + 1); 114 | 115 | for (int i = 0; i < postSpacing; i++) { 116 | for (int j = 0; j < postSpacing; j++) { 117 | bool bIsEdge[4]; 118 | 119 | for (int k = 0; k < 4; k++) { 120 | bIsEdge[k] = DoesEdgeExist(j, i, k, postSpacing); 121 | } 122 | 123 | disp.normals[i * postSpacing + j] = CalcNormalFromEdges( 124 | pDispInfo, disp.verts, 125 | j, i, bIsEdge, postSpacing 126 | ); 127 | } 128 | } 129 | } 130 | 131 | void Displacements::GenerateDispSurfTangentSpaces( 132 | const DispInfo* pDispInfo, const Plane* pPlane, 133 | const TexInfo* pTexInfo, 134 | Displacement& disp 135 | ) 136 | { 137 | Vector sAxis{ pTexInfo->textureVecs[0][0], pTexInfo->textureVecs[0][1], pTexInfo->textureVecs[0][2] }; 138 | Vector tAxis{ pTexInfo->textureVecs[1][0], pTexInfo->textureVecs[1][1], pTexInfo->textureVecs[1][2] }; 139 | 140 | int postSpacing = (1 << pDispInfo->power) + 1; 141 | int size = postSpacing * postSpacing; 142 | for (int i = 0; i < size; i++) { 143 | disp.binormals[i] = tAxis; 144 | disp.binormals[i].Normalise(); 145 | disp.tangents[i] = disp.normals[i].Cross(disp.binormals[i]); 146 | disp.tangents[i].Normalise(); 147 | disp.binormals[i] = disp.tangents[i].Cross(disp.normals[i]); 148 | disp.binormals[i].Normalise(); 149 | 150 | if (pPlane->normal.Dot(sAxis.Cross(tAxis)) > 0.0f) { 151 | disp.tangents[i] *= -1; 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Displacements/UVGen.cpp: -------------------------------------------------------------------------------- 1 | #include "Displacements.h" 2 | 3 | using namespace Displacements; 4 | using namespace BSPStructs; 5 | 6 | void Displacements::GenerateDispSurfUVs(const DispInfo* pDispInfo, float faceUVs[4][2], Displacement& disp) 7 | { 8 | int postSpacing = (1 << pDispInfo->power) + 1; 9 | float ooInt = (1.0f / static_cast(postSpacing - 1)); 10 | 11 | float edgeInt[2][2]; 12 | edgeInt[0][0] = (faceUVs[1][0] - faceUVs[0][0]) * ooInt; 13 | edgeInt[0][1] = (faceUVs[1][1] - faceUVs[0][1]) * ooInt; 14 | edgeInt[1][0] = (faceUVs[2][0] - faceUVs[3][0]) * ooInt; 15 | edgeInt[1][1] = (faceUVs[2][1] - faceUVs[3][1]) * ooInt; 16 | 17 | for (int i = 0; i < postSpacing; i++) { 18 | float endPts[2][2]; 19 | endPts[0][0] = edgeInt[0][0] * i + faceUVs[0][0]; 20 | endPts[0][1] = edgeInt[0][1] * i + faceUVs[0][1]; 21 | endPts[1][0] = edgeInt[1][0] * i + faceUVs[3][0]; 22 | endPts[1][1] = edgeInt[1][1] * i + faceUVs[3][1]; 23 | 24 | float seg[2], segInt[2]; 25 | seg[0] = endPts[1][0] - endPts[0][0]; 26 | seg[1] = endPts[1][1] - endPts[0][1]; 27 | segInt[0] = seg[0] * ooInt; 28 | segInt[1] = seg[1] * ooInt; 29 | 30 | for (int j = 0; j < postSpacing; j++) { 31 | seg[0] = segInt[0] * j; 32 | seg[1] = segInt[1] * j; 33 | 34 | int idx = (i * postSpacing + j) * 2; 35 | disp.uvs[idx] = endPts[0][0] + seg[0]; 36 | disp.uvs[idx + 1] = endPts[0][1] + seg[1]; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Errors/ParseError.cpp: -------------------------------------------------------------------------------- 1 | #include "ParseError.hpp" 2 | 3 | BSPErrors::ParseError::ParseError(const char* const message, BSPEnums::LUMP lump) : std::runtime_error(message), lump(lump) {} 4 | -------------------------------------------------------------------------------- /Errors/ParseError.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "FileFormat/Enums.h" 5 | 6 | namespace BSPErrors { 7 | class ParseError : public std::runtime_error { 8 | public: 9 | ParseError(const char* message, BSPEnums::LUMP lump); 10 | 11 | BSPEnums::LUMP lump; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /Errors/TriangulationError.cpp: -------------------------------------------------------------------------------- 1 | #include "TriangulationError.hpp" 2 | 3 | BSPErrors::TriangulationError::TriangulationError(const char* const message) : std::runtime_error(message) {} -------------------------------------------------------------------------------- /Errors/TriangulationError.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "FileFormat/Enums.h" 5 | 6 | namespace BSPErrors { 7 | class TriangulationError : public std::runtime_error { 8 | public: 9 | TriangulationError(const char* message); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /FileFormat/Enums.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace BSPEnums { 7 | enum class LUMP : uint32_t 8 | { 9 | ENTITIES, 10 | PLANES, 11 | TEXDATA, 12 | VERTICES, 13 | VISIBILITY, 14 | NODES, 15 | TEXINFO, 16 | FACES, 17 | LIGHTING, 18 | OCCLUSION, 19 | LEAFS, 20 | FACEIDS, 21 | EDGES, 22 | SURFEDGES, 23 | MODELS, 24 | WORLDLIGHTS, 25 | LEAFFACES, 26 | LEAFBRUSHES, 27 | BRUSHES, 28 | BRUSHSIDES, 29 | AREAS, 30 | AREAPORTALS, 31 | PORTALS, 32 | UNUSED0 = 22U, 33 | PROPCOLLISION = 22U, 34 | CLUSTERS, 35 | UNUSED1 = 23U, 36 | PROPHULLS = 23U, 37 | PORTALVERTS, 38 | UNUSED2 = 24U, 39 | PROPHULLVERTS = 24U, 40 | CLUSTERPORTALS, 41 | UNUSED3 = 25U, 42 | PROPTRIS = 25U, 43 | DISPINFO, 44 | ORIGINALFACES, 45 | PHYSDISP, 46 | PHYSCOLLIDE, 47 | VERTNORMALS, 48 | VERTNORMALINDICES, 49 | DISP_LIGHTMAP_ALPHAS, 50 | DISP_VERTS, 51 | DISP_LIGHTMAP_SAMPLE_POSITIONS, 52 | GAME_LUMP, 53 | LEAFWATERDATA, 54 | PRIMITIVES, 55 | PRIMVERTS, 56 | PRIMINDICES, 57 | PAKFILE, 58 | CLIPPORTALVERTS, 59 | CUBEMAPS, 60 | TEXDATA_STRING_DATA, 61 | TEXDATA_STRING_TABLE, 62 | OVERLAYS, 63 | LEAFMINDISTTOWATER, 64 | FACE_MACRO_TEXTURE_INFO, 65 | DISP_TRIS, 66 | PHYSCOLLIDESURFACE, 67 | PROP_BLOB = 49U, 68 | WATEROVERLAYS, 69 | LIGHTMAPPAGES, 70 | LEAF_AMBIENT_INDEX_HDR = 51U, 71 | LIGHTMAPPAGEINFOS, 72 | LEAF_AMBIENT_INDEX = 52U, 73 | LIGHTING_HDR, 74 | WORLDLIGHTS_HDR, 75 | LEAF_AMBIENT_LIGHTING_HDR, 76 | LEAF_AMBIENT_LIGHTING, 77 | XZIPPAKFILE, 78 | FACES_HDR, 79 | MAP_FLAGS, 80 | OVERLAY_FADES, 81 | OVERLAY_SYSTEM_LEVELS, 82 | PHYSLEVEL, 83 | DISP_MULTIBLEND, 84 | NONE = std::numeric_limits::max(), 85 | }; 86 | 87 | #define GAMELUMP_MAKE_CODE(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d) << 0) 88 | enum class GameLumpID : int32_t 89 | { 90 | DETAIL_PROPS = GAMELUMP_MAKE_CODE('d', 'p', 'r', 'p'), 91 | DETAIL_PROP_LIGHTING = GAMELUMP_MAKE_CODE('d', 'p', 'l', 't'), 92 | STATIC_PROPS = GAMELUMP_MAKE_CODE('s', 'p', 'r', 'p'), 93 | DETAIL_PROP_LIGHTING_HDR = GAMELUMP_MAKE_CODE('d', 'p', 'l', 'h') 94 | }; 95 | #undef GAMELUMP_MAKE_CODE 96 | 97 | enum class DetailPropOrientation : uint8_t 98 | { 99 | NORMAL = 0, 100 | SCREEN_ALIGNED, 101 | SCREEN_ALIGNED_VERTICAL 102 | }; 103 | 104 | enum class DetailPropType : uint8_t 105 | { 106 | MODEL = 0, 107 | SPRITE, 108 | SHAPE_CROSS, 109 | SHAPE_TRI 110 | }; 111 | 112 | enum class StaticPropFlags : uint8_t 113 | { 114 | // Flags field 115 | // These are automatically computed 116 | FLAG_FADES = 0x1, 117 | USE_LIGHTING_ORIGIN = 0x2, 118 | NO_DRAW = 0x4, // computed at run time based on dx level 119 | 120 | // These are set in WC 121 | IGNORE_NORMALS = 0x8, 122 | NO_SHADOW = 0x10, 123 | SCREEN_SPACE_FADE = 0x20, 124 | 125 | NO_PER_VERTEX_LIGHTING = 0x40, // in vrad, compute lighting at lighting origin, not for each vertex 126 | 127 | NO_SELF_SHADOWING = 0x80, // disable self shadowing in vrad 128 | 129 | WC_MASK = 0xd8 // all flags settable in hammer (?) 130 | }; 131 | 132 | enum class SURF : int32_t 133 | { 134 | NONE = 0x0, 135 | LIGHT = 0x1, 136 | SKY2D = 0x2, 137 | SKY = 0x4, 138 | WARP = 0x8, 139 | TRANS = 0x10, 140 | NOPORTAL = 0x20, 141 | TRIGGER = 0x40, 142 | NODRAW = 0x80, 143 | HINT = 0x100, 144 | SKIP = 0x200, 145 | NOLIGHT = 0x400, 146 | BUMPLIGHT = 0x800, 147 | NOSHADOWS = 0x1000, 148 | NODECALS = 0x2000, 149 | NOCHOP = 0x4000, 150 | HITBOX = 0x8000 151 | }; 152 | 153 | inline SURF operator |(SURF lhs, SURF rhs) 154 | { 155 | return static_cast(static_cast(lhs) | static_cast(rhs)); 156 | } 157 | 158 | inline SURF& operator |=(SURF& lhs, SURF rhs) 159 | { 160 | lhs = lhs | rhs; 161 | return lhs; 162 | } 163 | 164 | inline SURF operator &(SURF lhs, SURF rhs) 165 | { 166 | return static_cast(static_cast(lhs) & static_cast(rhs)); 167 | } 168 | 169 | inline SURF& operator &=(SURF& lhs, SURF rhs) 170 | { 171 | lhs = lhs & rhs; 172 | return lhs; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /FileFormat/Limits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace BSPStructs 7 | { 8 | constexpr size_t MIN_MAP_DISP_POWER = 2; 9 | constexpr size_t MAX_MAP_DISP_POWER = 4; 10 | 11 | // Max # of neighboring displacement touching a displacement's corner. 12 | constexpr size_t MAX_DISP_CORNER_NEIGHBORS = 4; 13 | 14 | #define NUM_DISP_POWER_VERTS(power) (((1 << (power)) + 1) * ((1 << (power)) + 1)) 15 | #define NUM_DISP_POWER_TRIS(power) ((1 << (power)) * (1 << (power)) * 2) 16 | 17 | constexpr size_t MAX_MAP_MODELS = 1024; 18 | constexpr size_t MAX_MAP_BRUSHES = 8192; 19 | constexpr size_t MAX_MAP_ENTITIES = 8192; 20 | constexpr size_t MAX_MAP_TEXINFO = 12288; 21 | constexpr size_t MAX_MAP_TEXDATA = 2048; 22 | constexpr size_t MAX_MAP_DISPINFO = 2048; 23 | constexpr size_t MAX_MAP_DISP_VERTS = MAX_MAP_DISPINFO * ((1 << MAX_MAP_DISP_POWER) + 1) * ((1 << MAX_MAP_DISP_POWER) + 1); 24 | constexpr size_t MAX_MAP_DISP_TRIS = (1 << MAX_MAP_DISP_POWER) * (1 << MAX_MAP_DISP_POWER) * 2; 25 | constexpr size_t MAX_DISPVERTS = NUM_DISP_POWER_VERTS(MAX_MAP_DISP_POWER); 26 | constexpr size_t MAX_DISPTRIS = NUM_DISP_POWER_TRIS(MAX_MAP_DISP_POWER); 27 | constexpr size_t MAX_MAP_AREAS = 256; 28 | constexpr size_t MAX_MAP_AREA_BYTES = MAX_MAP_AREAS / 8; 29 | constexpr size_t MAX_MAP_AREAPORTALS = 1024; 30 | constexpr size_t MAX_MAP_PLANES = 65536; 31 | constexpr size_t MAX_MAP_NODES = 65536; 32 | constexpr size_t MAX_MAP_BRUSHSIDES = 65536; 33 | constexpr size_t MAX_MAP_LEAFS = 65536; 34 | constexpr size_t MAX_MAP_VERTS = 65536; 35 | constexpr size_t MAX_MAP_VERTNORMALS = 256000; 36 | constexpr size_t MAX_MAP_VERTNORMALINDICES = 256000; 37 | constexpr size_t MAX_MAP_FACES = 65536; 38 | constexpr size_t MAX_MAP_LEAFFACES = 65536; 39 | constexpr size_t MAX_MAP_LEAFBRUSHES = 65536; 40 | constexpr size_t MAX_MAP_PORTALS = 65536; 41 | constexpr size_t MAX_MAP_CLUSTERS = 65536; 42 | constexpr size_t MAX_MAP_LEAFWATERDATA = 32768; 43 | constexpr size_t MAX_MAP_PORTALVERTS = 128000; 44 | constexpr size_t MAX_MAP_EDGES = 256000; 45 | constexpr size_t MAX_MAP_SURFEDGES = 512000; 46 | constexpr size_t MAX_MAP_LIGHTING = 0x1000000; 47 | constexpr size_t MAX_MAP_VISIBILITY = 0x1000000; 48 | constexpr size_t MAX_MAP_TEXTURES = 1024; 49 | constexpr size_t MAX_MAP_WORLDLIGHTS = 8192; 50 | constexpr size_t MAX_MAP_CUBEMAPSAMPLES = 1024; 51 | constexpr size_t MAX_MAP_OVERLAYS = 512; 52 | constexpr size_t MAX_MAP_WATEROVERLAYS = 16384; 53 | constexpr size_t MAX_MAP_TEXDATA_STRING_DATA = 256000; 54 | constexpr size_t MAX_MAP_TEXDATA_STRING_TABLE = 65536; 55 | constexpr size_t MAX_MAP_PRIMITIVES = 32768; 56 | constexpr size_t MAX_MAP_PRIMVERTS = 65536; 57 | constexpr size_t MAX_MAP_PRIMINDICES = 65536; 58 | 59 | constexpr uint8_t DETAIL_NAME_LENGTH = 128; 60 | constexpr uint8_t STATIC_PROP_NAME_LENGTH = 128; 61 | 62 | #undef NUM_DISP_POWER_VERTS 63 | #undef NUM_DISP_POWER_TRIS 64 | } 65 | -------------------------------------------------------------------------------- /FileFormat/Parser.cpp: -------------------------------------------------------------------------------- 1 | #include "Parser.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "Enums.h" 7 | #include "Errors/ParseError.hpp" 8 | 9 | using namespace BSPStructs; 10 | using namespace BSPEnums; 11 | using BSPErrors::ParseError; 12 | 13 | void BSPParser::GetLumpPtr( 14 | const uint8_t* pData, const size_t size, 15 | const Header* pHeader, const LUMP lumpType, 16 | const uint8_t** pPtrOut 17 | ) 18 | { 19 | if (pData == nullptr || pHeader == nullptr || pPtrOut == nullptr) { 20 | throw ParseError("Data, header or output pointers are null", lumpType); 21 | } 22 | 23 | const Lump& lump = pHeader->lumps[static_cast(lumpType)]; 24 | if (lump.offset < 0) { 25 | throw ParseError("Lump offset is before the start of the data", lumpType); 26 | } 27 | if (lump.offset + lump.length > size) { 28 | throw ParseError("Lump offset plus length overruns the data", lumpType); 29 | } 30 | 31 | *pPtrOut = pData + lump.offset; 32 | } 33 | 34 | void BSPParser::ParseHeader( 35 | const uint8_t* pData, const size_t size, 36 | const Header** pHeaderPtr 37 | ) 38 | { 39 | if (pData == nullptr || pHeaderPtr == nullptr) { 40 | throw ParseError("Data or header pointers are null", LUMP::NONE); 41 | } 42 | if (size < sizeof(Header)) { 43 | throw ParseError("Data size is smaller than the file header", LUMP::NONE); 44 | } 45 | 46 | *pHeaderPtr = reinterpret_cast(pData); 47 | if ((*pHeaderPtr)->ident != IDBSPHEADER) { 48 | throw ParseError("Header's identifier is not 'VBSP'", LUMP::NONE); 49 | } 50 | } 51 | 52 | template 53 | void ParseLumpBase( 54 | const uint8_t* pData, const size_t size, 55 | const Header* pHeader, 56 | const LumpDatatype** pArray, size_t* pLength, 57 | const LUMP lump, const size_t max 58 | ) 59 | { 60 | if ( 61 | pData == nullptr || 62 | pHeader == nullptr || 63 | pArray == nullptr || 64 | pLength == nullptr 65 | ) { 66 | throw ParseError("Data, header, array or length pointers are null", lump); 67 | } 68 | 69 | if (pHeader->lumps[static_cast(lump)].length % sizeof(LumpDatatype) != 0) { 70 | throw ParseError("Size of the lump is not an exact multiple of the size of its items", lump); 71 | } 72 | 73 | const uint8_t* pLumpData; 74 | BSPParser::GetLumpPtr(pData, size, pHeader, lump, &pLumpData); 75 | 76 | *pLength = pHeader->lumps[static_cast(lump)].length / sizeof(LumpDatatype); 77 | if (*pLength > max) { 78 | throw ParseError("Number of lump items exceeds the Source engine maximum", lump); 79 | } 80 | 81 | *pArray = reinterpret_cast(pLumpData); 82 | } 83 | 84 | void BSPParser::ParseArray( 85 | const uint8_t* pData, const size_t size, 86 | const Header* pHeader, 87 | const char** pArray, size_t* pLength, 88 | const LUMP lump, const size_t max 89 | ) 90 | { 91 | ParseLumpBase( 92 | pData, size, 93 | pHeader, 94 | pArray, pLength, 95 | lump, max 96 | ); 97 | } 98 | 99 | void BSPParser::ParseArray( 100 | const uint8_t* pData, const size_t size, 101 | const Header* pHeader, 102 | const int32_t** pArray, size_t* pLength, 103 | const LUMP lump, const size_t max 104 | ) 105 | { 106 | ParseLumpBase( 107 | pData, size, 108 | pHeader, 109 | pArray, pLength, 110 | lump, max 111 | ); 112 | } 113 | 114 | void BSPParser::ParseArray( 115 | const uint8_t* pData, const size_t size, 116 | const Header* pHeader, 117 | const Vector** pArray, size_t* pLength, 118 | const LUMP lump, const size_t max 119 | ) 120 | { 121 | ParseLumpBase( 122 | pData, size, 123 | pHeader, 124 | pArray, pLength, 125 | lump, max 126 | ); 127 | } 128 | 129 | void BSPParser::ParseLump( 130 | const uint8_t* pData, const size_t size, 131 | const Header* pHeader, 132 | const Plane** pArray, size_t* pLength 133 | ) { 134 | ParseLumpBase( 135 | pData, size, 136 | pHeader, 137 | pArray, pLength, 138 | LUMP::PLANES, MAX_MAP_PLANES 139 | ); 140 | } 141 | 142 | void BSPParser::ParseLump( 143 | const uint8_t* pData, const size_t size, 144 | const Header* pHeader, 145 | const Edge** pArray, size_t* pLength 146 | ) { 147 | ParseLumpBase( 148 | pData, size, 149 | pHeader, 150 | pArray, pLength, 151 | LUMP::EDGES, MAX_MAP_EDGES 152 | ); 153 | } 154 | 155 | void BSPParser::ParseLump( 156 | const uint8_t* pData, const size_t size, 157 | const Header* pHeader, 158 | const Face** pArray, size_t* pLength 159 | ) { 160 | ParseLumpBase( 161 | pData, size, 162 | pHeader, 163 | pArray, pLength, 164 | LUMP::FACES, MAX_MAP_FACES 165 | ); 166 | } 167 | 168 | void BSPParser::ParseLump( 169 | const uint8_t* pData, const size_t size, 170 | const Header* pHeader, 171 | const TexInfo** pArray, size_t* pLength 172 | ) { 173 | ParseLumpBase( 174 | pData, size, 175 | pHeader, 176 | pArray, pLength, 177 | LUMP::TEXINFO, MAX_MAP_TEXINFO 178 | ); 179 | } 180 | 181 | void BSPParser::ParseLump( 182 | const uint8_t* pData, const size_t size, 183 | const Header* pHeader, 184 | const TexData** pArray, size_t* pLength 185 | ) { 186 | ParseLumpBase( 187 | pData, size, 188 | pHeader, 189 | pArray, pLength, 190 | LUMP::TEXDATA, MAX_MAP_TEXDATA 191 | ); 192 | } 193 | 194 | void BSPParser::ParseLump( 195 | const uint8_t* pData, const size_t size, 196 | const Header* pHeader, 197 | const Model** pArray, size_t* pLength 198 | ) { 199 | ParseLumpBase( 200 | pData, size, 201 | pHeader, 202 | pArray, pLength, 203 | LUMP::MODELS, MAX_MAP_MODELS 204 | ); 205 | } 206 | 207 | void BSPParser::ParseLump( 208 | const uint8_t* pData, const size_t size, 209 | const Header* pHeader, 210 | const DispInfo** pArray, size_t* pLength 211 | ) { 212 | ParseLumpBase( 213 | pData, size, 214 | pHeader, 215 | pArray, pLength, 216 | LUMP::DISPINFO, MAX_MAP_DISPINFO 217 | ); 218 | } 219 | 220 | void BSPParser::ParseLump( 221 | const uint8_t* pData, const size_t size, 222 | const Header* pHeader, 223 | const DispVert** pArray, size_t* pLength 224 | ) { 225 | ParseLumpBase( 226 | pData, size, 227 | pHeader, 228 | pArray, pLength, 229 | LUMP::DISP_VERTS, MAX_MAP_DISP_VERTS 230 | ); 231 | } 232 | -------------------------------------------------------------------------------- /FileFormat/Parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Structs.h" 4 | #include "Enums.h" 5 | 6 | namespace BSPParser 7 | { 8 | void GetLumpPtr( 9 | const uint8_t* pData, size_t size, 10 | const BSPStructs::Header* pHeader, BSPEnums::LUMP lump, 11 | const uint8_t** pPtrOut 12 | ); 13 | 14 | void ParseHeader( 15 | const uint8_t* pData, size_t size, 16 | const BSPStructs::Header** pHeaderPtr 17 | ); 18 | 19 | void ParseArray( 20 | const uint8_t* pData, size_t size, 21 | const BSPStructs::Header* pHeader, 22 | const char** pArray, size_t* pLength, 23 | BSPEnums::LUMP lump, size_t max 24 | ); 25 | 26 | void ParseArray( 27 | const uint8_t* pData, size_t size, 28 | const BSPStructs::Header* pHeader, 29 | const int32_t** pArray, size_t* pLength, 30 | BSPEnums::LUMP lump, size_t max 31 | ); 32 | 33 | void ParseArray( 34 | const uint8_t* pData, size_t size, 35 | const BSPStructs::Header* pHeader, 36 | const BSPStructs::Vector** pArray, size_t* pLength, 37 | BSPEnums::LUMP lump, size_t max 38 | ); 39 | 40 | void ParseLump( 41 | const uint8_t* pData, size_t size, 42 | const BSPStructs::Header* pHeader, 43 | const BSPStructs::Plane** pArray, size_t* pLength 44 | ); 45 | 46 | 47 | void ParseLump( 48 | const uint8_t* pData, size_t size, 49 | const BSPStructs::Header* pHeader, 50 | const BSPStructs::Edge** pArray, size_t* pLength 51 | ); 52 | 53 | void ParseLump( 54 | const uint8_t* pData, size_t size, 55 | const BSPStructs::Header* pHeader, 56 | const BSPStructs::Face** pArray, size_t* pLength 57 | ); 58 | 59 | void ParseLump( 60 | const uint8_t* pData, size_t size, 61 | const BSPStructs::Header* pHeader, 62 | const BSPStructs::TexInfo** pArray, size_t* pLength 63 | ); 64 | 65 | void ParseLump( 66 | const uint8_t* pData, size_t size, 67 | const BSPStructs::Header* pHeader, 68 | const BSPStructs::TexData** pArray, size_t* pLength 69 | ); 70 | 71 | void ParseLump( 72 | const uint8_t* pData, size_t size, 73 | const BSPStructs::Header* pHeader, 74 | const BSPStructs::Model** pArray, size_t* pLength 75 | ); 76 | 77 | void ParseLump( 78 | const uint8_t* pData, size_t size, 79 | const BSPStructs::Header* pHeader, 80 | const BSPStructs::DispInfo** pArray, size_t* pLength 81 | ); 82 | 83 | void ParseLump( 84 | const uint8_t* pData, size_t size, 85 | const BSPStructs::Header* pHeader, 86 | const BSPStructs::DispVert** pArray, size_t* pLength 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /FileFormat/Structs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Limits.h" 6 | #include "Enums.h" 7 | 8 | namespace BSPStructs { 9 | constexpr int32_t IDBSPHEADER = 'V' + ('B' << 8) + ('S' << 16) + ('P' << 24); 10 | constexpr size_t HEADER_LUMPS = 64; 11 | 12 | #pragma region Base Types 13 | struct Vector 14 | { 15 | float x = 0, y = 0, z = 0; 16 | 17 | Vector operator+(const Vector& b) const; 18 | Vector operator-() const; 19 | Vector operator-(const Vector& b) const; 20 | Vector operator*(const Vector& b) const; 21 | Vector operator/(const Vector& b) const; 22 | 23 | Vector& operator+=(const Vector& b); 24 | Vector& operator-=(const Vector& b); 25 | Vector& operator*=(const Vector& b); 26 | Vector& operator/=(const Vector& b); 27 | 28 | Vector operator+(const float& b) const; 29 | Vector operator-(const float& b) const; 30 | Vector operator*(const float& b) const; 31 | Vector operator/(const float& b) const; 32 | 33 | Vector& operator+=(const float& b); 34 | Vector& operator-=(const float& b); 35 | Vector& operator*=(const float& b); 36 | Vector& operator/=(const float& b); 37 | 38 | inline float Dot(const Vector& b) const 39 | { 40 | return x * b.x + y * b.y + z * b.z; 41 | } 42 | Vector Cross(const Vector& b) const; 43 | void Normalise(); 44 | }; 45 | 46 | struct Vector2D 47 | { 48 | float x = 0, y = 0; 49 | 50 | Vector2D operator+(const Vector2D& b) const; 51 | Vector2D operator-() const; 52 | Vector2D operator-(const Vector2D& b) const; 53 | Vector2D operator*(const Vector2D& b) const; 54 | Vector2D operator/(const Vector2D& b) const; 55 | 56 | Vector2D& operator+=(const Vector2D& b); 57 | Vector2D& operator-=(const Vector2D& b); 58 | Vector2D& operator*=(const Vector2D& b); 59 | Vector2D& operator/=(const Vector2D& b); 60 | 61 | Vector2D operator+(const float& b) const; 62 | Vector2D operator-(const float& b) const; 63 | Vector2D operator*(const float& b) const; 64 | Vector2D operator/(const float& b) const; 65 | 66 | Vector2D& operator+=(const float& b); 67 | Vector2D& operator-=(const float& b); 68 | Vector2D& operator*=(const float& b); 69 | Vector2D& operator/=(const float& b); 70 | 71 | inline float Dot(const Vector2D& b) const 72 | { 73 | return x * b.x + y * b.y; 74 | } 75 | void Normalise(); 76 | }; 77 | 78 | struct QAngle 79 | { 80 | float x = 0, y = 0, z = 0; 81 | }; 82 | 83 | struct ColorRGBExp32 84 | { 85 | uint8_t r, g, b; 86 | int8_t exponent; 87 | }; 88 | #pragma endregion 89 | 90 | struct Lump 91 | { 92 | int32_t offset; // Byte offset into file 93 | int32_t length; // Length of lump data 94 | int32_t version; // Lump format version 95 | int32_t fourCC; // Uncompressed size, or 0 96 | }; 97 | 98 | struct Header 99 | { 100 | int32_t ident; // BSP file identifier (should always equal "VBSP") 101 | int32_t version; // Version of the BSP file (19-21) 102 | Lump lumps[HEADER_LUMPS]; // Lump directory 103 | int32_t mapRevision; // Map version number 104 | }; 105 | 106 | struct GameLump 107 | { 108 | BSPEnums::GameLumpID id; 109 | uint16_t flags; 110 | uint16_t version; 111 | int32_t offset; 112 | int32_t length; 113 | }; 114 | 115 | struct Plane 116 | { 117 | Vector normal; // Normal of the plane 118 | float distance; // Distance from origin 119 | int32_t type; // Plane axis identifier (unused) 120 | }; 121 | 122 | struct Edge 123 | { 124 | uint16_t vertices[2]; // Indices of each vertex that makes up this edge 125 | }; 126 | 127 | struct Face 128 | { 129 | uint16_t planeNum; 130 | uint8_t side; 131 | uint8_t onNode; 132 | int32_t firstEdge; 133 | int16_t numEdges; 134 | int16_t texInfo; 135 | int16_t dispInfo; 136 | int16_t surfaceVolumeFogId; 137 | uint8_t styles[4]; 138 | int32_t lightOffset; 139 | float area; // Area of the face in hammer units squared 140 | int32_t lightmapTextureMinsInLuxels[2]; 141 | int32_t lightmapTextureSizeInLuxels[2]; 142 | int32_t originalFace; 143 | uint16_t numPrimitives; 144 | uint16_t firstPrimitiveId; 145 | uint32_t smoothingGroups; 146 | }; 147 | 148 | struct Brush 149 | { 150 | int32_t firstSide; 151 | int32_t numSides; 152 | int32_t contents; 153 | }; 154 | 155 | struct BrushSide 156 | { 157 | uint16_t planeNum; 158 | int16_t texInfo; 159 | int16_t dispInfo; 160 | int16_t bevel; 161 | }; 162 | 163 | struct TexInfo 164 | { 165 | float textureVecs[2][4]; 166 | float lightmapVecs[2][4]; 167 | BSPEnums::SURF flags; 168 | int32_t texData; 169 | }; 170 | 171 | struct TexData 172 | { 173 | Vector reflectivity; 174 | int32_t nameStringTableId; 175 | int32_t width, height; 176 | int32_t viewWidth, viewHeight; 177 | }; 178 | 179 | struct Model 180 | { 181 | Vector mins, maxs; 182 | Vector origin; 183 | int32_t headNode; 184 | int32_t firstFace, numFaces; 185 | }; 186 | 187 | #pragma region Displacements 188 | struct DispSubNeighbour 189 | { 190 | uint16_t index = 0xFFFF; 191 | uint8_t orientation; 192 | uint8_t span; 193 | uint8_t neighbourSpan; 194 | 195 | inline bool IsValid() const { return index != 0xFFFF; } 196 | }; 197 | 198 | struct DispNeighbour 199 | { 200 | DispSubNeighbour subNeighbors[2]; 201 | }; 202 | 203 | struct DispCornerNeighbours 204 | { 205 | uint16_t neighbours[MAX_DISP_CORNER_NEIGHBORS]; 206 | uint8_t numNeighbours; 207 | }; 208 | 209 | struct DispInfo 210 | { 211 | Vector startPosition; 212 | 213 | int32_t dispVertStart; 214 | int32_t dispTriStart; 215 | 216 | int32_t power; 217 | int32_t minTess; 218 | 219 | float smoothingAngle; 220 | int32_t contents; 221 | uint16_t mapFace; 222 | int32_t lightmapAlphaStart; 223 | int32_t lightmapSamplePositionStart; 224 | 225 | DispNeighbour edgeNeighbours[4]; 226 | DispCornerNeighbours cornerNeighbours[4]; 227 | 228 | uint32_t allowedVerts[10]; 229 | }; 230 | 231 | struct DispVert 232 | { 233 | Vector vec; 234 | float dist; 235 | float alpha; 236 | }; 237 | #pragma endregion 238 | 239 | #pragma region Detail Props 240 | struct DetailObjectDict 241 | { 242 | char modelName[DETAIL_NAME_LENGTH]; 243 | }; 244 | 245 | struct DetailSpriteDict 246 | { 247 | // NOTE: All detail prop sprites must lie in the material detail/detailsprites 248 | Vector2D upperLeft; 249 | Vector2D lowerRight; 250 | Vector2D texUpperLeft; 251 | Vector2D texLowerRight; 252 | }; 253 | 254 | struct DetailObject 255 | { 256 | Vector origin; 257 | QAngle angles; 258 | uint16_t detailModel; // either index into DetailObjectDictLump_t or DetailPropSpriteLump_t 259 | uint16_t leaf; 260 | ColorRGBExp32 lighting; 261 | uint32_t lightStyles; 262 | uint8_t lightStyleCount; 263 | uint8_t swayAmount; // how much do the details sway 264 | uint8_t shapeAngle; // angle param for shaped sprites 265 | uint8_t shapeSize; // size param for shaped sprites 266 | BSPEnums::DetailPropOrientation orientation; 267 | uint8_t padding2[3]; 268 | BSPEnums::DetailPropType type; 269 | uint8_t padding3[3]; 270 | float flScale; // For sprites only currently 271 | }; 272 | 273 | struct DetailPropLightstyles 274 | { 275 | ColorRGBExp32 lighting; 276 | uint8_t style; 277 | }; 278 | #pragma endregion 279 | 280 | #pragma region Static Props 281 | struct StaticPropDict 282 | { 283 | char modelName[STATIC_PROP_NAME_LENGTH]; 284 | }; 285 | 286 | struct StaticPropV4 287 | { 288 | Vector origin; 289 | QAngle angles; 290 | uint16_t propType; 291 | uint16_t firstLeaf; 292 | uint16_t leafCount; 293 | uint8_t solid; 294 | BSPEnums::StaticPropFlags flags; 295 | int32_t skin; 296 | float fadeMinDist; 297 | float fadeMaxDist; 298 | Vector lightingOrigin; 299 | }; 300 | 301 | struct StaticPropV5 302 | { 303 | Vector origin; 304 | QAngle angles; 305 | uint16_t propType; 306 | uint16_t firstLeaf; 307 | uint16_t leafCount; 308 | uint8_t solid; 309 | BSPEnums::StaticPropFlags flags; 310 | int32_t skin; 311 | float fadeMinDist; 312 | float fadeMaxDist; 313 | Vector lightingOrigin; 314 | float flForcedFadeScale; 315 | }; 316 | 317 | struct StaticPropV6 318 | { 319 | Vector origin; 320 | QAngle angles; 321 | uint16_t propType; 322 | uint16_t firstLeaf; 323 | uint16_t leafCount; 324 | uint8_t solid; 325 | BSPEnums::StaticPropFlags flags; 326 | int32_t skin; 327 | float fadeMinDist; 328 | float fadeMaxDist; 329 | Vector lightingOrigin; 330 | float flForcedFadeScale; 331 | uint16_t minDXLevel; 332 | uint16_t maxDXLevel; 333 | }; 334 | 335 | struct StaticPropV7Multiplayer2013 336 | { 337 | Vector origin; 338 | QAngle angles; 339 | uint16_t propType; 340 | uint16_t firstLeaf; 341 | uint16_t leafCount; 342 | uint8_t solid; 343 | int32_t skin; 344 | float fadeMinDist; 345 | float fadeMaxDist; 346 | Vector lightingOrigin; 347 | float flForcedFadeScale; 348 | uint16_t minDXLevel; 349 | uint16_t maxDXLevel; 350 | uint32_t flags; 351 | uint8_t LightmapResX; 352 | uint8_t lightmapResY; 353 | }; 354 | 355 | struct StaticPropLeaf 356 | { 357 | uint16_t leaf; 358 | }; 359 | 360 | struct StaticPropLightstyles 361 | { 362 | ColorRGBExp32 lighting; 363 | }; 364 | #pragma endregion 365 | } 366 | -------------------------------------------------------------------------------- /FileFormat/Vector.cpp: -------------------------------------------------------------------------------- 1 | #include "Structs.h" 2 | 3 | #include 4 | 5 | using namespace BSPStructs; 6 | 7 | Vector Vector::operator+(const Vector& b) const 8 | { 9 | return Vector{ x + b.x, y + b.y, z + b.z }; 10 | } 11 | Vector Vector::operator-() const 12 | { 13 | return Vector{ -x, -y, -z }; 14 | } 15 | Vector Vector::operator-(const Vector& b) const 16 | { 17 | return Vector{ x - b.x, y - b.y, z - b.z }; 18 | } 19 | Vector Vector::operator*(const Vector& b) const 20 | { 21 | return Vector{ x * b.x, y * b.y, z * b.z }; 22 | } 23 | Vector Vector::operator/(const Vector& b) const 24 | { 25 | return Vector{ x / b.x, y / b.y, z / b.z }; 26 | } 27 | 28 | Vector& Vector::operator+=(const Vector& b) 29 | { 30 | x += b.x; 31 | y += b.y; 32 | z += b.z; 33 | return *this; 34 | } 35 | Vector& Vector::operator-=(const Vector& b) 36 | { 37 | x -= b.x; 38 | y -= b.y; 39 | z -= b.z; 40 | return *this; 41 | } 42 | Vector& Vector::operator*=(const Vector& b) 43 | { 44 | x *= b.x; 45 | y *= b.y; 46 | z *= b.z; 47 | return *this; 48 | } 49 | Vector& Vector::operator/=(const Vector& b) 50 | { 51 | x /= b.x; 52 | y /= b.y; 53 | z /= b.z; 54 | return *this; 55 | } 56 | 57 | Vector Vector::operator+(const float& b) const 58 | { 59 | return Vector{ x + b, y + b, z + b }; 60 | } 61 | Vector Vector::operator-(const float& b) const 62 | { 63 | return Vector{ x - b, y - b, z - b }; 64 | } 65 | Vector Vector::operator*(const float& b) const 66 | { 67 | return Vector{ x * b, y * b, z * b }; 68 | } 69 | Vector Vector::operator/(const float& b) const 70 | { 71 | return Vector{ x / b, y / b, z / b }; 72 | } 73 | 74 | Vector& Vector::operator+=(const float& b) 75 | { 76 | x += b; 77 | y += b; 78 | z += b; 79 | return *this; 80 | } 81 | Vector& Vector::operator-=(const float& b) 82 | { 83 | x -= b; 84 | y -= b; 85 | z -= b; 86 | return *this; 87 | } 88 | Vector& Vector::operator*=(const float& b) 89 | { 90 | x *= b; 91 | y *= b; 92 | z *= b; 93 | return *this; 94 | } 95 | Vector& Vector::operator/=(const float& b) 96 | { 97 | x /= b; 98 | y /= b; 99 | z /= b; 100 | return *this; 101 | } 102 | 103 | Vector Vector::Cross(const Vector& b) const 104 | { 105 | return Vector{ 106 | y * b.z - z * b.y, 107 | z * b.x - x * b.z, 108 | x * b.y - y * b.x 109 | }; 110 | } 111 | 112 | void Vector::Normalise() 113 | { 114 | float len = sqrtf(Dot(*this)); 115 | x /= len; 116 | y /= len; 117 | z /= len; 118 | } 119 | -------------------------------------------------------------------------------- /FileFormat/Vector2D.cpp: -------------------------------------------------------------------------------- 1 | #include "Structs.h" 2 | 3 | #include 4 | 5 | using namespace BSPStructs; 6 | 7 | Vector2D Vector2D::operator+(const Vector2D& b) const 8 | { 9 | return Vector2D{ x + b.x, y + b.y }; 10 | } 11 | Vector2D Vector2D::operator-() const 12 | { 13 | return Vector2D{ -x, -y }; 14 | } 15 | Vector2D Vector2D::operator-(const Vector2D& b) const 16 | { 17 | return Vector2D{ x - b.x, y - b.y }; 18 | } 19 | Vector2D Vector2D::operator*(const Vector2D& b) const 20 | { 21 | return Vector2D{ x * b.x, y * b.y }; 22 | } 23 | Vector2D Vector2D::operator/(const Vector2D& b) const 24 | { 25 | return Vector2D{ x / b.x, y / b.y }; 26 | } 27 | 28 | Vector2D& Vector2D::operator+=(const Vector2D& b) 29 | { 30 | x += b.x; 31 | y += b.y; 32 | return *this; 33 | } 34 | Vector2D& Vector2D::operator-=(const Vector2D& b) 35 | { 36 | x -= b.x; 37 | y -= b.y; 38 | return *this; 39 | } 40 | Vector2D& Vector2D::operator*=(const Vector2D& b) 41 | { 42 | x *= b.x; 43 | y *= b.y; 44 | return *this; 45 | } 46 | Vector2D& Vector2D::operator/=(const Vector2D& b) 47 | { 48 | x /= b.x; 49 | y /= b.y; 50 | return *this; 51 | } 52 | 53 | Vector2D Vector2D::operator+(const float& b) const 54 | { 55 | return Vector2D{ x + b, y + b }; 56 | } 57 | Vector2D Vector2D::operator-(const float& b) const 58 | { 59 | return Vector2D{ x - b, y - b }; 60 | } 61 | Vector2D Vector2D::operator*(const float& b) const 62 | { 63 | return Vector2D{ x * b, y * b }; 64 | } 65 | Vector2D Vector2D::operator/(const float& b) const 66 | { 67 | return Vector2D{ x / b, y / b }; 68 | } 69 | 70 | Vector2D& Vector2D::operator+=(const float& b) 71 | { 72 | x += b; 73 | y += b; 74 | return *this; 75 | } 76 | Vector2D& Vector2D::operator-=(const float& b) 77 | { 78 | x -= b; 79 | y -= b; 80 | return *this; 81 | } 82 | Vector2D& Vector2D::operator*=(const float& b) 83 | { 84 | x *= b; 85 | y *= b; 86 | return *this; 87 | } 88 | Vector2D& Vector2D::operator/=(const float& b) 89 | { 90 | x /= b; 91 | y /= b; 92 | return *this; 93 | } 94 | 95 | void Vector2D::Normalise() 96 | { 97 | float len = sqrtf(Dot(*this)); 98 | x /= len; 99 | y /= len; 100 | } 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Derpius 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 | # BSPParser 2 | Simple and modern library for parsing the Valve BSP Format. 3 | 4 | This is mainly for use in my other projects like VisTrace, and is acompanied by https://github.com/Derpius/VTFParser. 5 | 6 | Provides complete abstraction from the BSP file format for reading triangulated map faces. 7 | *Documentation coming soon* 8 | --------------------------------------------------------------------------------