├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
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 |
--------------------------------------------------------------------------------