├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── BRICK └── brickwall001c.tga ├── LICENSE ├── README.md ├── dist └── WebBSP.js ├── gulpfile.js ├── index.html ├── main.css ├── package-lock.json ├── package.json ├── src ├── BSP │ ├── BSP.ts │ ├── Lumps │ │ ├── BrushLump.ts │ │ ├── BrushSideLump.ts │ │ ├── CubemapLump.ts │ │ ├── DispInfoLump.ts │ │ ├── DispTrisLump.ts │ │ ├── DispVertLump.ts │ │ ├── EdgeLump.ts │ │ ├── EntityLump.ts │ │ ├── FaceLump.ts │ │ ├── GameLump.ts │ │ ├── GenericLump.ts │ │ ├── LeafAmbientIndexHDRLump.ts │ │ ├── LeafAmbientIndexLump.ts │ │ ├── LeafAmbientLightingLump.ts │ │ ├── LeafBrushLump.ts │ │ ├── LeafFaceLump.ts │ │ ├── LeafLump.ts │ │ ├── LightingLump.ts │ │ ├── Lump.ts │ │ ├── LumpHeader.ts │ │ ├── LumpType.ts │ │ ├── ModelLump.ts │ │ ├── NodeLump.ts │ │ ├── OriginalFaceLump.ts │ │ ├── PlaneLump.ts │ │ ├── SurfEdgeLump.ts │ │ ├── TexDataLump.ts │ │ ├── TexDataStringDataLump.ts │ │ ├── TexDataStringTableLump.ts │ │ ├── TexInfoLump.ts │ │ └── VertexLump.ts │ ├── Structs │ │ ├── Brush.ts │ │ ├── BrushSide.ts │ │ ├── ColorRGBExp32.ts │ │ ├── CompressedLightCube.ts │ │ ├── Cubemap.ts │ │ ├── DispCornerNeighbor.ts │ │ ├── DispInfo.ts │ │ ├── DispNeighbor.ts │ │ ├── DispSubNeighbor.ts │ │ ├── DispVert.ts │ │ ├── Edge.ts │ │ ├── Enums.ts │ │ ├── Face.ts │ │ ├── GameLumpStruct.ts │ │ ├── Leaf.ts │ │ ├── LeafAmbientIndex.ts │ │ ├── LeafAmbientLighting.ts │ │ ├── Model.ts │ │ ├── Node.ts │ │ ├── Plane.ts │ │ ├── TexData.ts │ │ └── TexInfo.ts │ └── Utils │ │ └── BinaryReader.ts ├── KeyboardListener.ts ├── MouseHandler.ts ├── Rendering │ ├── Camera │ │ ├── CameraState.ts │ │ ├── ICamera.ts │ │ ├── OrthoCamera.ts │ │ └── PerspectiveCamera.ts │ ├── EngineCore.ts │ ├── EngineState.ts │ ├── IEngineComponent.ts │ ├── Messaging │ │ ├── Message.ts │ │ └── MessageQueue.ts │ ├── RenderObjects │ │ ├── BSP │ │ │ ├── BSPFace.ts │ │ │ ├── BSPMesh.ts │ │ │ ├── BSPModel.ts │ │ │ └── Tree.ts │ │ │ │ ├── BSPLeaf.ts │ │ │ │ ├── BSPNode.ts │ │ │ │ ├── BSPTree.ts │ │ │ │ └── IBSPTree.ts │ │ ├── BSPResourceManager.ts │ │ ├── IRenderable.ts │ │ └── RenderObject.ts │ ├── Shaders │ │ ├── LayoutLocations.ts │ │ ├── Shader.ts │ │ ├── ShaderSource.ts │ │ └── UniformLocations.ts │ └── Textures │ │ ├── Texture.ts │ │ └── TextureDictionary.ts ├── Structs │ └── Vertex.ts ├── Utils │ ├── AddRange.ts │ ├── KeyPress.ts │ ├── LZMA │ │ └── lzma.ts │ ├── LimitAngle.ts │ ├── MeshFactory.ts │ ├── WrapAngle.ts │ └── ZipArray.ts └── main.ts ├── tsconfig.json ├── tsconfig.watch.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.js.map 3 | 4 | .tmp/ 5 | node_modules/ 6 | 7 | #Allow the final bundled file to be included 8 | !WebBSP.js -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "attach", 10 | "name": "Attach to Chrome", 11 | "port": 3000, 12 | "webRoot": "${workspaceFolder}" 13 | }, 14 | { 15 | "type": "chrome", 16 | "request": "launch", 17 | "name": "Launch Chrome against localhost", 18 | "sourceMaps": true, 19 | "url": "http://localhost:8080", 20 | "webRoot": "${workspaceFolder}" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "typescript", 8 | "tsconfig": "tsconfig.json", 9 | "problemMatcher": [ 10 | "$tsc" 11 | ] 12 | }, 13 | { 14 | "type": "gulp", 15 | "task": "watch", 16 | "problemMatcher": [ 17 | "$gulp-tsc" 18 | ] 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /BRICK/brickwall001c.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Exactol/WebGL-BSP/b199eb9a1419b5a3a47091e63ae48c63958a18e9/BRICK/brickwall001c.tga -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGL-BSP 2 | A 3D Source Engine BSP renderer 3 | 4 | Currently can display faces and displacements with estimated colors using reflectivity data. 5 | 6 | ![faces](https://i.imgur.com/ssqsHpv.png) 7 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require("gulp"), 2 | browserify = require("browserify"), 3 | source = require("vinyl-source-stream"), 4 | buffer = require("vinyl-buffer"), 5 | tsc = require("gulp-typescript"), 6 | sourcemaps = require("gulp-sourcemaps"), 7 | uglify = require("gulp-uglify-es").default, 8 | runSequence = require("run-sequence").use(gulp), 9 | clean = require("gulp-clean"), 10 | gutil = require("gulp-util"), 11 | plumber = require("gulp-plumber") 12 | debug = require("gulp-debug") 13 | print = require("gulp-print").default 14 | watchify = require("watchify") 15 | watch = require("gulp-watch") 16 | browserSync = require("browser-sync"); 17 | 18 | 19 | const path = { 20 | src: "src/**/*.ts", 21 | typeSrc: "@types/**/*.ts", 22 | jsDest: ".tmp/", 23 | finalDest: "dist/" 24 | 25 | } 26 | const tsProject = tsc.createProject("tsconfig.json"); 27 | const tsWatchProject = tsc.createProject("tsconfig.watch.json"); 28 | 29 | const libraryName = "WebBSP"; 30 | 31 | var bundler = watchify(browserify({ 32 | debug: true, 33 | standalone: libraryName, 34 | detectGlobals: true, 35 | cache: {}, 36 | packageCache: {}, 37 | fullPaths: true 38 | })); 39 | 40 | bundler.on("update", watchBundleUglify); 41 | 42 | gulp.task("build", function() { 43 | return gulp.src([path.src, path.typeSrc]) 44 | .pipe(tsProject()) 45 | .on('error', function (err) { gutil.log(gutil.colors.red('[Error]'), err.toString()); }) 46 | .pipe(gulp.dest(path.jsDest)); 47 | }); 48 | 49 | gulp.task("bundle-uglify", () => { 50 | var mainJsFile = path.jsDest + "/main.js"; 51 | var outputFileName = libraryName + ".js"; 52 | 53 | var bundler = browserify({ 54 | debug: true, 55 | standalone: libraryName, 56 | detectGlobals: true, 57 | }); 58 | 59 | return bundler.add(mainJsFile) 60 | .bundle() 61 | .pipe(source(outputFileName)) 62 | .pipe(buffer()) 63 | .pipe(sourcemaps.init({loadMaps: true})) 64 | .pipe(uglify()) 65 | .on('error', function (err) { gutil.log(gutil.colors.red('[Error]'), err.toString()); }) 66 | .pipe(sourcemaps.write("./")) 67 | .pipe(gulp.dest(path.finalDest)); 68 | }); 69 | 70 | function watchBundleUglify() { 71 | var mainJsFile = path.jsDest + "/main.js"; 72 | var outputFileName = libraryName + ".js"; 73 | 74 | return bundler.add(mainJsFile) 75 | .bundle() 76 | .pipe(source(outputFileName)) 77 | .pipe(buffer()) 78 | .pipe(sourcemaps.init({loadMaps: true})) 79 | .pipe(uglify()) 80 | .on('error', function (err) { gutil.log(gutil.colors.red('[Error]'), err.toString()); }) 81 | .pipe(sourcemaps.write("./")) 82 | .pipe(gulp.dest(path.finalDest)); 83 | } 84 | 85 | gulp.task("clean", function() { 86 | return ( 87 | gulp.src([path.jsDest, path.finalDest], {read: false}) 88 | .pipe(clean())); 89 | }); 90 | 91 | gulp.task("build-and-bundle", function() { 92 | runSequence("clean", "build", "bundle-uglify") 93 | }); 94 | 95 | gulp.task("watch", () => { 96 | browserSync.init({ 97 | server: { 98 | baseDir: "./", 99 | injectChanges: true, 100 | } 101 | }); 102 | watch(path.src, (file) => { 103 | // for some reason gulp dest will not send output file to it's subdirectory, so it needs to be calculated 104 | const subPath = getSubDirPath(file.path); 105 | console.log(subPath); 106 | gulp.src(file.path) 107 | .pipe(plumber()) 108 | .pipe(debug({title: "Compiling:"})) 109 | .pipe(tsWatchProject()) 110 | .on("error", function (err) { gutil.log(gutil.colors.red('[Error]'), err.toString()); }) 111 | .pipe(gulp.dest(path.jsDest + subPath)) 112 | .pipe(print((filepath) => `Compiled: ${filepath}`)); 113 | watchBundleUglify(); 114 | browserSync.reload(); 115 | }); 116 | }); 117 | 118 | function getSubDirPath(fullFilePath) { 119 | let fileSplit = fullFilePath.split("\\"); 120 | 121 | // remove file name 122 | fileSplit.pop(); 123 | 124 | // reconstruct string and split on \src\ 125 | fileSplit = fileSplit.join("\\").split("\\src\\"); 126 | 127 | if (fileSplit.length == 1) { 128 | // when file is not in subdirectory return nothing 129 | return ""; 130 | } 131 | return fileSplit.pop(); 132 | } 133 | 134 | gulp.task("default", ["build-and-bundle"]); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebGL BSP 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /main.css: -------------------------------------------------------------------------------- 1 | canvas { 2 | width: 100vw; 3 | height: 100vh; 4 | padding: 0%; 5 | margin: 0%; 6 | display: block; 7 | } 8 | 9 | body, html { 10 | overflow: hidden; 11 | margin: 0%; 12 | padding: 0%; 13 | } 14 | 15 | #sidebar { 16 | background-color: #343F49; 17 | /* background: linear-gradient(to bottom, #495867 0%,#495867 100%); */ 18 | position: fixed; 19 | display: none; 20 | z-index: 1; 21 | top: 0; 22 | left: 0; 23 | overflow-x: hidden; 24 | padding-top: 10px; 25 | height: 100%; 26 | width: 70px; 27 | border-style: solid; 28 | border-width: 0px; 29 | border-right-width: 2px; 30 | border-color: #FFA49E; 31 | box-shadow: 0 0 10px #FFA49E; 32 | vertical-align: middle; 33 | align-items: center; 34 | } 35 | 36 | /* #sidebar: */ 37 | 38 | button { 39 | border: none; 40 | background-color: transparent; 41 | outline: none; 42 | overflow: hidden; 43 | color: rgb(255, 85, 73); 44 | cursor: pointer; 45 | padding: 10px; 46 | font-size: 30px 47 | } 48 | 49 | button:hover { 50 | color: rgb(115, 188, 252) 51 | } 52 | 53 | #sidebarBtn { 54 | padding-top: 10px; 55 | width: 70px; 56 | position: absolute; 57 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "version": "", 4 | "dependencies": { 5 | "@types/gl-matrix": "^2.4.0", 6 | "@types/webgl-ext": "0.0.29", 7 | "@types/webgl2": "0.0.4", 8 | "gl-matrix": "^2.6.1", 9 | "gulp-print": "^5.0.0", 10 | "watchify": "^3.11.0" 11 | }, 12 | "devDependencies": { 13 | "browser-sync": "^2.24.5", 14 | "browserify": "^16.2.2", 15 | "gulp": "^3.9.1", 16 | "gulp-clean": "^0.4.0", 17 | "gulp-debug": "^4.0.0", 18 | "gulp-plumber": "^1.2.0", 19 | "gulp-sourcemaps": "^2.6.4", 20 | "gulp-typescript": "^5.0.0-alpha.3", 21 | "gulp-uglify": "^3.0.0", 22 | "gulp-uglify-es": "^1.0.4", 23 | "gulp-watch": "^5.0.0", 24 | "run-sequence": "^2.2.1", 25 | "typescript": "^2.9.2", 26 | "vinyl-buffer": "^1.0.1", 27 | "vinyl-source-stream": "^2.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/BSP/BSP.ts: -------------------------------------------------------------------------------- 1 | import { BinaryReader } from "./Utils/BinaryReader"; 2 | import { LumpType } from "./Lumps/LumpType"; 3 | import { LumpHeader } from "./Lumps/LumpHeader"; 4 | import { Lump } from "./Lumps/Lump"; 5 | import { EdgeLump } from "./Lumps/EdgeLump"; 6 | import { GenericLump } from "./Lumps/GenericLump"; 7 | import { VertexLump } from "./Lumps/VertexLump"; 8 | import { PlaneLump } from "./Lumps/PlaneLump"; 9 | import { SurfEdgeLump } from "./Lumps/SurfEdgeLump"; 10 | import { FaceLump } from "./Lumps/FaceLump"; 11 | import { OriginalFaceLump } from "./Lumps/OriginalFaceLump"; 12 | import { BrushLump } from "./Lumps/BrushLump"; 13 | import { BrushSideLump } from "./Lumps/BrushSideLump"; 14 | import { NodeLump } from "./Lumps/NodeLump"; 15 | import { LeafLump } from "./Lumps/LeafLump"; 16 | import { LeafFaceLump } from "./Lumps/LeafFaceLump"; 17 | import { LeafBrushLump } from "./Lumps/LeafBrushLump"; 18 | import { TexInfoLump } from "./Lumps/TexInfoLump"; 19 | import { TexDataLump } from "./Lumps/TexDataLump"; 20 | import { TexDataStringTableLump } from "./Lumps/TexDataStringTableLump"; 21 | import { TexDataStringDataLump } from "./Lumps/TexDataStringDataLump"; 22 | import { ModelLump } from "./Lumps/ModelLump"; 23 | import { EntityLump } from "./Lumps/EntityLump"; 24 | import { GameLump } from "./Lumps/GameLump"; 25 | import { CubemapLump } from "./Lumps/CubemapLump"; 26 | import { LightingLump } from "./Lumps/LightingLump"; 27 | import { LeafAmbientLightingLump } from "./Lumps/LeafAmbientLightingLump"; 28 | import { LeafAmbientIndexLump } from "./Lumps/LeafAmbientIndexLump"; 29 | import { LeafAmbientIndexHdrLump } from "./Lumps/LeafAmbientIndexHDRLump"; 30 | import { DispInfoLump } from "./Lumps/DispInfoLump"; 31 | import { DispVertLump } from "./Lumps/DispVertLump"; 32 | import { DispTrisLump } from "./Lumps/DispTrisLump"; 33 | 34 | // https://developer.valvesoftware.com/wiki/Source_BSP_File_Format 35 | // todo look into 36 | // tslint:disable-next-line:max-line-length 37 | // https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/mp/src/utils/common/bsplib.cpp 38 | export class BSP { 39 | public fileData: ArrayBuffer; 40 | public reader: BinaryReader; 41 | 42 | public ident: string; 43 | public version: number; 44 | 45 | public headerLumps: LumpHeader[] = []; 46 | public lumps: { [lumpType: number]: Lump } = {}; 47 | 48 | constructor(bspData: ArrayBuffer) { 49 | this.fileData = bspData; 50 | this.reader = new BinaryReader(this.fileData); 51 | 52 | // read header 53 | console.log("--Header--"); 54 | 55 | // read ident 56 | this.ident = ""; 57 | for (let i = 0; i < 4; i++) { 58 | this.ident += this.reader.readChar(); 59 | } 60 | console.log("Ident: " + this.ident); 61 | 62 | // read version 63 | this.version = this.reader.readInt32(); 64 | console.log("Version: " + this.version); 65 | 66 | // read header lumps. Each lump is 16 bytes 67 | console.log("--Reading Header Lumps--"); 68 | for (let i = 0; i < 64; i++) { 69 | // use helper function to convert LumpEnum to lump class type 70 | const lumpType = this.getLumpType(i); 71 | this.lumps[i] = new lumpType(new LumpHeader(i, this.reader.readBytes(16)), this.fileData); 72 | } 73 | } 74 | 75 | public readLump(lumpType: LumpType) { 76 | // check if lump has already been read 77 | if (this.lumps[lumpType].initialized) { 78 | return this.lumps[lumpType]; 79 | } 80 | 81 | const lump = this.lumps[lumpType]; 82 | 83 | // make sure lumps that this lump depends on are read first 84 | // TODO: not really implemented yet 85 | // lump.lumpDependencies.forEach((lumpDependency) => { 86 | // this.getLump(lumpDependency); 87 | // }); 88 | lump.read(); 89 | return lump; 90 | } 91 | 92 | public printLumps() { 93 | for (const lump in this.lumps) { 94 | if (this.lumps.hasOwnProperty(lump)) { 95 | const element = this.lumps[lump]; 96 | if (element.initialized) { 97 | console.log(element.toString()); 98 | } else { 99 | this.readLump(Number.parseInt(lump)); 100 | console.log(element); 101 | } 102 | } 103 | } 104 | } 105 | 106 | private getLumpType(lumpType: LumpType) { 107 | switch (lumpType) { 108 | case LumpType.Entities: 109 | return EntityLump; 110 | case LumpType.Planes: 111 | return PlaneLump; 112 | case LumpType.TexData: 113 | return TexDataLump; 114 | case LumpType.Vertexes: 115 | return VertexLump; 116 | // case LumpType.Visibility: TODO: parse 117 | case LumpType.Nodes: 118 | return NodeLump; 119 | case LumpType.TexInfo: 120 | return TexInfoLump; 121 | case LumpType.Faces: 122 | return FaceLump; 123 | case LumpType.Lighting: 124 | return LightingLump; 125 | // case LumpType.Occlusion: 126 | case LumpType.Leafs: 127 | return LeafLump; 128 | // case LumpType.FaceIds: 129 | case LumpType.Edges: 130 | return EdgeLump; 131 | case LumpType.SurfEdges: 132 | return SurfEdgeLump; 133 | case LumpType.Models: 134 | return ModelLump; 135 | // case LumpType.WorldLights: 136 | case LumpType.LeafFaces: 137 | return LeafFaceLump; 138 | case LumpType.LeafBrushes: 139 | return LeafBrushLump; 140 | case LumpType.Brushes: 141 | return BrushLump; 142 | case LumpType.BrushSides: 143 | return BrushSideLump; 144 | // case LumpType.Areas: 145 | // case LumpType.AreaPortals: 146 | // case LumpType.Portals: 147 | // case LumpType.Unused0: 148 | // case LumpType.PropCollsion: 149 | // case LumpType.Clusters: 150 | // case LumpType.Unused1: 151 | // case LumpType.PropHulls: 152 | // case LumpType.PortalVerts: 153 | // case LumpType.Unused2: 154 | // case LumpType.PropHullVerts: 155 | // case LumpType.ClusterPortals: 156 | // case LumpType.Unused3: 157 | // case LumpType.PropTris: 158 | case LumpType.DispInfo: 159 | return DispInfoLump; 160 | case LumpType.OriginalFaces: 161 | return OriginalFaceLump; 162 | // case LumpType.PhysDisp: 163 | // case LumpType.PhysCollide: 164 | // case LumpType.VertNormals: 165 | // case LumpType.VertNormalIndices: 166 | // case LumpType.DispLightmapAlphas: 167 | case LumpType.DispVerts: 168 | return DispVertLump; 169 | // case LumpType.DispLightmapSamplePositions: 170 | case LumpType.Game: 171 | return GameLump; 172 | // case LumpType.LeafWaterData: 173 | // case LumpType.Primitives: 174 | // case LumpType.PrimVerts: 175 | // case LumpType.PrimIndices: 176 | // case LumpType.Pakfile: 177 | // case LumpType.ClipPortalVerts: 178 | case LumpType.Cubemaps: 179 | return CubemapLump; 180 | case LumpType.TexDataStringData: 181 | return TexDataStringDataLump; 182 | case LumpType.TexDataStringTable: 183 | return TexDataStringTableLump; 184 | // case LumpType.Overlays: 185 | // case LumpType.LeafMinDistToWater: 186 | // case LumpType.FaceMacroTextureInfo: 187 | case LumpType.DispTris: 188 | return DispTrisLump; 189 | // case LumpType.PhysCollideSurface: 190 | // case LumpType.PropBlob: 191 | // case LumpType.WaterOverlays: 192 | // case LumpType.LightmapPages: 193 | case LumpType.LeafAmbientIndexHdr: 194 | return LeafAmbientIndexHdrLump; 195 | // case LumpType.LightmapPageInfos: 196 | case LumpType.LeafAmbientIndex: 197 | return LeafAmbientIndexLump; 198 | // case LumpType.LightingHdr: 199 | // case LumpType.WorldLightsHdr: 200 | // case LumpType.LeafAmbientLightingHdr: 201 | case LumpType.LeafAmbientLighting: 202 | return LeafAmbientLightingLump; 203 | // case LumpType.XZipPackfile: 204 | // case LumpType.FacesHdr: 205 | // case LumpType.MapFlags: 206 | // case LumpType.OverlayFades: 207 | // case LumpType.OverlaySystemLevels: 208 | // case LumpType.PhysLevel: 209 | // case LumpType.DispMultiBlend: 210 | 211 | default: 212 | return GenericLump; 213 | } 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /src/BSP/Lumps/BrushLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { Plane } from "../Structs/Plane"; 3 | import { LumpHeader } from "./LumpHeader"; 4 | import { LumpType } from "./LumpType"; 5 | import { BinaryReader, FLOAT_SIZE, INT_32_SIZE } from "../Utils/BinaryReader"; 6 | import { vec3 } from "gl-matrix"; 7 | import { Face } from "../Structs/Face"; 8 | import { Brush } from "../Structs/Brush"; 9 | 10 | export class BrushLump extends Lump { 11 | public brushes: Brush[] = []; 12 | 13 | constructor(header: LumpHeader, lumpData) { 14 | super(LumpType.Brushes, header, lumpData); 15 | } 16 | 17 | public read() { 18 | const reader = new BinaryReader(this.data); 19 | 20 | // each face is 12 bytes long 21 | for (let i = 0; i < this.header.lumpLength; i += INT_32_SIZE * 3) { 22 | this.brushes.push(new Brush( 23 | reader.readInt32(), 24 | reader.readInt32(), 25 | reader.readInt32() 26 | )); 27 | } 28 | 29 | this.initialized = true; 30 | } 31 | 32 | public toString(): string { 33 | let retStr = super.toString(); 34 | retStr += `\n${LumpType[this.lumpType]}: \n${this.brushes.join("\n")}`; 35 | 36 | return retStr; 37 | } 38 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/BrushSideLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { Plane } from "../Structs/Plane"; 3 | import { LumpHeader } from "./LumpHeader"; 4 | import { LumpType } from "./LumpType"; 5 | import { BinaryReader, FLOAT_SIZE, INT_32_SIZE, INT_16_SIZE, UINT_16_SIZE } from "../Utils/BinaryReader"; 6 | import { vec3 } from "gl-matrix"; 7 | import { Face } from "../Structs/Face"; 8 | import { Brush } from "../Structs/Brush"; 9 | import { BrushSide } from "../Structs/BrushSide"; 10 | 11 | export class BrushSideLump extends Lump { 12 | public brushes: BrushSide[] = []; 13 | 14 | constructor(header: LumpHeader, lumpData) { 15 | super(LumpType.BrushSides, header, lumpData); 16 | } 17 | 18 | public read() { 19 | const reader = new BinaryReader(this.data); 20 | 21 | // each face is 12 bytes long 22 | for (let i = 0; i < this.header.lumpLength; i += INT_16_SIZE * 3 + UINT_16_SIZE) { 23 | this.brushes.push(new BrushSide( 24 | reader.readUInt16(), 25 | reader.readInt16(), 26 | reader.readInt16(), 27 | reader.readInt16() 28 | )); 29 | } 30 | 31 | this.initialized = true; 32 | } 33 | 34 | public toString(): string { 35 | let retStr = super.toString(); 36 | retStr += `\n${LumpType[this.lumpType]}: \n${this.brushes.join("\n")}`; 37 | 38 | return retStr; 39 | } 40 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/CubemapLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader} from "../Utils/BinaryReader"; 5 | import { Face } from "../Structs/Face"; 6 | import { Cubemap } from "../Structs/Cubemap"; 7 | 8 | export class CubemapLump extends Lump { 9 | public cubemaps: Cubemap[] = []; 10 | 11 | constructor(header: LumpHeader, lumpData) { 12 | super(LumpType.Cubemaps, header, lumpData); 13 | } 14 | 15 | public read() { 16 | const reader = new BinaryReader(this.data); 17 | 18 | // each cubemap is 16 bytes long 19 | for (let i = 0; i < this.header.lumpLength; i += 16) { 20 | this.cubemaps.push(new Cubemap( 21 | [reader.readInt32(), reader.readInt32(), reader.readInt32()], 22 | reader.readInt32() 23 | )); 24 | } 25 | 26 | this.initialized = true; 27 | } 28 | 29 | public toString(): string { 30 | let retStr = super.toString(); 31 | retStr += `\n${LumpType[this.lumpType]}: \n${this.cubemaps.join("\n")}`; 32 | 33 | return retStr; 34 | } 35 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/DispInfoLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, SeekOrigin } from "../Utils/BinaryReader"; 5 | import { DispInfo } from "../Structs/DispInfo"; 6 | import { vec3 } from "gl-matrix"; 7 | import { DispNeighbor } from "../Structs/DispNeighbor"; 8 | import { DispSubNeighbor } from "../Structs/DispSubNeighbor"; 9 | import { DispCornerNeighbors } from "../Structs/DispCornerNeighbor"; 10 | 11 | export class DispInfoLump extends Lump { 12 | public dispInfos: DispInfo[] = []; 13 | 14 | constructor(header: LumpHeader, lumpData) { 15 | super(LumpType.DispInfo, header, lumpData); 16 | } 17 | 18 | public read() { 19 | const reader = new BinaryReader(this.data); 20 | const DISPINFO_SIZE = 176; 21 | 22 | // each dispInfo is 176 bytes long 23 | for (let i = 0; i < this.header.lumpLength; i += DISPINFO_SIZE) { 24 | const startPos = vec3.fromValues(reader.readFloat(), reader.readFloat(), reader.readFloat()); 25 | const dispVertStart = reader.readInt32(); 26 | const dispTriStart = reader.readInt32(); 27 | const power = reader.readInt32(); 28 | const minTess = reader.readInt32(); 29 | const smoothingAngle = reader.readFloat(); 30 | const contents = reader.readInt32(); 31 | const mapFace = reader.readUInt16(); 32 | const lightmapAlphaStart = reader.readInt32(); 33 | const lightmapSamplePositionStart = reader.readInt32(); 34 | 35 | const edgeNeighbors: DispNeighbor[] = []; 36 | for (let j = 0; j < 4; j++) { 37 | const subNeighbors: DispSubNeighbor[] = []; 38 | 39 | for (let l = 0; l < 2; l++) { 40 | const iNeighbor = reader.readUInt16(); 41 | const neighborOrientation = reader.readUInt8(); 42 | const span = reader.readUInt8(); 43 | reader.seek(1, SeekOrigin.Current); 44 | 45 | const neighborSpan = reader.readUInt8(); 46 | subNeighbors.push(new DispSubNeighbor( 47 | iNeighbor, 48 | neighborOrientation, 49 | span, 50 | neighborSpan 51 | )); 52 | } 53 | edgeNeighbors.push(new DispNeighbor(subNeighbors)); 54 | } 55 | 56 | const cornerNeighbors: DispCornerNeighbors[] = []; 57 | for (let k = 0; k < 4; k++) { 58 | cornerNeighbors.push(new DispCornerNeighbors( 59 | [reader.readUInt16(), reader.readUInt16(), reader.readUInt16(), reader.readUInt16()], 60 | reader.readUInt8() 61 | )); 62 | } 63 | 64 | reader.seek(6, SeekOrigin.Current); 65 | const allowedVerts: number[] = []; 66 | for (let z = 0; z < 10; z++) { 67 | allowedVerts.push(reader.readUInt32()); 68 | } 69 | 70 | this.dispInfos.push(new DispInfo( 71 | startPos, 72 | dispVertStart, 73 | dispTriStart, 74 | power, 75 | minTess, 76 | smoothingAngle, 77 | contents, 78 | mapFace, 79 | lightmapAlphaStart, 80 | lightmapSamplePositionStart, 81 | edgeNeighbors, 82 | cornerNeighbors, 83 | allowedVerts 84 | )); 85 | } 86 | this.initialized = true; 87 | } 88 | 89 | public toString(): string { 90 | let retStr = super.toString(); 91 | retStr += `\n${LumpType[this.lumpType]}: \n${this.dispInfos.join("\n")}`; 92 | 93 | return retStr; 94 | } 95 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/DispTrisLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { BinaryReader, FLOAT_SIZE, UINT_16_SIZE } from "../Utils/BinaryReader"; 4 | import { LumpType } from "./LumpType"; 5 | import { DispVert } from "../Structs/DispVert"; 6 | import { vec3 } from "gl-matrix"; 7 | import { DispTags } from "../Structs/Enums"; 8 | 9 | export class DispTrisLump extends Lump { 10 | public dispTris: DispTags[] = []; 11 | 12 | constructor(header: LumpHeader, lumpData) { 13 | super(LumpType.DispTris, header, lumpData); 14 | } 15 | 16 | public read() { 17 | const reader = new BinaryReader(this.data); 18 | 19 | for (let i = 0; i < this.header.lumpLength; i += UINT_16_SIZE) { 20 | this.dispTris.push(reader.readUInt16()); 21 | } 22 | 23 | this.initialized = true; 24 | } 25 | 26 | public toString(): string { 27 | let retStr = super.toString(); 28 | 29 | retStr += `\n${LumpType[this.lumpType]}: [${this.dispTris.join(", ")}]`; 30 | 31 | return retStr; 32 | } 33 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/DispVertLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { BinaryReader, FLOAT_SIZE, UINT_16_SIZE } from "../Utils/BinaryReader"; 4 | import { LumpType } from "./LumpType"; 5 | import { DispVert } from "../Structs/DispVert"; 6 | import { vec3 } from "gl-matrix"; 7 | 8 | export class DispVertLump extends Lump { 9 | public dispVerts: DispVert[] = []; 10 | 11 | constructor(header: LumpHeader, lumpData) { 12 | super(LumpType.DispVerts, header, lumpData); 13 | } 14 | 15 | public read() { 16 | const reader = new BinaryReader(this.data); 17 | 18 | for (let i = 0; i < this.header.lumpLength; i += FLOAT_SIZE * 5) { 19 | this.dispVerts.push(new DispVert( 20 | vec3.fromValues(reader.readFloat(), reader.readFloat(), reader.readFloat()), 21 | reader.readFloat(), 22 | reader.readFloat() 23 | )); 24 | } 25 | 26 | this.initialized = true; 27 | } 28 | 29 | public toString(): string { 30 | let retStr = super.toString(); 31 | 32 | retStr += `\n${LumpType[this.lumpType]}: ${this.dispVerts.join("\n")}`; 33 | 34 | return retStr; 35 | } 36 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/EdgeLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { Edge } from "../Structs/Edge"; 4 | import { BinaryReader, FLOAT_SIZE, UINT_16_SIZE } from "../Utils/BinaryReader"; 5 | import { LumpType } from "./LumpType"; 6 | 7 | export class EdgeLump extends Lump { 8 | public edges: Edge[] = []; 9 | 10 | constructor(header: LumpHeader, lumpData) { 11 | super(LumpType.Edges, header, lumpData); 12 | } 13 | 14 | public read() { 15 | const reader = new BinaryReader(this.data); 16 | 17 | for (let i = 0; i < this.header.lumpLength; i += UINT_16_SIZE * 2) { 18 | this.edges.push(new Edge(reader.readUInt16(), reader.readUInt16())); 19 | } 20 | 21 | this.initialized = true; 22 | } 23 | 24 | public toString(): string { 25 | let retStr = super.toString(); 26 | 27 | retStr += `\n${LumpType[this.lumpType]}: [${this.edges.join(", ")}]`; 28 | 29 | return retStr; 30 | } 31 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/EntityLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { Plane } from "../Structs/Plane"; 3 | import { LumpHeader } from "./LumpHeader"; 4 | import { LumpType } from "./LumpType"; 5 | import { BinaryReader, FLOAT_SIZE, INT_32_SIZE } from "../Utils/BinaryReader"; 6 | import { vec3 } from "gl-matrix"; 7 | import { Face } from "../Structs/Face"; 8 | 9 | export class EntityLump extends Lump { 10 | public textBuffer: string = ""; 11 | 12 | constructor(header: LumpHeader, lumpData) { 13 | super(LumpType.Entities, header, lumpData); 14 | } 15 | 16 | public read() { 17 | const reader = new BinaryReader(this.data); 18 | 19 | let tmpStr = reader.readString(false); 20 | 21 | while (tmpStr != null) { 22 | this.textBuffer += tmpStr; 23 | tmpStr = reader.readString(false); 24 | } 25 | 26 | this.initialized = true; 27 | } 28 | 29 | public toString(): string { 30 | let retStr = super.toString(); 31 | retStr += `\nTextBuffer: \n${this.textBuffer}`; 32 | 33 | return retStr; 34 | } 35 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/FaceLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader} from "../Utils/BinaryReader"; 5 | import { Face } from "../Structs/Face"; 6 | 7 | export class FaceLump extends Lump { 8 | public faces: Face[] = []; 9 | 10 | constructor(header: LumpHeader, lumpData) { 11 | super(LumpType.Faces, header, lumpData); 12 | } 13 | 14 | public read() { 15 | const reader = new BinaryReader(this.data); 16 | 17 | // each face is 56 bytes long 18 | for (let i = 0; i < this.header.lumpLength; i += 56) { 19 | this.faces.push(new Face( 20 | reader.readInt16(), 21 | reader.readInt8(), 22 | reader.readInt8(), 23 | reader.readInt32(), 24 | reader.readInt16(), 25 | reader.readInt16(), 26 | reader.readInt16(), 27 | reader.readInt16(), 28 | reader.readBytes(4), 29 | reader.readInt32(), 30 | reader.readFloat(), 31 | [reader.readInt32(), reader.readInt32()], 32 | [reader.readInt32(), reader.readInt32()], 33 | reader.readInt32(), 34 | reader.readUInt16(), 35 | reader.readUInt16(), 36 | reader.readUInt32(), 37 | )); 38 | } 39 | 40 | this.initialized = true; 41 | } 42 | 43 | public toString(): string { 44 | let retStr = super.toString(); 45 | retStr += `\n${LumpType[this.lumpType]}: \n${this.faces.join("\n")}`; 46 | 47 | return retStr; 48 | } 49 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/GameLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, INT_32_SIZE, UINT_16_SIZE} from "../Utils/BinaryReader"; 5 | import { Face } from "../Structs/Face"; 6 | import { GameLumpStruct } from "../Structs/GameLumpStruct"; 7 | 8 | export class GameLump extends Lump { 9 | public lumpCount: number = 0; 10 | public lumps: GameLumpStruct[] = []; 11 | 12 | constructor(header: LumpHeader, lumpData) { 13 | super(LumpType.Game, header, lumpData); 14 | } 15 | 16 | public read() { 17 | const reader = new BinaryReader(this.data); 18 | 19 | this.lumpCount = reader.readInt32(); 20 | 21 | // each face is 56 bytes long 22 | for (let i = 0; i < this.lumpCount; i++) { 23 | this.lumps.push(new GameLumpStruct( 24 | reader.readInt32(), 25 | reader.readUInt16(), 26 | reader.readUInt16(), 27 | reader.readInt32(), 28 | reader.readInt32() 29 | )); 30 | } 31 | 32 | this.initialized = true; 33 | } 34 | 35 | public toString(): string { 36 | let retStr = super.toString(); 37 | retStr += `\nLump Count: ${this.lumpCount} 38 | ${LumpType[this.lumpType]}: \n${this.lumps.join("\n")}`; 39 | 40 | return retStr; 41 | } 42 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/GenericLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | 5 | export class GenericLump extends Lump { 6 | constructor(headerLump: LumpHeader, lumpData: ArrayBuffer) { 7 | super(LumpType.Generic, headerLump, lumpData); 8 | } 9 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LeafAmbientIndexHDRLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, UINT_16_SIZE} from "../Utils/BinaryReader"; 5 | import { LeafAmbientIndex } from "../Structs/LeafAmbientIndex"; 6 | 7 | // version 20+. todo limit reading depending on version 8 | // identical to LeafAmbientIndex lump 9 | export class LeafAmbientIndexHdrLump extends Lump { 10 | public leafAmbientIndex: LeafAmbientIndex[] = []; 11 | 12 | constructor(header: LumpHeader, lumpData) { 13 | super(LumpType.LeafAmbientIndexHdr, header, lumpData); 14 | } 15 | 16 | public read() { 17 | const reader = new BinaryReader(this.data); 18 | 19 | // each leafAmbientLightIndex is 4 bytes long 20 | for (let i = 0; i < this.header.lumpLength; i += UINT_16_SIZE * 2) { 21 | this.leafAmbientIndex.push( new LeafAmbientIndex( 22 | reader.readUInt16(), 23 | reader.readUInt16() 24 | )); 25 | } 26 | 27 | this.initialized = true; 28 | } 29 | 30 | public toString(): string { 31 | let retStr = super.toString(); 32 | retStr += `\n${LumpType[this.lumpType]}: \n${this.leafAmbientIndex.join("\n\n")}`; 33 | 34 | return retStr; 35 | } 36 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LeafAmbientIndexLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, UINT_16_SIZE} from "../Utils/BinaryReader"; 5 | import { LeafAmbientIndex } from "../Structs/LeafAmbientIndex"; 6 | 7 | // version 20+. todo limit reading depending on version 8 | export class LeafAmbientIndexLump extends Lump { 9 | public leafAmbientIndex: LeafAmbientIndex[] = []; 10 | 11 | constructor(header: LumpHeader, lumpData) { 12 | super(LumpType.LeafAmbientIndex, header, lumpData); 13 | } 14 | 15 | public read() { 16 | const reader = new BinaryReader(this.data); 17 | 18 | // each leafAmbientLightIndex is 4 bytes long 19 | for (let i = 0; i < this.header.lumpLength; i += UINT_16_SIZE * 2) { 20 | this.leafAmbientIndex.push( new LeafAmbientIndex( 21 | reader.readUInt16(), 22 | reader.readUInt16() 23 | )); 24 | } 25 | 26 | this.initialized = true; 27 | } 28 | 29 | public toString(): string { 30 | let retStr = super.toString(); 31 | retStr += `\n${LumpType[this.lumpType]}: \n${this.leafAmbientIndex.join("\n\n")}`; 32 | 33 | return retStr; 34 | } 35 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LeafAmbientLightingLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader} from "../Utils/BinaryReader"; 5 | import { Leaf } from "../Structs/Leaf"; 6 | import { ColorRGBExp32 } from "../Structs/ColorRGBExp32"; 7 | import { LeafAmbientLighting } from "../Structs/LeafAmbientLighting"; 8 | import { CompressedLightCube } from "../Structs/CompressedLightCube"; 9 | 10 | // version 20+. todo limit reading depending on version 11 | export class LeafAmbientLightingLump extends Lump { 12 | public leafAmbientLighting: LeafAmbientLighting[] = []; 13 | 14 | constructor(header: LumpHeader, lumpData) { 15 | super(LumpType.LeafAmbientLighting, header, lumpData); 16 | } 17 | 18 | public read() { 19 | const reader = new BinaryReader(this.data); 20 | 21 | // each leafAmbientLight is 28 bytes long 22 | for (let i = 0; i < this.header.lumpLength; i += 28) { 23 | this.leafAmbientLighting.push( new LeafAmbientLighting ( 24 | new CompressedLightCube([ 25 | new ColorRGBExp32(reader.readUInt8(), reader.readUInt8(), reader.readUInt8(), reader.readInt8()), 26 | new ColorRGBExp32(reader.readUInt8(), reader.readUInt8(), reader.readUInt8(), reader.readInt8()), 27 | new ColorRGBExp32(reader.readUInt8(), reader.readUInt8(), reader.readUInt8(), reader.readInt8()), 28 | new ColorRGBExp32(reader.readUInt8(), reader.readUInt8(), reader.readUInt8(), reader.readInt8()), 29 | new ColorRGBExp32(reader.readUInt8(), reader.readUInt8(), reader.readUInt8(), reader.readInt8()), 30 | new ColorRGBExp32(reader.readUInt8(), reader.readUInt8(), reader.readUInt8(), reader.readInt8()), 31 | ]), 32 | reader.readUInt8(), 33 | reader.readUInt8(), 34 | reader.readUInt8(), 35 | reader.readUInt8(), 36 | )); 37 | } 38 | 39 | this.initialized = true; 40 | } 41 | 42 | public toString(): string { 43 | let retStr = super.toString(); 44 | retStr += `\n${LumpType[this.lumpType]}: \n${this.leafAmbientLighting.join("\n")}`; 45 | 46 | return retStr; 47 | } 48 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LeafBrushLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, INT_16_SIZE, UINT_16_SIZE} from "../Utils/BinaryReader"; 5 | 6 | export class LeafBrushLump extends Lump { 7 | public leafBrushes: number[] = []; 8 | 9 | constructor(header: LumpHeader, lumpData) { 10 | super(LumpType.LeafBrushes, header, lumpData); 11 | } 12 | 13 | public read() { 14 | const reader = new BinaryReader(this.data); 15 | 16 | for (let i = 0; i < this.header.lumpLength; i += UINT_16_SIZE) { 17 | this.leafBrushes.push(reader.readUInt16()); 18 | } 19 | 20 | this.initialized = true; 21 | } 22 | 23 | public toString(): string { 24 | let retStr = super.toString(); 25 | retStr += `\n${LumpType[this.lumpType]}: [${this.leafBrushes.join(", ")}]`; 26 | 27 | return retStr; 28 | } 29 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LeafFaceLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, UINT_16_SIZE} from "../Utils/BinaryReader"; 5 | 6 | export class LeafFaceLump extends Lump { 7 | public leafFaces: number[] = []; 8 | 9 | constructor(header: LumpHeader, lumpData) { 10 | super(LumpType.LeafFaces, header, lumpData); 11 | } 12 | 13 | public read() { 14 | const reader = new BinaryReader(this.data); 15 | 16 | for (let i = 0; i < this.header.lumpLength; i += UINT_16_SIZE) { 17 | this.leafFaces.push(reader.readUInt16()); 18 | } 19 | 20 | this.initialized = true; 21 | } 22 | 23 | public toString(): string { 24 | let retStr = super.toString(); 25 | retStr += `\n${LumpType[this.lumpType]}: [${this.leafFaces.join(", ")}]`; 26 | 27 | return retStr; 28 | } 29 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LeafLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader} from "../Utils/BinaryReader"; 5 | import { Leaf } from "../Structs/Leaf"; 6 | 7 | export class LeafLump extends Lump { 8 | public leaves: Leaf[] = []; 9 | 10 | constructor(header: LumpHeader, lumpData) { 11 | super(LumpType.Leafs, header, lumpData); 12 | } 13 | 14 | public read() { 15 | const reader = new BinaryReader(this.data); 16 | 17 | // each leaf is 56 bytes long 18 | // version 20 leaf is 32 bytes long 19 | // TODO: per version lump parsing 20 | for (let i = 0; i < this.header.lumpLength; i += 32) { 21 | const contents = reader.readInt32(); 22 | const cluster = reader.readInt16(); 23 | 24 | // todo ivestigate if this is the right way 25 | const bitfield = reader.readInt16(); 26 | const area = bitfield & 0xFF80 >> 7; 27 | const flags = bitfield & 0x007F; 28 | 29 | const mins: [number, number, number] = [reader.readInt16(), reader.readInt16(), reader.readInt16()]; 30 | const maxs: [number, number, number] = [reader.readInt16(), reader.readInt16(), reader.readInt16()]; 31 | 32 | const firstLeafFace = reader.readUInt16(); 33 | const numLeafFaces = reader.readUInt16(); 34 | 35 | const firstLeafBrush = reader.readUInt16(); 36 | const numLeafBrushes = reader.readUInt16(); 37 | 38 | const leafWaterDataID = reader.readInt16(); 39 | 40 | // padding? TODO: investigate why there are 2 missing bytes 41 | reader.readInt16(); 42 | 43 | this.leaves.push(new Leaf( 44 | contents, 45 | cluster, 46 | area, 47 | flags, 48 | mins, 49 | maxs, 50 | firstLeafFace, 51 | numLeafFaces, 52 | firstLeafBrush, 53 | numLeafBrushes, 54 | leafWaterDataID, 55 | )); 56 | } 57 | 58 | this.initialized = true; 59 | } 60 | 61 | public toString(): string { 62 | let retStr = super.toString(); 63 | retStr += `\n${LumpType[this.lumpType]}: \n${this.leaves.join("\n")}`; 64 | 65 | return retStr; 66 | } 67 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LightingLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader} from "../Utils/BinaryReader"; 5 | import { Leaf } from "../Structs/Leaf"; 6 | import { ColorRGBExp32 } from "../Structs/ColorRGBExp32"; 7 | 8 | export class LightingLump extends Lump { 9 | public lightmapSamples: ColorRGBExp32[] = []; 10 | 11 | constructor(header: LumpHeader, lumpData) { 12 | super(LumpType.Lighting, header, lumpData); 13 | } 14 | 15 | public read() { 16 | const reader = new BinaryReader(this.data); 17 | 18 | // each lightmap sample is 4 bytes long 19 | for (let i = 0; i < this.header.lumpLength; i += 4) { 20 | this.lightmapSamples.push(new ColorRGBExp32( 21 | reader.readUInt8(), 22 | reader.readUInt8(), 23 | reader.readUInt8(), 24 | reader.readInt8() 25 | )); 26 | } 27 | 28 | this.initialized = true; 29 | } 30 | 31 | public toString(): string { 32 | let retStr = super.toString(); 33 | retStr += `\n${LumpType[this.lumpType]}: \n${this.lightmapSamples.join("\n")}`; 34 | 35 | return retStr; 36 | } 37 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/Lump.ts: -------------------------------------------------------------------------------- 1 | import { LumpHeader } from "./LumpHeader"; 2 | import { LumpType } from "./LumpType"; 3 | import { BinaryReader, INT_32_SIZE } from "../Utils/BinaryReader"; 4 | 5 | // little-endian "LZMA" 6 | const LZMA_ID = 1095588428; 7 | 8 | export abstract class Lump { 9 | public lumpType: LumpType; 10 | public header: LumpHeader; 11 | public data: ArrayBuffer; 12 | public initialized = false; 13 | 14 | public lumpDependencies: LumpType[] = []; 15 | 16 | constructor(lType: LumpType, headerLump: LumpHeader, fileData: ArrayBuffer) { 17 | this.lumpType = lType; 18 | this.header = headerLump; 19 | this.data = fileData.slice(this.header.fileOffset, this.header.fileOffset + this.header.lumpLength); 20 | 21 | let potentiallyCompressed = false; 22 | // tslint:disable-next-line:prefer-for-of 23 | for (let i = 0; i < this.header.fourCC.length; i++) { 24 | const num = this.header.fourCC[i]; 25 | if (num !== 0) { 26 | potentiallyCompressed = true; 27 | break; 28 | } 29 | } 30 | 31 | if (potentiallyCompressed) { 32 | this.decompressLump(); 33 | } 34 | } 35 | 36 | public read() { 37 | 38 | } 39 | 40 | private decompressLump() { 41 | const reader = new BinaryReader(this.data); 42 | 43 | const ident = reader.readInt32(); 44 | 45 | // all lzma compressed lumps start with LZMA 46 | if (ident !== LZMA_ID) { 47 | return; 48 | } 49 | 50 | const actualSize = reader.readInt32(); 51 | const lzmaSize = reader.readInt32(); 52 | const properties: number[] = []; 53 | for (let i = 0; i < 5; i++) { 54 | properties.push(reader.readUInt8()); 55 | } 56 | 57 | throw Error("Decompressing lumps is not implemented."); 58 | } 59 | 60 | public toString(): string { 61 | return `${LumpType[this.lumpType]} 62 | ${this.header} 63 | Lump: 64 | Data: ${this.data} 65 | Initialized: ${this.initialized}`; 66 | } 67 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LumpHeader.ts: -------------------------------------------------------------------------------- 1 | import { LumpType } from "./LumpType"; 2 | import { BinaryReader, SeekOrigin } from "../Utils/BinaryReader"; 3 | 4 | 5 | 6 | export class LumpHeader { 7 | public fileOffset: number; // offset into file (bytes) 8 | public lumpLength!: number; // length of lump (bytes) 9 | public version!: number; // lump format version 10 | public fourCC!: Uint8Array; // lump ident code 11 | 12 | constructor(lType: LumpType, lumpData: Uint8Array) { 13 | // create a new reader at the offset of the lump location 14 | const reader = new BinaryReader(lumpData.buffer, lumpData.byteOffset); 15 | this.fileOffset = reader.readInt32(); 16 | this.lumpLength = reader.readInt32(); 17 | this.version = reader.readInt32(); 18 | 19 | // get 4 byte ident code 20 | this.fourCC = reader.readBytes(4); 21 | } 22 | 23 | public toString() { 24 | return `Offset: ${this.fileOffset} 25 | Length: ${this.lumpLength} 26 | Version: ${this.version} 27 | FourCC: ${this.fourCC}`; 28 | } 29 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/LumpType.ts: -------------------------------------------------------------------------------- 1 | export enum LumpType { 2 | Entities = 0, 3 | Planes = 1, 4 | TexData = 2, 5 | Vertexes = 3, 6 | Visibility = 4, 7 | Nodes = 5, 8 | TexInfo = 6, 9 | Faces = 7, 10 | Lighting = 8, 11 | Occlusion = 9, 12 | Leafs = 10, 13 | FaceIds = 11, 14 | Edges = 12, 15 | SurfEdges = 13, 16 | Models = 14, 17 | WorldLights = 15, 18 | LeafFaces = 16, 19 | LeafBrushes = 17, 20 | Brushes = 18, 21 | BrushSides = 19, 22 | Areas = 20, 23 | AreaPortals = 21, 24 | 25 | Portals = 22, // source 2004 26 | Unused0 = 22, // source 2007 27 | PropCollsion = 22, // source 2009 28 | 29 | Clusters = 23, // source 2004 30 | Unused1 = 23, // source 2007 31 | PropHulls = 23, // source 2009 32 | 33 | PortalVerts = 24, // source 2004 34 | Unused2 = 24, // source 2007 35 | PropHullVerts = 24, // source 2009 36 | 37 | ClusterPortals = 25, // source 2004 38 | Unused3 = 25, // source 2007 39 | PropTris = 25, // source 2009 40 | 41 | DispInfo = 26, 42 | OriginalFaces = 27, 43 | PhysDisp = 28, 44 | PhysCollide = 29, 45 | VertNormals = 30, 46 | VertNormalIndices = 31, 47 | DispLightmapAlphas = 32, 48 | DispVerts = 33, 49 | DispLightmapSamplePositions = 34, 50 | Game = 35, 51 | LeafWaterData = 36, 52 | Primitives = 37, 53 | PrimVerts = 38, 54 | PrimIndices = 39, 55 | Pakfile = 40, 56 | ClipPortalVerts = 41, 57 | Cubemaps = 42, 58 | TexDataStringData = 43, 59 | TexDataStringTable = 44, 60 | Overlays = 45, 61 | LeafMinDistToWater = 46, 62 | FaceMacroTextureInfo = 47, 63 | DispTris = 48, 64 | 65 | PhysCollideSurface = 49, // source 2004 66 | PropBlob = 49, // source 2007 67 | 68 | WaterOverlays = 50, 69 | 70 | LightmapPages = 51, // source 2006 71 | LeafAmbientIndexHdr = 51, // source 2007 72 | 73 | LightmapPageInfos = 52, // source 2006 74 | LeafAmbientIndex = 52, // source 2007 75 | 76 | LightingHdr = 53, 77 | WorldLightsHdr = 54, 78 | LeafAmbientLightingHdr = 55, 79 | LeafAmbientLighting = 56, 80 | XZipPackfile = 57, 81 | FacesHdr = 58, 82 | MapFlags = 59, 83 | OverlayFades = 60, 84 | OverlaySystemLevels = 61, 85 | PhysLevel = 62, 86 | DispMultiBlend = 63, 87 | 88 | Generic = -1 // placeholder lump 89 | } 90 | -------------------------------------------------------------------------------- /src/BSP/Lumps/ModelLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader} from "../Utils/BinaryReader"; 5 | import { Leaf } from "../Structs/Leaf"; 6 | import { Model } from "../Structs/Model"; 7 | import { vec3 } from "gl-matrix"; 8 | 9 | export class ModelLump extends Lump { 10 | public models: Model[] = []; 11 | 12 | constructor(header: LumpHeader, lumpData) { 13 | super(LumpType.Models, header, lumpData); 14 | } 15 | 16 | public read() { 17 | const reader = new BinaryReader(this.data); 18 | 19 | // each model is 48 bytes long 20 | for (let i = 0; i < this.header.lumpLength; i += 48) { 21 | this.models.push(new Model( 22 | vec3.fromValues(reader.readFloat(), reader.readFloat(), reader.readFloat()), 23 | vec3.fromValues(reader.readFloat(), reader.readFloat(), reader.readFloat()), 24 | vec3.fromValues(reader.readFloat(), reader.readFloat(), reader.readFloat()), 25 | reader.readInt32(), 26 | reader.readInt32(), 27 | reader.readInt32() 28 | )); 29 | } 30 | 31 | this.initialized = true; 32 | } 33 | 34 | public toString(): string { 35 | let retStr = super.toString(); 36 | retStr += `\n${LumpType[this.lumpType]}: \n${this.models.join("\n")}`; 37 | 38 | return retStr; 39 | } 40 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/NodeLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader} from "../Utils/BinaryReader"; 5 | import { Node } from "../Structs/Node"; 6 | 7 | export class NodeLump extends Lump { 8 | public nodes: Node[] = []; 9 | 10 | constructor(header: LumpHeader, lumpData) { 11 | super(LumpType.Nodes, header, lumpData); 12 | } 13 | 14 | public read() { 15 | const reader = new BinaryReader(this.data); 16 | 17 | // each node is 32 bytes long 18 | for (let i = 0; i < this.header.lumpLength; i += 32) { 19 | this.nodes.push(new Node( 20 | reader.readInt32(), 21 | [reader.readInt32(), reader.readInt32()], 22 | [reader.readInt16(), reader.readInt16(), reader.readInt16()], 23 | [reader.readInt16(), reader.readInt16(), reader.readInt16()], 24 | reader.readUInt16(), 25 | reader.readUInt16(), 26 | reader.readInt16(), 27 | reader.readInt16() 28 | )); 29 | } 30 | 31 | this.initialized = true; 32 | } 33 | 34 | public toString(): string { 35 | let retStr = super.toString(); 36 | retStr += `\n${LumpType[this.lumpType]}: \n${this.nodes.join("\n")}`; 37 | 38 | return retStr; 39 | } 40 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/OriginalFaceLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { Plane } from "../Structs/Plane"; 3 | import { LumpHeader } from "./LumpHeader"; 4 | import { LumpType } from "./LumpType"; 5 | import { BinaryReader, FLOAT_SIZE, INT_32_SIZE } from "../Utils/BinaryReader"; 6 | import { vec3 } from "gl-matrix"; 7 | import { Face } from "../Structs/Face"; 8 | 9 | export class OriginalFaceLump extends Lump { 10 | // identical to the face lump, so the Face class can be reused 11 | public faces: Face[] = []; 12 | 13 | constructor(header: LumpHeader, lumpData) { 14 | super(LumpType.OriginalFaces, header, lumpData); 15 | } 16 | 17 | public read() { 18 | const reader = new BinaryReader(this.data); 19 | 20 | // each face is 56 bytes long 21 | for (let i = 0; i < this.header.lumpLength; i += 56) { 22 | this.faces.push(new Face( 23 | reader.readInt16(), 24 | reader.readInt8(), 25 | reader.readInt8(), 26 | reader.readInt32(), 27 | reader.readInt16(), 28 | reader.readInt16(), 29 | reader.readInt16(), 30 | reader.readInt16(), 31 | reader.readBytes(4), 32 | reader.readInt32(), 33 | reader.readFloat(), 34 | [reader.readInt32(), reader.readInt32()], 35 | [reader.readInt32(), reader.readInt32()], 36 | reader.readInt32(), 37 | reader.readUInt16(), 38 | reader.readUInt16(), 39 | reader.readUInt32(), 40 | )); 41 | } 42 | 43 | this.initialized = true; 44 | } 45 | 46 | public toString(): string { 47 | let retStr = super.toString(); 48 | retStr += `\n${LumpType[this.lumpType]}: \n${this.faces.join("\n")}`; 49 | 50 | return retStr; 51 | } 52 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/PlaneLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { Plane } from "../Structs/Plane"; 3 | import { LumpHeader } from "./LumpHeader"; 4 | import { LumpType } from "./LumpType"; 5 | import { BinaryReader, FLOAT_SIZE, INT_32_SIZE } from "../Utils/BinaryReader"; 6 | import { vec3 } from "gl-matrix"; 7 | 8 | export class PlaneLump extends Lump { 9 | public planes: Plane[] = []; 10 | 11 | constructor(header: LumpHeader, lumpData) { 12 | super(LumpType.Planes, header, lumpData); 13 | } 14 | 15 | public read() { 16 | const reader = new BinaryReader(this.data); 17 | 18 | // each plane is 20 bytes long 19 | for (let i = 0; i < this.header.lumpLength; i += FLOAT_SIZE * 4 + INT_32_SIZE) { 20 | this.planes.push( 21 | new Plane( 22 | vec3.fromValues(reader.readFloat(), reader.readFloat(), reader.readFloat()), 23 | reader.readFloat(), 24 | reader.readInt32() 25 | )); 26 | } 27 | 28 | this.initialized = true; 29 | } 30 | 31 | public toString(): string { 32 | let retStr = super.toString(); 33 | retStr += `\n${LumpType[this.lumpType]}: \n${this.planes.join("\n")}`; 34 | 35 | return retStr; 36 | } 37 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/SurfEdgeLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { Plane } from "../Structs/Plane"; 3 | import { LumpHeader } from "./LumpHeader"; 4 | import { LumpType } from "./LumpType"; 5 | import { BinaryReader, FLOAT_SIZE, INT_32_SIZE } from "../Utils/BinaryReader"; 6 | import { vec3 } from "gl-matrix"; 7 | 8 | export class SurfEdgeLump extends Lump { 9 | public surfEdges: number[] = []; 10 | 11 | constructor(header: LumpHeader, lumpData) { 12 | super(LumpType.SurfEdges, header, lumpData); 13 | } 14 | 15 | public read() { 16 | const reader = new BinaryReader(this.data); 17 | 18 | // each surfedge is 4 bytes long 19 | for (let i = 0; i < this.header.lumpLength; i += INT_32_SIZE) { 20 | this.surfEdges.push(reader.readInt32()); 21 | } 22 | 23 | this.initialized = true; 24 | } 25 | 26 | public toString(): string { 27 | let retStr = super.toString(); 28 | retStr += `\n${LumpType[this.lumpType]}: [${this.surfEdges.join(", ")}]`; 29 | 30 | return retStr; 31 | } 32 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/TexDataLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, FLOAT_SIZE, INT_32_SIZE} from "../Utils/BinaryReader"; 5 | import { TexInfo } from "../Structs/TexInfo"; 6 | import { TexData } from "../Structs/TexData"; 7 | import { vec3 } from "gl-matrix"; 8 | 9 | export class TexDataLump extends Lump { 10 | public texDatas: TexData[] = []; 11 | 12 | constructor(header: LumpHeader, lumpData) { 13 | super(LumpType.TexData, header, lumpData); 14 | } 15 | 16 | public read() { 17 | const reader = new BinaryReader(this.data); 18 | 19 | // each texData is 32 bytes long 20 | for (let i = 0; i < this.header.lumpLength; i += FLOAT_SIZE * 3 + INT_32_SIZE * 5) { 21 | this.texDatas.push(new TexData( 22 | vec3.fromValues(reader.readFloat(), reader.readFloat(), reader.readFloat()), 23 | reader.readInt32(), 24 | reader.readInt32(), 25 | reader.readInt32(), 26 | reader.readInt32(), 27 | reader.readInt32() 28 | )); 29 | } 30 | 31 | this.initialized = true; 32 | } 33 | 34 | public toString(): string { 35 | let retStr = super.toString(); 36 | retStr += `\n${LumpType[this.lumpType]}: ${this.texDatas.join("\n")}`; 37 | 38 | return retStr; 39 | } 40 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/TexDataStringDataLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, INT_16_SIZE, INT_32_SIZE, CHAR_SIZE} from "../Utils/BinaryReader"; 5 | import { TexInfo } from "../Structs/TexInfo"; 6 | 7 | export class TexDataStringDataLump extends Lump { 8 | public stringData: string[] = []; 9 | 10 | constructor(header: LumpHeader, lumpData) { 11 | super(LumpType.TexDataStringData, header, lumpData); 12 | } 13 | 14 | public read() { 15 | const reader = new BinaryReader(this.data); 16 | let tmpStr = reader.readString(false); 17 | 18 | while (tmpStr != null) { 19 | this.stringData.push(tmpStr); 20 | tmpStr = reader.readString(false); 21 | } 22 | 23 | this.initialized = true; 24 | } 25 | 26 | public toString(): string { 27 | let retStr = super.toString(); 28 | retStr += `\n${LumpType[this.lumpType]}: [${this.stringData.join(", ")}]`; 29 | 30 | return retStr; 31 | } 32 | 33 | public readStringAtOffset(offset: number){ 34 | const reader = new BinaryReader(this.data, offset); 35 | return reader.readString(); 36 | } 37 | 38 | private readStrings(reader: BinaryReader) { 39 | let retStr = ""; 40 | let nextChar = reader.readChar(); 41 | while (nextChar !== "\0" && reader.position + CHAR_SIZE <= reader.length) { 42 | retStr += nextChar; 43 | nextChar = reader.readChar(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/TexDataStringTableLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, INT_16_SIZE, INT_32_SIZE} from "../Utils/BinaryReader"; 5 | import { TexInfo } from "../Structs/TexInfo"; 6 | 7 | export class TexDataStringTableLump extends Lump { 8 | public texDataTable: number[] = []; 9 | 10 | constructor(header: LumpHeader, lumpData) { 11 | super(LumpType.TexDataStringTable, header, lumpData); 12 | } 13 | 14 | public read() { 15 | const reader = new BinaryReader(this.data); 16 | 17 | for (let i = 0; i < this.header.lumpLength; i += INT_32_SIZE) { 18 | this.texDataTable.push(reader.readInt32()); 19 | } 20 | 21 | this.initialized = true; 22 | } 23 | 24 | public toString(): string { 25 | let retStr = super.toString(); 26 | retStr += `\n${LumpType[this.lumpType]}: [${this.texDataTable.join(", ")}]`; 27 | 28 | return retStr; 29 | } 30 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/TexInfoLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { LumpType } from "./LumpType"; 4 | import { BinaryReader, INT_16_SIZE} from "../Utils/BinaryReader"; 5 | import { TexInfo } from "../Structs/TexInfo"; 6 | 7 | export class TexInfoLump extends Lump { 8 | public texInfos: TexInfo[] = []; 9 | 10 | constructor(header: LumpHeader, lumpData) { 11 | super(LumpType.TexInfo, header, lumpData); 12 | } 13 | 14 | public read() { 15 | const reader = new BinaryReader(this.data); 16 | 17 | // each texInfo is 72 bytes long 18 | for (let i = 0; i < this.header.lumpLength; i += 72) { 19 | this.texInfos.push(new TexInfo( 20 | [[reader.readFloat(), reader.readFloat(), reader.readFloat(), reader.readFloat()], 21 | [reader.readFloat(), reader.readFloat(), reader.readFloat(), reader.readFloat()]], 22 | [[reader.readFloat(), reader.readFloat(), reader.readFloat(), reader.readFloat()], 23 | [reader.readFloat(), reader.readFloat(), reader.readFloat(), reader.readFloat()]], 24 | reader.readInt32(), 25 | reader.readInt32() 26 | )); 27 | } 28 | 29 | this.initialized = true; 30 | } 31 | 32 | public toString(): string { 33 | let retStr = super.toString(); 34 | retStr += `\n${LumpType[this.lumpType]}: ${this.texInfos.join("\n")}`; 35 | 36 | return retStr; 37 | } 38 | } -------------------------------------------------------------------------------- /src/BSP/Lumps/VertexLump.ts: -------------------------------------------------------------------------------- 1 | import { Lump } from "./Lump"; 2 | import { LumpHeader } from "./LumpHeader"; 3 | import { Edge } from "../Structs/Edge"; 4 | import { BinaryReader, FLOAT_SIZE } from "../Utils/BinaryReader"; 5 | import { LumpType } from "./LumpType"; 6 | import { vec3 } from "gl-matrix"; 7 | 8 | export class VertexLump extends Lump { 9 | public vertexes!: vec3[]; 10 | 11 | constructor(header: LumpHeader, lumpData) { 12 | super(LumpType.Vertexes, header, lumpData); 13 | } 14 | 15 | public read() { 16 | const reader = new BinaryReader(this.data); 17 | this.vertexes = []; 18 | 19 | // each vertex is 3 floats, 12 bytes total 20 | for (let i = 0; i < this.header.lumpLength; i += FLOAT_SIZE * 3) { 21 | this.vertexes.push(vec3.fromValues(reader.readFloat(), reader.readFloat(), reader.readFloat())); 22 | } 23 | 24 | this.initialized = true; 25 | } 26 | 27 | public toString(): string { 28 | let retStr = super.toString(); 29 | 30 | retStr += `\n${LumpType[this.lumpType]}: [(${this.vertexes.join("), (")}]`; 31 | 32 | return retStr; 33 | } 34 | } -------------------------------------------------------------------------------- /src/BSP/Structs/Brush.ts: -------------------------------------------------------------------------------- 1 | import { BrushContent } from "./Enums"; 2 | 3 | export class Brush { 4 | public firstSide: number; 5 | public numSides: number; 6 | public contents: BrushContent; 7 | 8 | constructor(firstSide: number, numSides: number, contents: BrushContent) { 9 | this.firstSide = firstSide; 10 | this.numSides = numSides; 11 | this.contents = contents; 12 | } 13 | 14 | public toString() { 15 | return `FirstSide: ${this.firstSide} 16 | NumSides: ${this.numSides} 17 | Contents: ${BrushContent[this.contents]}`; // todo investigate brush contents showing up as undefined 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/BSP/Structs/BrushSide.ts: -------------------------------------------------------------------------------- 1 | export class BrushSide { 2 | public planeNum: number; 3 | public texInfo: number; 4 | public dispInfo: number; 5 | public bevel: number; 6 | 7 | constructor(planeNum: number, texInfo: number, dispInfo: number, bevel: number) { 8 | this.planeNum = planeNum; 9 | this.texInfo = texInfo; 10 | this.dispInfo = dispInfo; 11 | this.bevel = bevel; 12 | } 13 | 14 | public toString() { 15 | return `PlaneNum: ${this.planeNum} 16 | texInfo: ${this.texInfo} 17 | dispInfo: ${this.dispInfo} 18 | bevel: ${this.bevel}`; 19 | } 20 | } -------------------------------------------------------------------------------- /src/BSP/Structs/ColorRGBExp32.ts: -------------------------------------------------------------------------------- 1 | export class ColorRGBExp32 { 2 | public r: number; 3 | public g: number; 4 | public b: number; 5 | public e: number; 6 | 7 | public constructor(r: number, g: number, b: number, e: number) { 8 | this.r = r; 9 | this.g = g; 10 | this.b = b; 11 | this.e = e; 12 | } 13 | 14 | public toString() { 15 | return `RGBE: [${this.r}, ${this.g}, ${this.b}] ${this.e}`; 16 | } 17 | } -------------------------------------------------------------------------------- /src/BSP/Structs/CompressedLightCube.ts: -------------------------------------------------------------------------------- 1 | import { ColorRGBExp32 } from "./ColorRGBExp32"; 2 | 3 | export class CompressedLightCube { 4 | public color: [ColorRGBExp32, ColorRGBExp32, ColorRGBExp32, ColorRGBExp32, ColorRGBExp32, ColorRGBExp32]; 5 | 6 | constructor(color: [ColorRGBExp32, ColorRGBExp32, ColorRGBExp32, ColorRGBExp32, ColorRGBExp32, ColorRGBExp32]) { 7 | this.color = color; 8 | } 9 | 10 | public toString() { 11 | return `Color: 12 | ${this.color.join("\n ")}`; 13 | } 14 | } -------------------------------------------------------------------------------- /src/BSP/Structs/Cubemap.ts: -------------------------------------------------------------------------------- 1 | export class Cubemap { 2 | public origin: [number, number, number]; 3 | public size: number; 4 | 5 | constructor(origin: [number, number, number], size: number) { 6 | this.origin = origin; 7 | this.size = size; 8 | } 9 | 10 | public toString() { 11 | return `Origin: [${this.origin}] 12 | Size: ${this.size}`; 13 | } 14 | } -------------------------------------------------------------------------------- /src/BSP/Structs/DispCornerNeighbor.ts: -------------------------------------------------------------------------------- 1 | import { DispSubNeighbor } from "./DispSubNeighbor"; 2 | import { DispNeighbor } from "./DispNeighbor"; 3 | 4 | const MAX_DISP_CORNER_NEIGHBORS = 4; 5 | 6 | export class DispCornerNeighbors { 7 | public neighbors: [number, number, number, number]; // indices of neighbors 8 | public nNeighbors: number; 9 | 10 | constructor(neighbors: [number, number, number, number], nNeighbors: number) { 11 | this.neighbors = neighbors; 12 | this.nNeighbors = nNeighbors; 13 | } 14 | 15 | public toString() { 16 | return `neighbors: [${this.neighbors[0]}, 17 | ${this.neighbors[1]}, 18 | ${this.neighbors[2]}, 19 | ${this.neighbors[3]}] 20 | numNeighbors: ${this.nNeighbors} 21 | `; 22 | } 23 | } -------------------------------------------------------------------------------- /src/BSP/Structs/DispInfo.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from "gl-matrix"; 2 | import { DispNeighbor } from "./DispNeighbor"; 3 | import { DispSubNeighbor } from "./DispSubNeighbor"; 4 | import { DispCornerNeighbors } from "./DispCornerNeighbor"; 5 | 6 | export class DispInfo { 7 | public startPosition: vec3; 8 | public dispVertStart: number; // index into LUMP_DISP_VERTS 9 | public dispTriStart: number; // index into LUMP_DISP_TRIS 10 | public power: number; 11 | public minTess: number; // minimum tesselation allowed 12 | public smoothingAngle: number; 13 | public contents: number; 14 | public mapFace: number; 15 | public lightmapAlphaStart: number; 16 | public lightmapSamplePositionStart: number; 17 | public edgeNeighbors: DispNeighbor[]; 18 | public cornerNeighbors: DispCornerNeighbors[]; 19 | public allowedVerts: number[]; 20 | 21 | constructor(startPosition: vec3, dispVertStart: number, dispTriStart: number, power: number, minTess: number, 22 | smoothingAngle: number, contents: number, mapFace: number, lightmapAlphaStart: number, 23 | lightmapSamplePositionStart: number, edgeNeighbors: DispNeighbor[], cornerNeighbors: DispCornerNeighbors[], 24 | allowedVerts: number[]) { 25 | this.startPosition = startPosition; 26 | this.dispVertStart = dispVertStart; 27 | this.dispTriStart = dispTriStart; 28 | this.power = power; 29 | this.minTess = minTess; 30 | this.smoothingAngle = smoothingAngle; 31 | this.contents = contents; 32 | this.mapFace = mapFace; 33 | this.lightmapAlphaStart = lightmapAlphaStart; 34 | this.lightmapSamplePositionStart = lightmapSamplePositionStart; 35 | this.edgeNeighbors = edgeNeighbors; 36 | this.cornerNeighbors = cornerNeighbors; 37 | this.allowedVerts = allowedVerts; 38 | } 39 | 40 | // tslint:disable-next-line:max-line-length 41 | // from https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/public/bspfile.h#L648https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/public/bspfile.h#L648 42 | public numVerts(): number { 43 | return ((1 << this.power) + 1) * ((1 << this.power) + 1); 44 | } 45 | 46 | public numTris(): number { 47 | return (1 << this.power) * (1 << this.power) * 2; 48 | } 49 | 50 | public numRows(): number { 51 | return 2 ** this.power + 1; 52 | } 53 | 54 | public toString() { 55 | return `StartPos: ${this.startPosition} 56 | DispVertStart: ${this.dispVertStart} 57 | DispTriStart: ${this.dispTriStart} 58 | Power: ${this.power} 59 | Min Tess: ${this.minTess} 60 | Smoothing Angle: ${this.smoothingAngle} 61 | Contents: ${this.contents} 62 | Map Face: ${this.mapFace} 63 | LightmapAlphaStart: ${this.lightmapAlphaStart} 64 | LightmapSamplePositionStart: ${this.lightmapSamplePositionStart} 65 | CornerNeighbors: 66 | ${[this.cornerNeighbors.join("\n ")]} 67 | EdgeNeighbors: 68 | ${[this.edgeNeighbors.join("\n ")]} 69 | AllowedVerts: ${this.allowedVerts} 70 | `; 71 | } 72 | } -------------------------------------------------------------------------------- /src/BSP/Structs/DispNeighbor.ts: -------------------------------------------------------------------------------- 1 | import { DispSubNeighbor } from "./DispSubNeighbor"; 2 | 3 | export class DispNeighbor { 4 | public subNeighbors: DispSubNeighbor[]; 5 | 6 | constructor(subNeighbors: DispSubNeighbor[]) { 7 | this.subNeighbors = subNeighbors; 8 | } 9 | 10 | public toString() { 11 | return `[ 12 | ${this.subNeighbors[0]}, 13 | ${this.subNeighbors[1]} 14 | ]`; 15 | } 16 | } -------------------------------------------------------------------------------- /src/BSP/Structs/DispSubNeighbor.ts: -------------------------------------------------------------------------------- 1 | import { DispOrientation, DispSpan } from "./Enums"; 2 | 3 | export class DispSubNeighbor { 4 | public iNeighbor: number; // index into ddispinfos 5 | public neighborOrientation: DispOrientation; 6 | public span: DispSpan; 7 | public neighborSpan: DispSpan; 8 | 9 | constructor(iNeighbor: number, neighborOrientation: DispOrientation, span: DispSpan, neighborSpan: DispSpan) { 10 | this.iNeighbor = iNeighbor; 11 | this.neighborOrientation = neighborOrientation; 12 | this.span = span; 13 | this.neighborSpan = neighborSpan; 14 | } 15 | 16 | public toString() { 17 | return `iNeighbor: ${this.iNeighbor} 18 | neighborOrientation: ${this.neighborOrientation} 19 | span: ${this.span} 20 | neighborSpan: ${this.neighborSpan} 21 | `; 22 | } 23 | 24 | // public isValid(): boolean { 25 | // return this.iNeighbor !== 0xFFFF; 26 | // } 27 | } -------------------------------------------------------------------------------- /src/BSP/Structs/DispVert.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from "gl-matrix"; 2 | 3 | export class DispVert { 4 | public vec: vec3; 5 | public dist: number; 6 | public alpha: number; 7 | 8 | constructor(vec: vec3, dist: number, alpha: number) { 9 | this.vec = vec; 10 | this.dist = dist; 11 | this.alpha = alpha; 12 | } 13 | 14 | public toString() { 15 | return `vec: ${this.vec} 16 | dist: ${this.dist} 17 | alpha: ${this.alpha} 18 | `; 19 | } 20 | } -------------------------------------------------------------------------------- /src/BSP/Structs/Edge.ts: -------------------------------------------------------------------------------- 1 | export class Edge { 2 | public vertexIndices: number[]; 3 | 4 | constructor(vertex1: number, vertex2: number) { 5 | this.vertexIndices = [vertex1, vertex2]; 6 | } 7 | 8 | // return flipped edge 9 | public reverse() { 10 | return new Edge(this.vertexIndices[1], this.vertexIndices[0]); 11 | } 12 | 13 | public getVertIndices(reverse = false) { 14 | if (reverse) { 15 | const reversedEdge = this.reverse(); 16 | return this.reverse().vertexIndices; 17 | } 18 | 19 | return this.vertexIndices; 20 | } 21 | 22 | public toString() { 23 | return `[${this.vertexIndices[0]}, ${this.vertexIndices[1]}]`; 24 | } 25 | } -------------------------------------------------------------------------------- /src/BSP/Structs/Enums.ts: -------------------------------------------------------------------------------- 1 | export enum BrushContent { 2 | EMPTY = 0, 3 | SOLID = 0x1, 4 | WINDOW = 0x2, 5 | AUX = 0x4, 6 | GRATE = 0x8, 7 | SLIME = 0x10, 8 | WATER = 0x20, 9 | MIST = 0x40, 10 | OPAQUE = 0x80, 11 | TEST_FOG_VOLUME = 0x100, 12 | UNUSED = 0x200, 13 | UNUSED6 = 0x400, 14 | TEAM1 = 0x800, 15 | TEAM2 = 0x1000, 16 | IGNORE_DRAW_OPAQUE = 0x2000, 17 | MOVEABLE = 0x4000, 18 | AREAPORTAL = 0x8000, 19 | PLAYERCLIP = 0x10000, 20 | MONSTERCLIP = 0x20000, 21 | CURRENT_0 = 0x40000, 22 | CURRENT_90 = 0x80000, 23 | CURRENT_180 = 0x100000, 24 | CURRENT_270 = 0x200000, 25 | CURRENT_UP = 0x400000, 26 | CURRENT_DOWN = 0x800000, 27 | ORIGIN = 0x1000000, 28 | MONSTER = 0x2000000, 29 | DEBRIS = 0x4000000, 30 | DETAIL = 0x8000000, 31 | TRANSLUCENT = 0x10000000, 32 | LADDER = 0x20000000, 33 | HITBOX = 0x40000000, 34 | } 35 | 36 | export enum SurfFlags { 37 | LIGHT = 0x1, 38 | SKY2D = 0x2, 39 | SKY = 0x4, 40 | WARP = 0x8, 41 | TRANS = 0x10, 42 | NO_PORTAL = 0x20, 43 | TRIGGER = 0x40, 44 | NODRAW = 0x80, 45 | HINT = 0x100, 46 | SKIP = 0x200, 47 | NO_LIGHT = 0x400, 48 | BUMP_LIGHT = 0x800, 49 | NO_SHADOWS = 0x1000, 50 | NO_DECALS = 0x2000, 51 | NO_CHOP = 0x4000, 52 | HITBOX = 0x8000, 53 | } 54 | 55 | // --------------------------------------------------------- 56 | // displacement enums 57 | // --------------------------------------------------------- 58 | 59 | // tslint:disable-next-line:max-line-length 60 | // https://github.com/ValveSoftware/source-sdk-2013/blob/0d8dceea4310fde5706b3ce1c70609d72a38efdf/sp/src/public/bspfile.h#L157 61 | export enum DispChildNode { 62 | CHILDNODE_UPPER_RIGHT = 0, 63 | CHILDNODE_UPPER_LEFT = 1, 64 | CHILDNODE_LOWER_LEFT = 2, 65 | CHILDNODE_LOWER_RIGHT = 3, 66 | } 67 | 68 | export enum DispCorner { 69 | CORNER_LOWER_LEFT = 0, 70 | CORNER_UPPER_LEFT = 1, 71 | CORNER_UPPER_RIGHT = 3, 72 | CORNER_LOWER_RIGHT = 2, 73 | } 74 | 75 | export enum DispNeighborEdge { 76 | NEIGHBOREDGE_LEFT = 0, 77 | NEIGHBOREDGE_TOP = 1, 78 | NEIGHBOREDGE_RIGHT = 2, 79 | NEIGHBOREDGE_BOTTOM = 3, 80 | } 81 | 82 | export enum DispSpan { 83 | CORNER_TO_CORNER = 0, 84 | CORNER_TO_MIDPOINT = 1, 85 | MIDPOINT_TO_CORNER = 2, 86 | } 87 | 88 | export enum DispOrientation { 89 | ORIENTATION_CCW_0 = 0, 90 | ORIENTATION_CCW_90 = 1, 91 | ORIENTATION_CCW_180 = 2, 92 | ORIENTATION_CCW_270 = 3, 93 | } 94 | 95 | export enum DispTags { 96 | DISPTRI_TAG_SURFACE = 0x1, 97 | DISPTRI_TAG_WALKABLE = 0x2, 98 | DISPTRI_TAG_BUILDABLE = 0x4, 99 | DISPTRI_FLAG_SURFPROP1 = 0x8, 100 | DISPTRI_FLAG_SURFPROP2 = 0x10, 101 | } 102 | 103 | // --------------------------------------------------------- -------------------------------------------------------------------------------- /src/BSP/Structs/Face.ts: -------------------------------------------------------------------------------- 1 | import { EdgeLump } from "../Lumps/EdgeLump"; 2 | import { VertexLump } from "../Lumps/VertexLump"; 3 | 4 | export class Face { 5 | public planeNum: number; 6 | public side: number; 7 | public onNode: number; 8 | public firstEdge: number; 9 | public numEdges: number; 10 | public texInfo: number; 11 | public dispInfo: number; 12 | public surfaceFogVolumeID: number; 13 | public style: Uint8Array; 14 | public lightOfs: number; 15 | public area: number; 16 | public lightmapTextureMinsInLuxels: [number, number]; 17 | public lightmapTextureSizeInLuxels: [number, number]; 18 | public origFace: number; 19 | public numPrims: number; 20 | public firstPrimID: number; 21 | public smoothingGroups: number; 22 | 23 | constructor(planeNum: number, side: number, onNode: number, firstEdge: number, numEdges: number, 24 | texInfo: number, dispInfo: number, surfFogVolID: number, style: Uint8Array, lightOfs: number, 25 | area: number, lightTexMin: [number, number], lightTexSize: [number, number], origFace: number, numPrims: number, 26 | firstPrimID: number, smoothingGroups: number) { 27 | this.planeNum = planeNum; 28 | this.side = side; 29 | this.onNode = onNode; 30 | this.firstEdge = firstEdge; 31 | this.numEdges = numEdges; 32 | this.texInfo = texInfo; 33 | this.dispInfo = dispInfo; 34 | this.surfaceFogVolumeID = surfFogVolID; 35 | this.style = style; 36 | this.lightOfs = lightOfs; 37 | this.area = area; 38 | this.lightmapTextureMinsInLuxels = lightTexMin; 39 | this.lightmapTextureSizeInLuxels = lightTexSize; 40 | this.origFace = origFace; 41 | this.numPrims = numPrims; 42 | this.firstPrimID = firstPrimID; 43 | this.smoothingGroups = smoothingGroups; 44 | } 45 | 46 | public toString() { 47 | return `PlaneNum: ${this.planeNum} 48 | Side: ${this.side} 49 | First Edge: ${this.firstEdge} 50 | Num Edges: ${this.numEdges} 51 | Smoothing Group: ${this.smoothingGroups} 52 | `; 53 | } 54 | } -------------------------------------------------------------------------------- /src/BSP/Structs/GameLumpStruct.ts: -------------------------------------------------------------------------------- 1 | export class GameLumpStruct { 2 | public id: number; 3 | public flags: number; 4 | public version: number; 5 | public fileOffset: number; 6 | public fileLength: number; 7 | 8 | constructor(id: number, flags: number, version: number, fileOffset: number, fileLength: number) { 9 | this.id = id; 10 | this.flags = flags; 11 | this.version = version; 12 | this.fileOffset = fileOffset; 13 | this.fileLength = fileLength; 14 | } 15 | 16 | public toString() { 17 | return `ID: ${this.id} 18 | Flags: ${this.flags} 19 | Version: ${this.version} 20 | FileOffset: ${this.fileOffset} 21 | FileLegnth: ${this.fileLength} 22 | `; 23 | } 24 | } -------------------------------------------------------------------------------- /src/BSP/Structs/Leaf.ts: -------------------------------------------------------------------------------- 1 | import { BrushContent } from "./Enums"; 2 | 3 | export class Leaf { 4 | public contents: BrushContent; 5 | public cluster: number; 6 | 7 | // area and flags share a 16 bit bitfield 8 | public area: number; // 9 bits 9 | public flags: number; // 7 bits 10 | 11 | public mins: [number, number, number]; 12 | public maxs: [number, number, number]; 13 | 14 | public firstLeafFace: number; 15 | public numLeafFaces: number; 16 | 17 | public firstLeafBrush: number; 18 | public numLeafBrushes: number; 19 | 20 | public leafWaterDataID: number; 21 | 22 | // todo. bsp 19 and below only 23 | // public ambientLightingData: CompressedLightCube 24 | // padding: number; 25 | 26 | public constructor(contents: BrushContent, cluster: number, area: number, flags: number, 27 | mins: [number, number, number], maxs: [number, number, number], firstLeafFace: number, numLeafFaces: number, 28 | firstLeafBrush: number, numLeafBrushes: number, leafWaterDataID: number) { 29 | 30 | this.contents = contents; 31 | this.cluster = cluster; 32 | this.area = area; 33 | this.flags = flags; 34 | this.mins = mins; 35 | this.maxs = maxs; 36 | this.firstLeafFace = firstLeafFace; 37 | this.numLeafFaces = numLeafFaces; 38 | this.firstLeafBrush = firstLeafBrush; 39 | this.numLeafBrushes = numLeafBrushes; 40 | this.leafWaterDataID = leafWaterDataID; 41 | } 42 | 43 | public toString() { 44 | return `Contents: ${this.contents} 45 | Cluster: ${this.cluster} 46 | Area: ${this.area} 47 | Flags: ${this.flags} 48 | Mins: ${this.mins} 49 | Maxs: ${this.maxs} 50 | FirstLeafFace: ${this.firstLeafFace} 51 | NumLeafFaces: ${this.numLeafFaces} 52 | FirstLeafBrush: ${this.firstLeafBrush} 53 | NumLeafBrushes: ${this.numLeafBrushes} 54 | LeafWaterDataID: ${this.leafWaterDataID} 55 | `; 56 | } 57 | } -------------------------------------------------------------------------------- /src/BSP/Structs/LeafAmbientIndex.ts: -------------------------------------------------------------------------------- 1 | export class LeafAmbientIndex { 2 | public ambientSampleCount: number; 3 | public firstAmbientSample: number; 4 | 5 | public constructor(ambientSampleCount: number, firstAmbientSample: number) { 6 | this.ambientSampleCount = ambientSampleCount; 7 | this.firstAmbientSample = firstAmbientSample; 8 | } 9 | 10 | public toString() { 11 | return `AmbientSampleCount: ${this.ambientSampleCount} 12 | FirstAmbientSample: ${this.firstAmbientSample}`; 13 | } 14 | } -------------------------------------------------------------------------------- /src/BSP/Structs/LeafAmbientLighting.ts: -------------------------------------------------------------------------------- 1 | import { CompressedLightCube } from "./CompressedLightCube"; 2 | 3 | export class LeafAmbientLighting { 4 | public cube: CompressedLightCube; 5 | public x: number; 6 | public y: number; 7 | public z: number; 8 | public pad: number; 9 | 10 | constructor(cube: CompressedLightCube, x: number, y: number, z: number, pad: number) { 11 | this.cube = cube; 12 | this.x = x; 13 | this.y = y; 14 | this.z = z; 15 | this.pad = pad; 16 | } 17 | 18 | public toString() { 19 | return `Cube: 20 | ${this.cube} 21 | XYZ: [${this.x}, ${this.y}, ${this.z}]`; 22 | } 23 | } -------------------------------------------------------------------------------- /src/BSP/Structs/Model.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from "gl-matrix"; 2 | 3 | export class Model { 4 | public mins: vec3; 5 | public maxs: vec3; 6 | public origin: vec3; 7 | public headNode: number; 8 | public firstFace: number; 9 | public numFaces: number; 10 | 11 | public constructor(mins: vec3, maxs: vec3, origin: vec3, headNode: number, firstFace: number, numFaces: number) { 12 | this.mins = mins; 13 | this.maxs = maxs; 14 | this.origin = origin; 15 | this.headNode = headNode; 16 | this.firstFace = firstFace; 17 | this.numFaces = numFaces; 18 | } 19 | 20 | public toString() { 21 | return `Mins: ${this.mins} 22 | Maxs: ${this.maxs} 23 | Origin: ${this.origin} 24 | HeadNode: ${this.headNode} 25 | FirstFace: ${this.firstFace} 26 | NumFaces: ${this.numFaces} 27 | `; 28 | } 29 | } -------------------------------------------------------------------------------- /src/BSP/Structs/Node.ts: -------------------------------------------------------------------------------- 1 | export class Node { 2 | public planeNum: number; 3 | public children: [number, number]; 4 | 5 | public mins: [number, number, number]; 6 | public maxs: [number, number, number]; 7 | 8 | public firstFace: number; 9 | public numFaces: number; 10 | 11 | public area: number; 12 | 13 | public padding: number; // padding to make it 32 bytes 14 | 15 | constructor(planeNum: number, children: [number, number], mins: [number, number, number], 16 | maxs: [number, number, number], firstFace: number, numFaces: number, area: number, padding: number) { 17 | this.planeNum = planeNum; 18 | this.children = children; 19 | this.mins = mins; 20 | this.maxs = maxs; 21 | this.firstFace = firstFace; 22 | this.numFaces = numFaces; 23 | this.area = area; 24 | this.padding = padding; 25 | } 26 | 27 | public toString() { 28 | return `Node: 29 | PlaneNum: ${this.planeNum} 30 | Children: ${this.children} 31 | Mins: [${this.mins.join(", ")}] 32 | Maxs: [${this.maxs.join(", ")}] 33 | FirstFace: ${this.firstFace} 34 | NumFaces: ${this.numFaces} 35 | Area: ${this.area} 36 | `; 37 | } 38 | } -------------------------------------------------------------------------------- /src/BSP/Structs/Plane.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from "gl-matrix"; 2 | 3 | export class Plane { 4 | public normal: vec3; // normal vector 5 | public dist: number; // distance from origin 6 | public type: number; // plane axis identifier 7 | 8 | constructor(norm: vec3, distance: number, planeType: number) { 9 | this.normal = norm; 10 | this.dist = distance; 11 | this.type = planeType; 12 | } 13 | 14 | public toString() { 15 | return `Normal: ${this.normal} 16 | Distance From Origin: ${this.dist} 17 | Type: ${this.type} 18 | `; 19 | } 20 | } -------------------------------------------------------------------------------- /src/BSP/Structs/TexData.ts: -------------------------------------------------------------------------------- 1 | import { vec3 } from "gl-matrix"; 2 | 3 | export class TexData { 4 | public reflectivity: vec3; 5 | public nameDataStringTableID: number; 6 | public width: number; 7 | public height: number; 8 | public viewWidth: number; 9 | public viewHeight: number; 10 | 11 | constructor(reflectivity: vec3, nameDataStringTableID: number, width: number, height: number, 12 | viewWidth: number, viewHeight: number) { 13 | this.reflectivity = reflectivity; 14 | this.nameDataStringTableID = nameDataStringTableID; 15 | this.width = width; 16 | this.height = height; 17 | this.viewWidth = viewWidth; 18 | this.viewHeight = viewHeight; 19 | } 20 | 21 | public toString() { 22 | return `Reflectivity: [${this.reflectivity}] 23 | NameDataStringTableID: ${this.nameDataStringTableID} 24 | Width: ${this.width} 25 | Height: ${this.height} 26 | ViewWidth: ${this.viewWidth} 27 | ViewHeight: ${this.viewHeight}`; 28 | } 29 | } -------------------------------------------------------------------------------- /src/BSP/Structs/TexInfo.ts: -------------------------------------------------------------------------------- 1 | import { SurfFlags } from "./Enums"; 2 | 3 | export class TexInfo { 4 | public textureVecs: number[][]; 5 | public lightmapVecs: number[][]; 6 | public flags: SurfFlags; 7 | public texData: number; 8 | 9 | constructor(texVecs: number[][], lightVecs: number[][], flags: number, texData: number) { 10 | this.textureVecs = texVecs; 11 | this.lightmapVecs = lightVecs; 12 | this.flags = flags; 13 | this.texData = texData; 14 | } 15 | 16 | public toString() { 17 | return `Texture Vecs: [${this.textureVecs.join("], [")}] 18 | Lightmap Vecs: [${this.lightmapVecs.join("], [")}] 19 | Flags: ${this.flags} 20 | TexData: ${this.texData}`; 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/BSP/Utils/BinaryReader.ts: -------------------------------------------------------------------------------- 1 | // A C# style Binary Reader 2 | 3 | export const INT_8_SIZE = 1; 4 | export const INT_16_SIZE = 2; 5 | export const INT_32_SIZE = 4; 6 | 7 | export const UINT_8_SIZE = 1; 8 | export const UINT_16_SIZE = 2; 9 | export const UINT_32_SIZE = 4; 10 | 11 | export const FLOAT_SIZE = 4; // float32 12 | export const DOUBLE_SIZE = 8; // float64 13 | 14 | export const BOOL_SIZE = 1; 15 | 16 | export const CHAR_SIZE = 1; 17 | 18 | export class BinaryReader { 19 | public position: number; 20 | public length: number; 21 | public isLittleEndian: boolean; 22 | private dataView: DataView; 23 | 24 | constructor(data: ArrayBuffer, offset = 0, endianness = Endianness.LittleEndian) { 25 | this.dataView = new DataView(data); 26 | if (endianness === Endianness.LittleEndian) { 27 | this.isLittleEndian = true; 28 | } else { 29 | this.isLittleEndian = false; 30 | } 31 | 32 | // start position at 0 by default 33 | this.position = offset; 34 | this.length = data.byteLength; 35 | } 36 | 37 | public readInt8(): number { 38 | if (this.position + INT_8_SIZE > this.length) { 39 | throw new Error("RangeError"); 40 | } 41 | const retVal = this.dataView.getInt8(this.position); 42 | 43 | // move position forward 44 | this.position += INT_8_SIZE; 45 | return retVal; 46 | } 47 | 48 | public readInt16(): number { 49 | if (this.position + INT_16_SIZE > this.length) { 50 | throw new Error("RangeError"); 51 | } 52 | const retVal = this.dataView.getInt16(this.position, this.isLittleEndian); 53 | 54 | // move position forward 55 | this.position += INT_16_SIZE; 56 | return retVal; 57 | } 58 | 59 | public readInt32(): number { 60 | if (this.position + INT_32_SIZE > this.length) { 61 | throw new Error("RangeError"); 62 | } 63 | const retVal = this.dataView.getInt32(this.position, this.isLittleEndian); 64 | 65 | // move position forward 66 | this.position += INT_32_SIZE; 67 | return retVal; 68 | } 69 | 70 | public readUInt8(): number { 71 | if (this.position + UINT_8_SIZE > this.length) { 72 | throw new Error("RangeError"); 73 | } 74 | const retVal = this.dataView.getUint8(this.position); 75 | 76 | // move position forward 77 | this.position += UINT_8_SIZE; 78 | return retVal; 79 | } 80 | 81 | public readUInt16(): number { 82 | if (this.position + UINT_16_SIZE > this.length) { 83 | throw new Error("RangeError"); 84 | } 85 | const retVal = this.dataView.getUint16(this.position, this.isLittleEndian); 86 | 87 | // move position forward 88 | this.position += UINT_16_SIZE; 89 | return retVal; 90 | } 91 | 92 | public readUInt32(): number { 93 | if (this.position + UINT_32_SIZE > this.length) { 94 | throw new Error("RangeError"); 95 | } 96 | const retVal = this.dataView.getUint16(this.position, this.isLittleEndian); 97 | 98 | // move position forward 99 | this.position += UINT_32_SIZE; 100 | return retVal; 101 | } 102 | 103 | public readFloat(): number { 104 | if (this.position + FLOAT_SIZE > this.length) { 105 | throw new Error("RangeError"); 106 | } 107 | const retVal = this.dataView.getFloat32(this.position, this.isLittleEndian); 108 | 109 | // move position forward 110 | this.position += FLOAT_SIZE; 111 | return retVal; 112 | } 113 | 114 | public readDouble(): number { 115 | if (this.position + DOUBLE_SIZE > this.length) { 116 | throw new Error("RangeError"); 117 | } 118 | // float 64 is a double 119 | const retVal = this.dataView.getFloat64(this.position, this.isLittleEndian); 120 | 121 | // move position forward 122 | this.position += DOUBLE_SIZE; 123 | return retVal; 124 | } 125 | 126 | public readBoolean(): boolean { 127 | if (this.position + BOOL_SIZE > this.length) { 128 | throw new Error("RangeError"); 129 | } 130 | // get bool as int 131 | const boolValInt = this.dataView.getInt8(this.position); 132 | 133 | // 0 = true, so if its not 0 it must be false 134 | const retVal = (boolValInt === 0); 135 | 136 | // move position forward 137 | this.position += BOOL_SIZE; 138 | return retVal; 139 | } 140 | 141 | public readBytes(numBytes: number): Uint8Array { 142 | if (this.position + numBytes > this.length) { 143 | throw new Error("RangeError"); 144 | } 145 | const retVal = new Uint8Array(this.dataView.buffer, this.position, numBytes); 146 | 147 | // move position forward 148 | this.position += numBytes; 149 | return retVal; 150 | } 151 | 152 | public readChar(): string { 153 | if (this.position + CHAR_SIZE > this.length) { 154 | throw new Error("RangeError"); 155 | } 156 | const retVal = String.fromCharCode(this.dataView.getInt8(this.position)); 157 | 158 | // move position forward 159 | this.position += CHAR_SIZE; 160 | return retVal; 161 | } 162 | 163 | // reads char, but doesn't move position forward 164 | public peekChar(): string { 165 | if (this.position + CHAR_SIZE > this.length) { 166 | throw new Error("RangeError"); 167 | } 168 | const retVal = String.fromCharCode(this.dataView.getInt8(this.position)); 169 | 170 | return retVal; 171 | } 172 | 173 | // reads null terminated string 174 | public readString(err = true): string | null { 175 | if (this.position + CHAR_SIZE > this.length) { 176 | // optional arg to either raise error or return null 177 | if (err) { 178 | throw new Error("RangeError"); 179 | } else { 180 | return null; 181 | } 182 | } 183 | 184 | let retStr = ""; 185 | let nextChar = this.readChar(); 186 | while (nextChar !== "\0" && this.position + CHAR_SIZE <= this.length) { 187 | retStr += nextChar; 188 | nextChar = this.readChar(); 189 | } 190 | 191 | return retStr; 192 | } 193 | 194 | public seek(numBytes: number, origin: SeekOrigin) { 195 | switch (origin) { 196 | case SeekOrigin.Beginning: 197 | if (numBytes > this.length) { 198 | throw new Error("RangeError"); 199 | } 200 | 201 | this.position = numBytes; 202 | break; 203 | 204 | case SeekOrigin.Current: 205 | if (this.position + numBytes > this.length) { 206 | throw new Error("RangeError"); 207 | } 208 | 209 | this.position += numBytes; 210 | break; 211 | 212 | case SeekOrigin.End: 213 | if (this.length + numBytes > this.length) { 214 | throw new Error("RangeError"); 215 | } 216 | this.position = this.length + numBytes; 217 | break; 218 | } 219 | } 220 | } 221 | 222 | export enum SeekOrigin { 223 | Current, 224 | Beginning, 225 | End, 226 | } 227 | 228 | export enum Endianness { 229 | LittleEndian, 230 | BigEndian, 231 | } -------------------------------------------------------------------------------- /src/KeyboardListener.ts: -------------------------------------------------------------------------------- 1 | import { ICamera, MoveDirection } from "./Rendering/Camera/ICamera"; 2 | import { EngineCore } from "./Rendering/EngineCore"; 3 | import { IEngineComponent } from "./Rendering/IEngineComponent"; 4 | import { Message, MessageType, MessagePriority } from "./Rendering/Messaging/Message"; 5 | import { MessageQueue } from "./Rendering/Messaging/MessageQueue"; 6 | import { Key, KeyPress, KeyModifier, KeyState } from "./Utils/KeyPress"; 7 | 8 | export class KeyboardListener implements IEngineComponent { 9 | public componentName = "KeyboardListener"; 10 | public listen = true; 11 | public zToggle = false; 12 | 13 | private wPressed = false; 14 | private aPressed = false; 15 | private sPressed = false; 16 | private dPressed = false; 17 | 18 | constructor(coreEngine: EngineCore) { 19 | window.addEventListener("keydown", (event) => { 20 | // use fallthrough cases to handle when shift is held 21 | switch (event.key) { 22 | case "W": 23 | case "w": 24 | this.wPressed = true; 25 | break; 26 | case "A": 27 | case "a": 28 | this.aPressed = true; 29 | break; 30 | case "S": 31 | case "s": 32 | this.sPressed = true; 33 | break; 34 | case "D": 35 | case "d": 36 | this.dPressed = true; 37 | break; 38 | case "Z": 39 | case "z": 40 | // toggle 41 | this.zToggle = !this.zToggle; 42 | 43 | // has to be high priority, otherwise pointer lock fails 44 | coreEngine.messageQueue.add( 45 | new Message(coreEngine, this, MessageType.Keypress, new KeyPress(Key.Z), MessagePriority.High) 46 | ); 47 | 48 | // request toggle frame rendering 49 | coreEngine.messageQueue.add( 50 | new Message(coreEngine, this, MessageType.ToggleRender, this.zToggle) 51 | ); 52 | break; 53 | case "Shift": 54 | coreEngine.messageQueue.add( 55 | new Message(coreEngine, this, MessageType.Keypress, 56 | new KeyPress(null, KeyState.Keydown, KeyModifier.Shift), 57 | MessagePriority.High) 58 | ); 59 | break; 60 | } 61 | }); 62 | 63 | window.addEventListener("keyup", (event) => { 64 | switch (event.key) { 65 | case "W": 66 | case "w": 67 | this.wPressed = false; 68 | break; 69 | case "A": 70 | case "a": 71 | this.aPressed = false; 72 | break; 73 | case "S": 74 | case "s": 75 | this.sPressed = false; 76 | break; 77 | case "D": 78 | case "d": 79 | this.dPressed = false; 80 | break; 81 | case "Shift": 82 | coreEngine.messageQueue.add( 83 | new Message(coreEngine, this, MessageType.Keypress, 84 | new KeyPress(null, KeyState.Keyup, KeyModifier.Shift), 85 | MessagePriority.High) 86 | ); 87 | break; 88 | } 89 | }); 90 | } 91 | 92 | public pollKeyboard(coreEngine: EngineCore) { 93 | if (this.wPressed) { 94 | if (this.zToggle) { 95 | coreEngine.messageQueue.add(new Message(coreEngine, this, MessageType.MoveCamera, MoveDirection.forward)); 96 | } 97 | } 98 | 99 | if (this.sPressed) { 100 | if (this.zToggle) { 101 | coreEngine.messageQueue.add(new Message(coreEngine, this, MessageType.MoveCamera, MoveDirection.backward)); 102 | } 103 | } 104 | 105 | if (this.dPressed) { 106 | if (this.zToggle) { 107 | coreEngine.messageQueue.add(new Message(coreEngine, this, MessageType.MoveCamera, MoveDirection.right)); 108 | } 109 | } 110 | 111 | if (this.aPressed) { 112 | if (this.zToggle) { 113 | coreEngine.messageQueue.add(new Message(coreEngine, this, MessageType.MoveCamera, MoveDirection.left)); 114 | } 115 | } 116 | } 117 | 118 | public onMessage(message: Message) { 119 | 120 | } 121 | } -------------------------------------------------------------------------------- /src/MouseHandler.ts: -------------------------------------------------------------------------------- 1 | import { ICamera } from "./Rendering/Camera/ICamera"; 2 | import { EngineCore } from "./Rendering/EngineCore"; 3 | import { Message, MessageType, MessagePriority } from "./Rendering/Messaging/Message"; 4 | import { IEngineComponent } from "./Rendering/IEngineComponent"; 5 | import { vec2, vec3 } from "gl-matrix"; 6 | import { KeyPress, Key } from "./Utils/KeyPress"; 7 | 8 | export class MouseHandler implements IEngineComponent { 9 | public componentName = "MouseHandler"; 10 | 11 | private _active = false; 12 | public set active(value) { 13 | const canvas = document.getElementById("canvas"); 14 | if (!canvas) { 15 | console.log("Failed to find canvas element"); 16 | return; 17 | } 18 | if (value) { 19 | canvas.requestPointerLock(); 20 | } else { 21 | document.exitPointerLock(); 22 | } 23 | 24 | this._active = value; 25 | } 26 | public get active() { 27 | return this._active; 28 | } 29 | 30 | constructor(coreEngine: EngineCore) { 31 | document.addEventListener("mousemove", this.mouseCallback.bind(this, coreEngine)); 32 | document.addEventListener("pointerlockchange", this.pointerLockChanged.bind(this, coreEngine)); 33 | document.addEventListener("pointerlockerror", () => console.log("Pointer lock failed")); 34 | } 35 | 36 | public mouseCallback(coreEngine: EngineCore, e: MouseEvent) { 37 | if (this.active) { 38 | coreEngine.messageQueue.add( 39 | new Message(coreEngine, this, MessageType.MouseMove, 40 | vec2.fromValues(e.movementX, e.movementY), MessagePriority.High) 41 | ); 42 | } 43 | } 44 | 45 | public pointerLockChanged(coreEngine: EngineCore, e: MouseEvent) { 46 | // pointer locked 47 | if (document.pointerLockElement !== document.getElementById("canvas")) { 48 | coreEngine.messageQueue.add( 49 | new Message(coreEngine, this, MessageType.ToggleCameraActive, false) 50 | ); 51 | } 52 | } 53 | 54 | public onMessage(message: Message) { 55 | switch (message.type) { 56 | case MessageType.ToggleCameraActive: 57 | this.active = message.data; 58 | break; 59 | 60 | default: 61 | break; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Rendering/Camera/CameraState.ts: -------------------------------------------------------------------------------- 1 | import { vec3, mat4 } from "gl-matrix"; 2 | 3 | export class CameraState { 4 | public position: vec3; 5 | 6 | public modelMatrix: mat4; 7 | public projectionMatrix: mat4; 8 | public viewMatrix: mat4; 9 | 10 | constructor(pos: vec3, modelMat: mat4, projMat: mat4, viewMat: mat4) { 11 | this.position = pos; 12 | this.modelMatrix = modelMat; 13 | this.projectionMatrix = projMat; 14 | this.viewMatrix = viewMat; 15 | } 16 | } -------------------------------------------------------------------------------- /src/Rendering/Camera/ICamera.ts: -------------------------------------------------------------------------------- 1 | import { vec3, mat4 } from "gl-matrix"; 2 | import { IEngineComponent } from "../IEngineComponent"; 3 | import { CameraState } from "./CameraState"; 4 | 5 | export interface ICamera extends IEngineComponent { 6 | position: vec3; 7 | 8 | horizontalFov: number; 9 | aspectRatio: number; 10 | 11 | speed: number; 12 | mulitplier: number; 13 | mouseSensitivity: number; 14 | 15 | up: vec3; 16 | 17 | modelMatrix: mat4; 18 | 19 | nearClip: number; 20 | farClip: number; 21 | 22 | horizontalAngle: number; 23 | verticalAngle: number; 24 | 25 | getFront(): vec3; 26 | getRight(): vec3; 27 | 28 | getViewMatrix(): mat4; 29 | getModelMatrix(): mat4; 30 | getProjectionMatrix(): mat4; 31 | 32 | updateAspectRatio(width: number, height: number): void; 33 | 34 | move(direction: MoveDirection); 35 | 36 | update(dX: number, dY: number, dTime: number): void; 37 | 38 | getCameraState(): CameraState; 39 | } 40 | 41 | export enum MoveDirection { 42 | forward, 43 | backward, 44 | left, 45 | right, 46 | } -------------------------------------------------------------------------------- /src/Rendering/Camera/OrthoCamera.ts: -------------------------------------------------------------------------------- 1 | import {ICamera, MoveDirection} from "./ICamera"; 2 | import { vec3, mat4 } from "gl-matrix"; 3 | import { limitAngle } from "../../Utils/LimitAngle"; 4 | import { wrapAngle } from "../../Utils/WrapAngle"; 5 | import { MessageType, Message } from "../Messaging/Message"; 6 | import { CameraState } from "./CameraState"; 7 | 8 | export class OrthoCamera implements ICamera { 9 | public componentName = "OrthoCamera"; 10 | 11 | public position: vec3; 12 | 13 | public horizontalFov = 45; 14 | public aspectRatio: number; 15 | 16 | public speed = 1; 17 | public mulitplier = 1.0; 18 | public mouseSensitivity = 5; 19 | 20 | public up: vec3 = vec3.fromValues(0, 1, 0); 21 | 22 | public modelMatrix: mat4 = mat4.identity(mat4.create()); 23 | 24 | public nearClip = 1; 25 | public farClip = 1000; 26 | 27 | private _horizontalAngle!: number; 28 | public get horizontalAngle(): number { 29 | return this._horizontalAngle; 30 | } 31 | public set horizontalAngle(value: number) { 32 | this._horizontalAngle = wrapAngle(value); 33 | } 34 | 35 | private _verticalAngle!: number; 36 | public get verticalAngle(): number { 37 | return this._verticalAngle; 38 | } 39 | public set verticalAngle(value: number) { 40 | this._verticalAngle = limitAngle(value, -89, 89); 41 | } 42 | 43 | constructor(width: number, height: number) { 44 | console.log("--Initializing Ortho Camera--"); 45 | console.log(" Canvas Width: " + width); 46 | console.log(" Canvas Height: " + height); 47 | 48 | if (height === 0) { 49 | console.log("Error, height cannot be 0"); 50 | this.aspectRatio = 0; 51 | } else { 52 | this.aspectRatio = width / height; 53 | } 54 | 55 | console.log(" Aspect Ratio: " + this.aspectRatio); 56 | 57 | // start camera at world center 58 | this.position = vec3.fromValues(0, 0, 0); 59 | 60 | this.horizontalAngle = 0; 61 | this.verticalAngle = 0; 62 | 63 | // calculate projection matrix 64 | this.getProjectionMatrix(); 65 | } 66 | 67 | public getFront(): vec3 { 68 | throw new Error("Method not implemented."); 69 | } 70 | public getRight(): vec3 { 71 | throw new Error("Method not implemented."); 72 | } 73 | public getViewMatrix(): mat4 { 74 | throw new Error("Method not implemented."); 75 | } 76 | public getModelMatrix(): mat4 { 77 | throw new Error("Method not implemented."); 78 | } 79 | public getProjectionMatrix(): mat4 { 80 | throw new Error("Method not implemented."); 81 | } 82 | public updateAspectRatio(width: number, height: number): void { 83 | throw new Error("Method not implemented."); 84 | } 85 | public moveForward(): void { 86 | throw new Error("Method not implemented."); 87 | } 88 | public moveBackword(): void { 89 | throw new Error("Method not implemented."); 90 | } 91 | public moveRight(): void { 92 | throw new Error("Method not implemented."); 93 | } 94 | public moveLeft(): void { 95 | throw new Error("Method not implemented."); 96 | } 97 | public update(dX: number, dY: number, dTime: number): void { 98 | throw new Error("Method not implemented."); 99 | } 100 | public move(direction: MoveDirection) { 101 | throw new Error("Method not implemented."); 102 | } 103 | public getCameraState(): CameraState { 104 | throw new Error("Method not implemented."); 105 | } 106 | public onMessage(message: Message) { 107 | throw new Error("Method not implemented."); 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /src/Rendering/Camera/PerspectiveCamera.ts: -------------------------------------------------------------------------------- 1 | import { vec3, mat4, glMatrix, vec2 } from "gl-matrix"; 2 | import { wrapAngle } from "../../Utils/WrapAngle"; 3 | import { limitAngle } from "../../Utils/LimitAngle"; 4 | import { EngineCore } from "../EngineCore"; 5 | import {ICamera, MoveDirection} from "./ICamera"; 6 | import { Message, MessageType } from "../Messaging/Message"; 7 | import { CameraState } from "./CameraState"; 8 | 9 | export class PerspectiveCamera implements ICamera { 10 | public componentName = "PerspectiveCamera"; 11 | 12 | public position: vec3; 13 | 14 | public horizontalFov = 45; 15 | public aspectRatio: number; 16 | 17 | public speed = 10; 18 | public mulitplier = 1.0; 19 | public mouseSensitivity = 5; 20 | 21 | public up: vec3 = vec3.fromValues(0, 1, 0); 22 | public modelMatrix: mat4 = mat4.identity(mat4.create()); 23 | 24 | public nearClip = 1; 25 | public farClip = 10000; 26 | 27 | private _horizontalAngle!: number; 28 | public get horizontalAngle(): number { 29 | return this._horizontalAngle; 30 | } 31 | public set horizontalAngle(value: number) { 32 | this._horizontalAngle = wrapAngle(value); 33 | } 34 | 35 | private _verticalAngle!: number; 36 | public get verticalAngle(): number { 37 | return this._verticalAngle; 38 | } 39 | public set verticalAngle(value: number) { 40 | this._verticalAngle = limitAngle(value, -89, 89); 41 | } 42 | 43 | constructor(width: number, height: number) { 44 | console.log("--Initializing Perspective Camera--"); 45 | console.log(" Canvas Width: " + width); 46 | console.log(" Canvas Height: " + height); 47 | 48 | if (height === 0) { 49 | console.log("Error, height cannot be 0"); 50 | this.aspectRatio = 0; 51 | } else { 52 | this.aspectRatio = width / height; 53 | } 54 | 55 | console.log(" Aspect Ratio: " + this.aspectRatio); 56 | 57 | // start camera at world center 58 | this.position = vec3.fromValues(0, 0, 0); 59 | 60 | this.horizontalAngle = 0; 61 | this.verticalAngle = 0; 62 | 63 | // compensate for fact that source uses Z as up axis, while openGL uses Y. 64 | mat4.rotateX(this.modelMatrix, this.modelMatrix, glMatrix.toRadian(-90)); 65 | 66 | // calculate projection matrix 67 | this.getProjectionMatrix(); 68 | } 69 | 70 | public getFront(): vec3 { 71 | const front = vec3.create(); 72 | 73 | // x 74 | front[0] = Math.cos(glMatrix.toRadian(this.verticalAngle)) * Math.cos(glMatrix.toRadian(this.horizontalAngle)); 75 | // y 76 | front[1] = Math.sin(glMatrix.toRadian(this.verticalAngle)); 77 | // z 78 | front[2] = Math.cos(glMatrix.toRadian(this.verticalAngle)) * Math.sin(glMatrix.toRadian(this.horizontalAngle)); 79 | 80 | vec3.normalize(front, front); 81 | 82 | return front; 83 | } 84 | 85 | public getRight(): vec3 { 86 | const front = this.getFront(); 87 | const right = vec3.create(); 88 | 89 | vec3.cross(right, front, this.up); 90 | 91 | vec3.normalize(right, right); 92 | 93 | return right; 94 | 95 | } 96 | 97 | public getProjectionMatrix(): mat4 { 98 | const projectionMatrix = mat4.create(); 99 | 100 | mat4.perspective(projectionMatrix, 101 | glMatrix.toRadian(this.horizontalFov), 102 | this.aspectRatio, this.nearClip, this.farClip); 103 | 104 | // tslint:disable-next-line:align 105 | return projectionMatrix; 106 | } 107 | 108 | public getViewMatrix(): mat4 { 109 | const positionPlusFront: vec3 = vec3.create(); 110 | vec3.add(positionPlusFront, this.position, this.getFront()); 111 | 112 | const viewMatrix = mat4.create(); 113 | mat4.lookAt(viewMatrix, this.position, positionPlusFront, this.up); 114 | 115 | return viewMatrix; 116 | } 117 | 118 | public getModelMatrix(): mat4 { 119 | return this.modelMatrix; 120 | } 121 | 122 | public updateAspectRatio(width: number, height: number) { 123 | if (height === 0) { 124 | console.log("Error, height cannot be 0"); 125 | return; 126 | } 127 | 128 | this.aspectRatio = width / height; 129 | } 130 | 131 | public move(direction: MoveDirection) { 132 | switch (direction) { 133 | case MoveDirection.forward: 134 | this.moveForward(); 135 | break; 136 | case MoveDirection.backward: 137 | this.moveBackword(); 138 | break; 139 | case MoveDirection.left: 140 | this.moveLeft(); 141 | break; 142 | case MoveDirection.right: 143 | this.moveRight(); 144 | break; 145 | } 146 | } 147 | 148 | private moveForward() { 149 | // get front matrix 150 | const front = this.getFront(); 151 | 152 | // x 153 | this.position[0] += (this.speed * this.mulitplier * front[0]); 154 | 155 | // y 156 | this.position[1] += (this.speed * this.mulitplier * front[1]); 157 | 158 | // z 159 | this.position[2] += (this.speed * this.mulitplier * front[2]); 160 | } 161 | 162 | private moveBackword() { 163 | // get front matrix 164 | const front = this.getFront(); 165 | 166 | // x 167 | this.position[0] -= (this.speed * this.mulitplier * front[0]); 168 | 169 | // y 170 | this.position[1] -= (this.speed * this.mulitplier * front[1]); 171 | 172 | // z 173 | this.position[2] -= (this.speed * this.mulitplier * front[2]); 174 | } 175 | 176 | private moveRight() { 177 | // 178 | const right = this.getRight(); 179 | 180 | // x 181 | this.position[0] += (this.speed * this.mulitplier * right[0]); 182 | 183 | // y 184 | this.position[1] += (this.speed * this.mulitplier * right[1]); 185 | 186 | // z 187 | this.position[2] += (this.speed * this.mulitplier * right[2]); 188 | } 189 | 190 | private moveLeft() { 191 | // 192 | const right = this.getRight(); 193 | 194 | // x 195 | this.position[0] -= (this.speed * this.mulitplier * right[0]); 196 | 197 | // y 198 | this.position[1] -= (this.speed * this.mulitplier * right[1]); 199 | 200 | // z 201 | this.position[2] -= (this.speed * this.mulitplier * right[2]); 202 | } 203 | 204 | public update(dX: number, dY: number, dTime: number) { 205 | this.horizontalAngle += dX * dTime * this.mouseSensitivity; 206 | // todo investigate jerkiness in vertical angle 207 | this.verticalAngle += -dY * dTime * this.mouseSensitivity; 208 | // console.log("vAngle: " + this.verticalAngle); 209 | // console.log("hAngle: " + this.horizontalAngle); 210 | } 211 | 212 | public getCameraState() { 213 | return new CameraState(this.position, this.getModelMatrix(), this.getProjectionMatrix(), this.getViewMatrix()); 214 | } 215 | 216 | public onMessage(message: Message) { 217 | switch (message.type) { 218 | case MessageType.MoveCamera: 219 | this.move(message.data); 220 | break; 221 | 222 | case MessageType.MouseMove: 223 | // message data is (dx, dy, dt) 224 | const vecMessage = message.data as vec3; 225 | this.update(vecMessage[0], vecMessage[1], vecMessage[2]); 226 | break; 227 | 228 | default: 229 | break; 230 | } 231 | } 232 | } -------------------------------------------------------------------------------- /src/Rendering/EngineCore.ts: -------------------------------------------------------------------------------- 1 | import {FragShader, VertShader} from "./Shaders/ShaderSource"; 2 | import { CreateShaderProgram } from "./Shaders/Shader"; 3 | import { ICamera} from "./Camera/ICamera"; 4 | import { RenderObject } from "./RenderObjects/RenderObject"; 5 | import { KeyboardListener } from "../KeyboardListener"; 6 | import { MouseHandler } from "../MouseHandler"; 7 | import { IRenderable } from "./RenderObjects/IRenderable"; 8 | import { PerspectiveCamera } from "./Camera/PerspectiveCamera"; 9 | import { MeshFactory } from "../Utils/MeshFactory"; 10 | import { Texture } from "./Textures/Texture"; 11 | import { vec4, vec3 } from "gl-matrix"; 12 | import { UniformLocations } from "./Shaders/UniformLocations"; 13 | import { TextureDictionary } from "./Textures/TextureDictionary"; 14 | import { MessageQueue } from "./Messaging/MessageQueue"; 15 | import { IEngineComponent } from "./IEngineComponent"; 16 | import { Message, MessageType } from "./Messaging/Message"; 17 | import { Key, KeyPress, KeyModifier, KeyState } from "../Utils/KeyPress"; 18 | 19 | export class EngineCore implements IEngineComponent { 20 | public componentName = "CoreEngine"; 21 | public gl: WebGL2RenderingContext; 22 | 23 | public messageQueue: MessageQueue; 24 | 25 | public cameras: ICamera[]; 26 | public activeCamera: ICamera; 27 | public controlsActive = false; 28 | 29 | // temporary. todo change to support multiple instances 30 | private static _renderer: EngineCore; 31 | public static get renderer(): EngineCore { 32 | return EngineCore._renderer; 33 | } 34 | public static set renderer(value: EngineCore) { 35 | EngineCore._renderer = value; 36 | } 37 | 38 | public gridSize = 15; 39 | public drawGrid = true; 40 | 41 | private renderObjects: IRenderable[] = []; 42 | // private grid: RenderObject; 43 | 44 | public keyboardListener!: KeyboardListener; 45 | public mouseHandler!: MouseHandler; 46 | 47 | private previousTime = 0; 48 | private deltaTime = 0; 49 | public renderFrame = false; 50 | 51 | constructor(gl: WebGL2RenderingContext) { 52 | console.log("--Initializing Core--"); 53 | 54 | // setup gl settings 55 | this.gl = gl; 56 | 57 | this.messageQueue = new MessageQueue(); 58 | 59 | this.gl.clearColor(0.0, 0, 0, 1.0); 60 | this.gl.clearDepth(1.0); 61 | this.gl.enable(this.gl.CULL_FACE); 62 | this.gl.cullFace(this.gl.FRONT); 63 | this.gl.enable(this.gl.DEPTH_TEST); 64 | this.gl.depthFunc(this.gl.LEQUAL); 65 | this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); 66 | 67 | // setup default camera 68 | this.cameras = [new PerspectiveCamera(this.gl.canvas.clientWidth, this.gl.canvas.clientHeight)]; 69 | this.activeCamera = this.cameras[0]; 70 | 71 | // setup keyboard listener 72 | this.keyboardListener = new KeyboardListener(this); 73 | this.mouseHandler = new MouseHandler(this); 74 | 75 | EngineCore.renderer = this; 76 | } 77 | 78 | public addRenderableObject(object: IRenderable) { 79 | this.renderObjects.push(object); 80 | } 81 | 82 | public clearRenderObjects() { 83 | this.renderObjects = []; 84 | } 85 | 86 | public main(currentTime = 0) { 87 | window.requestAnimationFrame(this.main.bind(this)); 88 | 89 | // poll keyboard 90 | this.keyboardListener.pollKeyboard(this); 91 | 92 | // dispatch messages 93 | this.messageQueue.dispatch(); 94 | 95 | // convert dTime to seconds 96 | this.deltaTime = (currentTime - this.previousTime) / 1000; 97 | this.previousTime = currentTime; 98 | 99 | if (this.renderFrame) { 100 | this.render(); 101 | } 102 | } 103 | 104 | public render() { 105 | // resize every frame so when user resizes canvas it is smooth 106 | this.resize(); 107 | 108 | // prepare frame for rendering 109 | this.gl.viewport(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight); 110 | this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); 111 | 112 | // render all objects 113 | const cameraState = this.activeCamera.getCameraState(); 114 | this.renderObjects.forEach((renderObject) => { 115 | // renderObject.draw(this.gl, this.gl.POINTS); 116 | renderObject.draw(this.gl, cameraState); 117 | }); 118 | } 119 | 120 | private resize() { 121 | const pixelRatio = window.devicePixelRatio || 1; 122 | 123 | const width = Math.floor(this.gl.canvas.clientWidth * pixelRatio); 124 | const height = Math.floor(this.gl.canvas.clientHeight * pixelRatio); 125 | 126 | if (this.gl.canvas.width !== width || this.gl.canvas.height !== height) { 127 | this.gl.canvas.width = width; 128 | this.gl.canvas.height = height; 129 | 130 | this.activeCamera.updateAspectRatio(width, height); 131 | } 132 | } 133 | 134 | // reroutes messages to their correct destination 135 | public onMessage(message: Message) { 136 | switch (message.type) { 137 | case MessageType.ToggleCameraActive: 138 | // toggles mouse input and toggles frame rendering 139 | this.controlsActive = message.data; 140 | this.mouseHandler.active = message.data; 141 | this.keyboardListener.zToggle = message.data; 142 | this.renderFrame = message.data; 143 | break; 144 | 145 | // case MessageType.ToggleControlsActive: 146 | // this.controlsActive = message.data; 147 | // this.mouseHandler.active = message.data 148 | 149 | case MessageType.ToggleRender: 150 | // if there is no movement, no need to keep rendering 151 | // TODO: disable this once brush ents / moving things are working 152 | this.renderFrame = message.data; 153 | break; 154 | 155 | case MessageType.MoveCamera: 156 | this.activeCamera.onMessage(message); 157 | break; 158 | 159 | case MessageType.MouseMove: 160 | // add deltatime to the vec2 containing (dx, dy) 161 | this.activeCamera.onMessage(new Message(this.activeCamera, this, MessageType.MouseMove, 162 | vec3.fromValues(message.data[0], message.data[1], this.deltaTime))); 163 | break; 164 | 165 | case MessageType.Keypress: 166 | switch (message.data.key) { 167 | case Key.Z: 168 | this.controlsActive = !this.controlsActive; 169 | this.renderFrame = !this.renderFrame; 170 | this.mouseHandler.active = this.controlsActive; 171 | break; 172 | 173 | case Key.Escape: 174 | this.controlsActive = false; 175 | this.renderFrame = false; 176 | this.mouseHandler.active = false; 177 | 178 | default: 179 | switch (message.data.modifier) { 180 | case KeyModifier.Shift: 181 | if (message.data.state === KeyState.Keydown) { 182 | this.activeCamera.mulitplier = 5.0; 183 | } else { 184 | this.activeCamera.mulitplier = 1.0; 185 | } 186 | break; 187 | 188 | default: 189 | break; 190 | } 191 | break; 192 | } 193 | default: 194 | break; 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /src/Rendering/EngineState.ts: -------------------------------------------------------------------------------- 1 | import { vec4 } from "gl-matrix"; 2 | 3 | export class EngineState { 4 | // public position: vec4; 5 | // public pitch: number; 6 | // public yaw: number; 7 | } -------------------------------------------------------------------------------- /src/Rendering/IEngineComponent.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "./Messaging/Message"; 2 | 3 | export interface IEngineComponent { 4 | componentName: string; 5 | onMessage(message: Message); 6 | } -------------------------------------------------------------------------------- /src/Rendering/Messaging/Message.ts: -------------------------------------------------------------------------------- 1 | import { IEngineComponent } from "../IEngineComponent"; 2 | 3 | // TODO: turn into interface for custo messages? 4 | export class Message { 5 | public to: IEngineComponent[]; 6 | public from: IEngineComponent; 7 | public type: MessageType; 8 | public data: any; 9 | public priority: MessagePriority; 10 | 11 | constructor(to: IEngineComponent[] | IEngineComponent, from: IEngineComponent, messageType: MessageType, 12 | data?: any, priority = MessagePriority.Low) { 13 | if (to instanceof Array) { 14 | this.to = to; 15 | } else { 16 | this.to = [to]; 17 | } 18 | this.from = from; 19 | this.type = messageType; 20 | this.data = data; 21 | this.priority = priority; 22 | } 23 | 24 | public toString() { 25 | let retStr = ""; 26 | this.to.forEach((recipient) => { 27 | retStr += `${this.from.componentName} -> ${recipient.componentName} - ${MessagePriority[this.priority]}:\n`; 28 | retStr += `${this.data} (${MessageType[this.type]})\n`; 29 | }); 30 | return retStr; 31 | } 32 | } 33 | 34 | export enum MessagePriority { 35 | High, 36 | Medium, 37 | Low, 38 | } 39 | 40 | export enum MessageType { 41 | ToggleCameraActive, // bool 42 | // ToggleControlsActive, // bool 43 | Keypress, // KeyPress class 44 | ToggleRender, // bool 45 | MoveCamera, // MoveDirection enum 46 | MouseMove, // vec2 (dx, dy) 47 | } -------------------------------------------------------------------------------- /src/Rendering/Messaging/MessageQueue.ts: -------------------------------------------------------------------------------- 1 | import { IEngineComponent } from "../IEngineComponent"; 2 | import { Message, MessagePriority } from "./Message"; 3 | 4 | export class MessageQueue { 5 | private lowPriorityMessages: Message[] = []; 6 | private mediumPriorityMessages: Message[] = []; 7 | // private subscribers: IEngineComponent[] = []; 8 | private logging: boolean; 9 | 10 | constructor(logging = false) { 11 | this.logging = logging; 12 | } 13 | 14 | public add(message: Message) { 15 | // sort by priority 16 | switch (message.priority) { 17 | case MessagePriority.High: 18 | // high priority messages should be dispatched immedietly 19 | this.dispatchMessage(message); 20 | break; 21 | case MessagePriority.Medium: 22 | this.mediumPriorityMessages.push(message); 23 | case MessagePriority.Low: 24 | this.lowPriorityMessages.push(message); 25 | default: 26 | break; 27 | } 28 | } 29 | 30 | public dispatch() { 31 | // dispatch medium priority messages first 32 | for (let i = 0; i < this.mediumPriorityMessages.length; i++) { 33 | const message = this.mediumPriorityMessages.shift(); 34 | this.dispatchMessage(message); 35 | } 36 | 37 | for (let i = 0; i < this.lowPriorityMessages.length; i++) { 38 | const message = this.lowPriorityMessages.shift(); 39 | this.dispatchMessage(message); 40 | } 41 | } 42 | 43 | public dispatchMessage(message: Message | undefined) { 44 | // validate message is valid 45 | if (message) { 46 | if (this.logging) { 47 | console.log(message.toString()); 48 | } 49 | 50 | const recipient = message.to; 51 | 52 | // validate recipient is valid 53 | if (recipient) { 54 | recipient.forEach((rec) => { 55 | rec.onMessage(message); 56 | }); 57 | } 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/BSP/BSPFace.ts: -------------------------------------------------------------------------------- 1 | import { Face } from "../../../BSP/Structs/Face"; 2 | import { BSP } from "../../../BSP/BSP"; 3 | import { Vertex, FALSE, TRUE } from "../../../Structs/Vertex"; 4 | import { LumpType } from "../../../BSP/Lumps/LumpType"; 5 | import { TexInfoLump } from "../../../BSP/Lumps/TexInfoLump"; 6 | import { PlaneLump } from "../../../BSP/Lumps/PlaneLump"; 7 | import { VertexLump } from "../../../BSP/Lumps/VertexLump"; 8 | import { TexDataLump } from "../../../BSP/Lumps/TexDataLump"; 9 | import { vec4, vec3, vec2 } from "gl-matrix"; 10 | import { SurfEdgeLump } from "../../../BSP/Lumps/SurfEdgeLump"; 11 | import { EdgeLump } from "../../../BSP/Lumps/EdgeLump"; 12 | import { addRange } from "../../../Utils/AddRange"; 13 | import { SurfFlags } from "../../../BSP/Structs/Enums"; 14 | import { DispVertLump } from "../../../BSP/Lumps/DispVertLump"; 15 | import { DispTrisLump } from "../../../BSP/Lumps/DispTrisLump"; 16 | import { DispInfo } from "../../../BSP/Structs/DispInfo"; 17 | import { DispInfoLump } from "../../../BSP/Lumps/DispInfoLump"; 18 | import { Visibility } from "../IRenderable"; 19 | import { TexDataStringDataLump } from "../../../BSP/Lumps/TexDataStringDataLump"; 20 | import { TexDataStringTableLump } from "../../../BSP/Lumps/TexDataStringTableLump"; 21 | import { Texture } from "../../Textures/Texture"; 22 | import { TextureDictionary } from "../../Textures/TextureDictionary"; 23 | import { EngineCore } from "../../EngineCore"; 24 | import { BSPResourceManager } from "../BSPResourceManager"; 25 | export class BSPFace { 26 | public visibility: Visibility = Visibility.Visible; 27 | public face: Face; 28 | public indices: number[] = []; 29 | public vertices: Vertex[]; 30 | public dispInfo?: DispInfo; 31 | public texture?: Texture; 32 | 33 | constructor(face: Face, bsp: BSP, resourceManager: BSPResourceManager) { 34 | this.face = face; 35 | this.vertices = this.getVertices(bsp, resourceManager); 36 | } 37 | 38 | public calcIndices(startIndex: number) { 39 | if (this.dispInfo != null) { 40 | this.calcDispIndices(startIndex); 41 | } else { 42 | this.calcFaceIndices(startIndex); 43 | } 44 | } 45 | 46 | // calculates the indices for a triangle fan 47 | private calcFaceIndices(startIndex: number) { 48 | for (let i = 1; i < this.vertices.length - 1; i++) { 49 | addRange(this.indices, 50 | [startIndex, 51 | startIndex + i, 52 | startIndex + i + 1]); 53 | } 54 | } 55 | 56 | private calcDispIndices(startIndex: number) { 57 | if (this.dispInfo == null) { 58 | console.log("Disp info was null!"); 59 | console.log(this); 60 | return []; 61 | } 62 | const rowSize = this.dispInfo.numRows(); 63 | 64 | // skip last row, there are no verts above to create the pattern 65 | for (let lineNum = 0; lineNum < (rowSize - 1); lineNum++) { 66 | const lineOffset = lineNum * rowSize; 67 | // const nextRowOffset = (lineNum + 1) * rowSize; 68 | 69 | for (let blockNum = 0; blockNum < (2 ** (this.dispInfo.power - 1)); blockNum++) { 70 | // index offset depending on what block it's in 71 | const blockOffset = lineOffset + (2 * blockNum); 72 | 73 | // even rows create |/|\| pattern 74 | if (lineNum % 2 === 0) { 75 | // -------------------- 76 | // create |/| section 77 | // -------------------- 78 | this.indices.push((blockOffset) + startIndex); 79 | this.indices.push((blockOffset + rowSize) + startIndex); 80 | this.indices.push((blockOffset + rowSize + 1) + startIndex); 81 | 82 | this.indices.push((blockOffset) + startIndex); 83 | this.indices.push((blockOffset + rowSize + 1) + startIndex); 84 | this.indices.push((blockOffset + 1) + startIndex); 85 | 86 | // -------------------- 87 | // create |\| section 88 | // -------------------- 89 | this.indices.push((blockOffset + 1) + startIndex); 90 | this.indices.push((blockOffset + rowSize + 1) + startIndex); 91 | this.indices.push((blockOffset + 2) + startIndex); 92 | 93 | this.indices.push((blockOffset + 2) + startIndex); 94 | this.indices.push((blockOffset + rowSize + 1) + startIndex); 95 | this.indices.push((blockOffset + rowSize + 2) + startIndex); 96 | } else { 97 | // odd rows create |\|/| pattern 98 | 99 | // -------------------- 100 | // create |\| section 101 | // -------------------- 102 | this.indices.push((blockOffset + 1) + startIndex); 103 | this.indices.push((blockOffset) + startIndex); 104 | this.indices.push((blockOffset + rowSize) + startIndex); 105 | 106 | this.indices.push((blockOffset + 1) + startIndex); 107 | this.indices.push((blockOffset + rowSize) + startIndex); 108 | this.indices.push((blockOffset + rowSize + 1) + startIndex); 109 | 110 | // -------------------- 111 | // create |/| section 112 | // -------------------- 113 | this.indices.push((blockOffset + 1) + startIndex); 114 | this.indices.push((blockOffset + rowSize + 1) + startIndex); 115 | this.indices.push((blockOffset + rowSize + 2) + startIndex); 116 | 117 | this.indices.push((blockOffset + 1) + startIndex); 118 | this.indices.push((blockOffset + rowSize + 2) + startIndex); 119 | this.indices.push((blockOffset + 2) + startIndex); 120 | } 121 | } 122 | } 123 | // console.log(this.indices); 124 | } 125 | 126 | public getMesh(): number[] { 127 | return BSPFace.verticesToMesh(this.vertices); 128 | } 129 | 130 | private getVertices(bsp, resourceManager: BSPResourceManager): Vertex[] { 131 | const texInfoLump = bsp.readLump(LumpType.TexInfo) as TexInfoLump; 132 | const texDataLump = bsp.readLump(LumpType.TexData) as TexDataLump; 133 | const texDataStringTable = bsp.readLump(LumpType.TexDataStringTable) as TexDataStringTableLump; 134 | const texDataStringDataLump = bsp.readLump(LumpType.TexDataStringData) as TexDataStringDataLump; 135 | 136 | const texInfo = texInfoLump.texInfos[this.face.texInfo]; 137 | const texData = texDataLump.texDatas[texInfo.texData]; 138 | const texDataStringOffset = texDataStringTable.texDataTable[texData.nameDataStringTableID]; 139 | const texName = texDataStringDataLump.readStringAtOffset(texDataStringOffset); 140 | 141 | // filter out transparent meshes temporarily 142 | if ( (texInfo.flags & SurfFlags.TRANS) === SurfFlags.TRANS) { 143 | this.visibility = Visibility.Hidden; 144 | } 145 | // filter out trigger brushes 146 | if ( (texInfo.flags & SurfFlags.TRIGGER) === SurfFlags.TRIGGER) { 147 | this.visibility = Visibility.Hidden; 148 | } 149 | // filter out sky brushes 150 | if ( (texInfo.flags & SurfFlags.SKY) === SurfFlags.SKY || 151 | (texInfo.flags & SurfFlags.SKY2D) === SurfFlags.SKY2D) { 152 | this.visibility = Visibility.Hidden; 153 | } 154 | // filter out skip and hint brushes 155 | if ( (texInfo.flags & SurfFlags.SKIP) === SurfFlags.SKIP || 156 | (texInfo.flags & SurfFlags.HINT) === SurfFlags.HINT) { 157 | this.visibility = Visibility.Hidden; 158 | } 159 | // filter out hitboxes 160 | if ( (texInfo.flags & SurfFlags.HITBOX) === SurfFlags.HITBOX) { 161 | this.visibility = Visibility.Hidden; 162 | } 163 | 164 | let baseColor: vec4; 165 | // texData offset will be -1 for SKIP/CLIP/INVISIBLE textures 166 | if (texInfo.texData === -1) { 167 | baseColor = vec4.fromValues(0, 1.0, 1.0, 1.0); 168 | } else { 169 | // reflectivity color gives a rough estimate of material color 170 | // credit to snake_biscuit for discovering this 171 | const reflectivityColor = texData.reflectivity; 172 | baseColor = vec4.fromValues(reflectivityColor[0], reflectivityColor[1], reflectivityColor[2], 1.0); 173 | } 174 | 175 | // setup texture info 176 | if (texName != null) { 177 | const gl = resourceManager.getGLContext(); 178 | const texDict = resourceManager.getTextureDictionary(); 179 | 180 | this.texture = new Texture(gl, baseColor, 0, texName); 181 | if (this.texture != null) { 182 | const id = texDict.addTexture(gl, this.texture); 183 | this.texture.id = id; 184 | } 185 | } else { 186 | console.log("Failed to read texture name"); 187 | } 188 | 189 | // if face is not displacement, it's dispInfo will be -1; 190 | if (this.face.dispInfo === -1) { 191 | return this.getFaceVertexes(bsp); 192 | } else { 193 | console.log("disp"); 194 | return this.getDispVertexes(bsp); 195 | } 196 | } 197 | 198 | private getDispVertexes(bsp: BSP): Vertex[] { 199 | const dispVertsLump = bsp.readLump(LumpType.DispVerts) as DispVertLump; 200 | // const dispTrisLump = bsp.readLump(LumpType.DispTris) as DispTrisLump; 201 | const dispInfoLump = bsp.readLump(LumpType.DispInfo) as DispInfoLump; 202 | 203 | this.dispInfo = dispInfoLump.dispInfos[this.face.dispInfo]; 204 | const rowSize = this.dispInfo.numRows(); 205 | 206 | // get the vertices of the face 207 | let faceVerts = this.getFaceVertexes(bsp); 208 | 209 | // "rotate" the vertices so that the first vertex matches the dispInfo start position 210 | for (let j = 0; j < faceVerts.length; j++) { 211 | const vert = faceVerts[j]; 212 | 213 | if (vec3.equals(vec3.round(vec3.create(), vert.position), vec3.round(vec3.create(), this.dispInfo.startPosition))) { 214 | // shift the verts so the start position is the first vertex 215 | const verts = faceVerts.slice(j); 216 | const lastVerts = faceVerts.slice(0, j); 217 | addRange(verts, lastVerts); 218 | 219 | faceVerts = verts; 220 | break; 221 | } 222 | } 223 | 224 | // ------ Disp Reading ---------- 225 | 226 | // create a scaffold to generate vertices 227 | 228 | // generate 2 columns from linear interpolation between the 4 corner vertices 229 | // V2 V3 230 | // ^ ^ 231 | // C1 C2 232 | // ^ ^ 233 | // C1 C2 234 | // ^ ^ 235 | // C1 C2 236 | // ^ ^ 237 | // V1 V4 238 | 239 | // after interpolating between matching vertices in each column 240 | // x -> x -> x -> x -> x 241 | // x -> x -> x -> x -> x 242 | // x -> x -> x -> x -> x 243 | // x -> x -> x -> x -> x 244 | // x -> x -> x -> x -> x 245 | 246 | 247 | // displacements must have 4 vertexes 248 | if (faceVerts.length !== 4) { 249 | console.log("Invalid displacement"); 250 | return []; 251 | } 252 | const dispVerts: Vertex[] = []; 253 | 254 | const edgeCol1: Vertex[] = []; 255 | const edgeCol2: Vertex[] = []; 256 | const vert1 = faceVerts[0]; 257 | const vert2 = faceVerts[1]; 258 | const vert3 = faceVerts[2]; 259 | const vert4 = faceVerts[3]; 260 | 261 | // linearly interpolate vertices for first row 262 | for (let i = 0; i < rowSize; i++) { 263 | const vertPos = vec3.create(); 264 | vec3.lerp(vertPos, vert1.position, vert2.position, i / (rowSize - 1)); 265 | edgeCol1.push(new Vertex(vertPos, vert1.normal)); 266 | } 267 | 268 | // linearly interpolate vertices for second row 269 | for (let i = 0; i < rowSize; i++) { 270 | const vertPos = vec3.create(); 271 | // interpolate in opposite direction, otherwise vertices are placed in wrong order 272 | vec3.lerp(vertPos, vert4.position, vert3.position, i / (rowSize - 1)); 273 | edgeCol2.push(new Vertex(vertPos, vert1.normal)); 274 | } 275 | 276 | for (let i = 0; i < edgeCol1.length; i++) { 277 | const helperVert1: Vertex = edgeCol1[i]; 278 | const helperVert2: Vertex = edgeCol2[i]; 279 | 280 | // linearly interpolate vertices 281 | for (let j = 0; j < rowSize; j++) { 282 | const vertPos = vec3.create(); 283 | vec3.lerp(vertPos, helperVert1.position, helperVert2.position, j / (rowSize - 1)); 284 | if (this.texture != null) { 285 | dispVerts.push(new Vertex(vertPos, vert1.normal, this.texture.placeholderColor, FALSE, 286 | vec2.fromValues(0, 0), this.texture.id)); 287 | } else { 288 | dispVerts.push(new Vertex(vertPos, vert1.normal)); 289 | } 290 | } 291 | } 292 | 293 | // apply vertex offsets 294 | for (let i = 0; i < this.dispInfo.numVerts(); i++) { 295 | const vert = dispVertsLump.dispVerts[i + this.dispInfo.dispVertStart]; 296 | vec3.scaleAndAdd(dispVerts[i].position, dispVerts[i].position, vert.vec, vert.dist); 297 | } 298 | return dispVerts; 299 | // return BSPFace.divideFace(faceVerts, rowSize); 300 | } 301 | 302 | private getFaceVertexes(bsp: BSP, uniqueVertexes = true): Vertex[] { 303 | const normal = (bsp.readLump(LumpType.Planes) as PlaneLump).planes[this.face.planeNum].normal; 304 | const vertLump = bsp.readLump(LumpType.Vertexes) as VertexLump; 305 | 306 | const vertPositions: vec3[] = []; 307 | for (let i = this.face.firstEdge; i < this.face.firstEdge + this.face.numEdges; i++) { 308 | const edgeIndex = (bsp.readLump(LumpType.SurfEdges) as SurfEdgeLump).surfEdges[i]; 309 | 310 | const reverseEdge = (edgeIndex < 0); 311 | 312 | // gets the vertex indexes and reverses them if they are negative; 313 | const vertIndices = (bsp.readLump(LumpType.Edges) as EdgeLump) 314 | .edges[Math.abs(edgeIndex)].getVertIndices(reverseEdge); 315 | 316 | vertPositions.push(vertLump.vertexes[vertIndices[0]]); 317 | vertPositions.push(vertLump.vertexes[vertIndices[1]]); 318 | } 319 | 320 | let vertexes: Vertex[] = []; 321 | if (uniqueVertexes) { 322 | // remove duplicate vertex positions and convert them to Vertexes 323 | vertexes = Array.from(new Set(vertPositions)).map((vert) => { 324 | if (this.texture != null) { 325 | // console.log(this.texture.id + 1); 326 | return new Vertex(vert, normal, this.texture.placeholderColor, FALSE, 327 | vec2.fromValues(0, 0), this.texture.id); 328 | } else { 329 | return new Vertex(vert, normal); 330 | } 331 | }); 332 | } else { 333 | vertexes = vertPositions.map((vert) => { 334 | return new Vertex(vert, normal); 335 | }); 336 | } 337 | 338 | return vertexes; 339 | } 340 | 341 | // combines all vertex data into one number array 342 | public static verticesToMesh(verts: Vertex[]) { 343 | const out: number[] = []; 344 | verts.forEach((vert) => { 345 | addRange(out, vert.position); 346 | addRange(out, vert.normal); 347 | addRange(out, vert.fallbackColor); 348 | out.push(vert.textureLoaded); 349 | addRange(out, vert.texCoord); 350 | out.push(vert.texIndex); 351 | }); 352 | return out; 353 | } 354 | } 355 | 356 | -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/BSP/BSPMesh.ts: -------------------------------------------------------------------------------- 1 | import { BSP } from "../../../BSP/BSP"; 2 | import { IRenderable, Visibility } from "../IRenderable"; 3 | import { LumpType } from "../../../BSP/Lumps/LumpType"; 4 | import { POSITION_ATTRIB_LOCATION, NORMAL_ATTRIB_LOCATION, TEXCOORD_ATTRIB_LOCATION, 5 | TEXINDEX_ATTRIB_LOCATION, 6 | FALLBACK_COLOR_ATTRIB_LOCATION, 7 | TEXTURE_LOADED_ATTRIB_LOCATION} from "../../Shaders/LayoutLocations"; 8 | import { FaceLump } from "../../../BSP/Lumps/FaceLump"; 9 | import { addRange } from "../../../Utils/AddRange"; 10 | import { BSPFace } from "./BSPFace"; 11 | import { Texture } from "../../Textures/Texture"; 12 | import { UniformLocations } from "../../Shaders/UniformLocations"; 13 | import { TextureDictionary } from "../../Textures/TextureDictionary"; 14 | import { BSPResourceManager } from "../BSPResourceManager"; 15 | import { ModelLump } from "../../../BSP/Lumps/ModelLump"; 16 | import { BSPModel } from "./BSPModel"; 17 | import { CameraState } from "../../Camera/CameraState"; 18 | 19 | export class BSPMesh implements IRenderable { 20 | public visibility = Visibility.Visible; 21 | 22 | private initialized = false; 23 | private renderMode = WebGL2RenderingContext.TRIANGLES; 24 | 25 | private models: BSPModel[] = []; 26 | 27 | constructor(gl: WebGL2RenderingContext, bsp: BSP) { 28 | const modelLump = bsp.readLump(LumpType.Models) as ModelLump; 29 | 30 | modelLump.models.forEach((model) => { 31 | this.models.push(new BSPModel(gl, bsp, model)); 32 | }); 33 | 34 | this.initialized = true; 35 | console.log("BSP Loaded"); 36 | } 37 | 38 | public draw(gl: WebGL2RenderingContext, cameraState: CameraState, renderModeOverride?: number) { 39 | if (!this.initialized) { 40 | console.log("Cannot render object, not initialized"); 41 | return; 42 | } 43 | if (this.visibility === Visibility.Hidden) { 44 | return; 45 | } 46 | 47 | // node 0 is world geometry 48 | // TODO: walk every bsp tree once entity info parsing is available and their offsets can be obtained 49 | this.models[0].draw(gl, cameraState, renderModeOverride); 50 | // this.models.forEach((model) => { 51 | // model.draw(gl, cameraState, renderModeOverride); 52 | // }); 53 | // if (renderModeOverride == null) { 54 | // gl.drawElements(this.renderMode, this.vertexCount, gl.UNSIGNED_INT, 0); 55 | // } else { 56 | // gl.drawElements(renderModeOverride, this.vertexCount, gl.UNSIGNED_INT, 0); 57 | // } 58 | } 59 | 60 | private getUniformLocations() { 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/BSP/BSPModel.ts: -------------------------------------------------------------------------------- 1 | import { Vertex } from "../../../Structs/Vertex"; 2 | import { BSPFace } from "./BSPFace"; 3 | import { IRenderable, Visibility } from "../IRenderable"; 4 | import { BSPResourceManager } from "../BSPResourceManager"; 5 | import { Model } from "../../../BSP/Structs/Model"; 6 | import { BSP } from "../../../BSP/BSP"; 7 | import { LumpType } from "../../../BSP/Lumps/LumpType"; 8 | import { FaceLump } from "../../../BSP/Lumps/FaceLump"; 9 | import { addRange } from "../../../Utils/AddRange"; 10 | import { BSPNode } from "./Tree.ts/BSPNode"; 11 | import { NodeLump } from "../../../BSP/Lumps/NodeLump"; 12 | import { vec3, vec4, mat4, glMatrix } from "gl-matrix"; 13 | import { CameraState } from "../../Camera/CameraState"; 14 | 15 | export class BSPModel implements IRenderable { 16 | public model: Model; 17 | public resourceManager: BSPResourceManager; 18 | 19 | public tree!: BSPNode; 20 | public visibility: Visibility = Visibility.Visible; 21 | 22 | private modelMatrix!: mat4; 23 | 24 | 25 | constructor(gl: WebGL2RenderingContext, bsp: BSP, model: Model) { 26 | this.resourceManager = new BSPResourceManager(gl, bsp); 27 | this.model = model; 28 | 29 | // don't need to calculate the tree if there is no faces 30 | if (model.numFaces === 0) { 31 | console.log("Empty Node"); 32 | this.visibility = Visibility.Hidden; 33 | return; 34 | } 35 | this.modelMatrix = mat4.create(); // TODO: parse this from the entity lump 36 | // mat4.fromTranslation(this.modelMatrix, model.origin); 37 | // compensate for fact that source uses Z as up axis, while openGL uses Y. 38 | // mat4.rotateX(this.modelMatrix, this.modelMatrix, glMatrix.toRadian(-90)); 39 | 40 | // calculate bsp tree 41 | const nodeLump = bsp.readLump(LumpType.Nodes) as NodeLump; 42 | this.tree = new BSPNode(nodeLump.nodes[model.headNode], this.resourceManager, this.modelMatrix); 43 | } 44 | 45 | public draw(gl: WebGL2RenderingContext, cameraState: CameraState, renderModeOverride?: number) { 46 | if (this.visibility === Visibility.Hidden) { 47 | return; 48 | } 49 | // recursively walk the bsp tree and draw at every leaf 50 | this.tree.draw(gl, cameraState, renderModeOverride); 51 | } 52 | } -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/BSP/Tree.ts/BSPLeaf.ts: -------------------------------------------------------------------------------- 1 | import { IBSPTree } from "./IBSPTree"; 2 | import { Leaf } from "../../../../BSP/Structs/Leaf"; 3 | import { BSP } from "../../../../BSP/BSP"; 4 | import { Visibility } from "../../IRenderable"; 5 | import { BSPResourceManager } from "../../BSPResourceManager"; 6 | import { Vertex } from "../../../../Structs/Vertex"; 7 | import { BSPFace } from "../BSPFace"; 8 | import { addRange } from "../../../../Utils/AddRange"; 9 | import { LumpType } from "../../../../BSP/Lumps/LumpType"; 10 | import { FaceLump } from "../../../../BSP/Lumps/FaceLump"; 11 | import { LeafFaceLump } from "../../../../BSP/Lumps/LeafFaceLump"; 12 | import { 13 | POSITION_ATTRIB_LOCATION, NORMAL_ATTRIB_LOCATION, FALLBACK_COLOR_ATTRIB_LOCATION, 14 | TEXTURE_LOADED_ATTRIB_LOCATION, TEXCOORD_ATTRIB_LOCATION, TEXINDEX_ATTRIB_LOCATION 15 | } from "../../../Shaders/LayoutLocations"; 16 | import { CameraState } from "../../../Camera/CameraState"; 17 | import { VertShader, FragShader } from "../../../Shaders/ShaderSource"; 18 | import { mat4 } from "gl-matrix"; 19 | 20 | export class BSPLeaf implements IBSPTree { 21 | public isNode = false; 22 | public visibility = Visibility.Visible; 23 | public leaf: Leaf; 24 | 25 | public resourceManager: BSPResourceManager; 26 | private shaderIndex: number = 0; 27 | 28 | private indexCount: number = 0; 29 | private indices: number[] = []; 30 | private vertices: Vertex[] = []; 31 | private faces: BSPFace[] = []; 32 | 33 | public VAO!: WebGLVertexArrayObject; 34 | public VBO!: WebGLBuffer; 35 | public EAO!: WebGLBuffer; // index buffer 36 | private renderMode = WebGL2RenderingContext.TRIANGLES; 37 | 38 | private modelMat: mat4; 39 | private initialized = false; 40 | 41 | constructor(leaf: Leaf, resourceManager: BSPResourceManager, modelMat: mat4) { 42 | this.leaf = leaf; 43 | this.modelMat = modelMat; 44 | 45 | this.resourceManager = resourceManager; 46 | 47 | // TODO: doesnt really seem to do anything? 48 | this.load(); 49 | } 50 | 51 | private async load() { 52 | this.shaderIndex = this.resourceManager.createShaderProgram(); 53 | this.resourceManager.addShaders(this.shaderIndex, [VertShader, FragShader]); 54 | 55 | // calculate the vertices and indices of the leaf 56 | const bsp = this.resourceManager.getBSP(); 57 | const faceLump = bsp.readLump(LumpType.Faces) as FaceLump; 58 | const leafFaceLump = bsp.readLump(LumpType.LeafFaces) as LeafFaceLump; 59 | 60 | let currentIndex = 0; 61 | for (let i = this.leaf.firstLeafFace; i < this.leaf.numLeafFaces + this.leaf.firstLeafFace; i++) { 62 | const leafFace = leafFaceLump.leafFaces[i]; 63 | const face = faceLump.faces[leafFace]; 64 | const bspFace = new BSPFace(face, bsp, this.resourceManager); 65 | this.faces.push(bspFace); 66 | 67 | // add vertices to mesh 68 | bspFace.calcIndices(currentIndex); 69 | 70 | addRange(this.vertices, bspFace.vertices); 71 | // dont add hidden faces to the indices 72 | if (bspFace.visibility === Visibility.Visible) { 73 | addRange(this.indices, bspFace.indices); 74 | } 75 | 76 | if (bspFace.dispInfo != null) { 77 | // highest index of displacement will be it's second to last index 78 | currentIndex = bspFace.indices[bspFace.indices.length - 2] + 1; 79 | } else { 80 | // highest index of face will be it's last index 81 | currentIndex = bspFace.indices[bspFace.indices.length - 1] + 1; 82 | } 83 | } 84 | 85 | this.indexCount = this.indices.length; 86 | // if leaf is empty we dont need to render it 87 | if (this.indexCount < 1) { 88 | return; 89 | } 90 | this.bufferData(); 91 | this.initialized = true; 92 | } 93 | 94 | public draw(gl: WebGL2RenderingContext, cameraState: CameraState, renderModeOverride?: number) { 95 | if (this.visibility === Visibility.Hidden || !this.initialized) { 96 | return; 97 | } 98 | 99 | // enable this leaf's shader 100 | gl.useProgram(this.resourceManager.getShaderProgram(this.shaderIndex)); 101 | 102 | const uniformLocations = this.resourceManager.getUniformLocations(this.shaderIndex); 103 | 104 | // upload uniforms 105 | gl.uniformMatrix4fv(uniformLocations.uModelMatLocation, 106 | false, 107 | cameraState.modelMatrix); 108 | 109 | gl.uniformMatrix4fv(uniformLocations.uViewMatLocation, 110 | false, 111 | cameraState.viewMatrix); 112 | 113 | gl.uniformMatrix4fv(uniformLocations.uProjectionMatrixLocation, 114 | false, 115 | cameraState.projectionMatrix); 116 | 117 | gl.bindVertexArray(this.VAO); 118 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.EAO); 119 | 120 | if (renderModeOverride == null) { 121 | gl.drawElements(this.renderMode, this.indexCount, gl.UNSIGNED_INT, 0); 122 | } else { 123 | gl.drawElements(renderModeOverride, this.indexCount, gl.UNSIGNED_INT, 0); 124 | } 125 | } 126 | 127 | // uploads leaf data to gpu 128 | private bufferData() { 129 | const gl = this.resourceManager.getGLContext(); 130 | 131 | // create buffers 132 | const _vbo = gl.createBuffer(); 133 | const _vao = gl.createVertexArray(); 134 | const _eao = gl.createBuffer(); 135 | 136 | // check buffer creation was successful 137 | if (_vbo == null) { 138 | console.log("Failed to generate VBO"); 139 | return; 140 | } 141 | 142 | if (_vao == null) { 143 | console.log("Failed to generate VAO"); 144 | return; 145 | } 146 | 147 | if (_eao == null) { 148 | console.log("Failed to generate EAO"); 149 | return; 150 | } 151 | this.VBO = _vbo; 152 | this.VAO = _vao; 153 | this.EAO = _eao; 154 | 155 | // bind buffers 156 | gl.bindBuffer(gl.ARRAY_BUFFER, this.VBO); 157 | 158 | const mesh = BSPFace.verticesToMesh(this.vertices); 159 | // buffer vertex data 160 | gl.bufferData(gl.ARRAY_BUFFER, 161 | new Float32Array(mesh), gl.STATIC_DRAW); 162 | 163 | // create vertex position VAO 164 | gl.bindVertexArray(this.VAO); 165 | gl.vertexAttribPointer( 166 | POSITION_ATTRIB_LOCATION, // attribute location 167 | 3, // size of attribute (vec3) 168 | gl.FLOAT, // type of attribute is float 169 | false, // does not need to be normalized 170 | 56, // 0 = move forward size * sizeof(type) each iteration to get the next position 171 | 0 // offset (start at beginnng of buffer) 172 | ); 173 | gl.enableVertexAttribArray(POSITION_ATTRIB_LOCATION); 174 | 175 | // define normal VAO 176 | gl.vertexAttribPointer( 177 | NORMAL_ATTRIB_LOCATION, // attribute location 178 | 3, // size of attribute (vec3) 179 | gl.FLOAT, // type of attribute is float 180 | true, // does need to be normalized 181 | 56, // 0 = move forward size * sizeof(type) each iteration to get the next position 182 | 12 // offset (start at beginnng of buffer) 183 | ); 184 | gl.enableVertexAttribArray(NORMAL_ATTRIB_LOCATION); 185 | 186 | // enable fallbackColor 187 | gl.vertexAttribPointer( 188 | FALLBACK_COLOR_ATTRIB_LOCATION, // attribute location 189 | 4, // size of attribute (vec4) 190 | gl.FLOAT, // type of attribute is float 191 | false, // does need to be normalized 192 | 56, // 0 = move forward size * sizeof(type) each iteration to get the next position 193 | 24 // offset (start at beginnng of buffer) 194 | ); 195 | gl.enableVertexAttribArray(FALLBACK_COLOR_ATTRIB_LOCATION); 196 | 197 | // enable textureLoaded 198 | gl.vertexAttribPointer( 199 | TEXTURE_LOADED_ATTRIB_LOCATION, // attribute location 200 | 1, // size of attribute (int) 201 | gl.FLOAT, // type of attribute is int 202 | false, // does need to be normalized 203 | 56, // 0 = move forward size * sizeof(type) each iteration to get the next position 204 | 40 // offset (start at beginnng of buffer) 205 | ); 206 | gl.enableVertexAttribArray(TEXTURE_LOADED_ATTRIB_LOCATION); 207 | 208 | // define texcoord VAO 209 | gl.vertexAttribPointer( 210 | TEXCOORD_ATTRIB_LOCATION, // attribute location 211 | 2, // size of attribute (vec2) 212 | gl.FLOAT, // type of attribute is float 213 | false, // does not need to be normalized 214 | 56, // 0 = move forward size * sizeof(type) each iteration to get the next position 215 | 44 // offset (start at beginnng of buffer) 216 | ); 217 | gl.enableVertexAttribArray(TEXCOORD_ATTRIB_LOCATION); 218 | 219 | // define texIndex VAO 220 | gl.vertexAttribPointer( 221 | TEXINDEX_ATTRIB_LOCATION, // attribute location 222 | 1, // size of attribute int32 223 | gl.FLOAT, // type of attribute is ints 224 | false, // does not need to be normalized 225 | 56, // 0 = move forward size * sizeof(type) each iteration to get the next position 226 | 52 // offset (start at beginnng of buffer) 227 | ); 228 | gl.enableVertexAttribArray(TEXINDEX_ATTRIB_LOCATION); 229 | 230 | // create EAO 231 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.EAO); 232 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 233 | new Uint32Array(this.indices), 234 | gl.STATIC_DRAW 235 | ); 236 | } 237 | 238 | // public toString(indent = ""): string { 239 | 240 | // } 241 | } -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/BSP/Tree.ts/BSPNode.ts: -------------------------------------------------------------------------------- 1 | import { IBSPTree } from "./IBSPTree"; 2 | import { Plane } from "../../../../BSP/Structs/Plane"; 3 | import { BSPFace } from "../BSPFace"; 4 | import { Node } from "../../../../BSP/Structs/Node"; 5 | import { BSP } from "../../../../BSP/BSP"; 6 | import { LumpType } from "../../../../BSP/Lumps/LumpType"; 7 | import { NodeLump } from "../../../../BSP/Lumps/NodeLump"; 8 | import { PlaneLump } from "../../../../BSP/Lumps/PlaneLump"; 9 | import { LeafLump } from "../../../../BSP/Lumps/LeafLump"; 10 | import { BSPLeaf } from "./BSPLeaf"; 11 | import { Visibility } from "../../IRenderable"; 12 | import { BSPResourceManager } from "../../BSPResourceManager"; 13 | import { CameraState } from "../../../Camera/CameraState"; 14 | import { mat4 } from "gl-matrix"; 15 | 16 | export class BSPNode implements IBSPTree { 17 | public isNode = true; 18 | public visibility = Visibility.Visible; 19 | 20 | public plane: Plane; 21 | public node: Node; 22 | public children: IBSPTree[] = []; 23 | 24 | public resourceManager: BSPResourceManager; 25 | 26 | constructor(node: Node, resourceManager: BSPResourceManager, modelMat: mat4) { 27 | this.node = node; 28 | this.resourceManager = resourceManager; 29 | const bsp = this.resourceManager.getBSP(); 30 | 31 | const planeLump = bsp.readLump(LumpType.Planes) as PlaneLump; 32 | this.plane = planeLump.planes[this.node.planeNum]; 33 | 34 | // determine children 35 | const leafLump = bsp.readLump(LumpType.Leafs) as LeafLump; 36 | const nodeLump = bsp.readLump(LumpType.Nodes) as NodeLump; 37 | 38 | // negative numbers are -(leafs + 1), not nodes 39 | // recursively will fill the bsp tree 40 | if (node.children[0] < 0) { 41 | this.children[0] = new BSPLeaf(leafLump.leaves[-(node.children[0] + 1)], this.resourceManager, modelMat); 42 | } else { 43 | this.children[0] = new BSPNode(nodeLump.nodes[node.children[0]], this.resourceManager, modelMat); 44 | } 45 | if (node.children[1] < 0) { 46 | this.children[1] = new BSPLeaf(leafLump.leaves[-(node.children[1] + 1)], this.resourceManager, modelMat); 47 | } else { 48 | this.children[1] = new BSPNode(nodeLump.nodes[node.children[1]], this.resourceManager, modelMat); 49 | } 50 | } 51 | 52 | public draw(gl: WebGL2RenderingContext, cameraState?: CameraState, renderModeOverride?: number) { 53 | if (this.visibility === Visibility.Hidden) { 54 | return; 55 | } 56 | 57 | // recursively walk bsp tree and render each node/leaf 58 | this.children[0].draw(gl, cameraState, renderModeOverride); 59 | this.children[1].draw(gl, cameraState, renderModeOverride); 60 | } 61 | 62 | // public toString(indent = ""): string { 63 | 64 | // } 65 | } -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/BSP/Tree.ts/BSPTree.ts: -------------------------------------------------------------------------------- 1 | import { BSPNode } from "./BSPNode"; 2 | import { BSPResourceManager } from "../../BSPResourceManager"; 3 | import { mat4 } from "gl-matrix"; 4 | import { Node } from "../../../../BSP/Structs/Node"; 5 | 6 | export class BSPTree extends BSPNode { 7 | public headNode: Node; 8 | 9 | constructor(node: Node, resourceManager: BSPResourceManager, modelMat: mat4) { 10 | super(node, resourceManager, modelMat); 11 | this.headNode = node; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/BSP/Tree.ts/IBSPTree.ts: -------------------------------------------------------------------------------- 1 | import { IRenderable } from "../../IRenderable"; 2 | 3 | export interface IBSPTree extends IRenderable { 4 | isNode: boolean; 5 | // toString(indent?: string): string; 6 | } -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/BSPResourceManager.ts: -------------------------------------------------------------------------------- 1 | import { CreateShaderProgram } from "../Shaders/Shader"; 2 | import { UniformLocations } from "../Shaders/UniformLocations"; 3 | import { TextureDictionary } from "../Textures/TextureDictionary"; 4 | import { BSP } from "../../BSP/BSP"; 5 | import { ShaderSource } from "../Shaders/ShaderSource"; 6 | 7 | export class BSPResourceManager { 8 | private gl: WebGL2RenderingContext; 9 | private textureDictionary: TextureDictionary; 10 | private bsp: BSP; 11 | private shaderPrograms: WebGLProgram[] = []; 12 | 13 | constructor(gl: WebGL2RenderingContext, bsp: BSP) { 14 | this.gl = gl; 15 | this.textureDictionary = new TextureDictionary(this.gl); 16 | this.bsp = bsp; 17 | } 18 | 19 | public getGLContext() { 20 | return this.gl; 21 | } 22 | public setGLContext(newContext: WebGL2RenderingContext) { 23 | this.gl = newContext; 24 | } 25 | 26 | public getTextureDictionary() { 27 | return this.textureDictionary; 28 | } 29 | 30 | public getBSP() { 31 | return this.bsp; 32 | } 33 | 34 | // all shader programs are stored in an array. This function will return the index for the position of that program 35 | public createShaderProgram(): number { 36 | const program = this.gl.createProgram(); 37 | if (program === null) { 38 | throw new Error("Failed to create shader program"); 39 | } 40 | this.shaderPrograms.push(program); 41 | return this.shaderPrograms.length - 1; 42 | } 43 | 44 | public getUniformLocations(index: number): UniformLocations { 45 | return new UniformLocations(this.gl, this.shaderPrograms[index]); 46 | } 47 | 48 | public getShaderProgram(index: number) { 49 | return this.shaderPrograms[index]; 50 | } 51 | 52 | public addShaders(index: number, shaders: ShaderSource[]) { 53 | shaders.forEach((shader) => { 54 | this.gl.attachShader(this.shaderPrograms[index], shader.compileShader(this.gl)); 55 | }); 56 | 57 | this.gl.linkProgram(this.shaderPrograms[index]); 58 | // check for errors 59 | if (!this.gl.getProgramParameter(this.shaderPrograms[index], this.gl.LINK_STATUS)) { 60 | console.log("Unable to initialize shader program: " + this.gl.getProgramInfoLog(this.shaderPrograms[index])); 61 | return null; 62 | } 63 | 64 | } 65 | } -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/IRenderable.ts: -------------------------------------------------------------------------------- 1 | import { CameraState } from "../Camera/CameraState"; 2 | 3 | export interface IRenderable { 4 | visibility: Visibility; 5 | draw(gl: WebGL2RenderingContext, cameraState?: CameraState, renderTypeOverride?: number): void; 6 | 7 | // update(); TODO: for updating game state 8 | } 9 | 10 | export enum Visibility { 11 | Visible, 12 | Hidden, 13 | } -------------------------------------------------------------------------------- /src/Rendering/RenderObjects/RenderObject.ts: -------------------------------------------------------------------------------- 1 | import { Vertex } from "../../Structs/Vertex"; 2 | import { IRenderable, Visibility } from "./IRenderable"; 3 | import { POSITION_ATTRIB_LOCATION } from "../Shaders/LayoutLocations"; 4 | import { CameraState } from "../Camera/CameraState"; 5 | 6 | export class RenderObject implements IRenderable { 7 | public visibility = Visibility.Visible; 8 | public VAO!: WebGLVertexArrayObject; 9 | public VBO!: WebGLBuffer; 10 | 11 | private verticeCount: number; 12 | private initialized = false; 13 | private renderType = WebGL2RenderingContext.POINTS; 14 | 15 | constructor(gl: WebGL2RenderingContext, vertices: Vertex[]) { 16 | this.verticeCount = vertices.length; 17 | 18 | // create buffers 19 | const _vbo = gl.createBuffer(); 20 | const _vao = gl.createVertexArray(); 21 | 22 | // check buffer creation was successful 23 | if (_vbo == null) { 24 | console.log("Failed to generate VBO"); 25 | return; 26 | } 27 | 28 | if (_vao == null) { 29 | console.log("Failed to generate VAO"); 30 | return; 31 | } 32 | 33 | this.VBO = _vbo; 34 | this.VAO = _vao; 35 | 36 | // bind buffers 37 | gl.bindBuffer(gl.ARRAY_BUFFER, this.VBO); 38 | 39 | // allocate buffer for all vertices and store them in it 40 | const vertexData = RenderObject.concatBuffers(vertices.map((a) => a.position)); 41 | 42 | // buffer vertex data 43 | gl.bufferData(gl.ARRAY_BUFFER, 44 | vertexData, gl.STATIC_DRAW); 45 | 46 | // create vertex position VAO 47 | gl.bindVertexArray(this.VAO); 48 | gl.enableVertexAttribArray(POSITION_ATTRIB_LOCATION); 49 | gl.vertexAttribPointer( 50 | POSITION_ATTRIB_LOCATION, // attribute location 51 | 3, // size of attribute (vec3) 52 | gl.FLOAT, // type of attribute is float 53 | false, // does not need to be normalized 54 | 0, // 0 = move forward size * sizeof(type) each iteration to get the next position 55 | 0 // offset (start at beginnng of buffer) 56 | ); 57 | 58 | this.initialized = true; 59 | } 60 | 61 | public bind(gl: WebGL2RenderingContext) { 62 | 63 | } 64 | 65 | public draw(gl: WebGL2RenderingContext, cameraState?: CameraState, renderTypeOverride?: number) { 66 | if (!this.initialized) { 67 | console.log("Cannot render object, not initialized"); 68 | return; 69 | } 70 | if (this.visibility === Visibility.Hidden) { 71 | return; 72 | } 73 | 74 | gl.bindVertexArray(this.VAO); 75 | 76 | if (renderTypeOverride != null) { 77 | gl.drawArrays(renderTypeOverride, 0, this.verticeCount); 78 | } else { 79 | gl.drawArrays(this.renderType, 0, this.verticeCount); 80 | } 81 | } 82 | 83 | // source: https://stackoverflow.com/a/14089496 84 | // tslint:disable-next-line:member-ordering 85 | private static concatBuffers(buffers: Float32Array[]): Float32Array { 86 | // create array of buffer lengths 87 | const length = buffers.map((a) => a.length ); 88 | 89 | // add up lengths of all buffers 90 | const bufOut = new Float32Array(length.reduce((a, b) => a + b, 0)); 91 | 92 | for (let i = 0; i < buffers.length; i++) { 93 | // calculate offset from start 94 | const offset = RenderObject.sum(length.slice(0, i)); 95 | 96 | // insert data starting at offset position 97 | bufOut.set(buffers[i], offset); 98 | } 99 | 100 | return bufOut; 101 | } 102 | 103 | // tslint:disable-next-line:member-ordering 104 | private static sum(array: number[]): number { 105 | // tslint:disable-next-line:only-arrow-functions 106 | return array.reduce((a, b) => a + b, 0); 107 | } 108 | } -------------------------------------------------------------------------------- /src/Rendering/Shaders/LayoutLocations.ts: -------------------------------------------------------------------------------- 1 | export const POSITION_ATTRIB_LOCATION = 0; 2 | export const NORMAL_ATTRIB_LOCATION = 1; 3 | export const FALLBACK_COLOR_ATTRIB_LOCATION = 2; 4 | export const TEXTURE_LOADED_ATTRIB_LOCATION = 3; 5 | export const TEXCOORD_ATTRIB_LOCATION = 4; 6 | export const TEXINDEX_ATTRIB_LOCATION = 5; -------------------------------------------------------------------------------- /src/Rendering/Shaders/Shader.ts: -------------------------------------------------------------------------------- 1 | import {ShaderSource} from "./ShaderSource"; 2 | 3 | // export class ShaderProgram { 4 | 5 | // } 6 | 7 | export function CreateShaderProgram(gl: WebGL2RenderingContext, sourceShaders: ShaderSource[]): WebGLProgram | null { 8 | console.log("--Initializing Shaders--"); 9 | console.log(" Number of shaders: " + sourceShaders.length); 10 | 11 | const compiledShaders: WebGLShader[] = []; 12 | 13 | // compile each shader and filter out those that failed 14 | sourceShaders.forEach((shader) => { 15 | const compiledShader = LoadShader(gl, shader); 16 | if (compiledShader != null) { 17 | compiledShaders.push(compiledShader); 18 | } 19 | }); 20 | 21 | // attach shaders to the program 22 | const shaderProgram = gl.createProgram(); 23 | compiledShaders.forEach((shader) => { 24 | gl.attachShader(shaderProgram, shader); 25 | }); 26 | 27 | gl.linkProgram(shaderProgram); 28 | 29 | // check for errors 30 | if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { 31 | console.log("Unable to initialize shader program: " + gl.getProgramInfoLog(shaderProgram)); 32 | return null; 33 | } 34 | 35 | // cleanup 36 | // compiledShaders.forEach(shader => { 37 | // gl.detachShader(shaderProgram, shader); 38 | // }); 39 | 40 | return shaderProgram; 41 | } 42 | 43 | export function LoadShader(gl: WebGL2RenderingContext, shaderSource: ShaderSource): WebGLShader | null { 44 | const shader: WebGLShader | null = gl.createShader(shaderSource.type); 45 | 46 | // compile shader 47 | gl.shaderSource(shader, shaderSource.source); 48 | gl.compileShader(shader); 49 | 50 | // check for errors 51 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 52 | console.log("----------Failed to compile shader----------\n" + shaderSource.source); 53 | console.log("Info log: " + gl.getShaderInfoLog(shader)); 54 | gl.deleteShader(shader); 55 | return null; 56 | } 57 | 58 | return shader; 59 | } 60 | 61 | // function loadTextFile(url: string, callback: Function, gl: WebGLRenderingContext): void { 62 | // var request: XMLHttpRequest = new XMLHttpRequest(); 63 | // request.open("GET", url, true); 64 | // request.addEventListener("load", function(): void { 65 | // callback(request.responseText, gl, urlToShaderType(url, gl)); 66 | // }); 67 | // request.send(); 68 | // } 69 | 70 | // function urlToShaderType(url: string, gl: WebGLRenderingContext): GLenum | null { 71 | // // get extension 72 | // const extension: string | undefined = url.split(".").pop(); 73 | 74 | // switch (extension) { 75 | // case "vert": 76 | // return gl.VERTEX_SHADER; 77 | 78 | // case "frag": 79 | // return gl.FRAGMENT_SHADER; 80 | 81 | // default: 82 | // return null; 83 | // } 84 | // } -------------------------------------------------------------------------------- /src/Rendering/Shaders/ShaderSource.ts: -------------------------------------------------------------------------------- 1 | export class ShaderSource { 2 | public source: string; 3 | public type: GLenum; 4 | 5 | constructor(_source: string, _type: GLenum) { 6 | this.source = _source; 7 | this.type = _type; 8 | } 9 | 10 | public compileShader(gl: WebGL2RenderingContext) { 11 | const shader = gl.createShader(this.type); 12 | 13 | // compile shader 14 | gl.shaderSource(shader, this.source); 15 | gl.compileShader(shader); 16 | 17 | // check for errors 18 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 19 | console.log("----------Failed to compile shader----------\n" + this.source); 20 | console.log("Info log: " + gl.getShaderInfoLog(shader)); 21 | gl.deleteShader(shader); 22 | throw new Error("Failed to compile shader"); 23 | } 24 | 25 | return shader; 26 | } 27 | } 28 | 29 | export const VertShader: ShaderSource = new ShaderSource( 30 | `#version 300 es 31 | 32 | layout (location = 0) in vec4 a_position; 33 | layout (location = 1) in vec4 a_normal; 34 | layout (location = 2) in vec4 a_fallbackColor; 35 | layout (location = 3) in float a_textureLoaded; 36 | layout (location = 4) in vec2 a_texCoord; 37 | layout (location = 5) in float a_texIndex; 38 | 39 | uniform mat4 u_model_mat; 40 | uniform mat4 u_view_mat; 41 | uniform mat4 u_projection_mat; 42 | 43 | out highp vec4 v_norm; 44 | out highp vec4 v_fallbackColor; 45 | out highp vec2 v_uv; 46 | 47 | flat out int v_texIndex; 48 | flat out int v_textureLoaded; 49 | 50 | void main() { 51 | gl_Position = u_projection_mat * u_view_mat * u_model_mat * a_position; 52 | 53 | //temp 54 | gl_PointSize = 20.0; 55 | 56 | v_norm = a_normal; 57 | v_fallbackColor = a_fallbackColor; 58 | v_textureLoaded = int(a_textureLoaded); 59 | v_uv = a_texCoord; 60 | v_texIndex = int(a_texIndex); 61 | 62 | }`, WebGLRenderingContext.VERTEX_SHADER); 63 | 64 | export const FragShader: ShaderSource = new ShaderSource( 65 | `#version 300 es 66 | precision mediump float; 67 | 68 | uniform highp sampler2DArray u_texture_array; 69 | 70 | in highp vec4 v_norm; 71 | in highp vec4 v_fallbackColor; 72 | in highp vec2 v_uv; 73 | 74 | 75 | flat in int v_texIndex; 76 | flat in int v_textureLoaded; 77 | 78 | out vec4 fragColor; 79 | 80 | void main() { 81 | vec4 color = vec4(0.2, 0.2, 0.2, 1.0); 82 | vec4 ambientColor = vec4(0.1, 0.1, 0.1, 1.0); 83 | 84 | vec4 lightDir = vec4(0.707107, 0.707107, 0, 1.0); 85 | 86 | float intensity = clamp(dot(v_norm, lightDir), 0.0, 1.0); 87 | if (intensity > 0.0) { 88 | color += (ambientColor * intensity); 89 | } 90 | 91 | color = clamp(color, 0.0, 1.0); 92 | fragColor = v_fallbackColor + color; 93 | 94 | // if (v_textureLoaded > 0) { 95 | // fragColor = vec4(1, 0, 0, 1); 96 | // // z index of sampler2DArray is the layer 97 | // // fragColor = texture(u_texture_array, vec3(v_uv, v_texIndex)) + color; 98 | // } else { 99 | // fragColor = vec4(0, 0, 1, 1); 100 | // // fragColor = v_fallbackColor + color; 101 | // } 102 | }`, WebGLRenderingContext.FRAGMENT_SHADER); -------------------------------------------------------------------------------- /src/Rendering/Shaders/UniformLocations.ts: -------------------------------------------------------------------------------- 1 | export class UniformLocations { 2 | public uModelMatLocation!: WebGLUniformLocation | null; 3 | public uViewMatLocation!: WebGLUniformLocation | null; 4 | public uProjectionMatrixLocation!: WebGLUniformLocation | null; 5 | public uTextureArrayLocation!: WebGLUniformLocation | null; 6 | 7 | constructor(gl: WebGL2RenderingContext, shaderProgram: WebGLProgram) { 8 | this.uModelMatLocation = gl.getUniformLocation(shaderProgram, "u_model_mat"); 9 | this.uViewMatLocation = gl.getUniformLocation(shaderProgram, "u_view_mat"); 10 | this.uProjectionMatrixLocation = gl.getUniformLocation(shaderProgram, "u_projection_mat"); 11 | this.uTextureArrayLocation = gl.getUniformLocation(shaderProgram, "u_sampler"); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Rendering/Textures/Texture.ts: -------------------------------------------------------------------------------- 1 | import { vec4 } from "gl-matrix"; 2 | 3 | export class Texture { 4 | public name?: string; 5 | public id: number; 6 | public image: HTMLImageElement; 7 | public placeholderColor: vec4; 8 | 9 | constructor(gl: WebGL2RenderingContext, placeholderColor: vec4, id: number, name?: string) { 10 | this.placeholderColor = placeholderColor; 11 | this.id = id; 12 | 13 | 14 | this.image = new Image(); 15 | if (name != null) { 16 | this.name = name; 17 | // TODO: construct url from name 18 | this.image.src = name + ".tga"; 19 | } 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /src/Rendering/Textures/TextureDictionary.ts: -------------------------------------------------------------------------------- 1 | import { Texture } from "./Texture"; 2 | import { addRange } from "../../Utils/AddRange"; 3 | 4 | // a singleton to hold all of the textures 5 | export class TextureDictionary { 6 | private textures: Texture[] = []; 7 | private arrayTexture!: WebGLTexture; 8 | private readonly height = 256; 9 | private readonly width = 256; 10 | private readonly internalFormat = WebGL2RenderingContext.RGBA8; 11 | private currentArrayIndex: number = 0; 12 | 13 | constructor(gl: WebGL2RenderingContext) { 14 | 15 | const texture = gl.createTexture(); 16 | if (texture === null) { 17 | console.log("Failed to create array texture"); 18 | return; 19 | } 20 | gl.activeTexture(gl.TEXTURE0); 21 | 22 | this.arrayTexture = texture; 23 | gl.bindTexture(gl.TEXTURE_2D_ARRAY, texture); 24 | const mipmapLevels = 1; // mipmap level 25 | const maxLayers = 255; // TODO: increase? max supported layers are 255 26 | 27 | // create the array texture that will hold all of the other textures 28 | // textures are stored in the z axis of the texture 29 | // gl.texImage3D(gl.TEXTURE_2D_ARRAY, mipmapLevels, this.internalFormat, this.width, this.height, maxLayers, 30 | // 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0].fill(0, 0, this.width * this.height * maxLayers))); 31 | // 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(repeatArray([0, 0, 0, 0], this.width * this.height * maxLayers))); 32 | 33 | gl.texStorage3D(gl.TEXTURE_2D_ARRAY, mipmapLevels, this.internalFormat, this.width, this.height, maxLayers); 34 | 35 | gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 36 | gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 37 | gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.REPEAT); 38 | gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.REPEAT); 39 | } 40 | 41 | public addTexture(gl: WebGL2RenderingContext, texture: Texture): number { 42 | // make sure that texture is not already stored 43 | for (const key in this.textures) { 44 | if (this.textures.hasOwnProperty(key)) { 45 | if (texture.name === this.textures[key].name) { 46 | return this.textures[key].id; 47 | } 48 | } 49 | } 50 | 51 | gl.activeTexture(gl.TEXTURE0); 52 | gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.arrayTexture); 53 | 54 | texture.id = this.currentArrayIndex; 55 | 56 | if (texture.image != null) { 57 | texture.image.onload = () => { 58 | gl.texSubImage3D(gl.TEXTURE_2D, 59 | 0, 60 | 0, 0, texture.id, 61 | this.width, this.height, 1, 62 | gl.RGBA, gl.UNSIGNED_BYTE, texture.image); 63 | }; 64 | } 65 | 66 | this.textures.push(texture); 67 | 68 | this.currentArrayIndex++; 69 | return this.currentArrayIndex - 1; 70 | } 71 | 72 | public getTextureIndex(name: string): number { 73 | // tslint:disable-next-line:prefer-for-of 74 | for (let i = 0; i < this.textures.length; i++) { 75 | if (this.textures[i].name === name) { 76 | return this.textures[i].id; 77 | } 78 | } 79 | 80 | return -1; 81 | } 82 | } 83 | 84 | // from https://stackoverflow.com/a/50672288 85 | function repeatArray(array: T[], numTimesRepeat: number): T[] { 86 | const retArray: T[] = []; 87 | for (let i = 0; i < numTimesRepeat; i++) { 88 | addRange(retArray, array); 89 | } 90 | return retArray; 91 | } -------------------------------------------------------------------------------- /src/Structs/Vertex.ts: -------------------------------------------------------------------------------- 1 | import {vec2, vec4, vec3} from "gl-matrix"; 2 | 3 | export const TRUE = 1; 4 | export const FALSE = 0; 5 | 6 | export class Vertex { 7 | // public size = (3 + 4 + 4) * 4; // size of struct in bytes 8 | 9 | public position: vec3; 10 | public normal: vec3; 11 | public fallbackColor: vec4; 12 | public textureLoaded: number; 13 | public texCoord: vec2; 14 | public texIndex: number; 15 | 16 | constructor(_pos: vec3, _norm: vec3 = vec3.create(), fallbackColor = vec4.create(), 17 | textureLoaded = FALSE, texCoord: vec2 = vec2.fromValues(0, 1), texIndex = 255) { 18 | this.position = _pos; 19 | this.normal = _norm; 20 | this.fallbackColor = fallbackColor; 21 | this.textureLoaded = textureLoaded; 22 | this.texCoord = texCoord; 23 | this.texIndex = texIndex; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Utils/AddRange.ts: -------------------------------------------------------------------------------- 1 | export function addRange(arr1, arr2) { 2 | arr2.forEach((item) => { 3 | arr1.push(item); 4 | }); 5 | } -------------------------------------------------------------------------------- /src/Utils/KeyPress.ts: -------------------------------------------------------------------------------- 1 | export enum Key { 2 | A = "A", 3 | B = "B", 4 | C = "C", 5 | D = "D", 6 | E = "E", 7 | F = "F", 8 | G = "G", 9 | H = "H", 10 | I = "I", 11 | J = "J", 12 | K = "K", 13 | L = "L", 14 | M = "M", 15 | N = "N", 16 | O = "O", 17 | P = "P", 18 | Q = "Q", 19 | R = "R", 20 | S = "S", 21 | T = "T", 22 | U = "U", 23 | V = "V", 24 | W = "W", 25 | X = "X", 26 | Y = "Y", 27 | Z = "Z", 28 | Escape = "Escape", 29 | } 30 | 31 | export enum KeyModifier { 32 | Shift = "Shift", 33 | } 34 | 35 | export enum KeyState { 36 | Keyup, 37 | Keydown, 38 | } 39 | 40 | export class KeyPress { 41 | public key: Key | null; 42 | public state: KeyState | undefined | null; 43 | public modifier: KeyModifier | undefined | null; 44 | 45 | constructor(key: Key | null, state?: KeyState, modifier?: KeyModifier) { 46 | this.key = key; 47 | this.modifier = modifier; 48 | this.state = state; 49 | } 50 | } -------------------------------------------------------------------------------- /src/Utils/LZMA/lzma.ts: -------------------------------------------------------------------------------- 1 | // export class LZMA { 2 | 3 | // } -------------------------------------------------------------------------------- /src/Utils/LimitAngle.ts: -------------------------------------------------------------------------------- 1 | // limit angle between min and max 2 | export function limitAngle(angle: number, min: number, max: number) { 3 | if (angle < min) { 4 | return min; 5 | } else if (angle > max) { 6 | return max; 7 | } else { 8 | return angle; 9 | } 10 | } -------------------------------------------------------------------------------- /src/Utils/MeshFactory.ts: -------------------------------------------------------------------------------- 1 | // import { Vertex } from "../Structs/Vertex"; 2 | // import { vec3, vec4 } from "gl-matrix"; 3 | export class MeshFactory { 4 | 5 | } 6 | // export class MeshFactory { 7 | // public static createSolidCube(SideLength: number = 10, color: vec4 = vec4.fromValues(1, 0, 0, 1)): Vertex[] { 8 | // const halfSideLength = SideLength / 2; // half side length, otherwise creates cube with 2x length requested 9 | 10 | // return [ 11 | // new Vertex(vec3.fromValues(-halfSideLength, -halfSideLength, -halfSideLength), color), 12 | // new Vertex(vec3.fromValues(-halfSideLength, -halfSideLength, halfSideLength), color), 13 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, -halfSideLength), color), 14 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, -halfSideLength), color), 15 | // new Vertex(vec3.fromValues(-halfSideLength, -halfSideLength, halfSideLength), color), 16 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, halfSideLength), color), 17 | 18 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, -halfSideLength), color), 19 | // new Vertex(vec3.fromValues(halfSideLength, halfSideLength, -halfSideLength), color), 20 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, halfSideLength), color), 21 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, halfSideLength), color), 22 | // new Vertex(vec3.fromValues(halfSideLength, halfSideLength, -halfSideLength), color), 23 | // new Vertex(vec3.fromValues(halfSideLength, halfSideLength, halfSideLength), color), 24 | 25 | // new Vertex(vec3.fromValues(-halfSideLength, -halfSideLength, -halfSideLength), color), 26 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, -halfSideLength), color), 27 | // new Vertex(vec3.fromValues(-halfSideLength, -halfSideLength, halfSideLength), color), 28 | // new Vertex(vec3.fromValues(-halfSideLength, -halfSideLength, halfSideLength), color), 29 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, -halfSideLength), color), 30 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, halfSideLength), color), 31 | 32 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, -halfSideLength), color), 33 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, halfSideLength), color), 34 | // new Vertex(vec3.fromValues(halfSideLength, halfSideLength, -halfSideLength), color), 35 | // new Vertex(vec3.fromValues(halfSideLength, halfSideLength, -halfSideLength), color), 36 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, halfSideLength), color), 37 | // new Vertex(vec3.fromValues(halfSideLength, halfSideLength, halfSideLength), color), 38 | 39 | // new Vertex(vec3.fromValues(-halfSideLength, -halfSideLength, -halfSideLength), color), 40 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, -halfSideLength), color), 41 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, -halfSideLength), color), 42 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, -halfSideLength), color), 43 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, -halfSideLength), color), 44 | // new Vertex(vec3.fromValues(halfSideLength, halfSideLength, -halfSideLength), color), 45 | 46 | // new Vertex(vec3.fromValues(-halfSideLength, -halfSideLength, halfSideLength), color), 47 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, halfSideLength), color), 48 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, halfSideLength), color), 49 | // new Vertex(vec3.fromValues(-halfSideLength, halfSideLength, halfSideLength), color), 50 | // new Vertex(vec3.fromValues(halfSideLength, -halfSideLength, halfSideLength), color), 51 | // new Vertex(vec3.fromValues(halfSideLength, halfSideLength, halfSideLength), color) 52 | // ]; 53 | // } 54 | // } -------------------------------------------------------------------------------- /src/Utils/WrapAngle.ts: -------------------------------------------------------------------------------- 1 | // wrap angle between 0 and 360 degrees 2 | export function wrapAngle(angle: number) { 3 | 4 | while (angle >= 360) { 5 | angle -= 360; 6 | } 7 | 8 | while (angle < 0) { 9 | angle += 360; 10 | } 11 | return angle; 12 | } -------------------------------------------------------------------------------- /src/Utils/ZipArray.ts: -------------------------------------------------------------------------------- 1 | // source https://gist.github.com/jonschlinkert/2c5e5cd8c3a561616e8572dd95ae15e3 2 | export function zip(a, b) { 3 | const arr: any[] = []; 4 | for (const key in a) { 5 | if (a.hasOwnProperty(key)) { 6 | arr.push([a[key], b[key]]); 7 | } 8 | } 9 | return arr; 10 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { EngineCore } from "./Rendering/EngineCore"; 2 | import { MeshFactory } from "./Utils/MeshFactory"; 3 | import { RenderObject } from "./Rendering/RenderObjects/RenderObject"; 4 | import { Vertex } from "./Structs/Vertex"; 5 | import { vec4, vec3 } from "gl-matrix"; 6 | import { BSP } from "./BSP/BSP"; 7 | import { LumpType } from "./BSP/Lumps/LumpType"; 8 | import { BSPMesh } from "./Rendering/RenderObjects/BSP/BSPMesh"; 9 | import { DispInfoLump } from "./BSP/Lumps/DispInfoLump"; 10 | import { FaceLump } from "./BSP/Lumps/FaceLump"; 11 | import { DispVert } from "./BSP/Structs/DispVert"; 12 | import { DispVertLump } from "./BSP/Lumps/DispVertLump"; 13 | import { DispTrisLump } from "./BSP/Lumps/DispTrisLump"; 14 | import { LeafLump } from "./BSP/Lumps/LeafLump"; 15 | 16 | // export function so it can be called globally 17 | // @ts-ignore 18 | window.initWebGL = initWebGL; 19 | 20 | function initWebGL(): void { 21 | const bspRenderer = new BSPRenderer(); 22 | } 23 | 24 | class BSPRenderer { 25 | public gl!: WebGL2RenderingContext | null; 26 | public renderer!: EngineCore; 27 | 28 | constructor() { 29 | const canvas: HTMLCanvasElement = document.getElementById("canvas") as HTMLCanvasElement; 30 | if (!canvas) { 31 | alert("Could not find canvas"); 32 | return; 33 | } 34 | 35 | // get webgl2 context 36 | this.gl = canvas.getContext("webgl2"); 37 | 38 | if (!this.gl) { 39 | alert("Unable to initialize WebGL2. Your browser may not support it."); 40 | return; 41 | } 42 | 43 | console.log("WebGL Version: " + this.gl.VERSION); 44 | console.log("WebGL Shader Language Version: " + this.gl.SHADING_LANGUAGE_VERSION); 45 | 46 | this.renderer = new EngineCore(this.gl); 47 | this.renderer.main(); 48 | 49 | this.setupBtnListeners(); 50 | } 51 | 52 | public setupBtnListeners() { 53 | // setup button listener for open file dialog 54 | const openBtn = document.getElementById("openBtn"); 55 | if (openBtn == null) { 56 | console.log("Open button was null"); 57 | return; 58 | } 59 | openBtn.addEventListener("click", this.openFileBtnCallback.bind(this), false); 60 | 61 | const fileDialog = document.getElementById("fileDialog") as HTMLInputElement; 62 | fileDialog.value = ""; 63 | 64 | if (!fileDialog) { 65 | return; 66 | } 67 | 68 | if (fileDialog == null) { 69 | console.log("fileDialog was null"); 70 | return; 71 | } 72 | 73 | // event that handles when file is selected 74 | fileDialog.addEventListener("change", () => { 75 | if (fileDialog.files == null) { 76 | console.log("selected files were null"); 77 | return; 78 | } 79 | 80 | const file = fileDialog.files[0]; 81 | if (file == null) { 82 | return; 83 | } 84 | 85 | // console.log(file); 86 | if (file.name.match(/.*\.(bsp)$/gm)) { 87 | const reader = new FileReader(); 88 | 89 | reader.onload = this.readBSP.bind(this); 90 | reader.readAsArrayBuffer(file); 91 | } else { 92 | console.log("Only BSP files are supported"); 93 | } 94 | }, false); 95 | } 96 | 97 | public openFileBtnCallback() { 98 | const fileDialog = document.getElementById("fileDialog") as HTMLInputElement; 99 | 100 | if (fileDialog == null) { 101 | console.log("fileDialog was null"); 102 | return; 103 | } 104 | 105 | fileDialog.click(); 106 | } 107 | 108 | public readBSP(e: FileReaderProgressEvent) { 109 | if (e.target == null) { 110 | throw new Error("BSP Read Error"); 111 | } 112 | const bsp = new BSP(e.target.result); 113 | 114 | if (this.gl == null) { 115 | return; 116 | } 117 | // const lump = bsp.readLump(LumpType.Leafs) as LeafLump; 118 | // console.log(lump.toString()); 119 | // bsp.printLumps(); 120 | this.renderer.clearRenderObjects(); 121 | this.renderer.addRenderableObject(new BSPMesh(this.gl, bsp)); 122 | // start rendering frames 123 | this.renderer.renderFrame = true; 124 | } 125 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "noImplicitAny": false, 7 | // "moduleResolution": "node", 8 | // "watch": true, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | "sourceMap": true, /* Generates corresponding '.map' file. */ 15 | // "outFile": "../main.js", /* Concatenate and emit output to single file. */ 16 | // "outDir": "../", /* Redirect output structure to the directory. */ 17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | "isolatedModules": false, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | "typeRoots": ["node_modules/@types/"], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | } 60 | } -------------------------------------------------------------------------------- /tsconfig.watch.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "noImplicitAny": false, 7 | // "moduleResolution": "node", 8 | // "watch": true, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | "sourceMap": true, /* Generates corresponding '.map' file. */ 15 | // "outFile": "../main.js", /* Concatenate and emit output to single file. */ 16 | // "outDir": "../", /* Redirect output structure to the directory. */ 17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | "typeRoots": ["node_modules/@types/"], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | } 60 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-empty": false, 9 | "indent": true, 10 | "variable-name": false, 11 | "no-console": false, 12 | "no-trailing-whitespace": false, 13 | "no-bitwise": false, 14 | "eofline": false, 15 | "no-consecutive-blank-lines": false, 16 | "ordered-imports": false, 17 | "max-classes-per-file": false, 18 | "trailing-comma": false, 19 | "member-ordering": false, 20 | "align": false, 21 | "prefer-for-of": false 22 | }, 23 | "rulesDirectory": [] 24 | } --------------------------------------------------------------------------------