├── .appveyor.yml ├── .editorconfig ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── COPYING.WTFPL ├── README.md ├── TODO ├── misc ├── hl1.bat ├── hl1.cfg ├── hl2.bat ├── hl2.cfg ├── hl2eps.bat ├── hl2eps.cfg ├── portal.bat ├── portal.cfg ├── portal2.cfg ├── rtbr.cfg ├── run-ep1.sh ├── run-ep2.sh ├── run-hl1.sh └── run-portal.sh └── src ├── OpenSource.c ├── ahash.h ├── atlas.c ├── atlas.h ├── bsp.c ├── bsp.h ├── cache.c ├── cache.h ├── camera.c ├── camera.h ├── collection.c ├── collection.h ├── common.h ├── dxt.c ├── dxt.h ├── etcpack.c ├── etcpack.h ├── filemap.c ├── filemap.h ├── khronos ├── glext.h └── wglext.h ├── libc.h ├── log.c ├── log.h ├── material.c ├── material.h ├── mempools.h ├── profiler.c ├── profiler.h ├── render.c ├── render.h ├── texture.c ├── texture.h ├── vbsp.h ├── vmfparser.c ├── vmfparser.h ├── vpk.h ├── vtf.h └── zip.h /.appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2019 3 | 4 | configuration: 5 | - Debug 6 | - Release 7 | 8 | environment: 9 | matrix: 10 | # - COMPILER: Visual Studio 14 2015 11 | # ARCHITECTURE: x64 12 | # - COMPILER: Visual Studio 14 2015 13 | # ARCHITECTURE: Win32 14 | # - COMPILER: Visual Studio 15 2017 15 | # ARCHITECTURE: x64 16 | # - COMPILER: Visual Studio 15 2017 17 | # ARCHITECTURE: Win32 18 | - COMPILER: Visual Studio 16 2019 19 | ARCHITECTURE: x64 20 | - COMPILER: Visual Studio 16 2019 21 | ARCHITECTURE: Win32 22 | 23 | build_script: 24 | - git submodule update --init --recursive 25 | - mkdir build 26 | - cd build 27 | - cmake .. -G "%COMPILER%" -A %ARCHITECTURE% 28 | - cmake --build . --config %CONFIGURATION% 29 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | tab_width = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CMake release build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | matrix: 8 | os: [windows-latest, ubuntu-latest] 9 | 10 | steps: 11 | - name: Apt update 12 | if: matrix.os == 'ubuntu-latest' 13 | run: sudo apt update 14 | - name: Install dependencies 15 | if: matrix.os == 'ubuntu-latest' 16 | run: sudo apt install libxfixes-dev mesa-common-dev libgl1-mesa-dev 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | with: 20 | submodules: true 21 | - name: Create build dir 22 | run: cmake -E make_directory ${{ runner.workspace }}/build 23 | - name: Configure 24 | working-directory: ${{ runner.workspace }}/build 25 | run: cmake ${{ github.workspace }} -DCMAKE_BUILD_TYPE=Release 26 | - name: Build 27 | working-directory: ${{ runner.workspace }}/build 28 | run: cmake --build . --config Release 29 | - name: Prepare release archive 30 | if: matrix.os == 'windows-latest' 31 | run: | 32 | cmake -E make_directory ${{ runner.workspace }}/OpenSource-win64 33 | cmake -E copy ${{ runner.workspace }}/build/Release/OpenSource.exe ${{ runner.workspace }}/OpenSource-win64/ 34 | cmake -E copy ${{ github.workspace }}/README.md ${{ runner.workspace }}/OpenSource-win64/ 35 | cmake -E copy ${{ github.workspace }}/misc/hl1.cfg ${{ github.workspace }}/misc/hl1.bat ${{ runner.workspace }}/OpenSource-win64/ 36 | cmake -E copy ${{ github.workspace }}/misc/hl2.cfg ${{ github.workspace }}/misc/hl2.bat ${{ runner.workspace }}/OpenSource-win64/ 37 | cmake -E copy ${{ github.workspace }}/misc/hl2eps.cfg ${{ github.workspace }}/misc/hl2eps.bat ${{ runner.workspace }}/OpenSource-win64/ 38 | cmake -E copy ${{ github.workspace }}/misc/portal.cfg ${{ github.workspace }}/misc/portal.bat ${{ runner.workspace }}/OpenSource-win64/ 39 | powershell Compress-Archive ${{ runner.workspace }}/OpenSource-win64/* ${{ runner.workspace }}/OpenSource-win64.zip 40 | - name: Upload artifacts 41 | if: matrix.os == 'windows-latest' 42 | uses: actions/upload-artifact@v3 43 | with: 44 | name: OpenSource-win64 45 | path: ${{ runner.workspace }}/OpenSource-win64 46 | - name: Make GitHub release if tagged 47 | if: matrix.os == 'windows-latest' && startsWith(github.ref, 'refs/tags/v') 48 | uses: softprops/action-gh-release@v0.1.5 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | # Note-worthy description of changes in release 53 | #body: # optional 54 | # Path to load note-worthy description of changes in release from 55 | #body_path: # optional 56 | # Gives the release a custom name. Defaults to tag name 57 | #name: # optional 58 | # Creates a draft release. Defaults to false 59 | draft: true # optional 60 | # Identify the release as a prerelease. Defaults to false 61 | prerelease: true # optional 62 | # Newline-delimited list of path globs for asset files to upload 63 | # TODO prepare proper zip with useful things 64 | files: ${{ runner.workspace }}/OpenSource-win64.zip 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Debug/ 2 | Release/ 3 | .DS_Store 4 | xcuserdata 5 | project.xcworkspace 6 | compile_commands.json 7 | *~ 8 | *.swp 9 | *.swo 10 | *.o 11 | *.a 12 | *.log 13 | *.bsp 14 | *.app 15 | build/ 16 | tags 17 | .vs 18 | *.opendb 19 | *.db 20 | *.user 21 | .cache 22 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3p/atto"] 2 | path = src/atto 3 | url = https://github.com/w23/atto.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | dist: bionic 3 | addons: 4 | apt: 5 | packages: 6 | - libxfixes-dev 7 | - mesa-common-dev 8 | - libgl1-mesa-dev 9 | compiler: 10 | - clang 11 | - gcc 12 | script: 13 | - mkdir build 14 | - cd build 15 | - cmake -DCMAKE_BUILD_TYPE=Release .. 16 | - cmake --build . 17 | 18 | matrix: 19 | include: 20 | - name: "MSVC2017" 21 | os: windows 22 | env: 23 | - MSBUILD_PATH="c:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin" 24 | script: 25 | - export PATH=$MSBUILD_PATH:$PATH 26 | - mkdir build 27 | - cd build 28 | - cmake -G "Visual Studio 15 2017" -A x64 .. 29 | - cmake --build . --config Release 30 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(OpenSource) 3 | 4 | if(WIN32) 5 | # required to set SUBSYSTEM linker flag to WINDOWS 6 | # because we don't want it to be CONSOLE 7 | set(EXE_SUBSYSTEM WIN32) 8 | endif() 9 | 10 | add_subdirectory(src/atto) 11 | 12 | set(SOURCES 13 | src/OpenSource.c 14 | src/atlas.c 15 | src/bsp.c 16 | src/cache.c 17 | src/camera.c 18 | src/collection.c 19 | src/dxt.c 20 | src/filemap.c 21 | src/log.c 22 | src/material.c 23 | src/profiler.c 24 | src/render.c 25 | src/texture.c 26 | src/vmfparser.c 27 | ) 28 | 29 | set(HEADERS 30 | src/ahash.h 31 | src/atlas.h 32 | src/bsp.h 33 | src/cache.h 34 | src/camera.h 35 | src/collection.h 36 | src/common.h 37 | src/dxt.h 38 | src/etcpack.h 39 | src/filemap.h 40 | src/libc.h 41 | src/log.h 42 | src/material.h 43 | src/mempools.h 44 | src/profiler.h 45 | src/render.h 46 | src/texture.h 47 | src/vbsp.h 48 | src/vmfparser.h 49 | src/vpk.h 50 | src/vtf.h 51 | src/zip.h 52 | ) 53 | 54 | add_executable(OpenSource ${EXE_SUBSYSTEM} ${SOURCES} ${HEADERS}) 55 | 56 | target_link_libraries(OpenSource atto) 57 | set_target_properties(OpenSource PROPERTIES 58 | C_STANDARD 99 59 | C_STANDARD_REQUIRED TRUE 60 | C_EXTENSIONS ON) 61 | if(MSVC) 62 | add_compile_definitions(OpenSource _CRT_SECURE_NO_WARNINGS) 63 | endif() 64 | target_include_directories(OpenSource PRIVATE src/khronos) 65 | target_compile_options(OpenSource PRIVATE 66 | $<$:/W4 /WX> 67 | $<$>:-Wall -Wextra -pedantic -Werror> 68 | ) 69 | -------------------------------------------------------------------------------- /COPYING.WTFPL: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Github build status](https://github.com/w23/OpenSource/actions/workflows/build.yml/badge.svg)](https://github.com/w23/OpenSource/actions/workflows/build.yml) [![Build Status](https://travis-ci.org/w23/OpenSource.png)](https://travis-ci.org/w23/OpenSource) [![Build status](https://ci.appveyor.com/api/projects/status/rgu44jqi1kt2jpw9?svg=true)](https://ci.appveyor.com/project/w23/opensource) 2 | 3 | OpenSource 4 | ========== 5 | A utility for loading and rendering many Source VBSP maps together as a single giant mesh. It can be used to see how big the game world is, just for amusement. 6 | 7 | ## Current status 8 | It is not production quality and is not ready for any professional and/or unsupervised use. It still has a lot of visual and other glitches. See issues. 9 | However, it should be generally stable. It does run on Raspberry Pi. The entire Half-Life 2 fits into < 512MiB video memory and renders ~1.5 million triangles at about 10fps. 10 | 11 | If you wish, you could check out the *old* branch for a version from 2012 that was used for this video of entire Half-Life 1 world: https://www.youtube.com/watch?v=-SaRdQdW-Ik 12 | 13 | ## What works 14 | - It builds and runs on Windows, Linux/X11 and Raspberry Pi (bare VideoCore libs, w/o X11); No macOS support yet, stay tuned. 15 | - VBSP format version 19 and 20, most of the maps from these games: 16 | - Half-Life: Source 17 | - Half-Life 2 18 | - Half-Life 2: Episode One 19 | - Half-Life 2: Episode Two 20 | - Portal 21 | - Portal 2. Well, somewhat. Its levels are not positioned correctly, requiring a lot of manual config patching, which has not been done. 22 | - Basic support for the following map features: 23 | - Face geometry 24 | - Displacements 25 | - Base[0] textures 26 | - DXT1/3/5 textures 27 | - Reading VPK2 files 28 | - Reading materials from pakfile lumps 29 | - Packing textures with ETC1 on Raspberry Pi (packer is very naive and probably broken) 30 | 31 | ## Building 32 | 33 | Requires CMake. Something like this: 34 | ``` 35 | cmake -E make_directory build 36 | cmake . -B build -DCMAKE_BUILD_TYPE=Release 37 | cmake --build build 38 | ``` 39 | 40 | ## Getting binaries 41 | If you don't want to build it yourself, you can find some pre-built Windows binaries in [Releases](https://github.com/w23/OpenSource/releases) 42 | 43 | ## Running 44 | Basically you point OpenSource binary to a cfg file. It should automatically load game resources from default Steam install directory. 45 | There is a couple of pre-made cfg files for a few games. You can find them in pre-built archives, or in misc/ directory of this repo. 46 | 47 | Preconfigured games are: 48 | - `hl1.cfg`: Half-Life 1. For this you need to install "Half-Life: Source" game from Steam. Regular Half-Life won't work. 49 | - `hl2.cfg`: Half-Life 2. 50 | - `hl2eps.cfg`: Half-Life 2, including Episode One, and Episode Two. You need them to be installed from Steam. 51 | 52 | Additional options: 53 | - `-s` -- specify Steam install directory if it's different from the default one. 54 | - `-m` -- add additional map to load at origin 55 | - `-p` -- add a custom VPK file to load resources from 56 | - `-d` -- add a custom directory to load resources from 57 | - `-n` -- specify a limit to number of maps to load 58 | 59 | Notes: 60 | - Arguments order matters: options only apply to what follows them. E.g. `./OpenSource hl1.cfg -s ` will not use `` for loading resources for `hl1.cfg`, but `./OpenSource -s hl1.cfg` will. 61 | - cfg files are not strictly necessary, it is possible to load maps only using arguments. However, landmark patching functionality is only supported via cfg files. 62 | 63 | ## Streaming (ON HOLD) 64 | Development was done almost entirely live. 65 | 66 | Stream links: 67 | - [Twitch/ProvodGL](https://twitch.tv/provodgl) 68 | - [YouTube](https://www.youtube.com/c/IvanAvdeev/live) 69 | 70 | You can also check out [previous streams recordings](https://www.youtube.com/playlist?list=PLP0z1CQXyu5DjL_3-7lukQmKGYq2LhxKA) and [stuff planned for next streams](https://github.com/w23/OpenSource/projects/1). 71 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | + grab input 2 | + hide cursor 3 | - simpler camera with limited pitch 4 | + evdev 5 | + kbd 6 | + mouse 7 | + joy 8 | + scan 9 | - move atlas to atto 10 | - move filemap to atto 11 | + rpi build 12 | - entities 13 | - informative log 14 | + displacements 15 | - vpk reading || vpk_fuse 16 | - textures 17 | - materials 18 | - bsp models 19 | - models 20 | - console 21 | - running stats 22 | - ver 20 lightmaps 23 | - visibility clusters 24 | - multiple levels 25 | - bump lightmaps 26 | - valve lighting 27 | 28 | KNOWN ISSUES: 29 | 1. displacement lightmap coordinates are broken. There are visible artifacts. 30 | And core loading them detects wrong dimensions that are larger than the lightmap size 31 | -------------------------------------------------------------------------------- /misc/hl1.bat: -------------------------------------------------------------------------------- 1 | OpenSource.exe hl1.cfg 2 | -------------------------------------------------------------------------------- /misc/hl1.cfg: -------------------------------------------------------------------------------- 1 | gamedir "Half-Life 2" 2 | dir "hl1" 3 | vpk "hl1_hd/hl1_hd_pak_dir.vpk" 4 | vpk "hl1/hl1_pak_dir.vpk" 5 | vpk "hl2/hl2_misc_dir.vpk" 6 | vpk "hl2/hl2_textures_dir.vpk" 7 | max_maps 128 8 | 9 | map c0a0a 10 | 11 | // FIXME Half-Life 1 also needs landmark patching in a few places 12 | -------------------------------------------------------------------------------- /misc/hl2.bat: -------------------------------------------------------------------------------- 1 | OpenSource.exe hl2.cfg 2 | -------------------------------------------------------------------------------- /misc/hl2.cfg: -------------------------------------------------------------------------------- 1 | gamedir "Half-Life 2/hl2" 2 | vpk "hl2_textures_dir.vpk" 3 | vpk "hl2_misc_dir.vpk" 4 | vpk "hl2_pak_dir.vpk" 5 | dir "" 6 | max_maps 99 7 | 8 | map d1_trainstation_01 9 | 10 | patch_landmarks { 11 | "d1_trainstation_05" { 12 | "landmark_trainstation_04-05" "-8561.0 -4127.0 0.0" 13 | } 14 | "d1_canals_11" { 15 | "canals_trans_0809" "" 16 | } 17 | "d3_c17_01" { 18 | "landmark_trainstation_04-05" "-5424.0 -992.0 -439.0" 19 | } 20 | "d3_c17_03" { 21 | "d3_c17_02-03" "-5880 -4204 135" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /misc/hl2eps.bat: -------------------------------------------------------------------------------- 1 | OpenSource.exe hl2eps.cfg 2 | -------------------------------------------------------------------------------- /misc/hl2eps.cfg: -------------------------------------------------------------------------------- 1 | gamedir "Half-Life 2/hl2" 2 | vpk "hl2_textures_dir.vpk" 3 | vpk "hl2_misc_dir.vpk" 4 | vpk "hl2_pak_dir.vpk" 5 | dir "" 6 | max_maps 200 7 | z_far 1000000 8 | 9 | map d1_trainstation_01 10 | 11 | gamedir "Half-Life 2/episodic" 12 | vpk "ep1_pak_dir.vpk" 13 | dir "" 14 | map { 15 | name ep1_c17_00 16 | offset "27000 31000 -2400" 17 | } 18 | 19 | gamedir "Half-Life 2/ep2" 20 | vpk "ep2_pak_dir.vpk" 21 | dir "" 22 | map { 23 | name ep2_outland_01 24 | offset "-40000.000000 26000.000000 -2000.000000" 25 | } 26 | 27 | patch_landmarks { 28 | "d1_trainstation_05" { 29 | "landmark_trainstation_04-05" "-8561.0 -4127.0 0.0" 30 | } 31 | "d1_canals_11" { 32 | "canals_trans_0809" "" 33 | } 34 | "d3_c17_01" { 35 | "landmark_trainstation_04-05" "-5424.0 -992.0 -439.0" 36 | } 37 | "d3_c17_03" { 38 | "d3_c17_02-03" "-5880 -4204 135" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /misc/portal.bat: -------------------------------------------------------------------------------- 1 | OpenSource.exe portal.cfg 2 | -------------------------------------------------------------------------------- /misc/portal.cfg: -------------------------------------------------------------------------------- 1 | gamedir "Portal/portal" 2 | dir "" 3 | vpk "portal_pak_dir.vpk" 4 | 5 | gamedir "Portal/hl2" 6 | vpk "hl2_misc_dir.vpk" 7 | vpk "hl2_textures_dir.vpk" 8 | 9 | max_maps 128 10 | map testchmb_a_00 11 | -------------------------------------------------------------------------------- /misc/portal2.cfg: -------------------------------------------------------------------------------- 1 | //gamedir "Half-Life 2/hl2" 2 | //vpk "hl2_textures_dir.vpk" 3 | //vpk "hl2_misc_dir.vpk" 4 | 5 | //gamedir "Portal/portal" 6 | //vpk portal_pak_dir.vpk 7 | 8 | gamedir "Portal 2/portal2" 9 | vpk pak01_dir.vpk 10 | dir "" 11 | 12 | max_maps 63 13 | //max_maps 1 14 | map "sp_a1_intro1" 15 | map "sp_a1_intro2" 16 | map "sp_a1_intro3" 17 | map "sp_a1_intro4" 18 | map "sp_a1_intro5" 19 | map "sp_a1_intro6" 20 | map "sp_a1_intro7" 21 | map "sp_a1_wakeup" 22 | map "sp_a2_intro" 23 | map "sp_a2_laser_intro" 24 | map "sp_a2_laser_stairs" 25 | map "sp_a2_dual_lasers" 26 | map "sp_a2_laser_over_goo" 27 | map "sp_a2_catapult_intro" 28 | map "sp_a2_trust_fling" 29 | map "sp_a2_pit_flings" 30 | map "sp_a2_fizzler_intro" 31 | map "sp_a2_sphere_peek" 32 | map "sp_a2_ricochet" 33 | map "sp_a2_bridge_intro" 34 | map "sp_a2_bridge_the_gap" 35 | map "sp_a2_turret_intro" 36 | map "sp_a2_laser_relays" 37 | map "sp_a2_turret_blocker" 38 | map "sp_a2_laser_vs_turret" 39 | map "sp_a2_pull_the_rug" 40 | map "sp_a2_column_blocker" 41 | map "sp_a2_laser_chaining" 42 | map "sp_a2_triple_laser" 43 | map "sp_a2_bts1" 44 | map "sp_a2_bts2" 45 | map "sp_a2_bts3" 46 | map "sp_a2_bts4" 47 | map "sp_a2_bts5" 48 | map "sp_a2_bts6" 49 | map "sp_a2_core" 50 | map "sp_a3_00" 51 | map "sp_a3_01" 52 | map "sp_a3_03" 53 | map "sp_a3_jump_intro" 54 | map "sp_a3_bomb_flings" 55 | map "sp_a3_crazy_box" 56 | map "sp_a3_transition01" 57 | map "sp_a3_speed_ramp" 58 | map "sp_a3_speed_flings" 59 | map "sp_a3_portal_intro" 60 | map "sp_a3_end" 61 | map "sp_a4_intro" 62 | map "sp_a4_tb_intro" 63 | map "sp_a4_tb_trust_drop" 64 | map "sp_a4_tb_wall_button" 65 | map "sp_a4_tb_polarity" 66 | map "sp_a4_tb_catch" 67 | map "sp_a4_stop_the_box" 68 | map "sp_a4_laser_catapult" 69 | map "sp_a4_laser_platform" 70 | map "sp_a4_speed_tb_catch" 71 | map "sp_a4_jump_polarity" 72 | map "sp_a4_finale1" 73 | map "sp_a4_finale2" 74 | map "sp_a4_finale3" 75 | map "sp_a4_finale4" 76 | -------------------------------------------------------------------------------- /misc/rtbr.cfg: -------------------------------------------------------------------------------- 1 | gamedir "Source SDK Base 2013 Singleplayer" 2 | vpk "hl2/hl2_textures_dir.vpk" 3 | vpk "hl2/hl2_misc_dir.vpk" 4 | vpk "episodic/ep1_pak_dir.vpk" 5 | vpk "ep2/ep2_pak_dir.vpk" 6 | vpk "sourcetest/sourcetest_pak_dir.vpk" 7 | vpk "platform/platform_misc_dir.vpk" 8 | dir "" 9 | 10 | gamedir "../sourcemods/RaisingTheBarRedux_div2" 11 | vpk "mapbase/mapbase_shared/shared_content_v7_0_dir.vpk" 12 | vpk "mapbase/mapbase_hl2/hl2_materials_dir.vpk" 13 | vpk "mapbase/mapbase_hl2/lostcoast_materials_dir.vpk" 14 | vpk "mapbase/mapbase_hl2/hl2_scenes_dir.vpk" 15 | vpk "mapbase/mapbase_hl2/hl2_mapbase_content_dir.vpk" 16 | vpk "mapbase/mapbase_episodic/episodic_mapbase_content_dir.vpk" 17 | vpk "mapbase/mapbase_episodic/episodic_scenes_dir.vpk" 18 | vpk "mapbase/mapbase_episodic/episodic_materials_dir.vpk" 19 | dir "" 20 | 21 | max_maps 100 22 | map rtbr_d1_trainstation01 23 | -------------------------------------------------------------------------------- /misc/run-ep1.sh: -------------------------------------------------------------------------------- 1 | HL2DIR="$HOME/.local/share/Steam/steamapps/common/Half-Life 2" 2 | EPDIR="$HL2DIR/episodic" 3 | 4 | ./build/desktop-rel-cc/OpenSource \ 5 | -d "$EPDIR" \ 6 | -p "$EPDIR/ep1_pak_dir.vpk" \ 7 | -p "$HL2DIR/hl2/hl2_pak_dir.vpk" \ 8 | -p "$HL2DIR/hl2/hl2_misc_dir.vpk" \ 9 | -p "$HL2DIR/hl2/hl2_textures_dir.vpk" \ 10 | -n 128 \ 11 | ep1_c17_00 12 | -------------------------------------------------------------------------------- /misc/run-ep2.sh: -------------------------------------------------------------------------------- 1 | HL2DIR="$HOME/.local/share/Steam/steamapps/common/Half-Life 2" 2 | EP2DIR="$HL2DIR/ep2" 3 | 4 | ./build/desktop-rel-cc/OpenSource \ 5 | -d "$EP2DIR" \ 6 | -p "$EP2DIR/ep2_pak_dir.vpk" \ 7 | -p "$HL2DIR/hl2/hl2_pak_dir.vpk" \ 8 | -p "$HL2DIR/hl2/hl2_misc_dir.vpk" \ 9 | -p "$HL2DIR/hl2/hl2_textures_dir.vpk" \ 10 | -n 128 \ 11 | ep2_outland_01 12 | -------------------------------------------------------------------------------- /misc/run-hl1.sh: -------------------------------------------------------------------------------- 1 | HL2DIR="$HOME/.local/share/Steam/steamapps/common/Half-Life 2" 2 | HL1DIR="$HL2DIR/hl1" 3 | 4 | ./build/desktop-rel-cc/OpenSource \ 5 | -d "$HL1DIR" \ 6 | -p "$HL1DIR"_hd/hl1_hd_pak_dir.vpk \ 7 | -p "$HL1DIR/hl1_pak_dir.vpk" \ 8 | -p "$HL2DIR/hl2/hl2_misc_dir.vpk" \ 9 | -p "$HL2DIR/hl2/hl2_textures_dir.vpk" \ 10 | -n 128 \ 11 | c0a0a 12 | -------------------------------------------------------------------------------- /misc/run-portal.sh: -------------------------------------------------------------------------------- 1 | BASEDIR="$HOME/.local/share/Steam/steamapps/common/Portal" 2 | HL2DIR="$BASEDIR/hl2" 3 | PDIR="$BASEDIR/portal" 4 | 5 | ./build/desktop-rel-cc/OpenSource \ 6 | -d "$PDIR" \ 7 | -p "$PDIR/portal_pak_dir.vpk" \ 8 | -p "$HL2DIR/hl2_misc_dir.vpk" \ 9 | -p "$HL2DIR/hl2_textures_dir.vpk" \ 10 | -n 128 \ 11 | testchmb_a_00 12 | -------------------------------------------------------------------------------- /src/OpenSource.c: -------------------------------------------------------------------------------- 1 | #include "bsp.h" 2 | #include "cache.h" 3 | #include "collection.h" 4 | #include "mempools.h" 5 | #include "common.h" 6 | #include "log.h" 7 | //#include "profiler.h" 8 | #include "camera.h" 9 | #include "vmfparser.h" 10 | 11 | #include "atto/app.h" 12 | #include "atto/math.h" 13 | 14 | #ifdef _MSC_VER 15 | #pragma warning(disable:4221) 16 | #endif 17 | 18 | static char persistent_data[128*1024*1024]; 19 | static char temp_data[256*1024*1024]; 20 | 21 | static struct Stack stack_temp = { 22 | .storage = temp_data, 23 | .size = sizeof(temp_data), 24 | .cursor = 0 25 | }; 26 | 27 | static struct Stack stack_persistent = { 28 | .storage = persistent_data, 29 | .size = sizeof(persistent_data), 30 | .cursor = 0 31 | }; 32 | 33 | static struct Memories mem = { 34 | &stack_temp, 35 | &stack_persistent 36 | }; 37 | 38 | typedef enum { 39 | MapFlags_Empty = 0, 40 | MapFlags_Loaded = 1, 41 | MapFlags_FixedOffset = 2, 42 | MapFlags_Broken = 4 43 | } MapFlags; 44 | 45 | typedef struct Map { 46 | char *name; 47 | char *depend_name; 48 | int flags; 49 | struct AVec3f offset; 50 | struct AVec3f debug_offset; 51 | struct BSPModel model; 52 | struct Map *prev, *next; 53 | const struct Map *parent; 54 | struct AVec3f parent_offset; 55 | } Map; 56 | 57 | typedef struct Patch { 58 | const char *map_name; 59 | int delete; 60 | BSPLandmark landmark; 61 | struct Patch *next; 62 | } Patch; 63 | 64 | static struct { 65 | struct Camera camera; 66 | int forward, right, run; 67 | struct AVec3f center; 68 | float R; 69 | 70 | struct ICollection *collection_chain; 71 | 72 | Patch *patches; 73 | 74 | Map *maps_begin, *maps_end; 75 | int maps_count, maps_limit; 76 | Map *selected_map; 77 | } g; 78 | 79 | static struct { 80 | const char *steam_basedir; 81 | int maps_limit; 82 | } g_cfg; 83 | 84 | static Map *opensrcAllocMap(StringView name) { 85 | if (g.maps_count >= g_cfg.maps_limit) { 86 | PRINTF("Map limit reached, not trying to add map " PRI_SV, PRI_SVV(name)); 87 | return NULL; 88 | } 89 | 90 | Map *map = g.maps_begin; 91 | while (map) { 92 | if (strncmp(map->name, name.str, name.length) == 0) 93 | return map; 94 | map = map->next; 95 | } 96 | 97 | const int total_size = sizeof(Map) + name.length + 1; 98 | char *buffer = stackAlloc(&stack_persistent, total_size); 99 | if (!buffer) { 100 | PRINT("Not enough memory"); 101 | return NULL; 102 | } 103 | 104 | memset(buffer, 0, total_size); 105 | 106 | map = (void*)buffer; 107 | map->name = buffer + sizeof(Map); 108 | map->depend_name = map->name + name.length + 1; 109 | 110 | memcpy(map->name, name.str, name.length); 111 | 112 | if (!g.maps_end) 113 | g.maps_begin = map; 114 | else { 115 | g.maps_end->next = map; 116 | map->prev = g.maps_end; 117 | } 118 | 119 | g.maps_end = map; 120 | 121 | ++g.maps_count; 122 | 123 | PRINTF("Added new map to the queue: " PRI_SV, PRI_SVV(name)); 124 | return map; 125 | } 126 | 127 | void openSourceAddMap(StringView name) { 128 | opensrcAllocMap(name); 129 | } 130 | 131 | static void mapUpdatePosition(Map *map) { 132 | if (map->parent && !(map->flags & MapFlags_FixedOffset)) 133 | map->offset = aVec3fAdd(map->parent_offset, aVec3fAdd(map->parent->offset, map->parent->debug_offset)); 134 | 135 | PRINTF("Map %s global_offset %f %f %f", map->name, 136 | map->offset.x, map->offset.y, map->offset.z); 137 | } 138 | 139 | static enum BSPLoadResult loadMap(Map *map, ICollection *collection) { 140 | BSPLoadModelContext loadctx = { 141 | .collection = collection, 142 | .persistent = &stack_persistent, 143 | .tmp = &stack_temp, 144 | .model = &map->model, 145 | .name = { .str = map->name, .length = (int)strlen(map->name) }, 146 | .prev_map_name = { .str = NULL, .length = 0 }, 147 | .next_map_name = { .str = NULL, .length = 0 }, 148 | }; 149 | 150 | if (map->prev) { 151 | loadctx.prev_map_name.str = map->prev->name; 152 | loadctx.prev_map_name.length = (int)strlen(map->prev->name); 153 | } 154 | 155 | if (map->next) { 156 | loadctx.next_map_name.str = map->next->name; 157 | loadctx.next_map_name.length = (int)strlen(map->next->name); 158 | } 159 | 160 | const enum BSPLoadResult result = bspLoadWorldspawn(loadctx); 161 | if (result != BSPLoadResult_Success) { 162 | PRINTF("Cannot load map \"%s\": %d", map->name, result); 163 | return result; 164 | } 165 | 166 | aAppDebugPrintf("Loaded %s to %u draw calls", map->name, map->model.detailed.draws_count); 167 | aAppDebugPrintf("AABB (%f, %f, %f) - (%f, %f, %f)", 168 | map->model.aabb.min.x, 169 | map->model.aabb.min.y, 170 | map->model.aabb.min.z, 171 | map->model.aabb.max.x, 172 | map->model.aabb.max.y, 173 | map->model.aabb.max.z); 174 | 175 | for (const Patch *p = g.patches; p; p = p->next) { 176 | if (p->delete || strcasecmp(p->map_name, map->name) != 0) 177 | continue; 178 | 179 | int found = 0; 180 | for (int i = 0; i < map->model.landmarks_count; ++i) { 181 | struct BSPLandmark *lm = map->model.landmarks + i; 182 | if (strcasecmp(p->landmark.name, lm->name) == 0) { 183 | found = 1; 184 | break; 185 | } 186 | } 187 | 188 | if (found) 189 | continue; 190 | 191 | if (map->model.landmarks_count == BSP_MAX_LANDMARKS) { 192 | PRINTF("Too many landmarks for map %s", map->name); 193 | break; 194 | } 195 | 196 | PRINTF("Injecting landmark %s %f %f %f to map %s", 197 | p->landmark.name, 198 | p->landmark.origin.x, 199 | p->landmark.origin.y, 200 | p->landmark.origin.z, 201 | map->name); 202 | memmove(map->model.landmarks + 1, map->model.landmarks, sizeof(BSPLandmark) * map->model.landmarks_count); 203 | map->model.landmarks[0] = p->landmark; 204 | ++map->model.landmarks_count; 205 | } 206 | 207 | PRINTF("Landmarks: %d", map->model.landmarks_count); 208 | for (int i = 0; i < map->model.landmarks_count; ++i) { 209 | struct BSPLandmark *lm = map->model.landmarks + i; 210 | 211 | int deleted = 0; 212 | for (const Patch *p = g.patches; p; p = p->next) { 213 | if (strcasecmp(p->map_name, map->name) == 0 && strcasecmp(p->landmark.name, lm->name) == 0) { 214 | if (p->delete) { 215 | PRINTF("Deleting landmark %s", p->landmark.name); 216 | --map->model.landmarks_count; 217 | memmove(lm, lm + 1, sizeof(BSPLandmark) * ((size_t)map->model.landmarks_count - (size_t)i)); 218 | deleted = 1; 219 | } else { 220 | PRINTF("Modifying landmark %s %f %f %f -> %f %f %f of map %s", 221 | p->landmark.name, 222 | p->landmark.origin.x, 223 | p->landmark.origin.y, 224 | p->landmark.origin.z, 225 | lm->origin.x, 226 | lm->origin.y, 227 | lm->origin.z, 228 | map->name); 229 | lm->origin = p->landmark.origin; 230 | } 231 | 232 | continue; 233 | } 234 | } 235 | 236 | if (deleted) { 237 | --i; 238 | continue; 239 | } 240 | 241 | PRINTF("\t%d: %s -> (%f, %f, %f)", i + 1, lm->name, 242 | lm->origin.x, lm->origin.y, lm->origin.z); 243 | } 244 | 245 | if (map != g.maps_begin && map->model.landmarks_count > 0 && !(map->flags & MapFlags_FixedOffset)) { 246 | for (int k = 0; k < map->model.landmarks_count; ++k) { 247 | const struct BSPLandmark *m1 = map->model.landmarks + k; 248 | 249 | for (struct Map *map2 = g.maps_begin; map2; map2 = map2->next) { 250 | if (map2 == map || !(map2->flags & MapFlags_Loaded)) 251 | continue; 252 | 253 | for (int j = 0; j < map2->model.landmarks_count; ++j) { 254 | const struct BSPLandmark *m2 = map2->model.landmarks + j; 255 | if (strcmp(m1->name, m2->name) == 0) { 256 | map->parent = map2; 257 | map->parent_offset = aVec3fSub(m2->origin, m1->origin); 258 | PRINTF("Map %s parent: %s", map->name, map2->name); 259 | goto loaded; 260 | } // if landmarks match 261 | } // for all landmarks of map 2 262 | } // for all maps (2) 263 | } // for all landmarks of map 1 264 | } 265 | 266 | loaded: 267 | map->flags |= MapFlags_Loaded; 268 | mapUpdatePosition(map); 269 | 270 | return BSPLoadResult_Success; 271 | } 272 | 273 | static void opensrcInit() { 274 | cacheInit(&stack_persistent); 275 | 276 | if (!renderInit()) { 277 | PRINT("Failed to initialize render"); 278 | aAppTerminate(-1); 279 | } 280 | 281 | bspInit(); 282 | 283 | if (BSPLoadResult_Success != loadMap(g.maps_begin, g.collection_chain)) 284 | aAppTerminate(-2); 285 | 286 | g.center = aVec3fMulf(aVec3fAdd(g.maps_begin->model.aabb.min, g.maps_begin->model.aabb.max), .5f); 287 | float r = aVec3fLength(aVec3fSub(g.maps_begin->model.aabb.max, g.maps_begin->model.aabb.min)) * .5f; 288 | 289 | if (g.R < 10000.f) { 290 | g.R = r * 30.f; 291 | } 292 | 293 | aAppDebugPrintf("Center %f, %f, %f, R~=%f", g.center.x, g.center.y, g.center.z, r); 294 | 295 | const float t = 0; 296 | cameraLookAt(&g.camera, 297 | aVec3fAdd(g.center, aVec3fMulf(aVec3f(cosf(t*.5f), sinf(t*.5f), .25f), r*.5f)), 298 | g.center, aVec3f(0.f, 0.f, 1.f)); 299 | } 300 | 301 | static void opensrcResize(ATimeUs timestamp, unsigned int old_w, unsigned int old_h) { 302 | (void)(timestamp); (void)(old_w); (void)(old_h); 303 | renderResize(a_app_state->width, a_app_state->height); 304 | 305 | cameraProjection(&g.camera, 1.f, g.R, 3.1415926f/2.f, (float)a_app_state->width / (float)a_app_state->height); 306 | cameraRecompute(&g.camera); 307 | } 308 | 309 | static void opensrcPaint(ATimeUs timestamp, float dt) { 310 | (void)(timestamp); (void)(dt); 311 | 312 | float move = dt * (g.run?3000.f:300.f); 313 | cameraMove(&g.camera, aVec3f(g.right * move, 0.f, -g.forward * move)); 314 | cameraRecompute(&g.camera); 315 | 316 | renderBegin(); 317 | 318 | int triangles = 0; 319 | int can_load_map = 1; 320 | for (struct Map *map = g.maps_begin; map; map = map->next) { 321 | if (map->flags & MapFlags_Broken) 322 | continue; 323 | 324 | if (!(map->flags & MapFlags_Loaded)) { 325 | if (can_load_map) { 326 | if (BSPLoadResult_Success != loadMap(map, g.collection_chain)) 327 | map->flags |= MapFlags_Broken; 328 | } 329 | 330 | can_load_map = 0; 331 | continue; 332 | } 333 | 334 | const RDrawParams params = { 335 | .camera = &g.camera, 336 | .translation = aVec3fAdd(map->offset, map->debug_offset), 337 | .selected = map == g.selected_map 338 | }; 339 | 340 | renderModelDraw(¶ms, &map->model); 341 | 342 | for (int i = 0; i < map->model.detailed.draws_count; ++i) 343 | triangles += map->model.detailed.draws[i].count / 3; 344 | } 345 | 346 | renderEnd(&g.camera); 347 | 348 | // if (profilerFrame(&stack_temp)) { 349 | // PRINTF("Total triangles: %d", triangles); 350 | // } 351 | } 352 | 353 | static void opensrcKeyPress(ATimeUs timestamp, AKey key, int pressed) { 354 | (void)(timestamp); (void)(key); (void)(pressed); 355 | //printf("KEY %u %d %d\n", timestamp, key, pressed); 356 | 357 | struct AVec3f map_offset = aVec3ff(0); 358 | int moved_map = 0; 359 | 360 | switch (key) { 361 | case AK_Esc: 362 | if (!pressed) break; 363 | if (a_app_state->grabbed) 364 | aAppGrabInput(0); 365 | else 366 | // TODO graceful termination 367 | aAppTerminate(0); 368 | break; 369 | case AK_W: g.forward += pressed?1:-1; break; 370 | case AK_S: g.forward += pressed?-1:1; break; 371 | case AK_A: g.right += pressed?-1:1; break; 372 | case AK_D: g.right += pressed?1:-1; break; 373 | case AK_LeftShift: g.run = pressed; break; 374 | 375 | default: break; 376 | } 377 | 378 | if (pressed) { 379 | switch(key) { 380 | case AK_Up: map_offset.x += 1.f; moved_map = 1; break; 381 | case AK_Down: map_offset.x -= 1.f; moved_map = 1; break; 382 | case AK_Left: map_offset.y -= 1.f; moved_map = 1; break; 383 | case AK_Right: map_offset.y += 1.f; moved_map = 1; break; 384 | case AK_PageUp: map_offset.z += 1.f; moved_map = 1; break; 385 | case AK_PageDown: map_offset.z -= 1.f; moved_map = 1; break; 386 | case AK_Tab: 387 | g.selected_map = g.selected_map ? g.selected_map->next : g.maps_begin; 388 | if (g.selected_map) 389 | PRINTF("Selected map %s", g.selected_map->name); 390 | break; 391 | case AK_Q: 392 | g.selected_map = NULL; 393 | break; 394 | default: break; 395 | } 396 | 397 | if (moved_map && g.selected_map) { 398 | Map *map = g.selected_map; 399 | if (g.run) { 400 | map_offset.x *= 100.f; 401 | map_offset.y *= 100.f; 402 | map_offset.z *= 100.f; 403 | } 404 | map->debug_offset = aVec3fAdd(map->debug_offset, map_offset); 405 | PRINTF("Map %s offset: %f %f %f", map->name, map->debug_offset.x, map->debug_offset.y, map->debug_offset.z); 406 | 407 | for (Map *m = map; m; m = m->next) 408 | mapUpdatePosition(m); 409 | } 410 | } 411 | } 412 | 413 | static void opensrcPointer(ATimeUs timestamp, int dx, int dy, unsigned int btndiff) { 414 | (void)(timestamp); (void)(dx); (void)(dy); (void)(btndiff); 415 | //printf("PTR %u %d %d %x\n", timestamp, dx, dy, btndiff); 416 | if (a_app_state->grabbed) { 417 | cameraRotatePitch(&g.camera, dy * -4e-3f); 418 | cameraRotateYaw(&g.camera, dx * -4e-3f); 419 | cameraRecompute(&g.camera); 420 | } else if (btndiff) 421 | aAppGrabInput(1); 422 | } 423 | 424 | static struct ICollection *addToCollectionChain(struct ICollection *chain, struct ICollection *next) { 425 | if (chain) { 426 | struct ICollection *coll = chain; 427 | while (coll->next) 428 | coll = coll->next; 429 | coll->next = next; 430 | return chain; 431 | } 432 | 433 | return next; 434 | } 435 | 436 | static void opensrcAddLandmarkPatch(StringView map, StringView key, StringView value) { 437 | if (key.length >= BSP_LANDMARK_NAME_LENGTH) { 438 | PRINTF(PRI_SV " is too long", PRI_SVV(key)); 439 | return; 440 | } 441 | 442 | struct AVec3f origin = aVec3ff(0); 443 | // FIXME sscanf limit by value.length 444 | if (value.length >= 5 && 3 != sscanf(value.str, "%f %f %f", &origin.x, &origin.y, &origin.z)) { 445 | PRINTF(PRI_SV " format is wrong", PRI_SVV(value)); 446 | return; 447 | } 448 | 449 | Patch *new_patch = stackAlloc(mem.persistent, sizeof(Patch)); 450 | new_patch->next = g.patches; 451 | g.patches = new_patch; 452 | 453 | char *map_name = stackAlloc(mem.persistent, (size_t)map.length + 1); 454 | memcpy(map_name, map.str, map.length); 455 | map_name[map.length] = '\0'; 456 | new_patch->map_name = map_name; 457 | 458 | new_patch->delete = value.length < 5; 459 | 460 | memcpy(new_patch->landmark.name, key.str, key.length); 461 | new_patch->landmark.name[key.length] = '\0'; 462 | new_patch->landmark.origin = origin; 463 | } 464 | 465 | typedef struct { 466 | StringView gamedir; 467 | StringView map_name; 468 | StringView map_offset; 469 | } Config; 470 | 471 | static char *buildSteamPath(const StringView *gamedir, const StringView *path) { 472 | const int steam_basedir_length = (int)strlen(g_cfg.steam_basedir); 473 | 474 | const int length = steam_basedir_length + gamedir->length + path->length + 4; 475 | char *value = stackAlloc(&stack_temp, length); 476 | if (!value) 477 | return 0; 478 | 479 | int offset = 0; 480 | memcpy(value + offset, g_cfg.steam_basedir, steam_basedir_length); offset += steam_basedir_length; 481 | value[offset++] = '/'; 482 | memcpy(value + offset, gamedir->str, gamedir->length); offset += gamedir->length; 483 | value[offset++] = '/'; 484 | memcpy(value + offset, path->str, path->length); offset += path->length; 485 | value[offset] = '\0'; 486 | 487 | return value; 488 | } 489 | 490 | static VMFAction configPatchCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv); 491 | static VMFAction configReadCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv); 492 | 493 | static VMFAction configLandmarkCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) { 494 | Config *cfg = state->user_data; 495 | 496 | switch (entry) { 497 | case VMFEntryType_KeyValue: 498 | opensrcAddLandmarkPatch(cfg->map_name, kv->key, kv->value); 499 | break; 500 | case VMFEntryType_SectionClose: 501 | cfg->map_name.length = 0; 502 | state->callback = configPatchCallback; 503 | break; 504 | default: 505 | PRINTF("%s: Unexpected entry %d", __func__, entry); 506 | return VMFAction_SemanticError; 507 | } 508 | 509 | return VMFAction_Continue; 510 | } 511 | 512 | static VMFAction configMapCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) { 513 | Config *cfg = state->user_data; 514 | 515 | switch (entry) { 516 | case VMFEntryType_KeyValue: 517 | if (strncasecmp("name", kv->key.str, kv->key.length) == 0) { 518 | cfg->map_name = kv->value; 519 | } else if (strncasecmp("offset", kv->key.str, kv->key.length) == 0) { 520 | cfg->map_offset = kv->value; 521 | } else { 522 | PRINTF("%s: Unexpected key \"" PRI_SV "\"", __func__, PRI_SVV(kv->key)); 523 | return VMFAction_SemanticError; 524 | } 525 | break; 526 | case VMFEntryType_SectionClose: 527 | if (cfg->map_name.length < 1) { 528 | PRINTF("%s: Invalid map name \"" PRI_SV "\"", __func__, PRI_SVV(cfg->map_name)); 529 | return VMFAction_SemanticError; 530 | } 531 | Map *m = opensrcAllocMap(cfg->map_name); 532 | if (m && cfg->map_offset.length >= 5) { 533 | float x, y, z; 534 | // FIXME map_offset is not null-terminated 535 | if (3 == sscanf(cfg->map_offset.str, "%f %f %f", &x, &y, &z)) { 536 | m->flags |= MapFlags_FixedOffset; 537 | m->offset = aVec3f(x, y, z); 538 | } else { 539 | PRINTF("Cannot read offset " PRI_SV, PRI_SVV(cfg->map_offset)); 540 | } 541 | } 542 | state->callback = configReadCallback; 543 | break; 544 | default: 545 | PRINTF("%s: Unexpected entry %d", __func__, entry); 546 | return VMFAction_SemanticError; 547 | } 548 | 549 | return VMFAction_Continue; 550 | } 551 | 552 | static VMFAction configPatchCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) { 553 | Config *cfg = state->user_data; 554 | 555 | switch (entry) { 556 | case VMFEntryType_SectionOpen: 557 | if (kv->key.length < 1) { 558 | PRINTF("%s: Unexpected section \"" PRI_SV "\"", __func__, PRI_SVV(kv->key)); 559 | return VMFAction_SemanticError; 560 | } 561 | cfg->map_name = kv->key; 562 | state->callback = configLandmarkCallback; 563 | break; 564 | case VMFEntryType_SectionClose: 565 | return VMFAction_Exit; 566 | default: 567 | PRINTF("%s: Unexpected entry %d", __func__, entry); 568 | return VMFAction_SemanticError; 569 | } 570 | 571 | return VMFAction_Continue; 572 | } 573 | 574 | static VMFAction configReadCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) { 575 | Config *cfg = state->user_data; 576 | 577 | switch (entry) { 578 | case VMFEntryType_KeyValue: 579 | if (strncasecmp("gamedir", kv->key.str, kv->key.length) == 0) { 580 | cfg->gamedir = kv->value; 581 | } else if (strncasecmp("vpk", kv->key.str, kv->key.length) == 0) { 582 | char *value = buildSteamPath(&cfg->gamedir, &kv->value); 583 | g.collection_chain = addToCollectionChain(g.collection_chain, collectionCreateVPK(&mem, value)); 584 | stackFreeUpToPosition(&stack_temp, value); 585 | } else if (strncasecmp("dir", kv->key.str, kv->key.length) == 0) { 586 | char *value = buildSteamPath(&cfg->gamedir, &kv->value); 587 | g.collection_chain = addToCollectionChain(g.collection_chain, collectionCreateFilesystem(&mem, value)); 588 | stackFreeUpToPosition(&stack_temp, value); 589 | } else if (strncasecmp("max_maps", kv->key.str, kv->key.length) == 0) { 590 | // FIXME null-terminate 591 | g_cfg.maps_limit = atoi(kv->value.str); 592 | } else if (strncasecmp("map", kv->key.str, kv->key.length) == 0) { 593 | openSourceAddMap(kv->value); 594 | } else if (strncasecmp("z_far", kv->key.str, kv->key.length) == 0) { 595 | // FIXME null-terminate 596 | g.R = (float)atof(kv->value.str); 597 | } else { 598 | PRINTF("%s: Unexpected key \"" PRI_SV "\"", __func__, PRI_SVV(kv->key)); 599 | // TODO return VMFAction_SemanticError; 600 | return VMFAction_Exit; 601 | } 602 | break; 603 | case VMFEntryType_SectionOpen: 604 | if (strncasecmp("patch_landmarks", kv->key.str, kv->key.length) == 0) 605 | state->callback = configPatchCallback; 606 | else if (strncasecmp("map", kv->key.str, kv->key.length) == 0) { 607 | cfg->map_name.length = cfg->map_offset.length = 0; 608 | state->callback = configMapCallback; 609 | } else { 610 | PRINTF("%s: Unexpected section \"" PRI_SV "\"", __func__, PRI_SVV(kv->key)); 611 | return VMFAction_SemanticError; 612 | } 613 | break; 614 | default: 615 | PRINTF("%s: Unexpected entry %d", __func__, entry); 616 | return VMFAction_SemanticError; 617 | } 618 | 619 | return VMFAction_Continue; 620 | } 621 | 622 | static int configReadFile(const char *cfgfile) { 623 | AFile file; 624 | aFileReset(&file); 625 | if (AFile_Success != aFileOpen(&file, cfgfile)) 626 | return 0; 627 | 628 | char *buffer = stackAlloc(&stack_temp, file.size); 629 | if (!buffer) 630 | return 0; 631 | 632 | if (file.size != aFileReadAtOffset(&file, 0, file.size, buffer)) 633 | return 0; 634 | 635 | Config config = { 636 | .gamedir = { .str = NULL, .length = 0 } 637 | }; 638 | 639 | VMFState pstate = { 640 | .user_data = &config, 641 | .data = { .str = buffer, .length = (int)file.size }, 642 | .callback = configReadCallback 643 | }; 644 | 645 | aFileClose(&file); 646 | 647 | // TODO remove 648 | PRINTF("Parsing config file \"%s\" (%d):\n```\n%.*s\n```", cfgfile, pstate.data.length, pstate.data.length, pstate.data.str); 649 | 650 | int result = VMFResult_Success == vmfParse(&pstate); 651 | 652 | stackFreeUpToPosition(&stack_temp, buffer); 653 | return result; 654 | } 655 | 656 | const char *getDefaultSteamBaseDir() { 657 | #ifdef _WIN32 658 | const char *base_dir = getenv("programfiles(x86)"); 659 | const char *steam_prefix = "Steam/steamapps/common"; 660 | #else // LINUX 661 | const char *base_dir = getenv("HOME"); 662 | const char *steam_prefix = ".local/share/Steam/steamapps/common"; 663 | #endif 664 | const int base_dir_length = base_dir ? (int)strlen(base_dir) : 0; 665 | const int steam_basedir_length = (int)strlen(steam_prefix) + base_dir_length + 2; 666 | char *steam_basedir_w = stackAlloc(mem.persistent, steam_basedir_length); 667 | sprintf(steam_basedir_w, "%s/%s", base_dir, steam_prefix); 668 | return steam_basedir_w; 669 | } 670 | 671 | typedef int (*ArgFunc)(const char *value, void *user_ptr); 672 | 673 | typedef struct { 674 | const char *arg; 675 | const char *desc; 676 | ArgFunc func; 677 | void *user_ptr; 678 | } Arg; 679 | 680 | void argsPrintUsage(const Arg* args, int nargs, char const* prog) { 681 | PRINTF("Usage: %s", prog); 682 | const Arg* in = NULL; 683 | for (int i = 0; i < nargs; ++i) { 684 | const Arg* arg = args + i; 685 | if (arg->arg) { 686 | PRINTF("\t-%s: %s", arg->arg, arg->desc); 687 | } else { 688 | if (!in) 689 | in = arg; 690 | } 691 | } 692 | 693 | if (in) 694 | PRINTF("\tfree arguments: %s", in->desc); 695 | } 696 | 697 | int argsParse(const Arg* args, int nargs, int argc, char const* const* argv) { 698 | const Arg* in = NULL; 699 | for (int i = 0; i < nargs; ++i) { 700 | if (!args[i].arg) { 701 | in = args + i; 702 | break; 703 | } 704 | } 705 | 706 | // TODO: 707 | // - handle '--' as delimiter 708 | // - handle boolean flags w/o arguments 709 | // - long/short args 710 | // - optional/mandatory args 711 | for (int i = 1; i < argc; ++i) { 712 | char const *arg = argv[i]; 713 | const Arg *handler = NULL; 714 | if (arg[0] == '-') { 715 | for (int j = 0; j < nargs; ++j) { 716 | const Arg* parg = args + j; 717 | if (parg->arg && arg[1] == parg->arg[0]) { 718 | if (i == argc - 1) { 719 | PRINTF("Option %s requres an argument", arg); 720 | return 0; 721 | } 722 | handler = parg; 723 | ++i; 724 | arg = argv[i]; 725 | break; 726 | } 727 | } 728 | } else { 729 | handler = in; 730 | } 731 | 732 | if (!handler) { 733 | PRINTF("Unrecognized option %s", arg); 734 | return 0; 735 | } 736 | 737 | if (handler->func(arg, handler->user_ptr) == 0) { 738 | PRINTF("Error handling %s%s with argument '%s'", handler->arg ? "option -" : "free option", handler->arg ? handler->arg : "", arg); 739 | return 0; 740 | } 741 | } 742 | 743 | return 1; 744 | } 745 | 746 | int argStoreString(const char *str, void *user_ptr) { 747 | const char** pstr = user_ptr; 748 | *pstr = str; 749 | return 1; 750 | } 751 | 752 | int argStoreInt(const char *str, void *user_ptr) { 753 | int *pint = user_ptr; 754 | // FIXME null-terminate 755 | *pint = atoi(str); 756 | return 1; 757 | } 758 | 759 | int argAddVpkToCollection(const char *str, void *unused) { 760 | (void)unused; 761 | ICollection *collection = collectionCreateVPK(&mem, str); 762 | if (!collection) { 763 | PRINTF("Cannot open VPK %s", str); 764 | return 0; 765 | } 766 | g.collection_chain = addToCollectionChain(g.collection_chain, collection); 767 | return 1; 768 | } 769 | 770 | int argAddDirToCollection(const char *str, void *unused) { 771 | (void)unused; 772 | ICollection *collection = collectionCreateFilesystem(&mem, str); 773 | if (!collection) { 774 | PRINTF("Cannot open filesystem collection %s", str); 775 | return 0; 776 | } 777 | g.collection_chain = addToCollectionChain(g.collection_chain, collection); 778 | return 1; 779 | } 780 | 781 | int argReadConfigFile(const char *str, void *unused) { 782 | (void)unused; 783 | if (!configReadFile(str)) { 784 | PRINTF("Cannot read config file %s", str); 785 | return 0; 786 | } 787 | return 1; 788 | } 789 | 790 | int argAddMap(const char *str, void *unused) { 791 | (void)unused; 792 | const StringView map = { .str = str, .length = (int)strlen(str) }; 793 | openSourceAddMap(map); 794 | return 1; 795 | } 796 | 797 | static Arg g_args[] = { 798 | {"s", "Override steam basedir", argStoreString, (void*)&g_cfg.steam_basedir}, 799 | {"m", "Add map name to list of maps to load", argAddMap, NULL}, 800 | {"p", "Add VPK file to list of files to load assets from", argAddVpkToCollection, NULL}, 801 | {"d", "Add directory to list of files to load assets from", argAddDirToCollection, NULL}, 802 | {"n", "Specify a limit of number of maps to load", argStoreInt, &g_cfg.maps_limit}, 803 | // TODO -h 804 | {NULL, "Game configuration file to load", argReadConfigFile, NULL}, 805 | }; 806 | 807 | void attoAppInit(struct AAppProctable *proctable) { 808 | //profilerInit(); 809 | 810 | logOpen("OpenSource.log"); 811 | 812 | g.collection_chain = NULL; 813 | g.patches = NULL; 814 | g.maps_count = 0; 815 | g.selected_map = NULL; 816 | g.R = 0; 817 | 818 | g_cfg.maps_limit = 1; 819 | g_cfg.steam_basedir = getDefaultSteamBaseDir(); 820 | PRINTF("Default platform steam basedir = %s", g_cfg.steam_basedir); 821 | 822 | if (!argsParse(g_args, COUNTOF(g_args), a_app_state->argc, a_app_state->argv)) { 823 | PRINT("Error parsing command line arguments"); 824 | goto print_usage_and_exit; 825 | } 826 | 827 | if (g_cfg.maps_limit < 1) 828 | g_cfg.maps_limit = 1; 829 | 830 | if (!g.maps_count || !g.collection_chain) { 831 | aAppDebugPrintf("At least one map and one collection required"); 832 | goto print_usage_and_exit; 833 | } 834 | 835 | opensrcInit(); 836 | 837 | proctable->resize = opensrcResize; 838 | proctable->paint = opensrcPaint; 839 | proctable->key = opensrcKeyPress; 840 | proctable->pointer = opensrcPointer; 841 | return; 842 | 843 | print_usage_and_exit: 844 | argsPrintUsage(g_args, COUNTOF(g_args), a_app_state->argv[0]); 845 | aAppTerminate(1); 846 | } 847 | -------------------------------------------------------------------------------- /src/ahash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include /* memcpy */ 3 | #include /* size_t */ 4 | 5 | /* simplest possible insert-only single-linked hash table */ 6 | 7 | typedef void *(*AHashAllocFunc)(void *alloc_param, size_t size); 8 | typedef unsigned long (*AHashKeyHashFunc)(const void *key); 9 | typedef int (*AHashKeyCompareFunc)(const void *left, const void *right); 10 | 11 | struct AHashBucket_; 12 | 13 | typedef struct { 14 | /* must be set before aHashInit */ 15 | long nbuckets; 16 | /* note-to-self: it is actually trivially possible to have variable-size keys and items */ 17 | long key_size; 18 | long value_size; 19 | /* must return properly aligned (e.g. 16) regions */ 20 | void *alloc_param; 21 | AHashAllocFunc alloc; 22 | AHashKeyHashFunc key_hash; 23 | AHashKeyCompareFunc key_compare; 24 | 25 | struct { 26 | long item_size; 27 | long value_offset; 28 | long next_ptr_offset; 29 | struct AHashBucket_ *buckets; 30 | } impl_; 31 | struct { 32 | long items; 33 | long empty_buckets; 34 | long worst_bucket_items; 35 | } stat; 36 | } AHash; 37 | 38 | void aHashInit(AHash *hash); 39 | void *aHashInsert(AHash *hash, const void *key, const void *value); 40 | void *aHashGet(const AHash *hash, const void *key); 41 | 42 | /* helper for hashing memory contents, e.g. strings and binary data 43 | * DON'T use it to hash structs, as those might have holes due to alignment, 44 | * and actual memory contents of these holes is not specified (UB?) */ 45 | unsigned long aHashBytesHash(const void *data, size_t size); 46 | 47 | /* can be directly used if key is a zero-terminated string */ 48 | unsigned long aHashStringHash(const void *string); 49 | 50 | #define AHASH_IMPLEMENT 51 | #ifdef AHASH_IMPLEMENT 52 | #ifndef AHASH_VALUE_ALIGNMENT 53 | #define AHASH_VALUE_ALIGNMENT 16 /* worst case for SSE & friends */ 54 | #endif 55 | 56 | struct AHashBucket_ { 57 | void *items; 58 | long count; 59 | }; 60 | 61 | struct AHashItemData_ { 62 | void *key; 63 | void *value; 64 | void **next; 65 | }; 66 | 67 | static struct AHashItemData_ a__hashGetItemData(const AHash *hash, void *item) { 68 | char *bytes = item; 69 | return (struct AHashItemData_){ 70 | .key = bytes, 71 | .value = bytes + hash->impl_.value_offset, 72 | .next = (void**)(bytes + hash->impl_.next_ptr_offset) 73 | }; 74 | } 75 | 76 | void aHashInit(AHash *hash) { 77 | #define AHASH_ALIGNED_SIZE(S,A) (((S+A-1)/A)*A) 78 | hash->impl_.value_offset = AHASH_ALIGNED_SIZE(hash->key_size, AHASH_VALUE_ALIGNMENT); 79 | hash->impl_.next_ptr_offset = hash->impl_.value_offset + AHASH_ALIGNED_SIZE(hash->value_size, sizeof(void*)); 80 | hash->impl_.item_size = hash->impl_.next_ptr_offset + sizeof(void*); 81 | hash->impl_.buckets = hash->alloc(hash->alloc_param, sizeof(struct AHashBucket_) * hash->nbuckets); 82 | for (long i = 0; i < hash->nbuckets; ++i) 83 | hash->impl_.buckets[i] = (struct AHashBucket_){ 84 | .items = 0, 85 | .count = 0 86 | }; 87 | hash->stat.items = 0; 88 | hash->stat.empty_buckets = hash->nbuckets; 89 | hash->stat.worst_bucket_items = 0; 90 | } 91 | 92 | void *aHashInsert(AHash *hash, const void *key, const void *value) { 93 | const long index = hash->key_hash(key) % hash->nbuckets; 94 | struct AHashBucket_ *const bucket = hash->impl_.buckets + index; 95 | 96 | void *item = bucket->items; 97 | struct AHashItemData_ prev_item_data; 98 | prev_item_data.next = &bucket->items; 99 | while(item != 0) { 100 | prev_item_data = a__hashGetItemData(hash, item); 101 | item = *prev_item_data.next; 102 | } 103 | 104 | void *const new_item = hash->alloc(hash->alloc_param, hash->impl_.item_size); 105 | const struct AHashItemData_ item_data = a__hashGetItemData(hash, new_item); 106 | // FIXME key is string and is < key_size most of the time 107 | memcpy(item_data.key, key, hash->key_size); 108 | memcpy(item_data.value, value, hash->value_size); 109 | item_data.next[0] = 0; 110 | *prev_item_data.next = new_item; 111 | 112 | if (!bucket->count) hash->stat.empty_buckets--; 113 | ++bucket->count; 114 | if (bucket->count > hash->stat.worst_bucket_items) hash->stat.worst_bucket_items = bucket->count; 115 | ++hash->stat.items; 116 | return new_item; 117 | } 118 | 119 | void *aHashGet(const AHash *hash, const void *key) { 120 | const long index = hash->key_hash(key) % hash->nbuckets; 121 | const struct AHashBucket_ *const bucket = hash->impl_.buckets + index; 122 | 123 | void *item = bucket->items; 124 | while (item) { 125 | const struct AHashItemData_ data = a__hashGetItemData(hash, item); 126 | if (0 == hash->key_compare(key, data.key)) 127 | return data.value; 128 | item = *data.next; 129 | } 130 | return 0; 131 | } 132 | 133 | /* FNV-1a */ 134 | unsigned long aHashBytesHash(const void *data, size_t size) { 135 | #if __LP64__ 136 | unsigned long hash = 0xcbf29ce484222325ul; 137 | const unsigned long mul = 1099511628211ul; 138 | #else 139 | unsigned long hash = 0x811c9dc5ul; 140 | const unsigned long mul = 16777619ul; 141 | #endif 142 | for (size_t i = 0; i < size; ++i) { 143 | hash ^= ((const unsigned char*)data)[i]; 144 | hash *= mul; 145 | } 146 | return hash; 147 | } 148 | 149 | unsigned long aHashStringHash(const void *string) { 150 | return aHashBytesHash(string, strlen(string)); 151 | } 152 | #endif 153 | -------------------------------------------------------------------------------- /src/atlas.c: -------------------------------------------------------------------------------- 1 | #include "atlas.h" 2 | 3 | struct AtlasRect { unsigned int x, y, w, h; }; 4 | 5 | /* temp memory is freed by caller */ 6 | enum AtlasResult atlasCompute(const struct AtlasContext* context) { 7 | const unsigned int max_rects_count = 1 + context->rects_count; 8 | unsigned int rects_count = 1; 9 | if (context->temp_storage.size < sizeof(struct AtlasRect) * max_rects_count) 10 | return Atlas_ErrorInsufficientTemp; 11 | struct AtlasRect *rects = context->temp_storage.ptr; 12 | 13 | rects[0].x = rects[0].y = 0; 14 | rects[0].w = context->width; rects[0].h = context->height; 15 | 16 | for (unsigned int i = 0; i < context->rects_count; ++i) { 17 | const struct AtlasVec * const item = (void*)((char*)context->rects + i * context->rects_stride); 18 | const unsigned int area = item->x * item->y; 19 | 20 | /* find best fit for this rect */ 21 | struct AtlasRect *fit = 0; 22 | struct AtlasRect *first_empty = 0; /* optimization */ 23 | unsigned int wasted_area = context->width * context->height; 24 | for (unsigned int j = 0; j < rects_count; ++j) { 25 | struct AtlasRect *const r = rects + j; 26 | if (!first_empty && (!r->w || !r->h)) first_empty = r; 27 | 28 | if (r->w < item->x || r->h < item->y) 29 | continue; 30 | 31 | /* exact match is the best */ 32 | if (r->w == item->x && r->h == item->y) { 33 | fit = r; 34 | break; 35 | } 36 | 37 | const unsigned int r_area = r->w * r->h; 38 | const unsigned int fit_waste = r_area - area; 39 | if (!fit || ((r->w < fit->w || r->h < fit->h) && (fit_waste <= wasted_area))) { 40 | wasted_area = fit_waste; 41 | fit = r; 42 | } 43 | } /* find best fit in all empty rects */ 44 | 45 | if (!fit) 46 | /* cannot allocate space for this lightmap fragment */ 47 | return Atlas_ErrorDoesntFit; 48 | 49 | struct AtlasVec * const pos = (void*)((char*)context->pos + i * context->pos_stride); 50 | 51 | pos->x = fit->x; 52 | pos->y = fit->y; 53 | 54 | /* how to split */ 55 | unsigned int rem_width = fit->w - item->x; 56 | unsigned int rem_height = fit->h - item->y; 57 | if (!rem_width && !rem_height) { 58 | fit->w = fit->h = 0; 59 | continue; 60 | } 61 | 62 | if (rem_width && rem_height && !first_empty) 63 | first_empty = rects + (rects_count++); 64 | 65 | /* split! */ 66 | if (rem_width > rem_height) { 67 | if (rem_height) { 68 | first_empty->x = fit->x + item->x; 69 | first_empty->y = fit->y; 70 | first_empty->w = rem_width; 71 | first_empty->h = fit->h; 72 | fit->y += item->y; 73 | fit->w = item->x; 74 | fit->h = rem_height; 75 | } else { 76 | fit->x += item->x; 77 | fit->w = rem_width; 78 | } 79 | } else { 80 | if (rem_width) { 81 | first_empty->x = fit->x; 82 | first_empty->y = fit->y + item->y; 83 | first_empty->w = fit->w; 84 | first_empty->h = rem_height; 85 | fit->x += item->x; 86 | fit->w = rem_width; 87 | fit->h = item->y; 88 | } else { 89 | fit->y += item->y; 90 | fit->h = rem_height; 91 | } 92 | } /* split */ 93 | } /* for all input rects */ 94 | 95 | return Atlas_Success; 96 | } 97 | -------------------------------------------------------------------------------- /src/atlas.h: -------------------------------------------------------------------------------- 1 | #ifndef ATLAS_H__INCLUDED 2 | #define ATLAS_H__INCLUDED 3 | 4 | enum AtlasResult { 5 | Atlas_Success = 0, 6 | Atlas_ErrorDoesntFit, 7 | Atlas_ErrorInsufficientTemp 8 | }; 9 | 10 | struct AtlasVec { unsigned int x, y; }; 11 | 12 | struct AtlasContext { 13 | /* temporary buffer/scrap space for atlas to use */ 14 | /* worst case consumption: 4 * sizeof(unsigned int) * (1 + rects_count) */ 15 | struct { 16 | void *ptr; 17 | unsigned int size; 18 | } temp_storage; 19 | 20 | /* input */ 21 | unsigned int width, height; 22 | const struct AtlasVec *rects; 23 | unsigned int rects_count; 24 | unsigned int rects_stride; 25 | 26 | /* output */ 27 | struct AtlasVec *pos; 28 | unsigned int pos_stride; 29 | }; 30 | 31 | /* TODO ? 32 | struct AtlasStats { 33 | unsigned int wasted_pixels; 34 | struct AtlasVec min_waste, max_waste; 35 | unsigned int exact_matches; 36 | }; 37 | */ 38 | 39 | enum AtlasResult atlasCompute(const struct AtlasContext* context); 40 | 41 | #endif /* ATLAS_H__INCLUDED */ 42 | -------------------------------------------------------------------------------- /src/bsp.c: -------------------------------------------------------------------------------- 1 | #include "bsp.h" 2 | #include "atlas.h" 3 | #include "vbsp.h" 4 | #include "collection.h" 5 | #include "mempools.h" 6 | #include "vmfparser.h" 7 | #include "common.h" 8 | 9 | // DEBUG 10 | #include "texture.h" 11 | 12 | #ifdef _MSC_VER 13 | #pragma warning(disable:4221) 14 | #endif 15 | 16 | #define R2S(r) bspLoadResultString(r) 17 | 18 | const char *bspLoadResultString(enum BSPLoadResult result) { 19 | switch(result) { 20 | case BSPLoadResult_Success: return "BSPLoadResult_Success"; 21 | case BSPLoadResult_ErrorFileOpen: return "BSPLoadResult_ErrorFileOpen"; 22 | case BSPLoadResult_ErrorFileFormat: return "BSPLoadResult_ErrorFileFormat"; 23 | case BSPLoadResult_ErrorMemory: return "BSPLoadResult_ErrorMemory"; 24 | case BSPLoadResult_ErrorTempMemory: return "BSPLoadResult_ErrorTempMemory"; 25 | case BSPLoadResult_ErrorCapabilities: return "BSPLoadResult_ErrorCapabilities"; 26 | default: return "UNKNOWN"; 27 | } 28 | } 29 | 30 | struct AnyLump { 31 | const void *p; 32 | uint32_t n; 33 | }; 34 | 35 | struct Lumps { 36 | uint32_t version; 37 | #define LIST_LUMPS \ 38 | BSPLUMP(Entity, char, entities); \ 39 | BSPLUMP(Plane, struct VBSPLumpPlane, planes); \ 40 | BSPLUMP(TexData, struct VBSPLumpTexData, texdata); \ 41 | BSPLUMP(Vertex, struct VBSPLumpVertex, vertices); \ 42 | \ 43 | BSPLUMP(Node, struct VBSPLumpNode, nodes); \ 44 | BSPLUMP(TexInfo, struct VBSPLumpTexInfo, texinfos); \ 45 | BSPLUMP(Face, struct VBSPLumpFace, faces); \ 46 | BSPLUMP(LightMap, struct VBSPLumpLightMap, lightmaps); \ 47 | \ 48 | BSPLUMP(Leaf, struct VBSPLumpLeaf, leaves); \ 49 | \ 50 | BSPLUMP(Edge, struct VBSPLumpEdge, edges); \ 51 | BSPLUMP(Surfedge, int32_t, surfedges); \ 52 | BSPLUMP(Model, struct VBSPLumpModel, models); \ 53 | \ 54 | BSPLUMP(LeafFace, uint16_t, leaffaces); \ 55 | \ 56 | BSPLUMP(DispInfo, struct VBSPLumpDispInfo, dispinfos); \ 57 | \ 58 | BSPLUMP(DispVerts, struct VBSPLumpDispVert, dispverts); \ 59 | \ 60 | BSPLUMP(PakFile, uint8_t, pakfile); \ 61 | \ 62 | BSPLUMP(TexDataStringData, char, texdatastringdata); \ 63 | BSPLUMP(TexDataStringTable, int32_t, texdatastringtable); \ 64 | \ 65 | BSPLUMP(FaceHDR, struct VBSPLumpFace, faces_hdr); \ 66 | \ 67 | BSPLUMP(LightMapHDR, struct VBSPLumpLightMap, lightmaps_hdr); \ 68 | 69 | 70 | #define BSPLUMP(name,type,field) struct{const type *p;uint32_t n;} field 71 | LIST_LUMPS 72 | #undef BSPLUMP 73 | }; 74 | 75 | /* data needed for making lightmap atlas */ 76 | struct Face { 77 | const struct VBSPLumpFace *vface; 78 | /* read directly from lumps */ 79 | int vertices; 80 | int indices; 81 | int width, height; 82 | const struct VBSPLumpLightMap *samples; 83 | const struct VBSPLumpTexInfo *texinfo; 84 | const struct VBSPLumpTexData *texdata; 85 | const struct VBSPLumpDispInfo *dispinfo; 86 | int dispquadvtx[4]; // filled only when displaced 87 | int dispstartvtx; 88 | const Material *material; 89 | 90 | /* filled as a result of atlas allocation */ 91 | int atlas_x, atlas_y; 92 | }; 93 | 94 | struct LoadModelContext { 95 | struct Stack *tmp; 96 | struct ICollection *collection; 97 | const struct Lumps *lumps; 98 | const struct VBSPLumpModel *model; 99 | struct Face *faces; 100 | int faces_count; 101 | int vertices; 102 | int indices; 103 | int max_draw_vertices; 104 | struct { 105 | int pixels; 106 | int max_width; 107 | int max_height; 108 | RTexture texture; 109 | } lightmap; 110 | }; 111 | 112 | enum FacePreload { 113 | FacePreload_Ok, 114 | FacePreload_Skip, 115 | FacePreload_Inconsistent 116 | }; 117 | 118 | static struct { 119 | const Material *coarse_material; 120 | struct { 121 | int color[256]; 122 | int exponent[256]; 123 | } lightmap_tables; 124 | } bsp_global; 125 | 126 | static inline int shouldSkipFace(const struct VBSPLumpFace *face, const struct Lumps *lumps) { 127 | (void)(face); (void)(lumps); 128 | //const struct VBSPLumpTexInfo *tinfo = lumps->texinfos.p + face->texinfo; 129 | return /*(tinfo->flags & (VBSP_Surface_NoDraw | VBSP_Surface_NoLight)) ||*/ face->lightmap_offset == 0xffffffffu 130 | || face->lightmap_offset < 4; 131 | } 132 | 133 | static enum FacePreload bspFacePreloadMetadata(struct LoadModelContext *ctx, 134 | struct Face *face, unsigned index) { 135 | const struct Lumps * const lumps = ctx->lumps; 136 | #define FACE_CHECK(cond) \ 137 | if (!(cond)) { PRINTF("F%u: check failed: (%s)", index, #cond); return FacePreload_Inconsistent; } 138 | FACE_CHECK(index < lumps->faces.n); 139 | 140 | const struct VBSPLumpFace * const vface = lumps->faces.p + index; 141 | face->vface = vface; 142 | 143 | if (vface->texinfo < 0) return FacePreload_Skip; 144 | FACE_CHECK((unsigned)vface->texinfo < lumps->texinfos.n); 145 | face->texinfo = lumps->texinfos.p + vface->texinfo; 146 | 147 | if (shouldSkipFace(vface, lumps)) return FacePreload_Skip; 148 | FACE_CHECK(face->texinfo->texdata < lumps->texdata.n); 149 | face->texdata = lumps->texdata.p + face->texinfo->texdata; 150 | 151 | FACE_CHECK(face->texdata->name_string_table_id < lumps->texdatastringtable.n); 152 | const int32_t texdatastringdata_offset = lumps->texdatastringtable.p[face->texdata->name_string_table_id]; 153 | FACE_CHECK(texdatastringdata_offset >= 0 && (uint32_t)texdatastringdata_offset < lumps->texdatastringdata.n); 154 | /* FIXME validate string: has \0 earlier than end */ 155 | const char *texture = lumps->texdatastringdata.p + texdatastringdata_offset; 156 | //PRINTF("F%u: texture %s", index, face->texture); 157 | face->material = materialGet(texture, ctx->collection, ctx->tmp); 158 | if (!face->material) 159 | return FacePreload_Skip; 160 | 161 | if (vface->dispinfo >= 0) { 162 | FACE_CHECK((unsigned)vface->dispinfo < lumps->dispinfos.n); 163 | face->dispinfo = lumps->dispinfos.p + vface->dispinfo; 164 | const int side = (1 << face->dispinfo->power) + 1; 165 | FACE_CHECK(vface->num_edges == 4); 166 | face->vertices = side * side; 167 | face->indices = (side - 1) * (side - 1) * 6; /* triangle list */ 168 | 169 | /* TODO 170 | * some of the episode 2 maps have the min_tess set to flag mode, and flags are 0xe 171 | * 172 | if (face->dispinfo->min_tess != 0) { 173 | if ((uint32_t)face->dispinfo->min_tess & 0x80000000u) { 174 | if ((uint32_t)face->dispinfo->min_tess & 0x7fffffffu) 175 | PRINTF("min_tess has flags: %x", (uint32_t)face->dispinfo->min_tess & 0x7fffffffu); 176 | } else 177 | PRINTF("Power: %d, min_tess: %d, vertices: %d", 178 | face->dispinfo->power, face->dispinfo->min_tess, face->vertices); 179 | } 180 | */ 181 | 182 | face->dispstartvtx = 0; 183 | } else { 184 | face->dispinfo = 0; 185 | face->vertices = vface->num_edges; 186 | face->indices = (face->vertices - 2) * 3; 187 | } 188 | 189 | /* Check for basic reference consistency */ 190 | FACE_CHECK(vface->plane < lumps->planes.n); 191 | FACE_CHECK(vface->num_edges > 2); 192 | FACE_CHECK(vface->first_edge < lumps->surfedges.n && lumps->surfedges.n - vface->first_edge >= (unsigned)vface->num_edges); 193 | 194 | FACE_CHECK(vface->lightmap_offset % sizeof(struct VBSPLumpLightMap) == 0); 195 | 196 | const int lm_width = vface->lightmap_size[0] + 1; 197 | const int lm_height = vface->lightmap_size[1] + 1; 198 | const unsigned lightmap_size = lm_width * lm_height; 199 | const unsigned sample_offset = vface->lightmap_offset / sizeof(struct VBSPLumpLightMap); 200 | FACE_CHECK(sample_offset < lumps->lightmaps.n && lumps->lightmaps.n - sample_offset >= lightmap_size); 201 | 202 | const int32_t *surfedges = lumps->surfedges.p + vface->first_edge; 203 | unsigned int prev_end = 0xffffffffu; 204 | for (int i = 0; i < vface->num_edges; ++i) { 205 | uint32_t edge_index; 206 | int istart; 207 | 208 | if (surfedges[i] >= 0) { 209 | edge_index = surfedges[i]; 210 | istart = 0; 211 | } else { 212 | edge_index = -surfedges[i]; 213 | istart = 1; 214 | } 215 | 216 | if (edge_index >= lumps->edges.n) { 217 | PRINTF("Error: face%u surfedge%d/%d references edge %u > max edges %u", 218 | index, i, vface->num_edges, edge_index, lumps->edges.n); 219 | return FacePreload_Inconsistent; 220 | } 221 | 222 | const unsigned int vstart = lumps->edges.p[edge_index].v[istart]; 223 | const unsigned int vend = lumps->edges.p[edge_index].v[1^istart]; 224 | 225 | if (face->dispinfo) { 226 | face->dispquadvtx[i] = vstart; 227 | if (fabsf(lumps->vertices.p[vstart].x - face->dispinfo->start_pos.x) < .5f 228 | && fabsf(lumps->vertices.p[vstart].y - face->dispinfo->start_pos.y) < .5f 229 | && fabsf(lumps->vertices.p[vstart].z - face->dispinfo->start_pos.z) < .5f) { 230 | face->dispstartvtx = i; 231 | } 232 | } 233 | 234 | FACE_CHECK(vstart < lumps->vertices.n); 235 | FACE_CHECK(prev_end == 0xffffffffu || prev_end == vstart); 236 | 237 | prev_end = vend; 238 | } 239 | 240 | face->width = lm_width; 241 | face->height = lm_height; 242 | face->samples = lumps->lightmaps.p + sample_offset; 243 | if (lm_width > ctx->lightmap.max_width) ctx->lightmap.max_width = lm_width; 244 | if (lm_height > ctx->lightmap.max_height) ctx->lightmap.max_height = lm_height; 245 | 246 | ctx->lightmap.pixels += lightmap_size; 247 | ctx->vertices += face->vertices; 248 | ctx->indices += face->indices; 249 | ctx->faces_count++; 250 | 251 | return FacePreload_Ok; 252 | } 253 | 254 | const int c_max_draw_vertices = 65536; 255 | 256 | static uint8_t scaleLightmapColor(int c, int exp) { 257 | const unsigned c2 = 258 | (bsp_global.lightmap_tables.exponent[exp+128] * bsp_global.lightmap_tables.color[c]) >> 12; 259 | 260 | //const int c1 = 255.f * pow(c * powf(2.f, exp) / 255.f, 1.0f / 2.2f) * .5f; 261 | //PRINTF("%d^%d => %d, %d => %d", c, exp, c1, c2, c2 - c1); 262 | 263 | return c2 < 255 ? (uint8_t)c2 : 255; 264 | } 265 | 266 | static enum BSPLoadResult bspLoadModelPreloadFaces(struct LoadModelContext *ctx) { 267 | ctx->faces = stackGetCursor(ctx->tmp); 268 | 269 | int current_draw_vertices = 0; 270 | 271 | for (int i = 0; i < ctx->model->num_faces; ++i) { 272 | struct Face face; 273 | const enum FacePreload result = bspFacePreloadMetadata(ctx, &face, ctx->model->first_face + i); 274 | if (result == FacePreload_Ok) { 275 | current_draw_vertices += face.vertices; 276 | 277 | struct Face *stored_face = stackAlloc(ctx->tmp, sizeof(struct Face)); 278 | if (!stored_face) { 279 | PRINTF("Error: cannot allocate %zu temp bytes", sizeof(struct Face)); 280 | return BSPLoadResult_ErrorTempMemory; 281 | } 282 | *stored_face = face; 283 | continue; 284 | } 285 | 286 | if (result != FacePreload_Skip) 287 | return BSPLoadResult_ErrorFileFormat; 288 | } 289 | 290 | if (!ctx->faces_count) { 291 | PRINTF("Error: no visible faces found%s", ""); 292 | return BSPLoadResult_ErrorFileFormat; /* FIXME handle this */ 293 | } 294 | 295 | if (ctx->max_draw_vertices < current_draw_vertices) 296 | ctx->max_draw_vertices = current_draw_vertices; 297 | return BSPLoadResult_Success; 298 | } 299 | 300 | static enum BSPLoadResult bspLoadModelLightmaps(struct LoadModelContext *ctx) { 301 | /* TODO optional sort lightmaps */ 302 | 303 | struct AtlasContext atlas_context; 304 | atlas_context.temp_storage.ptr = stackGetCursor(ctx->tmp); 305 | atlas_context.temp_storage.size = (int)stackGetFree(ctx->tmp); 306 | atlas_context.width = 16; /* TODO opengl caps */ 307 | atlas_context.height = 16; 308 | atlas_context.rects = (void*)(&ctx->faces[0].width); 309 | atlas_context.rects_count = ctx->faces_count; 310 | atlas_context.rects_stride = sizeof(ctx->faces[0]); 311 | atlas_context.pos = (void*)(&ctx->faces[0].atlas_x); 312 | atlas_context.pos_stride = sizeof(ctx->faces[0]); 313 | while (atlas_context.width < (unsigned)ctx->lightmap.max_width) atlas_context.width <<= 1; 314 | while (atlas_context.height < (unsigned)ctx->lightmap.max_height) atlas_context.height <<= 1; 315 | while (atlas_context.width * atlas_context.height < (unsigned)ctx->lightmap.pixels) 316 | if (atlas_context.width < atlas_context.height) atlas_context.width <<= 1; else atlas_context.height <<= 1; 317 | 318 | for(;;) { 319 | const enum AtlasResult result = atlasCompute(&atlas_context); 320 | 321 | PRINTF("atlas: %u %u %d", atlas_context.width, atlas_context.height, result); 322 | 323 | if (result == Atlas_Success) 324 | break; 325 | 326 | if (result == Atlas_ErrorInsufficientTemp) 327 | return BSPLoadResult_ErrorTempMemory; 328 | 329 | if (atlas_context.width < atlas_context.height) atlas_context.width <<= 1; else atlas_context.height <<= 1; 330 | if (atlas_context.width > 2048 || atlas_context.height > 2048) /* TODO limit based on GL driver caps */ 331 | return BSPLoadResult_ErrorCapabilities; 332 | } 333 | 334 | /* Build an atlas texture based on calculated fragment positions */ 335 | const size_t atlas_size = sizeof(uint16_t) * atlas_context.width * atlas_context.height; 336 | uint16_t *const pixels = stackAlloc(ctx->tmp, atlas_size); 337 | if (!pixels) return BSPLoadResult_ErrorTempMemory; 338 | memset(pixels, 0x0f, atlas_size); /* TODO debug pattern */ 339 | 340 | for (int i = 0; i < ctx->faces_count; ++i) { 341 | const struct Face *const face = ctx->faces + i; 342 | ASSERT((unsigned)face->atlas_x + face->width <= atlas_context.width); 343 | ASSERT((unsigned)face->atlas_y + face->height <= atlas_context.height); 344 | for (int y = 0; y < face->height; ++y) { 345 | for (int x = 0; x < face->width; ++x) { 346 | const struct VBSPLumpLightMap *const pixel = face->samples + x + (int)(y * face->width); 347 | 348 | const unsigned int 349 | r = scaleLightmapColor(pixel->r, pixel->exponent), 350 | g = scaleLightmapColor(pixel->g, pixel->exponent), 351 | b = scaleLightmapColor(pixel->b, pixel->exponent); 352 | 353 | pixels[face->atlas_x + x + (face->atlas_y + y) * atlas_context.width] 354 | = (uint16_t)(((r&0xf8) << 8) | ((g&0xfc) << 3) | (b >> 3)); 355 | } /* for x */ 356 | } /* for y */ 357 | } /* fot all visible faces */ 358 | 359 | RTextureUploadParams upload; 360 | upload.width = atlas_context.width; 361 | upload.height = atlas_context.height; 362 | upload.format = RTexFormat_RGB565; 363 | upload.pixels = pixels; 364 | upload.mip_level = -2; 365 | upload.type = RTexType_2D; 366 | upload.wrap = RTexWrap_Clamp; 367 | renderTextureInit(&ctx->lightmap.texture); 368 | renderTextureUpload(&ctx->lightmap.texture, upload); 369 | //ctx->lightmap.texture.min_filter = RTmF_Nearest; 370 | 371 | /* pixels buffer is not needed anymore */ 372 | stackFreeUpToPosition(ctx->tmp, pixels); 373 | 374 | return BSPLoadResult_Success; 375 | } 376 | 377 | static inline struct AVec3f aVec3fLumpVec(struct VBSPLumpVertex v) { return aVec3f(v.x, v.y, v.z); } 378 | 379 | static inline float clamp(float x, float min, float max) { 380 | return x < min ? min : (x > max ? max : x); 381 | } 382 | 383 | #ifdef DEBUG_DISP_LIGHTMAP 384 | static int shouldSwapUV(struct AVec3f mapU, struct AVec3f mapV, const struct AVec3f *v) { 385 | float mappedU = 0.f, mappedV = 0.f; 386 | for (int i = 0; i < 4; ++i) { 387 | const float U = aVec3fDot(mapU, aVec3fSub(v[(i+1)%4], v[i])); 388 | if (U > mappedU) mappedU = U; 389 | const float V = aVec3fDot(mapV, aVec3fSub(v[(i+1)%4], v[i])); 390 | if (V > mappedV) mappedV = V; 391 | } 392 | 393 | const float dX1 = aVec3fLength2(aVec3fSub(v[3], v[0])); 394 | const float dX2 = aVec3fLength2(aVec3fSub(v[2], v[1])); 395 | const float dY1 = aVec3fLength2(aVec3fSub(v[1], v[0])); 396 | const float dY2 = aVec3fLength2(aVec3fSub(v[2], v[3])); 397 | const float maxDX = (dX1 > dX2) ? dX1 : dX2; 398 | const float maxDY = (dY1 > dY2) ? dY1 : dY2; 399 | //PRINTF("mappedU=%f mappedV=%f maxDX=%f, maxDY=%f", mappedU, mappedV, maxDX, maxDY); 400 | return (mappedU > mappedV) != (maxDX > maxDY); 401 | } 402 | #endif /* DEBUG_DISP_LIGHTMAP */ 403 | 404 | static void bspLoadDisplacement( 405 | const struct LoadModelContext *ctx, 406 | const struct Face *face, 407 | struct BSPModelVertex *out_vertices, uint16_t *out_indices, int index_shift) { 408 | const int side = (1 << face->dispinfo->power) + 1; 409 | const struct VBSPLumpVertex *const vertices = ctx->lumps->vertices.p; 410 | const struct VBSPLumpTexInfo * const tinfo = face->texinfo; 411 | const struct VBSPLumpDispVert *const dispvert = ctx->lumps->dispverts.p + face->dispinfo->vtx_start; 412 | 413 | //if (face->dispstartvtx != 0) PRINTF("dispstartvtx = %d", face->dispstartvtx); 414 | 415 | const struct AVec3f vec[4] = { /* bl, tl, tr, br */ 416 | aVec3fLumpVec(vertices[face->dispquadvtx[(face->dispstartvtx + 0)%4]]), 417 | aVec3fLumpVec(vertices[face->dispquadvtx[(face->dispstartvtx + 1)%4]]), 418 | aVec3fLumpVec(vertices[face->dispquadvtx[(face->dispstartvtx + 2)%4]]), 419 | aVec3fLumpVec(vertices[face->dispquadvtx[(face->dispstartvtx + 3)%4]])}; 420 | 421 | /* 422 | const struct AVec3f ovec[4] = { 423 | aVec3fAdd(vec[0], aVec3fMulf(aVec3f(dispvert[0].x, dispvert[0].y, dispvert[0].z), dispvert[0].dist)), 424 | aVec3fAdd(vec[1], aVec3fMulf(aVec3f(dispvert[side*(side-1)].x, dispvert[side*(side-1)].y, dispvert[side*(side-1)].z), dispvert[side*(side-1)].dist)), 425 | aVec3fAdd(vec[2], aVec3fMulf(aVec3f(dispvert[side*side-1].x, dispvert[side*side-1].y, dispvert[side*side-1].z), dispvert[side*side-1].dist)), 426 | aVec3fAdd(vec[3], aVec3fMulf(aVec3f(dispvert[side-1].x, dispvert[side-1].y, dispvert[side-1].z), dispvert[side-1].dist))}; 427 | */ 428 | 429 | const struct AVec3f lm_map_u = aVec3f( 430 | tinfo->lightmap_vecs[0][0], tinfo->lightmap_vecs[0][1], tinfo->lightmap_vecs[0][2]); 431 | const float luxels_per_unit = aVec3fLength(lm_map_u); 432 | float length_lm_u = luxels_per_unit * floatMax( 433 | aVec3fLength(aVec3fSub(vec[3], vec[0])), 434 | aVec3fLength(aVec3fSub(vec[2], vec[1]))); 435 | float length_lm_v = luxels_per_unit * floatMax( 436 | aVec3fLength(aVec3fSub(vec[1], vec[0])), 437 | aVec3fLength(aVec3fSub(vec[2], vec[3]))); 438 | 439 | const struct AVec4f tex_map_u = aVec4f( 440 | tinfo->texture_vecs[0][0], tinfo->texture_vecs[0][1], 441 | tinfo->texture_vecs[0][2], tinfo->texture_vecs[0][3]); 442 | const struct AVec4f tex_map_v = aVec4f( 443 | tinfo->texture_vecs[1][0], tinfo->texture_vecs[1][1], 444 | tinfo->texture_vecs[1][2], tinfo->texture_vecs[1][3]); 445 | 446 | #ifdef DEBUG_DISP_LIGHTMAP 447 | const int swap = shouldSwapUV( 448 | aVec3f(tinfo->lightmap_vecs[0][0], tinfo->lightmap_vecs[0][1], tinfo->lightmap_vecs[0][2]), 449 | aVec3f(tinfo->lightmap_vecs[1][0], tinfo->lightmap_vecs[1][1], tinfo->lightmap_vecs[1][2]), vec); 450 | #endif /*ifdef DEBUG_DISP_LIGHTMAP*/ 451 | 452 | const struct AVec2f atlas_scale = aVec2f(1.f / ctx->lightmap.texture.width, 1.f / ctx->lightmap.texture.height); 453 | const struct AVec2f atlas_offset = aVec2f( 454 | .5f + face->atlas_x /*+ tinfo->lightmap_vecs[0][3] - face->face->lightmap_min[0]*/, 455 | .5f + face->atlas_y /*+ tinfo->lightmap_vecs[1][3] - face->face->lightmap_min[1]*/); 456 | 457 | if (length_lm_u < 0. || length_lm_u >= face->width 458 | || length_lm_v < 0. || length_lm_v >= face->height) { 459 | PRINTF("LM OOB: (%f, %f) (%d, %d)", length_lm_u, length_lm_v, face->width, face->height); 460 | if (length_lm_u >= face->width) length_lm_u = (float)(face->width - 1); 461 | if (length_lm_v >= face->height) length_lm_v = (float)(face->height - 1); 462 | } 463 | 464 | /* 465 | PRINTF("%f %f %f %f", 466 | tinfo->lightmap_vecs[0][3] * atlas_scale.x, face->face->lightmap_min[0] * atlas_scale.x, 467 | tinfo->lightmap_vecs[1][3] * atlas_scale.y, face->face->lightmap_min[1] * atlas_scale.y); 468 | */ 469 | 470 | const float div_side = 1.f / (side - 1); 471 | for (int y = 0; y < side; ++y) { 472 | const float ty = (float)y * div_side; 473 | const struct AVec3f vl = aVec3fMix(vec[0], vec[1], ty); 474 | const struct AVec3f vr = aVec3fMix(vec[3], vec[2], ty); 475 | for (int x = 0; x < side; ++x) { 476 | const float tx = (float)x * div_side; 477 | struct BSPModelVertex * const v = out_vertices + y * side + x; 478 | const struct VBSPLumpDispVert * const dv = dispvert + y * side + x; 479 | 480 | v->vertex = aVec3fMix(vl, vr, tx); 481 | v->lightmap_uv = aVec2f(tx * length_lm_u, ty * length_lm_v); 482 | v->tex_uv = aVec2f( 483 | aVec4fDot(aVec4f3(v->vertex, 1.f), tex_map_u), 484 | aVec4fDot(aVec4f3(v->vertex, 1.f), tex_map_v)); 485 | v->vertex = aVec3fAdd(aVec3fMix(vl, vr, tx), aVec3fMulf(aVec3f(dv->x, dv->y, dv->z), dv->dist)); 486 | 487 | if (v->lightmap_uv.x < 0 || v->lightmap_uv.y < 0 || v->lightmap_uv.x > face->width || v->lightmap_uv.y > face->height) 488 | PRINTF("Error: DISP OOB LM F:V%d: x=%f y=%f z=%f tx=%f, ty=%f u=%f v=%f w=%d h=%d", 489 | x + y * side, v->vertex.x, v->vertex.y, v->vertex.z, tx, ty, v->lightmap_uv.x, v->lightmap_uv.y, face->width, face->height); 490 | 491 | v->lightmap_uv = aVec2fMul(aVec2fAdd(v->lightmap_uv, atlas_offset), atlas_scale); 492 | 493 | #if 0 494 | #ifdef DEBUG_DISP_LIGHTMAP 495 | v->normal = aVec3f(face->dispstartvtx/3.f, swap, dv->dist / 100.f); 496 | #else 497 | /* FIXME normal */ 498 | v->normal = aVec3ff(0.f); 499 | #endif 500 | #endif 501 | } 502 | } 503 | 504 | for (int y = 0; y < side - 1; ++y) { 505 | for (int x = 0; x < side - 1; ++x) { 506 | const int ibase = index_shift + y * side + x; 507 | ASSERT(ibase + side + 1 < 65536); 508 | const uint16_t base = (uint16_t)ibase; 509 | *out_indices++ = base; 510 | *out_indices++ = base + (uint16_t)side + 1; 511 | *out_indices++ = base + (uint16_t)side; 512 | *out_indices++ = base; 513 | *out_indices++ = base + 1; 514 | *out_indices++ = base + (uint16_t)side + 1; 515 | } 516 | } 517 | } 518 | 519 | static void bspLoadFace( 520 | const struct LoadModelContext *ctx, 521 | const struct Face *face, 522 | struct BSPModelVertex *out_vertices, uint16_t *out_indices, int index_shift) { 523 | const struct VBSPLumpFace *vface = face->vface; 524 | const struct VBSPLumpTexInfo * const tinfo = face->texinfo; 525 | struct AVec3f normal; 526 | normal.x = ctx->lumps->planes.p[vface->plane].x; 527 | normal.y = ctx->lumps->planes.p[vface->plane].y; 528 | normal.z = ctx->lumps->planes.p[vface->plane].z; 529 | if (vface->side) normal = aVec3fNeg(normal); 530 | 531 | const struct AVec4f lm_map_u = aVec4f( 532 | tinfo->lightmap_vecs[0][0], tinfo->lightmap_vecs[0][1], 533 | tinfo->lightmap_vecs[0][2], tinfo->lightmap_vecs[0][3] - vface->lightmap_min[0]); 534 | const struct AVec4f lm_map_v = aVec4f( 535 | tinfo->lightmap_vecs[1][0], tinfo->lightmap_vecs[1][1], 536 | tinfo->lightmap_vecs[1][2], tinfo->lightmap_vecs[1][3] - vface->lightmap_min[1]); 537 | 538 | const struct AVec4f tex_map_u = aVec4f( 539 | tinfo->texture_vecs[0][0], tinfo->texture_vecs[0][1], 540 | tinfo->texture_vecs[0][2], tinfo->texture_vecs[0][3]); 541 | const struct AVec4f tex_map_v = aVec4f( 542 | tinfo->texture_vecs[1][0], tinfo->texture_vecs[1][1], 543 | tinfo->texture_vecs[1][2], tinfo->texture_vecs[1][3]); 544 | 545 | const int32_t * const surfedges = ctx->lumps->surfedges.p + vface->first_edge; 546 | for (int iedge = 0; iedge < vface->num_edges; ++iedge) { 547 | const uint16_t vstart = (surfedges[iedge] >= 0) 548 | ? ctx->lumps->edges.p[surfedges[iedge]].v[0] 549 | : ctx->lumps->edges.p[-surfedges[iedge]].v[1]; 550 | 551 | const struct VBSPLumpVertex * const lv = ctx->lumps->vertices.p + vstart; 552 | struct BSPModelVertex * const vertex = out_vertices + iedge; 553 | 554 | vertex->vertex = aVec3f(lv->x, lv->y, lv->z); 555 | //vertex->normal = normal; 556 | vertex->lightmap_uv = aVec2f( 557 | aVec4fDot(aVec4f3(vertex->vertex, 1.f), lm_map_u), 558 | aVec4fDot(aVec4f3(vertex->vertex, 1.f), lm_map_v)); 559 | vertex->tex_uv = aVec2f( 560 | aVec4fDot(aVec4f3(vertex->vertex, 1.f), tex_map_u), 561 | aVec4fDot(aVec4f3(vertex->vertex, 1.f), tex_map_v)); 562 | 563 | vertex->lightmap_uv.x = clamp(vertex->lightmap_uv.x, 0.f, (float)face->width); 564 | vertex->lightmap_uv.y = clamp(vertex->lightmap_uv.y, 0.f, (float)face->height); 565 | 566 | /* 567 | if (vertex->lightmap_uv.x < 0 || vertex->lightmap_uv.y < 0 || vertex->lightmap_uv.x > face->width || vertex->lightmap_uv.y > face->height) 568 | PRINTF("Error: OOB LM F:V%u: x=%f y=%f z=%f u=%f v=%f w=%d h=%d", iedge, lv->x, lv->y, lv->z, vertex->lightmap_uv.x, vertex->lightmap_uv.y, face->width, face->height); 569 | */ 570 | 571 | vertex->lightmap_uv.x = (vertex->lightmap_uv.x + face->atlas_x + .5f) / ctx->lightmap.texture.width; 572 | vertex->lightmap_uv.y = (vertex->lightmap_uv.y + face->atlas_y + .5f) / ctx->lightmap.texture.height; 573 | 574 | if (iedge > 1) { 575 | out_indices[(iedge-2)*3+0] = (uint16_t)(index_shift + 0); 576 | out_indices[(iedge-2)*3+1] = (uint16_t)(index_shift + iedge); 577 | out_indices[(iedge-2)*3+2] = (uint16_t)(index_shift + iedge - 1); 578 | } 579 | } 580 | } 581 | 582 | static int faceMaterialCompare(const void *a, const void *b) { 583 | const struct Face *fa = a, *fb = b; 584 | 585 | if (fa->material == fb->material) 586 | return 0; 587 | 588 | return (int)(fa->material->base_texture.texture - fb->material->base_texture.texture); 589 | } 590 | 591 | static enum BSPLoadResult bspLoadModelDraws(const struct LoadModelContext *ctx, struct Stack *persistent, 592 | struct BSPModel *model) { 593 | void * const tmp_cursor = stackGetCursor(ctx->tmp); 594 | 595 | struct BSPModelVertex * const vertices_buffer 596 | = stackAlloc(ctx->tmp, sizeof(struct BSPModelVertex) * ctx->max_draw_vertices); 597 | if (!vertices_buffer) return BSPLoadResult_ErrorTempMemory; 598 | 599 | /* each vertex after second in a vface is a new triangle */ 600 | uint16_t * const indices_buffer = stackAlloc(ctx->tmp, sizeof(uint16_t) * ctx->indices); 601 | if (!indices_buffer) return BSPLoadResult_ErrorTempMemory; 602 | 603 | qsort(ctx->faces, ctx->faces_count, sizeof(*ctx->faces), faceMaterialCompare); 604 | 605 | { 606 | int vbo_offset = 0, vertex_pos = 0; 607 | model->detailed.draws_count = 1; 608 | model->coarse.draws_count = 1; 609 | for (int iface = 0; iface < ctx->faces_count; ++iface) { 610 | const struct Face *face = ctx->faces + iface; 611 | 612 | const int update_vbo_offset = (vertex_pos - vbo_offset) + face->vertices >= c_max_draw_vertices; 613 | if (update_vbo_offset || (iface > 0 && faceMaterialCompare(ctx->faces+iface-1,face) != 0)) { 614 | //PRINTF("%p -> %p", (void*)ctx->faces[iface-1].material->base_texture[0], (void*)face->material->base_texture[0]); 615 | ++model->detailed.draws_count; 616 | } 617 | 618 | if (update_vbo_offset) { 619 | vbo_offset = vertex_pos; 620 | ++model->coarse.draws_count; 621 | } 622 | 623 | vertex_pos += face->vertices; 624 | } 625 | } 626 | 627 | PRINTF("Faces: %d -> %d detailed draws", ctx->faces_count, model->detailed.draws_count); 628 | 629 | model->detailed.draws = stackAlloc(persistent, sizeof(struct BSPDraw) * model->detailed.draws_count); 630 | model->coarse.draws = stackAlloc(persistent, sizeof(struct BSPDraw) * model->coarse.draws_count); 631 | 632 | int vertex_pos = 0; 633 | int draw_indices_start = 0, indices_pos = 0; 634 | int vbo_offset = 0; 635 | int idraw = 0; 636 | struct BSPDraw *detailed_draw = model->detailed.draws - 1, 637 | *coarse_draw = model->coarse.draws - 1; 638 | 639 | for (int iface = 0; iface < ctx->faces_count/* + 1*/; ++iface) { 640 | const struct Face *face = ctx->faces + iface; 641 | 642 | const int update_vbo_offset = (vertex_pos - vbo_offset) + face->vertices >= c_max_draw_vertices; 643 | 644 | if (update_vbo_offset) { 645 | PRINTF("vbo_offset %d -> %d", vbo_offset, vertex_pos); 646 | vbo_offset = vertex_pos; 647 | } 648 | 649 | if (update_vbo_offset || iface == 0 || faceMaterialCompare(ctx->faces+iface-1,face) != 0) { 650 | ++detailed_draw; 651 | detailed_draw->start = draw_indices_start; 652 | detailed_draw->count = 0; 653 | detailed_draw->vbo_offset = vbo_offset; 654 | detailed_draw->material = face->material; 655 | 656 | ++idraw; 657 | ASSERT(idraw <= model->detailed.draws_count); 658 | } 659 | 660 | if (update_vbo_offset || iface == 0) { 661 | ++coarse_draw; 662 | coarse_draw->start = draw_indices_start; 663 | coarse_draw->count = 0; 664 | coarse_draw->vbo_offset = vbo_offset; 665 | coarse_draw->material = bsp_global.coarse_material; 666 | } 667 | 668 | if (face->dispinfo) { 669 | bspLoadDisplacement(ctx, face, vertices_buffer + vertex_pos, indices_buffer + indices_pos, vertex_pos - vbo_offset); 670 | } else { 671 | bspLoadFace(ctx, face, vertices_buffer + vertex_pos, indices_buffer + indices_pos, vertex_pos - vbo_offset); 672 | } 673 | 674 | for (int i = 0; i < face->vertices; ++i) { 675 | vertices_buffer[vertex_pos + i].average_color.r = 676 | (uint8_t)(face->material->average_color.x * 255.f); 677 | vertices_buffer[vertex_pos + i].average_color.g = 678 | (uint8_t)(face->material->average_color.y * 255.f); 679 | vertices_buffer[vertex_pos + i].average_color.b = 680 | (uint8_t)(face->material->average_color.z * 255.f); 681 | } 682 | 683 | vertex_pos += face->vertices; 684 | indices_pos += face->indices; 685 | 686 | detailed_draw->count += indices_pos - draw_indices_start; 687 | coarse_draw->count += indices_pos - draw_indices_start; 688 | 689 | //vertex_pos = 0; 690 | draw_indices_start = indices_pos; 691 | } 692 | ASSERT(idraw == model->detailed.draws_count); 693 | 694 | renderBufferCreate(&model->ibo, RBufferType_Index, sizeof(uint16_t) * ctx->indices, indices_buffer); 695 | renderBufferCreate(&model->vbo, RBufferType_Vertex, sizeof(struct BSPModelVertex) * vertex_pos, vertices_buffer); 696 | 697 | stackFreeUpToPosition(ctx->tmp, tmp_cursor); 698 | return BSPLoadResult_Success; 699 | } 700 | 701 | static enum BSPLoadResult bspLoadModel( 702 | struct ICollection *collection, struct BSPModel *model, struct Stack *persistent, struct Stack *temp, 703 | const struct Lumps *lumps, unsigned index) { 704 | struct LoadModelContext context; 705 | memset(&context, 0, sizeof context); 706 | 707 | ASSERT(index < lumps->models.n); 708 | 709 | context.tmp = temp; 710 | context.collection = collection; 711 | context.lumps = lumps; 712 | context.model = lumps->models.p + index; 713 | 714 | /* Step 1. Collect lightmaps for all faces */ 715 | enum BSPLoadResult result = bspLoadModelPreloadFaces(&context); 716 | if (result != BSPLoadResult_Success) { 717 | PRINTF("Error: bspLoadModelPreloadFaces() => %s", R2S(result)); 718 | return result; 719 | } 720 | 721 | /* Step 2. Build an atlas of all lightmaps */ 722 | result = bspLoadModelLightmaps(&context); 723 | if (result != BSPLoadResult_Success) { 724 | PRINTF("Error: bspLoadModelLightmaps() => %s", R2S(result)); 725 | return result; 726 | } 727 | 728 | /* Step 3. Generate draw operations data */ 729 | result = bspLoadModelDraws(&context, persistent, model); 730 | if (result != BSPLoadResult_Success) { 731 | //aGLTextureDestroy(&context.lightmap.texture); 732 | return result; 733 | } 734 | 735 | model->lightmap = context.lightmap.texture; 736 | model->aabb.min.x = context.model->min.x; 737 | model->aabb.min.y = context.model->min.y; 738 | model->aabb.min.z = context.model->min.z; 739 | model->aabb.max.x = context.model->max.x; 740 | model->aabb.max.y = context.model->max.y; 741 | model->aabb.max.z = context.model->max.z; 742 | 743 | return BSPLoadResult_Success; 744 | } // bspLoadModel() 745 | 746 | static const char *bsp_skybox_suffix[6] = { 747 | "rt", "lf", "ft", "bk", "up", "dn" }; 748 | 749 | static void bspLoadSkybox(StringView name, ICollection *coll, Stack *tmp, struct BSPModel *model) { 750 | PRINTF("Loading skybox %.*s", name.length, name.str); 751 | 752 | char *zname = alloca(name.length + 3 + 7); 753 | memset(zname, 0, (int)name.length + 3 + 7); 754 | memcpy(zname, "skybox/", 7); 755 | memcpy(zname + 7, name.str, name.length); 756 | 757 | for (int i = 0; i < 6; ++i) { 758 | memcpy(zname + name.length + 7, bsp_skybox_suffix[i], 2); 759 | model->skybox[i] = materialGet(zname, coll, tmp); 760 | } 761 | } 762 | 763 | typedef struct { 764 | const char *name; 765 | StringView value; 766 | } EntityProp; 767 | 768 | #define ENTITY_LIST_PROPS \ 769 | ENTITY_PROP(ClassName, classname) \ 770 | ENTITY_PROP(TargetName, targetname) \ 771 | ENTITY_PROP(Origin, origin) \ 772 | ENTITY_PROP(SkyName, skyname) \ 773 | ENTITY_PROP(Landmark, landmark) \ 774 | ENTITY_PROP(Map, map) \ 775 | 776 | typedef enum { 777 | #define ENTITY_PROP(name, string) \ 778 | EntityPropIndex_##name, 779 | ENTITY_LIST_PROPS 780 | #undef ENTITY_PROP 781 | } EntityPropIndex; 782 | 783 | typedef struct { 784 | BSPLoadModelContext *ctx; 785 | EntityProp *props; 786 | int props_count; 787 | } Entity; 788 | 789 | typedef BSPLoadResult (*BspProcessEntityProc)(BSPLoadModelContext *ctx, const Entity *entity); 790 | 791 | BSPLoadResult bspReadAndAddLandmark(BSPLoadModelContext *ctx, StringView target_name, StringView origin) { 792 | 793 | struct BSPModel *model = ctx->model; 794 | if (model->landmarks_count == BSP_MAX_LANDMARKS) { 795 | PRINT("Too many landmarks"); 796 | return BSPLoadResult_ErrorMemory; 797 | } 798 | 799 | struct BSPLandmark *landmark = model->landmarks + model->landmarks_count; 800 | if (target_name.length >= (int)sizeof(landmark->name)) { 801 | PRINTF("Landmark name \"%.*s\" is too long", 802 | PRI_SVV(target_name)); 803 | return BSPLoadResult_ErrorMemory; 804 | } 805 | 806 | memcpy(landmark->name, target_name.str, target_name.length); 807 | landmark->name[target_name.length] = '\0'; 808 | 809 | // FIXME props[EntityPropIndex_Origin].value is not null-terminated suman 810 | if (3 != sscanf(origin.str, "%f %f %f", 811 | &landmark->origin.x, 812 | &landmark->origin.y, 813 | &landmark->origin.z)) 814 | { 815 | PRINTF("Cannot read x, y, z from origin=\"%.*s\"", PRI_SVV(origin)); 816 | return BSPLoadResult_ErrorFileFormat; 817 | } 818 | 819 | ++model->landmarks_count; 820 | 821 | return BSPLoadResult_Success; 822 | } 823 | 824 | static BSPLoadResult bspProcessEntityInfoLandmark(BSPLoadModelContext *ctx, const Entity *entity) { 825 | const StringView target_name = entity->props[EntityPropIndex_TargetName].value; 826 | const StringView origin = entity->props[EntityPropIndex_Origin].value; 827 | 828 | return bspReadAndAddLandmark(ctx, target_name, origin); 829 | } 830 | 831 | static BSPLoadResult bspProcessEntityInfoLandmarkEntry(BSPLoadModelContext *ctx, const Entity *entity) { 832 | const int landmark_name_length = ctx->prev_map_name.length + ctx->name.length + 5; 833 | char *landmark_name_buf = stackAlloc(ctx->tmp, landmark_name_length); 834 | memcpy(landmark_name_buf, ctx->prev_map_name.str, ctx->prev_map_name.length); 835 | memcpy(landmark_name_buf + ctx->prev_map_name.length, "_to_", 4); 836 | memcpy(landmark_name_buf + ctx->prev_map_name.length + 4, ctx->name.str, ctx->name.length); 837 | landmark_name_buf[landmark_name_length-1] = '\0'; 838 | 839 | const StringView target_name = { .str = landmark_name_buf, .length = landmark_name_length }; 840 | const StringView origin = entity->props[EntityPropIndex_Origin].value; 841 | 842 | const BSPLoadResult result = bspReadAndAddLandmark(ctx, target_name, origin); 843 | stackFreeUpToPosition(ctx->tmp, landmark_name_buf); 844 | 845 | return result; 846 | } 847 | 848 | static BSPLoadResult bspProcessEntityInfoLandmarkExit(BSPLoadModelContext *ctx, const Entity *entity) { 849 | const int landmark_name_length = ctx->next_map_name.length + ctx->name.length + 5; 850 | char *landmark_name_buf = stackAlloc(ctx->tmp, landmark_name_length); 851 | memcpy(landmark_name_buf, ctx->name.str, ctx->name.length); 852 | memcpy(landmark_name_buf + ctx->name.length, "_to_", 4); 853 | memcpy(landmark_name_buf + ctx->name.length + 4, ctx->next_map_name.str, ctx->next_map_name.length); 854 | landmark_name_buf[landmark_name_length-1] = '\0'; 855 | 856 | const StringView target_name = { .str = landmark_name_buf, .length = landmark_name_length }; 857 | const StringView origin = entity->props[EntityPropIndex_Origin].value; 858 | 859 | const BSPLoadResult result = bspReadAndAddLandmark(ctx, target_name, origin); 860 | stackFreeUpToPosition(ctx->tmp, landmark_name_buf); 861 | 862 | return result; 863 | } 864 | 865 | static BSPLoadResult bspProcessEntityTriggerChangelevel(struct BSPLoadModelContext *ctx, const Entity *entity) { 866 | (void)ctx; 867 | openSourceAddMap(entity->props[EntityPropIndex_Map].value); 868 | return BSPLoadResult_Success; 869 | } 870 | 871 | static BSPLoadResult bspProcessEntityWorldspawn(struct BSPLoadModelContext *ctx, const Entity *entity) { 872 | (void)ctx; 873 | const StringView skyname = entity->props[EntityPropIndex_SkyName].value; 874 | 875 | if (skyname.length > 0) { 876 | const StringView sky = { skyname.str, skyname.length }; 877 | bspLoadSkybox(sky, ctx->collection, ctx->tmp, ctx->model); 878 | } 879 | 880 | return BSPLoadResult_Success; 881 | } 882 | 883 | static struct { 884 | const char *classname; 885 | BspProcessEntityProc proc; 886 | } entity_procs[] = { 887 | {"info_landmark", bspProcessEntityInfoLandmark}, 888 | {"info_landmark_entry", bspProcessEntityInfoLandmarkEntry}, 889 | {"info_landmark_exit", bspProcessEntityInfoLandmarkExit}, 890 | {"trigger_changelevel", bspProcessEntityTriggerChangelevel}, 891 | {"worldspawn", bspProcessEntityWorldspawn}, 892 | }; 893 | 894 | static VMFAction bspReadEntityProps(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) { 895 | Entity *entity = state->user_data; 896 | 897 | switch (entry) { 898 | case VMFEntryType_KeyValue: 899 | for (int i = 0; i < entity->props_count; ++i) { 900 | if (strncmp(entity->props[i].name, kv->key.str, kv->key.length) == 0) { 901 | entity->props[i].value = kv->value; 902 | break; 903 | } 904 | } 905 | break; 906 | case VMFEntryType_SectionOpen: 907 | for (int i = 0; i < entity->props_count; ++i) { 908 | entity->props[i].value.str = NULL; 909 | entity->props[i].value.length = 0; 910 | } 911 | 912 | break; 913 | case VMFEntryType_SectionClose: 914 | for (int i = 0; i < (int)COUNTOF(entity_procs); ++i) { 915 | const StringView classname = entity->props[EntityPropIndex_ClassName].value; 916 | if (strncmp(entity_procs[i].classname, classname.str, classname.length) == 0) { 917 | entity_procs[i].proc(entity->ctx, entity); 918 | break; 919 | } 920 | } 921 | break; 922 | } 923 | 924 | return VMFAction_Continue; 925 | } 926 | 927 | BSPLoadResult bspReadEntities(BSPLoadModelContext *ctx, const char *str, int length) { 928 | ctx->model->landmarks_count = 0; 929 | 930 | EntityProp props[] = { 931 | #define ENTITY_PROP(name, string) \ 932 | {#string, {NULL, 0}}, 933 | ENTITY_LIST_PROPS 934 | #undef ENTITY_PROP 935 | }; 936 | 937 | Entity entity = { 938 | .ctx = ctx, 939 | .props = props, 940 | .props_count = COUNTOF(props), 941 | }; 942 | 943 | VMFState parser_state = { 944 | .user_data = &entity, 945 | .data = { .str = str, .length = length }, 946 | .callback = bspReadEntityProps, 947 | }; 948 | 949 | return VMFResult_Success == vmfParse(&parser_state) 950 | ? BSPLoadResult_Success : BSPLoadResult_ErrorFileFormat; 951 | } 952 | 953 | static int lumpRead(const char *name, const struct VBSPLumpHeader *header, 954 | struct IFile *file, struct Stack *tmp, 955 | struct AnyLump *out_ptr, uint32_t item_size) { 956 | out_ptr->p = stackAlloc(tmp, header->size); 957 | if (!out_ptr->p) { 958 | PRINTF("Not enough temp memory to allocate storage for lump %s; need: %u (%x)", name, header->size, header->size); 959 | return -1; 960 | } 961 | 962 | const size_t bytes = file->read(file, header->file_offset, header->size, (void*)out_ptr->p); 963 | if (bytes != header->size) { 964 | PRINTF("Cannot read full lump %s, read only %zu bytes out of %u", name, bytes, header->size); 965 | return -1; 966 | } 967 | 968 | PRINTF("Read lump %s, offset %u, size %u bytes / %u item = %u elements", 969 | name, header->file_offset, header->size, item_size, header->size / item_size); 970 | 971 | out_ptr->n = header->size / item_size; 972 | return 1; 973 | } 974 | 975 | enum BSPLoadResult bspLoadWorldspawn(BSPLoadModelContext context) { 976 | enum BSPLoadResult result = BSPLoadResult_Success; 977 | struct IFile *file = 0; 978 | if (CollectionOpen_Success != 979 | collectionChainOpen(context.collection, context.name.str /* FIXME assumes null-terminated string */, File_Map, &file)) { 980 | return BSPLoadResult_ErrorFileOpen; 981 | } 982 | 983 | void *tmp_cursor = stackGetCursor(context.tmp); 984 | struct ICollection *pakfile = NULL; 985 | 986 | struct VBSPHeader vbsp_header; 987 | size_t bytes = file->read(file, 0, sizeof vbsp_header, &vbsp_header); 988 | if (bytes < sizeof(vbsp_header)) { 989 | PRINTF("Size is too small: %zu <= %zu", bytes, sizeof(struct VBSPHeader)); 990 | result = BSPLoadResult_ErrorFileFormat; 991 | goto exit; 992 | } 993 | 994 | if (vbsp_header.ident[0] != 'V' || vbsp_header.ident[1] != 'B' || 995 | vbsp_header.ident[2] != 'S' || vbsp_header.ident[3] != 'P') { 996 | PRINTF("Error: invalid ident => %c%c%c%c != VBSP", 997 | vbsp_header.ident[0], vbsp_header.ident[1], vbsp_header.ident[2], vbsp_header.ident[3]); 998 | result = BSPLoadResult_ErrorFileFormat; 999 | goto exit; 1000 | } 1001 | 1002 | if (vbsp_header.version < 19 || vbsp_header.version > 21) { 1003 | PRINTF("Error: invalid version: %u != 19 or 20 or 21", vbsp_header.version); 1004 | result = BSPLoadResult_ErrorFileFormat; 1005 | goto exit; 1006 | } 1007 | 1008 | PRINTF("VBSP version %u opened", vbsp_header.version); 1009 | 1010 | struct Lumps lumps; 1011 | lumps.version = vbsp_header.version; 1012 | #define BSPLUMP(name, type, field) \ 1013 | if (1 != lumpRead(#name, vbsp_header.lump_headers + VBSP_Lump_##name, file, context.tmp, \ 1014 | (struct AnyLump*)&lumps.field, sizeof(type))) { \ 1015 | result = BSPLoadResult_ErrorFileFormat; \ 1016 | goto exit; \ 1017 | } 1018 | LIST_LUMPS 1019 | #undef BSPLUMP 1020 | 1021 | if (lumps.lightmaps.n == 0) { 1022 | memcpy(&lumps.lightmaps, &lumps.lightmaps_hdr, sizeof(lumps.lightmaps)); 1023 | memcpy(&lumps.faces, &lumps.faces_hdr, sizeof(lumps.faces)); 1024 | } 1025 | 1026 | if (lumps.pakfile.n > 0) { 1027 | struct Memories memories = { context.tmp, context.tmp }; 1028 | pakfile = collectionCreatePakfile(&memories, lumps.pakfile.p, lumps.pakfile.n); 1029 | if (pakfile) 1030 | pakfile->next = context.collection; 1031 | } 1032 | 1033 | result = bspLoadModel(pakfile ? pakfile : context.collection, context.model, context.persistent, context.tmp, &lumps, 0); 1034 | if (result != BSPLoadResult_Success) { 1035 | PRINTF("Error: bspLoadModel() => %s", R2S(result)); 1036 | goto exit; 1037 | } 1038 | 1039 | result = bspReadEntities(&context, lumps.entities.p, lumps.entities.n); 1040 | if (result != BSPLoadResult_Success) 1041 | PRINTF("Error: bspReadEntities() => %s", R2S(result)); 1042 | 1043 | exit: 1044 | if (pakfile) 1045 | pakfile->close(pakfile); 1046 | 1047 | stackFreeUpToPosition(context.tmp, tmp_cursor); 1048 | if (file) file->close(file); 1049 | return result; 1050 | } 1051 | 1052 | void bspInit() { 1053 | bsp_global.coarse_material = materialGet("opensource/coarse", NULL, NULL); 1054 | 1055 | const int scaling_factor = 4096; 1056 | for (int i = 0; i < 256; ++i) { 1057 | const int exp = i - 128; 1058 | bsp_global.lightmap_tables.exponent[i] = (exp < -15 || exp > 15) ? 0 : 1059 | (int)((float)scaling_factor * powf(2.f, (float)exp / 2.2f - 1.f)); 1060 | 1061 | bsp_global.lightmap_tables.color[i] = 1062 | (int)(255.f * powf((float)i / 255.f, 1.f / 2.2f)); 1063 | } 1064 | } 1065 | -------------------------------------------------------------------------------- /src/bsp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "material.h" 3 | #include "render.h" 4 | #include "atto/math.h" 5 | #include "common.h" 6 | 7 | struct AABB { struct AVec3f min, max; }; 8 | 9 | struct BSPModelVertex { 10 | struct AVec3f vertex; 11 | //struct AVec3f normal; 12 | struct AVec2f lightmap_uv; 13 | struct AVec2f tex_uv; 14 | struct { uint8_t r, g, b; } average_color; 15 | }; 16 | 17 | struct BSPDraw { 18 | const Material *material; 19 | unsigned int start, count; 20 | unsigned int vbo_offset; 21 | }; 22 | 23 | #define BSP_LANDMARK_NAME_LENGTH 64 24 | #define BSP_MAX_LANDMARKS 32 25 | 26 | typedef struct BSPLandmark { 27 | char name[BSP_LANDMARK_NAME_LENGTH]; 28 | struct AVec3f origin; 29 | } BSPLandmark; 30 | 31 | struct BSPDrawSet { 32 | int draws_count; 33 | struct BSPDraw *draws; 34 | }; 35 | 36 | typedef enum { 37 | BSPSkyboxDir_RT, 38 | BSPSkyboxDir_LF, 39 | BSPSkyboxDir_FT, 40 | BSPSkyboxDir_BK, 41 | BSPSkyboxDir_UP, 42 | BSPSkyboxDir_DN, 43 | BSPSkyboxDir_COUNT 44 | } BSPSkyboxDir; 45 | 46 | struct BSPModel { 47 | struct AABB aabb; 48 | RTexture lightmap; 49 | RBuffer vbo, ibo; 50 | 51 | const Material *skybox[BSPSkyboxDir_COUNT]; 52 | 53 | struct BSPDrawSet detailed; 54 | struct BSPDrawSet coarse; 55 | 56 | struct BSPLandmark landmarks[BSP_MAX_LANDMARKS]; 57 | int landmarks_count; 58 | 59 | struct AVec3f player_start; 60 | }; 61 | 62 | struct ICollection; 63 | struct MemoryPool; 64 | struct Stack; 65 | 66 | typedef struct BSPLoadModelContext { 67 | struct ICollection *collection; 68 | struct Stack *persistent; 69 | struct Stack *tmp; 70 | 71 | /* allocated by caller, populated by callee */ 72 | struct BSPModel *model; 73 | 74 | StringView name; 75 | StringView prev_map_name, next_map_name; 76 | } BSPLoadModelContext; 77 | 78 | typedef enum BSPLoadResult { 79 | BSPLoadResult_Success, 80 | BSPLoadResult_ErrorFileOpen, 81 | BSPLoadResult_ErrorFileFormat, 82 | BSPLoadResult_ErrorMemory, 83 | BSPLoadResult_ErrorTempMemory, 84 | BSPLoadResult_ErrorCapabilities 85 | } BSPLoadResult; 86 | 87 | /* should be called AFTER renderInit() */ 88 | void bspInit(); 89 | 90 | enum BSPLoadResult bspLoadWorldspawn(BSPLoadModelContext context); 91 | 92 | void openSourceAddMap(StringView name); 93 | -------------------------------------------------------------------------------- /src/cache.c: -------------------------------------------------------------------------------- 1 | #include "cache.h" 2 | #include "material.h" 3 | #include "texture.h" 4 | #define AHASH_IMPLEMENT 5 | #include "ahash.h" 6 | #include "mempools.h" 7 | 8 | static struct { 9 | AHash materials; 10 | AHash textures; 11 | } g; 12 | 13 | static void initHash(AHash *hash, struct Stack *pool, long item_size) { 14 | hash->alloc_param = pool; 15 | hash->alloc = (AHashAllocFunc)stackAlloc; 16 | hash->nbuckets = 64; 17 | hash->key_size = 256; 18 | hash->value_size = item_size; 19 | hash->key_hash = aHashStringHash; 20 | hash->key_compare = (AHashKeyCompareFunc)strcmp; 21 | aHashInit(hash); 22 | } 23 | 24 | void cacheInit(struct Stack *pool) { 25 | initHash(&g.materials, pool, sizeof(struct Material)); 26 | initHash(&g.textures, pool, sizeof(struct Texture)); 27 | } 28 | 29 | const struct Material *cacheGetMaterial(const char *name) { 30 | return aHashGet(&g.materials, name); 31 | } 32 | 33 | void cachePutMaterial(const char *name, const struct Material *mat /* copied */) { 34 | aHashInsert(&g.materials, name, mat); 35 | } 36 | 37 | const struct Texture *cacheGetTexture(const char *name) { 38 | return aHashGet(&g.textures, name); 39 | } 40 | 41 | void cachePutTexture(const char *name, const struct Texture *tex /* copied */) { 42 | aHashInsert(&g.textures, name, tex); 43 | } 44 | -------------------------------------------------------------------------------- /src/cache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Stack; 4 | 5 | void cacheInit(struct Stack* pool); 6 | 7 | struct Material; 8 | struct Texture; 9 | 10 | const struct Material *cacheGetMaterial(const char *name); 11 | void cachePutMaterial(const char *name, const struct Material *mat /* copied */); 12 | 13 | const struct Texture *cacheGetTexture(const char *name); 14 | void cachePutTexture(const char *name, const struct Texture *tex /* copied */); 15 | -------------------------------------------------------------------------------- /src/camera.c: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | 3 | void cameraRecompute(struct Camera *cam) { 4 | cam->dir = aVec3fNormalize(cam->dir); 5 | const struct AVec3f 6 | z = aVec3fNeg(cam->dir), 7 | x = aVec3fNormalize(aVec3fCross(cam->up, z)), 8 | y = aVec3fCross(z, x); 9 | cam->axes = aMat3fv(x, y, z); 10 | cam->orientation = aMat3fTranspose(cam->axes); 11 | cam->view_projection = aMat4fMul(cam->projection, 12 | aMat4f3(cam->orientation, aVec3fMulMat(cam->orientation, aVec3fNeg(cam->pos)))); 13 | } 14 | 15 | void cameraProjection(struct Camera *cam, float znear, float zfar, float horizfov, float aspect) { 16 | const float w = 2.f * znear * tanf(horizfov / 2.f), h = w / aspect; 17 | cam->z_far = zfar; 18 | cam->z_near = znear; 19 | //aAppDebugPrintf("%f %f %f %f -> %f %f", near, far, horizfov, aspect, w, h); 20 | cam->projection = aMat4fPerspective(znear, zfar, w, h); 21 | } 22 | 23 | void cameraLookAt(struct Camera *cam, struct AVec3f pos, struct AVec3f at, struct AVec3f up) { 24 | cam->pos = pos; 25 | cam->dir = aVec3fNormalize(aVec3fSub(at, pos)); 26 | cam->up = up; 27 | cameraRecompute(cam); 28 | } 29 | 30 | void cameraMove(struct Camera *cam, struct AVec3f v) { 31 | cam->pos = aVec3fAdd(cam->pos, aVec3fMulf(cam->axes.X, v.x)); 32 | cam->pos = aVec3fAdd(cam->pos, aVec3fMulf(cam->axes.Y, v.y)); 33 | cam->pos = aVec3fAdd(cam->pos, aVec3fMulf(cam->axes.Z, v.z)); 34 | } 35 | 36 | void cameraRotateYaw(struct Camera *cam, float yaw) { 37 | const struct AMat3f rot = aMat3fRotateAxis(cam->up, yaw); 38 | cam->dir = aVec3fMulMat(rot, cam->dir); 39 | } 40 | 41 | void cameraRotatePitch(struct Camera *cam, float pitch) { 42 | /* TODO limit pitch */ 43 | const struct AMat3f rot = aMat3fRotateAxis(cam->axes.X, pitch); 44 | cam->dir = aVec3fMulMat(rot, cam->dir); 45 | } 46 | -------------------------------------------------------------------------------- /src/camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "atto/math.h" 3 | 4 | struct Camera { 5 | struct AMat4f projection; 6 | struct AMat4f view_projection; 7 | struct AMat3f axes, orientation; 8 | struct AVec3f pos, dir, up; 9 | float z_near, z_far; 10 | }; 11 | 12 | void cameraRecompute(struct Camera *cam); 13 | void cameraProjection(struct Camera *cam, float znear, float zfar, float horizfov, float aspect); 14 | void cameraLookAt(struct Camera *cam, struct AVec3f pos, struct AVec3f at, struct AVec3f up); 15 | void cameraMove(struct Camera *cam, struct AVec3f v); 16 | void cameraRotateYaw(struct Camera *cam, float yaw); 17 | void cameraRotatePitch(struct Camera *cam, float pitch); 18 | -------------------------------------------------------------------------------- /src/collection.c: -------------------------------------------------------------------------------- 1 | #include "collection.h" 2 | #include "common.h" 3 | #include "vpk.h" 4 | #include "zip.h" 5 | 6 | #include "atto/app.h" 7 | 8 | #ifndef _WIN32 9 | #include 10 | #else 11 | #define alloca _alloca 12 | #pragma warning(disable:4204) 13 | #endif 14 | 15 | enum CollectionOpenResult collectionChainOpen(struct ICollection *collection, 16 | const char *name, enum FileType type, struct IFile **out_file) { 17 | while (collection) { 18 | enum CollectionOpenResult result = collection->open(collection, name, type, out_file); 19 | if (result == CollectionOpen_Success) return result; 20 | if (result == CollectionOpen_NotFound) { 21 | collection = collection->next; 22 | } else { 23 | /* TODO print error */ 24 | return result; 25 | } 26 | } 27 | 28 | return CollectionOpen_NotFound; 29 | } 30 | 31 | struct FilesystemCollectionFile { 32 | struct IFile head; 33 | struct AFile file; 34 | struct Stack *temp; 35 | }; 36 | 37 | struct FilesystemCollection { 38 | struct ICollection head; 39 | struct Memories mem; 40 | char *prefix; 41 | }; 42 | 43 | static size_t filesystemCollectionFile_Read(struct IFile *file, size_t offset, size_t size, void *buffer) { 44 | struct FilesystemCollectionFile *f = (void*)file; 45 | const size_t result = aFileReadAtOffset(&f->file, offset, size, buffer); 46 | return result != AFileError ? result : 0; 47 | } 48 | 49 | static void filesystemCollectionFile_Close(struct IFile *file) { 50 | struct FilesystemCollectionFile *f = (void*)file; 51 | aFileClose(&f->file); 52 | stackFreeUpToPosition(f->temp, f); 53 | } 54 | 55 | static void filesystemCollectionClose(struct ICollection *collection) { 56 | (void)(collection); 57 | /* TODO ensure all files are closed */ 58 | /* TODO free memory */ 59 | } 60 | 61 | static char *makeResourceFilename(struct Stack *temp, const char *prefix, const char *name, enum FileType type) { 62 | const char *subdir = NULL; 63 | const char *suffix = NULL; 64 | 65 | switch (type) { 66 | case File_Map: subdir = "maps/"; suffix = ".bsp"; break; 67 | case File_Material: subdir = "materials/"; suffix = ".vmt"; break; 68 | case File_Texture: subdir = "materials/"; suffix = ".vtf"; break; 69 | case File_Model: subdir = "models/"; suffix = ".mdl"; break; 70 | default: ASSERT(!"Invalid class"); 71 | } 72 | 73 | const int prefix_len = prefix ? (int)strlen(prefix) : 0; 74 | const int subdir_len = (int)strlen(subdir); 75 | const int name_len = (int)strlen(name); 76 | const int suffix_len = (int)strlen(suffix); 77 | const int name_length = prefix_len + subdir_len + name_len + suffix_len + 1; 78 | 79 | char *output = stackAlloc(temp, name_length); 80 | if (!output) return NULL; 81 | 82 | char *c = output; 83 | if (prefix) 84 | for (int i = 0; i < prefix_len; ++i) 85 | *c++ = prefix[i]; 86 | 87 | for (int i = 0; i < subdir_len; ++i) 88 | *c++ = subdir[i]; 89 | 90 | for (int i = 0; i < name_len; ++i) { 91 | const char C = (char)tolower(name[i]); 92 | *c++ = (C == '\\') ? '/' : C; 93 | } 94 | 95 | for (int i = 0; i < suffix_len; ++i) 96 | *c++ = suffix[i]; 97 | 98 | *c = '\0'; 99 | 100 | return output; 101 | } 102 | 103 | static enum CollectionOpenResult filesystemCollectionOpen(struct ICollection *collection, 104 | const char *name, enum FileType type, struct IFile **out_file) { 105 | struct FilesystemCollection *fsc = (struct FilesystemCollection*)collection; 106 | 107 | *out_file = NULL; 108 | 109 | struct FilesystemCollectionFile *file = stackAlloc(fsc->mem.temp, sizeof(*file)); 110 | char *filename = makeResourceFilename(fsc->mem.temp, fsc->prefix, name, type); 111 | 112 | if (!file || !filename) { 113 | PRINTF("Not enough memory for file %s", name); 114 | return CollectionOpen_NotEnoughMemory; 115 | } 116 | 117 | if (aFileOpen(&file->file, filename) != AFile_Success) { 118 | if (type == File_Map) 119 | PRINTF("Cannot open map %s", filename); 120 | stackFreeUpToPosition(fsc->mem.temp, file); 121 | return CollectionOpen_NotFound; 122 | 123 | } 124 | 125 | file->head.size = file->file.size; 126 | file->head.read = filesystemCollectionFile_Read; 127 | file->head.close = filesystemCollectionFile_Close; 128 | file->temp = fsc->mem.temp; 129 | *out_file = &file->head; 130 | 131 | stackFreeUpToPosition(fsc->mem.temp, filename); 132 | return CollectionOpen_Success; 133 | } 134 | 135 | struct ICollection *collectionCreateFilesystem(struct Memories *mem, const char *dir) { 136 | const int dir_len = (int)strlen(dir); 137 | struct FilesystemCollection *collection = stackAlloc(mem->persistent, sizeof(*collection) + dir_len + 2); 138 | 139 | if (!collection) 140 | return NULL; 141 | 142 | memset(collection, 0, sizeof *collection); 143 | collection->mem = *mem; 144 | collection->prefix = (char*)(collection + 1); 145 | 146 | memcpy(collection->prefix, dir, dir_len); 147 | collection->prefix[dir_len] = '/'; 148 | collection->prefix[dir_len+1] = '\0'; 149 | 150 | collection->head.open = filesystemCollectionOpen; 151 | collection->head.close = filesystemCollectionClose; 152 | return &collection->head; 153 | } 154 | 155 | struct StringView { 156 | const char *s; 157 | int len; 158 | }; 159 | #define PRI_SV "%.*s" 160 | #define PASS_SV(sv) sv.len, sv.s 161 | #define PASS_PSV(sv) sv->len, sv->s 162 | 163 | static struct StringView readString(const char **c, const char *end) { 164 | struct StringView ret = { *c, 0 }; 165 | while (*c < end && **c != '\0') ++(*c); 166 | ret.len = (int)(*c - ret.s); 167 | ++(*c); 168 | return ret; 169 | } 170 | 171 | struct VPKFileMetadata { 172 | struct StringView filename; 173 | int archive; 174 | struct { 175 | size_t off, size; 176 | } dir, arc; 177 | }; 178 | 179 | #define MAX_VPK_ARCHIVES 152 180 | 181 | struct VPKCollection { 182 | struct ICollection head; 183 | struct Memories mem; 184 | struct { 185 | const char *data; 186 | int size; 187 | } dir; 188 | struct AFile archives[MAX_VPK_ARCHIVES]; 189 | struct VPKFileMetadata *files; 190 | int files_count; 191 | }; 192 | 193 | struct VPKCollectionFile { 194 | struct IFile head; 195 | const struct VPKFileMetadata *metadata; 196 | struct VPKCollection *collection; 197 | }; 198 | 199 | static void vpkCollectionClose(struct ICollection *collection) { 200 | (void)(collection); 201 | /* FIXME close handles and free memory */ 202 | } 203 | 204 | static size_t vpkCollectionFileRead(struct IFile *file, size_t offset, size_t size, void *buffer) { 205 | struct VPKCollectionFile *f = (struct VPKCollectionFile*)file; 206 | const struct VPKFileMetadata *meta = f->metadata; 207 | 208 | size_t size_read = 0; 209 | if (offset < meta->dir.size) { 210 | const void *begin = ((char*)f->collection->dir.data) + offset + meta->dir.off; 211 | const size_t dir_size_left = meta->dir.size - offset; 212 | if (size <= dir_size_left) { 213 | memcpy(buffer, begin, size); 214 | return size; 215 | } 216 | 217 | const size_t size_to_read = size - dir_size_left; 218 | memcpy(buffer, begin, size_to_read); 219 | 220 | buffer = ((char*)buffer) + size_to_read; 221 | offset += size_to_read; 222 | size -= size_to_read; 223 | size_read += size_to_read; 224 | } 225 | 226 | offset -= meta->dir.size; 227 | 228 | if (offset < meta->arc.size) 229 | size_read += aFileReadAtOffset(&f->collection->archives[meta->archive], meta->arc.off + offset, size, buffer); 230 | 231 | return size_read; 232 | } 233 | 234 | static void vpkCollectionFileClose(struct IFile *file) { 235 | struct VPKCollectionFile *f = (void*)file; 236 | stackFreeUpToPosition(f->collection->mem.temp, f); 237 | } 238 | 239 | static enum CollectionOpenResult vpkCollectionFileOpen(struct ICollection *collection, 240 | const char *name, enum FileType type, struct IFile **out_file) { 241 | struct VPKCollection *vpkc = (struct VPKCollection*)collection; 242 | 243 | *out_file = NULL; 244 | 245 | struct VPKCollectionFile *file = stackAlloc(vpkc->mem.temp, sizeof(*file)); 246 | char *filename = makeResourceFilename(vpkc->mem.temp, NULL, name, type); 247 | 248 | if (!file || !filename) { 249 | PRINTF("Not enough memory for file %s", name); 250 | return CollectionOpen_NotEnoughMemory; 251 | } 252 | 253 | // binary search 254 | { 255 | const struct VPKFileMetadata *begin = vpkc->files; 256 | int count = vpkc->files_count; 257 | 258 | while (count > 0) { 259 | int item = count / 2; 260 | 261 | const struct VPKFileMetadata *meta = begin + item; 262 | const int comparison = strcmp(filename, meta->filename.s); 263 | if (comparison == 0) { 264 | file->metadata = meta; 265 | file->collection = vpkc; 266 | file->head.size = meta->arc.size + meta->dir.size; 267 | file->head.read = vpkCollectionFileRead; 268 | file->head.close = vpkCollectionFileClose; 269 | *out_file = &file->head; 270 | stackFreeUpToPosition(vpkc->mem.temp, filename); 271 | return CollectionOpen_Success; 272 | } 273 | 274 | if (comparison < 0) { 275 | count = item; 276 | } else { 277 | count = count - item - 1; 278 | begin += item + 1; 279 | } 280 | } 281 | } 282 | 283 | stackFreeUpToPosition(vpkc->mem.temp, file); 284 | return CollectionOpen_NotFound; 285 | } 286 | 287 | static int vpkMetadataCompare(const void *a, const void *b) { 288 | const struct VPKFileMetadata *am = a, *bm = b; 289 | return strcmp(am->filename.s, bm->filename.s); 290 | } 291 | 292 | struct ICollection *collectionCreateVPK(struct Memories *mem, const char *dir_filename) { 293 | PRINTF("Opening collection %s", dir_filename); 294 | struct VPKCollection *collection = stackAlloc(mem->persistent, sizeof(*collection)); 295 | 296 | if (!collection) 297 | return NULL; 298 | 299 | memset(collection, 0, sizeof *collection); 300 | collection->mem = *mem; 301 | 302 | void *const temp_stack_top = stackGetCursor(mem->temp); 303 | 304 | { 305 | struct AFile dir_file; 306 | if (AFile_Success != aFileOpen(&dir_file, dir_filename)) { 307 | PRINTF("Cannot open %s", dir_filename); 308 | aAppTerminate(-1); 309 | } 310 | 311 | char *data = stackAlloc(mem->persistent, dir_file.size); 312 | if (!data) { 313 | PRINTF("Cannot allocate %zu bytes of persistent memory", dir_file.size); 314 | aAppTerminate(-1); 315 | } 316 | 317 | if (aFileReadAtOffset(&dir_file, 0, dir_file.size, data) != dir_file.size) { 318 | PRINTF("Cannot read entire directory of %zu bytes", dir_file.size); 319 | aAppTerminate(-1); 320 | } 321 | 322 | collection->dir.data = data; 323 | collection->dir.size = (int)dir_file.size; 324 | 325 | aFileClose(&dir_file); 326 | } 327 | 328 | const char *dir = collection->dir.data; 329 | const size_t size = collection->dir.size; 330 | 331 | if (size <= sizeof(VPK1Header)) { 332 | PRINT("VPK header is too small"); 333 | exit(-1); 334 | } 335 | 336 | const VPK1Header *header = (void*)collection->dir.data; 337 | 338 | if (header->signature != VPK_SIGNATURE) { 339 | PRINTF("Wrong VPK signature %08x", header->signature); 340 | exit(-1); 341 | } 342 | 343 | if (header->version < 1 || header->version > 2) { 344 | PRINTF("VPK version %u is not supported", header->version); 345 | exit(-1); 346 | } 347 | 348 | struct VPKFileMetadata *files_begin = stackGetCursor(mem->persistent), *files_end = files_begin; 349 | 350 | int max_archives = -1; 351 | const char *const end = dir + size; 352 | const char *c = dir + ((header->version == 1) ? sizeof(VPK1Header) : sizeof(VPK2Header)); 353 | for (;;) { 354 | // read extension 355 | const struct StringView ext = readString(&c, end); 356 | if (ext.len == 0) 357 | break; 358 | 359 | for (;;) { 360 | // read path 361 | const struct StringView path = readString(&c, end); 362 | if (path.len == 0) 363 | break; 364 | 365 | for (;;) { 366 | // read filename 367 | const struct StringView filename = readString(&c, end); 368 | if (filename.len == 0) 369 | break; 370 | 371 | if ((unsigned long)(end - c) < sizeof(struct VPKTreeEntry)) { 372 | PRINT("Incomplete VPKTreeEntry struct"); 373 | exit(-1); 374 | } 375 | 376 | const struct VPKTreeEntry *const entry = (const struct VPKTreeEntry*)c; 377 | c += sizeof(struct VPKTreeEntry); 378 | 379 | if (entry->terminator != VPK_TERMINATOR) { 380 | PRINTF("Wrong terminator: %04x", entry->terminator); 381 | exit(-1); 382 | } 383 | 384 | const int filename_len = 3 + path.len + filename.len + ext.len; 385 | char *filename_temp = stackAlloc(mem->temp, filename_len); 386 | if (!filename_temp) { 387 | PRINT("Not enough temp memory"); 388 | exit(-1); 389 | } 390 | 391 | memcpy(filename_temp, path.s, path.len); 392 | filename_temp[path.len] = '/'; 393 | memcpy(filename_temp + path.len + 1, filename.s, filename.len); 394 | filename_temp[path.len + 1 + filename.len] = '.'; 395 | memcpy(filename_temp + path.len + 1 + filename.len + 1, ext.s, ext.len); 396 | filename_temp[filename_len-1] = '\0'; 397 | 398 | #define DUMP_VPK_CONTENTS 0 399 | #if DUMP_VPK_CONTENTS 400 | PRINTF("%s crc=%08x pre=%d arc=%d(%04x) off=%d len=%d", 401 | filename_temp, 402 | entry->crc, 403 | entry->preloadBytes, entry->archive, entry->archive, 404 | entry->archiveOffset, entry->archiveLength); 405 | #endif 406 | 407 | struct VPKFileMetadata *file = stackAlloc(mem->persistent, sizeof(struct VPKFileMetadata)); 408 | if (!file) { 409 | PRINT("Not enough persistent memory"); 410 | exit(-1); 411 | } 412 | memset(file, 0, sizeof(*file)); 413 | 414 | file->filename.s = filename_temp; 415 | file->filename.len = filename_len - 1; 416 | if (entry->preloadBytes) { 417 | file->dir.off = c - (char*)dir; 418 | file->dir.size = entry->preloadBytes; 419 | } 420 | 421 | if (entry->archiveLength) { 422 | file->archive = entry->archive != 0x7fff ? entry->archive : -1; 423 | file->arc.off = entry->archiveOffset; 424 | file->arc.size = entry->archiveLength; 425 | } 426 | 427 | if (file->archive > max_archives) 428 | max_archives = file->archive; 429 | 430 | files_end = file + 1; 431 | ++collection->files_count; 432 | 433 | c += entry->preloadBytes; 434 | } // for filenames 435 | } // for paths 436 | } // for extensions 437 | 438 | // sort 439 | qsort(files_begin, files_end - files_begin, sizeof(*files_begin), vpkMetadataCompare); 440 | 441 | // store filenames in persistent memory 442 | for (struct VPKFileMetadata *file = files_begin; file != files_end; ++file) { 443 | char *string = stackAlloc(mem->persistent, file->filename.len + 1); 444 | if (!string) { 445 | PRINT("Not enough persistent memory"); 446 | exit(-1); 447 | } 448 | 449 | memcpy(string, file->filename.s, file->filename.len + 1); 450 | file->filename.s = string; 451 | } 452 | 453 | // open archives 454 | if (max_archives >= MAX_VPK_ARCHIVES) { 455 | PRINTF("Too many archives: %d", max_archives); 456 | exit(-1); 457 | } 458 | 459 | const int dirfile_len = (int)strlen(dir_filename) + 1; 460 | char *arcname = alloca(dirfile_len); 461 | if (!arcname || dirfile_len < 8) { 462 | PRINT("WTF"); 463 | exit(-1); 464 | } 465 | memcpy(arcname, dir_filename, dirfile_len); 466 | for (int i = 0; i <= max_archives; ++i) { 467 | sprintf(arcname + dirfile_len - 8, "%03d.vpk", i); 468 | if (AFile_Success != aFileOpen(collection->archives+i, arcname)) { 469 | PRINTF("Cannot open archive %s", arcname); 470 | exit(-1); 471 | } 472 | } 473 | 474 | stackFreeUpToPosition(mem->temp, temp_stack_top); 475 | collection->head.open = vpkCollectionFileOpen; 476 | collection->head.close = vpkCollectionClose; 477 | collection->files = files_begin; 478 | 479 | return &collection->head; 480 | } 481 | 482 | struct PakfileFileMetadata { 483 | struct StringView filename; 484 | const void *data; 485 | uint32_t size; 486 | }; 487 | 488 | struct PakfileCollection { 489 | struct ICollection head; 490 | struct Memories mem; 491 | struct PakfileFileMetadata *files; 492 | int files_count; 493 | }; 494 | 495 | struct PakfileCollectionFile { 496 | struct IFile head; 497 | const struct PakfileFileMetadata *metadata; 498 | struct Stack *stack; 499 | }; 500 | 501 | static void pakfileCollectionClose(struct ICollection *collection) { 502 | struct PakfileCollection *pc = (void*)collection; 503 | stackFreeUpToPosition(pc->mem.persistent, pc->files); 504 | } 505 | 506 | static size_t pakfileCollectionFileRead(struct IFile *file, size_t offset, size_t size, void *buffer) { 507 | struct PakfileCollectionFile *f = (struct PakfileCollectionFile*)file; 508 | const struct PakfileFileMetadata *meta = f->metadata; 509 | 510 | if (offset > meta->size) 511 | return 0; 512 | if (offset + size > meta->size) 513 | size = meta->size - offset; 514 | memcpy(buffer, (const char*)meta->data + offset, size); 515 | return size; 516 | } 517 | 518 | static void pakfileCollectionFileClose(struct IFile *file) { 519 | struct PakfileCollectionFile *f = (void*)file; 520 | stackFreeUpToPosition(f->stack, f); 521 | } 522 | 523 | static enum CollectionOpenResult pakfileCollectionFileOpen(struct ICollection *collection, 524 | const char *name, enum FileType type, struct IFile **out_file) { 525 | struct PakfileCollection *pakfilec = (struct PakfileCollection*)collection; 526 | 527 | *out_file = NULL; 528 | 529 | struct PakfileCollectionFile *file = stackAlloc(pakfilec->mem.temp, sizeof(*file)); 530 | char *filename = makeResourceFilename(pakfilec->mem.temp, NULL, name, type); 531 | 532 | if (!file || !filename) { 533 | PRINTF("Not enough memory for file %s", name); 534 | return CollectionOpen_NotEnoughMemory; 535 | } 536 | 537 | // binary search 538 | { 539 | const struct PakfileFileMetadata *begin = pakfilec->files; 540 | int count = pakfilec->files_count; 541 | 542 | while (count > 0) { 543 | int item = count / 2; 544 | 545 | const struct PakfileFileMetadata *meta = begin + item; 546 | const int comparison = strncmp(filename, meta->filename.s, meta->filename.len); 547 | if (comparison == 0) { 548 | file->metadata = meta; 549 | file->stack = pakfilec->mem.temp; 550 | file->head.size = meta->size; 551 | file->head.read = pakfileCollectionFileRead; 552 | file->head.close = pakfileCollectionFileClose; 553 | *out_file = &file->head; 554 | stackFreeUpToPosition(pakfilec->mem.temp, filename); 555 | return CollectionOpen_Success; 556 | } 557 | 558 | if (comparison < 0) { 559 | count = item; 560 | } else { 561 | count = count - item - 1; 562 | begin += item + 1; 563 | } 564 | } 565 | } 566 | 567 | stackFreeUpToPosition(pakfilec->mem.temp, file); 568 | return CollectionOpen_NotFound; 569 | } 570 | 571 | static int pakfileMetadataCompare(const void *a, const void *b) { 572 | const struct PakfileFileMetadata *am = a, *bm = b; 573 | return strncmp(am->filename.s, bm->filename.s, 574 | am->filename.len < bm->filename.len ? 575 | am->filename.len : bm->filename.len); 576 | } 577 | 578 | struct ICollection *collectionCreatePakfile(struct Memories *mem, const void *pakfile, uint32_t size) { 579 | 580 | (void)mem; 581 | 582 | // 1. need to find zip end of directory 583 | if (size < (int)sizeof(struct ZipEndOfDirectory)) { 584 | PRINT("Invalid pakfile size"); 585 | return NULL; 586 | } 587 | 588 | int eod_offset = size - sizeof(struct ZipEndOfDirectory); 589 | const struct ZipEndOfDirectory *eod; 590 | for (;;) { 591 | eod = (const void*)((const char*)pakfile + eod_offset); 592 | // TODO what if comment contain signature? 593 | if (eod->signature == ZipEndOfDirectory_Signature) 594 | break; 595 | 596 | --eod_offset; 597 | } 598 | 599 | if (!eod) { 600 | PRINT("End-of-directory not found"); 601 | return NULL; 602 | } 603 | 604 | if (eod->dir_offset > size || eod->dir_size > size || eod->dir_size + eod->dir_offset > size) { 605 | PRINTF("Wrong pakfile directory sizes; size=%u, dir_offset=%u, dir_size=%u", 606 | size, eod->dir_offset, eod->dir_size); 607 | return NULL; 608 | } 609 | 610 | struct PakfileFileMetadata *metadata_start = stackGetCursor(mem->persistent); 611 | int files_count = 0; 612 | 613 | const char *dir = (const char*)pakfile + eod->dir_offset, *dir_end = dir + eod->dir_size; 614 | for (;;) { 615 | if (dir == dir_end) 616 | break; 617 | 618 | if (dir_end - dir < (long)sizeof(struct ZipFileHeader)) { 619 | PRINT("Corrupted directory"); 620 | break; 621 | } 622 | 623 | const struct ZipFileHeader *fileheader = (void*)dir; 624 | if (fileheader->signature != ZipFileHeader_Signature) { 625 | PRINTF("Wrong file header signature: %08x", fileheader->signature); 626 | break; 627 | } 628 | 629 | // TODO overflow 630 | const char *next_dir = dir + sizeof(struct ZipFileHeader) + fileheader->filename_length + fileheader->extra_field_length + fileheader->file_comment_length; 631 | if (dir > dir_end) { 632 | PRINT("Corrupted directory"); 633 | break; 634 | } 635 | 636 | if (fileheader->compression != 0 || fileheader->uncompressed_size != fileheader->compressed_size) { 637 | PRINTF("Compression method %d is not supported", fileheader->compression); 638 | } else { 639 | const char *filename = dir + sizeof(struct ZipFileHeader); 640 | 641 | const char *local = (const char*)pakfile + fileheader->local_offset; 642 | if (size - (local - (const char*)pakfile) < (long)sizeof(struct ZipLocalFileHeader)) { 643 | PRINT("Local file header OOB"); 644 | break; 645 | } 646 | 647 | const struct ZipLocalFileHeader *localheader = (void*)local; 648 | if (localheader->signature != ZipLocalFileHeader_Signature) { 649 | PRINTF("Invalid local file header signature %08x", localheader->signature); 650 | break; 651 | } 652 | 653 | // TODO overflow 654 | local += sizeof(*localheader) + localheader->filename_length + localheader->extra_field_length; 655 | 656 | if ((local - (const char*)pakfile) + fileheader->compressed_size > size) { 657 | PRINT("File data OOB"); 658 | break; 659 | } 660 | 661 | struct PakfileFileMetadata *metadata = stackAlloc(mem->persistent, sizeof(*metadata)); 662 | if (!metadata) { 663 | PRINT("Not enough memory"); 664 | exit(-1); 665 | } 666 | 667 | metadata->data = local; 668 | metadata->size = fileheader->uncompressed_size; 669 | metadata->filename.s = filename; 670 | metadata->filename.len = fileheader->filename_length; 671 | ++files_count; 672 | 673 | /* 674 | PRINTF("FILE \""PRI_SV"\" size=%d offset=%d", 675 | PASS_SV(metadata->filename), fileheader->uncompressed_size, fileheader->local_offset); 676 | */ 677 | } 678 | 679 | dir = next_dir; 680 | } 681 | 682 | if (dir != dir_end) { 683 | PRINT("Something went wrong"); 684 | exit(-1); 685 | } 686 | 687 | // sort 688 | qsort(metadata_start, files_count, sizeof(*metadata_start), pakfileMetadataCompare); 689 | 690 | struct PakfileCollection *collection = stackAlloc(mem->persistent, sizeof(*collection)); 691 | collection->mem = *mem; 692 | collection->files = metadata_start; 693 | collection->files_count = files_count; 694 | collection->head.open = pakfileCollectionFileOpen; 695 | collection->head.close = pakfileCollectionClose; 696 | 697 | return &collection->head; 698 | } 699 | -------------------------------------------------------------------------------- /src/collection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "filemap.h" 3 | #include "mempools.h" 4 | #include 5 | 6 | typedef struct IFile { 7 | size_t size; 8 | /* read size bytes into buffer 9 | * returns bytes read, or < 0 on error. error codes aren't specified */ 10 | size_t (*read)(struct IFile *file, size_t offset, size_t size, void *buffer); 11 | /* free any internal resources. 12 | * will not free memory associated with this structure itself */ 13 | void (*close)(struct IFile *file); 14 | } IFile; 15 | 16 | enum CollectionOpenResult { 17 | CollectionOpen_Success, 18 | CollectionOpen_NotFound, /* such item was not found in collection */ 19 | CollectionOpen_NotEnoughMemory, 20 | CollectionOpen_Internal /* collection data is inconsistent internally, e.g. corrupted archive */ 21 | }; 22 | 23 | enum FileType { 24 | File_Map, 25 | File_Material, 26 | File_Texture, 27 | File_Model 28 | }; 29 | 30 | typedef struct ICollection { 31 | /* free any internal resources, but don't deallocate this structure itself */ 32 | void (*close)(struct ICollection *collection); 33 | enum CollectionOpenResult (*open)(struct ICollection *collection, 34 | const char *name, enum FileType type, struct IFile **out_file); 35 | struct ICollection *next; 36 | } ICollection; 37 | 38 | enum CollectionOpenResult collectionChainOpen(struct ICollection *collection, 39 | const char *name, enum FileType type, struct IFile **out_file); 40 | 41 | struct ICollection *collectionCreateFilesystem(struct Memories *mem, const char *dir); 42 | struct ICollection *collectionCreateVPK(struct Memories *mem, const char *dir_filename); 43 | struct ICollection *collectionCreatePakfile(struct Memories *mem, const void *pakfile, uint32_t size); 44 | 45 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "libc.h" 3 | 4 | #define STR1(m) #m 5 | #define STR(m) STR1(m) 6 | 7 | #ifdef _MSC_VER 8 | // MSVC compiler 9 | #define PRINTF_ARG(fmt) _Printf_format_string_ fmt 10 | #define PRINTF_ATTR(fmt_index, vargs_index) 11 | #else 12 | // gcc-compatible 13 | #define PRINTF_ARG(fmt) fmt 14 | #define PRINTF_ATTR(fmt_index, vargs_index) __attribute__((format(printf, fmt_index, vargs_index))) 15 | #endif 16 | 17 | #define COUNTOF(a) (sizeof(a) / sizeof(*(a))) 18 | 19 | typedef struct { 20 | const char *str; 21 | int length; 22 | } StringView; 23 | 24 | #define PRI_SV "%.*s" 25 | #define PRI_SVV(s) ((s).length), ((s).str) 26 | -------------------------------------------------------------------------------- /src/dxt.c: -------------------------------------------------------------------------------- 1 | #include "dxt.h" 2 | #include "libc.h" 3 | #include 4 | 5 | static uint16_t dxtColorSum(int m1, uint16_t c1, int m2, uint16_t c2, int add, int denom) { 6 | const int mask_r = 0xf800, shift_r = 11; 7 | const int mask_g = 0x07e0, shift_g = 5; 8 | const int mask_b = 0x001f, shift_b = 0; 9 | const int r = 10 | (((c1 & mask_r) >> shift_r) * m1 + 11 | ((c2 & mask_r) >> shift_r) * m2 + add) / denom; 12 | const int g = 13 | (((c1 & mask_g) >> shift_g) * m1 + 14 | ((c2 & mask_g) >> shift_g) * m2 + add) / denom; 15 | const int b = 16 | (((c1 & mask_b) >> shift_b) * m1 + 17 | ((c2 & mask_b) >> shift_b) * m2 + add) / denom; 18 | return (uint16_t)(((r << shift_r) & mask_r) | ((g << shift_g) & mask_g) | ((b << shift_b) & mask_b)); 19 | } 20 | 21 | void dxtUnpack(struct DXTUnpackContext ctx, int offset) { 22 | if (ctx.width < 4 || ctx.height < 4 || ctx.width & 3 || ctx.height & 3) 23 | return; 24 | 25 | const uint16_t transparent = 0; 26 | const uint8_t *src = (const uint8_t*)ctx.packed; 27 | for (int y = 0; y < ctx.height; y+=4) { 28 | uint16_t *dst_4x4 = (uint16_t*)ctx.output + ctx.width * y; 29 | for (int x = 0; x < ctx.width; x+=4, dst_4x4 += 4, src += offset) { 30 | uint16_t c[4]; 31 | memcpy(c, src, 2); 32 | memcpy(c+1, src + 2, 2); 33 | 34 | if (c[0] > c[1]) { 35 | c[2] = dxtColorSum(2, c[0], 1, c[1], 1, 3); 36 | c[3] = dxtColorSum(1, c[0], 2, c[1], 1, 3); 37 | } else { 38 | c[2] = dxtColorSum(1, c[0], 1, c[1], 0, 2); 39 | c[3] = transparent; 40 | } 41 | 42 | uint16_t *pix = dst_4x4; 43 | for (int r = 0; r < 4; ++r, pix += ctx.width) { 44 | const uint8_t bitmap = src[4 + r]; 45 | pix[3] = c[(bitmap >> 6) & 3]; 46 | pix[2] = c[(bitmap >> 4) & 3]; 47 | pix[1] = c[(bitmap >> 2) & 3]; 48 | pix[0] = c[(bitmap >> 0) & 3]; 49 | } /* for all rows in 4x4 */ 50 | } /* for x */ 51 | } /* for y */ 52 | } 53 | 54 | void dxt1Unpack(struct DXTUnpackContext ctx) { 55 | dxtUnpack(ctx, 8); 56 | } 57 | 58 | void dxt5Unpack(struct DXTUnpackContext ctx) { 59 | ctx.packed = ((char*)ctx.packed) + 8; 60 | dxtUnpack(ctx, 16); 61 | } 62 | -------------------------------------------------------------------------------- /src/dxt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct DXTUnpackContext { 4 | int width, height; 5 | const void *packed; 6 | void *output; 7 | }; 8 | 9 | void dxt1Unpack(struct DXTUnpackContext ctx); 10 | void dxt5Unpack(struct DXTUnpackContext ctx); 11 | -------------------------------------------------------------------------------- /src/etcpack.c: -------------------------------------------------------------------------------- 1 | #include "etcpack.h" 2 | #include "libc.h" 3 | 4 | static const int etc1_mod_table[8][4] = { 5 | {2, 8, -2, -8}, 6 | {5, 17, -5, -17}, 7 | {9, 29, -9, -29}, 8 | {13, 42, -13, -42}, 9 | {18, 60, -18, -60}, 10 | {24, 80, -24, -80}, 11 | {33, 106, -33, -106}, 12 | {47, 183, -47, -183}, 13 | }; 14 | 15 | typedef struct { 16 | ETC1Color base; 17 | int table; 18 | int error; 19 | uint8_t msb, lsb; 20 | } ETC1SubblockPacked; 21 | 22 | static int clamp8(int i) { return (i < 0) ? 0 : (i > 255) ? 255 : i; } 23 | 24 | #define ETC1_PIXEL_ERROR_MAX 1000 25 | static int etc1PixelError(ETC1Color a, ETC1Color b) { 26 | return abs(a.r - b.r) + abs(a.g - b.g) + abs(a.b - b.b); 27 | } 28 | 29 | static ETC1SubblockPacked etc1PackSubblock2x4(const ETC1Color *in4x2) { 30 | ETC1Color average = {.r = 0, .g = 0, .b = 0}; 31 | 32 | for (int i = 0; i < 8; ++i) { 33 | average.r += in4x2[i].r; 34 | average.g += in4x2[i].g; 35 | average.b += in4x2[i].b; 36 | } 37 | 38 | average.r >>= 3; 39 | average.g >>= 3; 40 | average.b >>= 3; 41 | 42 | ETC1SubblockPacked packed = { 43 | .error = ETC1_PIXEL_ERROR_MAX * 8, 44 | }; 45 | for (int itbl = 0; itbl < 8; ++itbl) { 46 | const int *const pmod = etc1_mod_table[itbl]; 47 | ETC1SubblockPacked variant = { 48 | .base = average, 49 | .table = itbl, 50 | .error = 0, 51 | .msb = 0, .lsb = 0, 52 | }; 53 | for (int ip = 0; ip < 8; ++ip) { 54 | const ETC1Color c = in4x2[ip]; 55 | int best_pixel_error = ETC1_PIXEL_ERROR_MAX; 56 | int best_pixel_imod = 0; 57 | for (int im = 0; im < 4; ++im) { 58 | const int mod = pmod[im]; 59 | const ETC1Color mc = { 60 | .r = clamp8(variant.base.r + mod), 61 | .g = clamp8(variant.base.g + mod), 62 | .b = clamp8(variant.base.b + mod) 63 | }; 64 | const int perr = etc1PixelError(c, mc); 65 | 66 | if (perr < best_pixel_error) { 67 | best_pixel_error = perr; 68 | best_pixel_imod = im; 69 | } 70 | } 71 | 72 | variant.lsb >>= 1; 73 | variant.msb >>= 1; 74 | variant.lsb |= (best_pixel_imod & 1) << 7; 75 | variant.msb |= (best_pixel_imod & 2) << 7; 76 | 77 | variant.error += best_pixel_error; 78 | } 79 | 80 | if (variant.error < packed.error) 81 | packed = variant; 82 | } 83 | return packed; 84 | } 85 | 86 | void etc1PackBlock(const ETC1Color *in4x4, uint8_t *out) { 87 | const ETC1SubblockPacked sub1 = etc1PackSubblock2x4(in4x4); 88 | const ETC1SubblockPacked sub2 = etc1PackSubblock2x4(in4x4 + 8); 89 | 90 | out[0] = (sub1.base.r & 0xf0) | (sub2.base.r >> 4); 91 | out[1] = (sub1.base.g & 0xf0) | (sub2.base.g >> 4); 92 | out[2] = (sub1.base.b & 0xf0) | (sub2.base.b >> 4); 93 | out[3] = (sub1.table << 5) | (sub2.table << 2) | 0x00; // diffbit = 0, flipbit = 0 94 | out[4] = sub2.msb; 95 | out[5] = sub1.msb; 96 | out[6] = sub2.lsb; 97 | out[7] = sub1.lsb; 98 | } 99 | -------------------------------------------------------------------------------- /src/etcpack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct { int r, g, b; } ETC1Color; 6 | 7 | // in4x4 layout is column-major 8 | void etc1PackBlock(const ETC1Color *in4x4, uint8_t *out); 9 | -------------------------------------------------------------------------------- /src/filemap.c: -------------------------------------------------------------------------------- 1 | #include "filemap.h" 2 | #include "common.h" 3 | #include "log.h" 4 | 5 | #ifndef _WIN32 6 | #include 7 | #include 8 | #include /* open */ 9 | #include /* close */ 10 | #include /* perror */ 11 | 12 | void aFileReset(struct AFile *file) { 13 | file->size = 0; 14 | file->impl_.fd = -1; 15 | } 16 | 17 | enum AFileResult aFileOpen(struct AFile *file, const char *filename) { 18 | file->impl_.fd = open(filename, O_RDONLY); 19 | if (file->impl_.fd < 0) 20 | return AFile_Fail; 21 | 22 | struct stat stat; 23 | fstat(file->impl_.fd, &stat); 24 | file->size = stat.st_size; 25 | 26 | return AFile_Success; 27 | } 28 | 29 | size_t aFileReadAtOffset(struct AFile *file, size_t off, size_t size, void *buffer) { 30 | ssize_t rd = pread(file->impl_.fd, buffer, size, off); 31 | if (rd < 0) 32 | perror("pread(fd)"); 33 | return rd; 34 | } 35 | 36 | void aFileClose(struct AFile *file) { 37 | if (file->impl_.fd > 0) { 38 | close(file->impl_.fd); 39 | aFileReset(file); 40 | } 41 | } 42 | 43 | #else 44 | 45 | void aFileReset(struct AFile *file) { 46 | file->size = 0; 47 | file->impl_.handle = INVALID_HANDLE_VALUE; 48 | } 49 | 50 | enum AFileResult aFileOpen(struct AFile *file, const char *filename) { 51 | const int filename_len = (int)strlen(filename); 52 | char *slashes = _alloca(filename_len + 1); 53 | for (int i = 0; filename[i] != '\0'; ++i) 54 | slashes[i] = filename[i] != '/' ? filename[i] : '\\'; 55 | slashes[filename_len] = '\0'; 56 | 57 | wchar_t *filename_w; 58 | const int buf_length = MultiByteToWideChar(CP_UTF8, 0, slashes, -1, NULL, 0); 59 | filename_w = _alloca(buf_length * sizeof(wchar_t)); 60 | MultiByteToWideChar(CP_UTF8, 0, slashes, -1, filename_w, buf_length); 61 | 62 | file->impl_.handle = CreateFileW(filename_w, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 63 | if (file->impl_.handle == INVALID_HANDLE_VALUE) 64 | return AFile_Fail; 65 | 66 | LARGE_INTEGER splurge_integer = { 0 }; 67 | if (!GetFileSizeEx(file->impl_.handle, &splurge_integer)) { 68 | CloseHandle(file->impl_.handle); 69 | file->impl_.handle = INVALID_HANDLE_VALUE; 70 | return AFile_Fail; 71 | } 72 | 73 | file->size = (size_t)splurge_integer.QuadPart; 74 | return AFile_Success; 75 | } 76 | 77 | size_t aFileReadAtOffset(struct AFile *file, size_t off, size_t size, void *buffer) { 78 | OVERLAPPED overlapped; 79 | memset(&overlapped, 0, sizeof(overlapped)); 80 | overlapped.Offset = (DWORD)off; 81 | 82 | DWORD read = 0; 83 | if (!ReadFile(file->impl_.handle, buffer, (DWORD)size, &read, &overlapped)) 84 | PRINTF("Failed to read from file %p", file->impl_.handle); 85 | 86 | return read; 87 | } 88 | 89 | void aFileClose(struct AFile *file) { 90 | CloseHandle(file->impl_.handle); 91 | } 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /src/filemap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "libc.h" 4 | 5 | typedef struct AFile { 6 | size_t size; 7 | struct { 8 | #ifndef _WIN32 9 | int fd; 10 | #else 11 | HANDLE handle; 12 | #endif 13 | } impl_; 14 | } AFile; 15 | 16 | #define AFileError ((size_t)-1) 17 | 18 | enum AFileResult { 19 | AFile_Success, 20 | AFile_Fail 21 | }; 22 | 23 | /* reset file to default state, useful for initialization */ 24 | void aFileReset(struct AFile *file); 25 | enum AFileResult aFileOpen(struct AFile *file, const char *filename); 26 | size_t aFileReadAtOffset(struct AFile *file, size_t off, size_t size, void *buffer); 27 | void aFileClose(struct AFile *file); 28 | -------------------------------------------------------------------------------- /src/libc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include /* printf */ 5 | #include /* malloc */ 6 | #include /* offsetof, size_t */ 7 | #include /* memset */ 8 | #ifndef _WIN32 9 | #include 10 | #include /* strncasecmp */ 11 | #else 12 | #define WIN32_LEAN_AND_MEAN 13 | #define NOMINMAX 14 | #include 15 | #undef near 16 | #undef far 17 | #define strcasecmp _stricmp 18 | #define strncasecmp _strnicmp 19 | #define alloca _alloca 20 | #endif 21 | #include /* isspace */ 22 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | #include 4 | #include 5 | 6 | static struct { 7 | FILE *logfile; 8 | } glog = {0}; 9 | 10 | void logOpen(const char *logfile) { 11 | glog.logfile = fopen(logfile, "w"); 12 | } 13 | 14 | void logClose(void) { 15 | fclose(glog.logfile); 16 | } 17 | 18 | void logPrintf(const char *format, ...) { 19 | va_list args; 20 | va_start(args, format); 21 | 22 | if (glog.logfile) { 23 | va_list args_copy; 24 | va_copy(args_copy, args); 25 | vfprintf(glog.logfile, format, args_copy); 26 | va_end(args_copy); 27 | } 28 | 29 | vfprintf(stderr, format, args); 30 | va_end(args); 31 | } 32 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | void logOpen(const char *logfile); 4 | void logClose(void); 5 | 6 | void logPrintf(PRINTF_ARG(const char *format), ...) PRINTF_ATTR(1, 2); 7 | 8 | #define PRINTF(fmt, ...) logPrintf(__FILE__ ":" STR(__LINE__) ": " fmt "\n", __VA_ARGS__) 9 | #define PRINT(msg) logPrintf(__FILE__ ":" STR(__LINE__) ": %s\n", msg) 10 | 11 | #define ASSERT(cond) if (!(cond)){PRINTF("%s failed", #cond); abort();} 12 | -------------------------------------------------------------------------------- /src/material.c: -------------------------------------------------------------------------------- 1 | #include "material.h" 2 | #include "texture.h" 3 | #include "cache.h" 4 | #include "collection.h" 5 | #include "vmfparser.h" 6 | #include "common.h" 7 | 8 | #ifdef _MSC_VER 9 | #pragma warning(disable:4221) 10 | #endif 11 | 12 | typedef struct { 13 | ICollection *collection; 14 | Stack *temp; 15 | StringView shader; 16 | Material *mat; 17 | int depth; 18 | } MaterialContext; 19 | 20 | static VMFAction materialReadKeyValue(MaterialContext *ctx, const VMFKeyValue *kv) { 21 | if (kv->value.length > 127) 22 | return VMFAction_SemanticError; 23 | 24 | char value[128]; 25 | memset(value, 0, sizeof value); 26 | memcpy(value, kv->value.str, kv->value.length); 27 | 28 | if (strncasecmp("$basetexture", kv->key.str, kv->key.length) == 0) { 29 | ctx->mat->base_texture.texture = textureGet(value, ctx->collection, ctx->temp); 30 | } else if (strncasecmp("$basetexturetransform", kv->key.str, kv->key.length) == 0) { 31 | AVec2f center, scale, translate; 32 | float rotate; 33 | if (7 != sscanf(value, "center %f %f scale %f %f rotate %f translate %f %f", 34 | ¢er.x, ¢er.y, &scale.x, &scale.y, &rotate, &translate.x, &translate.y)) { 35 | PRINTF("ERR: transform has wrong format: \"%s\"", value); 36 | } else { 37 | ctx->mat->base_texture.transform.scale = scale; 38 | ctx->mat->base_texture.transform.translate = translate; 39 | // TODO support rotation 40 | } 41 | } else if (strncasecmp("include", kv->key.str, kv->key.length) == 0) { 42 | char *vmt = strstr(value, ".vmt"); 43 | if (vmt) 44 | *vmt = '\0'; 45 | if (strstr(value, "materials/") == value) 46 | *ctx->mat = *materialGet(value + 10, ctx->collection, ctx->temp); 47 | } 48 | 49 | return VMFAction_Continue; 50 | } 51 | 52 | static VMFAction materialParserCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv); 53 | static VMFAction materialShaderCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv); 54 | static VMFAction materialPatchCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv); 55 | 56 | static VMFAction materialPatchCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) { 57 | //PRINTF("Entry %d (%.*s -> %.*s)", entry, PRI_SVV(kv->key), PRI_SVV(kv->value)); 58 | MaterialContext *ctx = state->user_data; 59 | 60 | VMFAction retval = VMFAction_SemanticError; 61 | 62 | switch (entry) { 63 | case VMFEntryType_KeyValue: 64 | retval = materialReadKeyValue(ctx, kv); 65 | break; 66 | case VMFEntryType_SectionOpen: 67 | ++ctx->depth; 68 | retval = VMFAction_Continue; 69 | break; 70 | case VMFEntryType_SectionClose: 71 | --ctx->depth; 72 | retval = ctx->depth == 0 ? VMFAction_Exit : VMFAction_Continue; 73 | break; 74 | } 75 | 76 | return retval; 77 | } 78 | 79 | static VMFAction materialShaderCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) { 80 | //PRINTF("Entry %d (%.*s -> %.*s)", entry, PRI_SVV(kv->key), PRI_SVV(kv->value)); 81 | MaterialContext *ctx = state->user_data; 82 | 83 | VMFAction retval = VMFAction_SemanticError; 84 | 85 | switch (entry) { 86 | case VMFEntryType_KeyValue: 87 | // Ignore any nested settings for no 88 | retval = (ctx->depth == 1) ? materialReadKeyValue(ctx, kv) : VMFAction_Continue; 89 | break; 90 | case VMFEntryType_SectionOpen: 91 | ++ctx->depth; 92 | retval = VMFAction_Continue; 93 | break; 94 | case VMFEntryType_SectionClose: 95 | --ctx->depth; 96 | retval = ctx->depth == 0 ? VMFAction_Exit : VMFAction_Continue; 97 | break; 98 | } 99 | 100 | return retval; 101 | } 102 | 103 | static void mtextureInit(MTexture *t) { 104 | memset(t, 0, sizeof(*t)); 105 | t->transform.scale = aVec2f(1.f, 1.f); 106 | } 107 | 108 | static VMFAction materialParserCallback(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv) { 109 | //PRINTF("Entry %d (%.*s -> %.*s)", entry, PRI_SVV(kv->key), PRI_SVV(kv->value)); 110 | MaterialContext *ctx = state->user_data; 111 | 112 | ctx->mat->average_color = aVec3f(0,1,1); 113 | ctx->mat->shader = MShader_Unknown; 114 | mtextureInit(&ctx->mat->base_texture); 115 | 116 | VMFAction retval = VMFAction_SemanticError; 117 | 118 | switch (entry) { 119 | case VMFEntryType_KeyValue: 120 | break; 121 | case VMFEntryType_SectionOpen: 122 | ++ctx->depth; 123 | if (strncasecmp("patch", kv->key.str, kv->key.length) == 0) { 124 | state->callback = materialPatchCallback; 125 | } else { 126 | ctx->shader = kv->key; 127 | if (strncasecmp("unlitgeneric", kv->key.str, kv->key.length) == 0 128 | || strncasecmp("SDK_UnlitGeneric", kv->key.str, kv->key.length) == 0 129 | || strncasecmp("sky", kv->key.str, kv->key.length) == 0) 130 | ctx->mat->shader = MShader_UnlitGeneric; 131 | else if (strncasecmp("lightmappedgeneric", kv->key.str, kv->key.length) == 0 132 | || strncasecmp("SDK_LightmappedGeneric", kv->key.str, kv->key.length) == 0 133 | || strncasecmp("sdk_worldvertextransition", kv->key.str, kv->key.length) == 0 134 | || strncasecmp("worldvertextransition", kv->key.str, kv->key.length) == 0) 135 | ctx->mat->shader = MShader_LightmappedGeneric; 136 | else 137 | PRINTF("Unknown material shader " PRI_SV, PRI_SVV(kv->key)); 138 | state->callback = materialShaderCallback; 139 | } 140 | retval = VMFAction_Continue; 141 | break; 142 | case VMFEntryType_SectionClose: 143 | break; 144 | } 145 | 146 | return retval; 147 | } 148 | 149 | static int materialLoad(struct IFile *file, struct ICollection *coll, Material *output, struct Stack *tmp) { 150 | char *buffer = stackAlloc(tmp, file->size); 151 | 152 | if (!buffer) { 153 | PRINT("Out of temp memory"); 154 | return 0; 155 | } 156 | 157 | const int read_size = (int)file->read(file, 0, file->size, buffer); 158 | if ((int)file->size != read_size) { 159 | PRINTF("Cannot read material file: %d != %d", (int)file->size, read_size); 160 | return 0; 161 | } 162 | 163 | MaterialContext ctx = { 164 | .collection = coll, 165 | .temp = tmp, 166 | .mat = output, 167 | .depth = 0, 168 | }; 169 | 170 | VMFState parser_state = { 171 | .user_data = &ctx, 172 | .data = { .str = buffer, .length = (int)file->size }, 173 | .callback = materialParserCallback 174 | }; 175 | 176 | const int success = VMFResult_Success == vmfParse(&parser_state); 177 | 178 | if (!success) 179 | PRINTF("Failed to read material with contents:\n" PRI_SV, PRI_SVV(parser_state.data)); 180 | 181 | if (success && ctx.mat->base_texture.texture) 182 | ctx.mat->average_color = ctx.mat->base_texture.texture->avg_color; 183 | 184 | stackFreeUpToPosition(tmp, buffer); 185 | 186 | return success; 187 | } 188 | 189 | const Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp) { 190 | const Material *mat = cacheGetMaterial(name); 191 | if (mat) return mat; 192 | 193 | struct IFile *matfile; 194 | if (CollectionOpen_Success != collectionChainOpen(collection, name, File_Material, &matfile)) { 195 | PRINTF("Material \"%s\" not found", name); 196 | return cacheGetMaterial("opensource/placeholder"); 197 | } 198 | 199 | Material localmat; 200 | memset(&localmat, 0, sizeof localmat); 201 | if (materialLoad(matfile, collection, &localmat, tmp) == 0) { 202 | PRINTF("Material \"%s\" found, but could not be loaded", name); 203 | } else { 204 | cachePutMaterial(name, &localmat); 205 | mat = cacheGetMaterial(name); 206 | } 207 | 208 | matfile->close(matfile); 209 | return mat ? mat : cacheGetMaterial("opensource/placeholder"); 210 | } 211 | 212 | -------------------------------------------------------------------------------- /src/material.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "atto/math.h" 3 | 4 | struct ICollection; 5 | struct Texture; 6 | struct Stack; 7 | 8 | typedef enum { 9 | MShader_Unknown, 10 | MShader_LightmappedOnly, 11 | MShader_LightmappedGeneric, 12 | MShader_UnlitGeneric, 13 | 14 | MShader_COUNT 15 | } MShader; 16 | 17 | typedef struct { 18 | const struct Texture *texture; 19 | struct { 20 | AVec2f translate; 21 | AVec2f scale; 22 | } transform; 23 | } MTexture; 24 | 25 | typedef struct Material { 26 | MShader shader; 27 | struct AVec3f average_color; 28 | MTexture base_texture; 29 | } Material; 30 | 31 | const Material *materialGet(const char *name, struct ICollection *collection, struct Stack *tmp); 32 | -------------------------------------------------------------------------------- /src/mempools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "common.h" 3 | #include "log.h" 4 | #include 5 | 6 | typedef struct Stack { 7 | char *storage; 8 | size_t size, cursor; 9 | } Stack; 10 | 11 | static inline void *stackGetCursor(const struct Stack *stack) { 12 | return stack->storage + stack->cursor; 13 | } 14 | static inline size_t stackGetFree(const struct Stack *stack) { 15 | return stack->size - stack->cursor; 16 | } 17 | static inline void *stackAlloc(struct Stack *stack, size_t size) { 18 | size = 4 * ((size + 3) / 4); // alignment 19 | if (stack->size - stack->cursor < size) 20 | return 0; 21 | 22 | void *ret = stack->storage + stack->cursor; 23 | stack->cursor += size; 24 | return ret; 25 | } 26 | static inline void stackFree(struct Stack *stack, size_t size) { 27 | ASSERT(size <= stack->cursor); 28 | stack->cursor -= size; 29 | } 30 | static inline void stackFreeUpToPosition(struct Stack *stack, void *marker) { 31 | ASSERT((char*)marker >= stack->storage); 32 | const size_t marker_pos = ((char*)marker - stack->storage); 33 | ASSERT(marker_pos <= stack->cursor); 34 | stackFree(stack, stack->cursor - marker_pos); 35 | } 36 | 37 | struct Memories { 38 | struct Stack *temp; 39 | struct Stack *persistent; 40 | }; 41 | -------------------------------------------------------------------------------- /src/profiler.c: -------------------------------------------------------------------------------- 1 | #include "profiler.h" 2 | 3 | static struct { 4 | int cursor; 5 | struct { 6 | const char *msg; 7 | ATimeUs delta; 8 | } event[65536]; 9 | int frame; 10 | ATimeUs last_print_time; 11 | ATimeUs profiler_time; 12 | ATimeUs frame_deltas, last_frame; 13 | int counted_frame; 14 | } profiler; 15 | 16 | void profilerInit() { 17 | profiler.cursor = 0; 18 | profiler.frame = 0; 19 | profiler.last_print_time = 0; 20 | profiler.profiler_time = 0; 21 | profiler.frame_deltas = profiler.last_frame = 0; 22 | profiler.counted_frame = 0; 23 | } 24 | 25 | void profileEvent(const char *msg, ATimeUs delta) { 26 | ATTO_ASSERT(profiler.cursor < 65536); 27 | if (profiler.cursor < 0 || profiler.cursor >= (int)COUNTOF(profiler.event)) 28 | return; 29 | profiler.event[profiler.cursor].msg = msg; 30 | profiler.event[profiler.cursor].delta = delta; 31 | ++profiler.cursor; 32 | } 33 | 34 | typedef struct { 35 | const char *name; 36 | int count; 37 | ATimeUs total_time; 38 | ATimeUs min_time; 39 | ATimeUs max_time; 40 | } ProfilerLocation; 41 | 42 | int profilerFrame(struct Stack *stack_temp) { 43 | int retval = 0; 44 | const ATimeUs start = aAppTime(); 45 | profiler.frame_deltas += start - profiler.last_frame; 46 | 47 | void *tmp_cursor = stackGetCursor(stack_temp); 48 | const int max_array_size = (int)(stackGetFree(stack_temp) / sizeof(ProfilerLocation)); 49 | int array_size = 0; 50 | ProfilerLocation *array = tmp_cursor; 51 | int total_time = 0; 52 | for (int i = 0; i < profiler.cursor; ++i) { 53 | ProfilerLocation *loc = 0; 54 | for (int j = 0; j < array_size; ++j) 55 | if (array[j].name == profiler.event[i].msg) { 56 | loc = array + j; 57 | break; 58 | } 59 | if (!loc) { 60 | ATTO_ASSERT(array_size< max_array_size); 61 | loc = array + array_size++; 62 | loc->name = profiler.event[i].msg; 63 | loc->count = 0; 64 | loc->total_time = 0; 65 | loc->min_time = 0x7fffffffu; 66 | loc->max_time = 0; 67 | } 68 | 69 | ++loc->count; 70 | const ATimeUs delta = profiler.event[i].delta; 71 | loc->total_time += delta; 72 | total_time += delta; 73 | if (delta < loc->min_time) loc->min_time = delta; 74 | if (delta > loc->max_time) loc->max_time = delta; 75 | } 76 | 77 | ++profiler.counted_frame; 78 | ++profiler.frame; 79 | 80 | if (start - profiler.last_print_time > 1000000) { 81 | PRINT("================================================="); 82 | const ATimeUs dt = profiler.frame_deltas / profiler.counted_frame; 83 | PRINTF("avg frame = %dus (fps = %f)", dt, 1000000. / dt); 84 | PRINTF("PROF: frame=%d, total_frame_time=%d total_prof_time=%d, avg_prof_time=%d events=%d unique=%d", 85 | profiler.frame, total_time, profiler.profiler_time, profiler.profiler_time / profiler.frame, 86 | profiler.cursor, array_size); 87 | 88 | for (int i = 0; i < array_size; ++i) { 89 | const ProfilerLocation *loc = array + i; 90 | PRINTF("T%d: total=%d count=%d min=%d max=%d, avg=%d %s", 91 | i, loc->total_time, loc->count, loc->min_time, loc->max_time, 92 | loc->total_time / loc->count, loc->name); 93 | } 94 | 95 | #if 0 96 | #define TOP_N 10 97 | int max_time[TOP_N] = {0}; 98 | int max_count[TOP_N] = {0}; 99 | for (int i = 0; i < array_size; ++i) { 100 | const ProfilerLocation *loc = array + i; 101 | for (int j = 0; j < TOP_N; ++j) 102 | if (array[max_time[j]].max_time < loc->max_time) { 103 | for (int k = j + 1; k < TOP_N; ++k) max_time[k] = max_time[k - 1]; 104 | max_time[j] = i; 105 | break; 106 | } 107 | for (int j = 0; j < TOP_N; ++j) 108 | if (array[max_count[j]].count < loc->count) { 109 | for (int k = j + 1; k < TOP_N; ++k) max_count[k] = max_count[k - 1]; 110 | max_count[j] = i; 111 | break; 112 | } 113 | } 114 | if (array_size > TOP_N) { 115 | for (int i = 0; i < TOP_N; ++i) { 116 | const ProfilerLocation *loc = array + max_time[i]; 117 | PRINTF("T%d %d: total=%d count=%d min=%d max=%d, avg=%d %s", 118 | i, max_time[i], loc->total_time, loc->count, loc->min_time, loc->max_time, 119 | loc->total_time / loc->count, loc->name); 120 | } 121 | for (int i = 0; i < TOP_N; ++i) { 122 | const ProfilerLocation *loc = array + max_count[i]; 123 | PRINTF("C%d %d: total=%d count=%d min=%d max=%d, avg=%d %s", 124 | i, max_count[i], loc->total_time, loc->count, loc->min_time, loc->max_time, 125 | loc->total_time / loc->count, loc->name); 126 | } 127 | } 128 | #endif 129 | 130 | profiler.last_print_time = start; 131 | profiler.counted_frame = 0; 132 | profiler.frame_deltas = 0; 133 | retval = 1; 134 | } 135 | 136 | profiler.last_frame = start; 137 | profiler.profiler_time += aAppTime() - start; 138 | profiler.cursor = 0; 139 | profileEvent("PROFILER", aAppTime() - start); 140 | return retval; 141 | } 142 | -------------------------------------------------------------------------------- /src/profiler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "mempools.h" 3 | #include "atto/app.h" 4 | 5 | void profilerInit(); 6 | void profileEvent(const char *msg, ATimeUs delta); 7 | int profilerFrame(struct Stack *stack_temp); 8 | -------------------------------------------------------------------------------- /src/render.c: -------------------------------------------------------------------------------- 1 | #include "render.h" 2 | #include "texture.h" 3 | #include "bsp.h" 4 | #include "cache.h" 5 | #include "common.h" 6 | #include "profiler.h" 7 | #include "camera.h" 8 | 9 | #include "atto/app.h" 10 | #include "atto/platform.h" 11 | 12 | #ifdef ATTO_PLATFORM_X11 13 | #define GL_GLEXT_PROTOTYPES 1 14 | #include 15 | #include 16 | #include 17 | #define ATTO_GL_DESKTOP 18 | #endif /* ifdef ATTO_PLATFORM_X11 */ 19 | 20 | #ifdef ATTO_PLATFORM_RPI 21 | #include 22 | #include 23 | #define ATTO_GL_ES 24 | #endif /* ifdef ATTO_PLATFORM_RPI */ 25 | 26 | #ifdef ATTO_PLATFORM_WINDOWS 27 | #include "libc.h" 28 | #include 29 | #include 30 | #define ATTO_GL_DESKTOP 31 | #endif /* ifdef ATTO_PLATFORM_WINDOWS */ 32 | 33 | #ifdef ATTO_PLATFORM_OSX 34 | #include 35 | #define ATTO_GL_DESKTOP 36 | #endif 37 | 38 | #define RENDER_ERRORCHECK 39 | //#define RENDER_GL_TRACE 40 | 41 | //#define RENDER_GL_PROFILE_FUNC profileEvent 42 | 43 | #ifndef RENDER_ASSERT 44 | #define RENDER_ASSERT(cond) \ 45 | if (!(cond)) { \ 46 | aAppDebugPrintf("ERROR @ %s:%d: (%s) failed", __FILE__, __LINE__, #cond); \ 47 | aAppTerminate(-1); \ 48 | } 49 | #endif /* ifndef RENDER_ASSERT */ 50 | 51 | #ifdef RENDER_GL_PROFILE_FUNC 52 | #define RENDER_GL_PROFILE_PREAMBLE const ATimeUs profile_time_start__ = aAppTime(); 53 | #define RENDER_GL_PROFILE_START const ATimeUs agl_profile_start_ = aAppTime(); 54 | #define RENDER_GL_PROFILE_END RENDER_GL_PROFILE_FUNC(__FUNCTION__, aAppTime() - agl_profile_start_); 55 | #define RENDER_GL_PROFILE_END_NAME(name) RENDER_GL_PROFILE_FUNC(name, aAppTime() - agl_profile_start_); 56 | #else 57 | #define RENDER_GL_PROFILE_PREAMBLE 58 | #define RENDER_GL_PROFILE_FUNC(...) 59 | #endif 60 | 61 | //#define RENDER_GL_DEBUG 62 | #ifdef RENDER_GL_DEBUG 63 | static void a__GlPrintError(const char *message, int error) { 64 | const char *errstr = "UNKNOWN"; 65 | switch (error) { 66 | case GL_INVALID_ENUM: errstr = "GL_INVALID_ENUM"; break; 67 | case GL_INVALID_VALUE: errstr = "GL_INVALID_VALUE"; break; 68 | case GL_INVALID_OPERATION: errstr = "GL_INVALID_OPERATION"; break; 69 | #ifdef GL_STACK_OVERFLOW 70 | case GL_STACK_OVERFLOW: errstr = "GL_STACK_OVERFLOW"; break; 71 | #endif 72 | #ifdef GL_STACK_UNDERFLOW 73 | case GL_STACK_UNDERFLOW: errstr = "GL_STACK_UNDERFLOW"; break; 74 | #endif 75 | case GL_OUT_OF_MEMORY: errstr = "GL_OUT_OF_MEMORY"; break; 76 | #ifdef GL_TABLE_TOO_LARGE 77 | case GL_TABLE_TOO_LARGE: errstr = "GL_TABLE_TOO_LARGE"; break; 78 | #endif 79 | }; 80 | PRINTF("%s %s (%#x)", message, errstr, error); 81 | } 82 | #define RENDER_GL_GETERROR(f) \ 83 | const int glerror = glGetError(); \ 84 | if (glerror != GL_NO_ERROR) { \ 85 | a__GlPrintError(__FILE__ ":" RENDER__GL_STR(__LINE__) ": " #f " returned ", glerror); \ 86 | RENDER_ASSERT(!"GL error"); \ 87 | } 88 | #else 89 | #define RENDER_GL_GETERROR(f) 90 | #endif 91 | #define RENDER__GL_STR__(s) #s 92 | #define RENDER__GL_STR(s) RENDER__GL_STR__(s) 93 | #ifdef RENDER_GL_TRACE 94 | #define RENDER_GL_TRACE_PRINT PRINTF 95 | #else 96 | #define RENDER_GL_TRACE_PRINT(...) 97 | #endif 98 | #define GL_CALL(f) do{\ 99 | RENDER_GL_TRACE_PRINT("%s", #f); \ 100 | RENDER_GL_PROFILE_PREAMBLE \ 101 | f; \ 102 | RENDER_GL_PROFILE_FUNC(#f, aAppTime() - profile_time_start__); \ 103 | RENDER_GL_GETERROR(f) \ 104 | } while(0) 105 | 106 | #ifdef _WIN32 107 | #define WGL__FUNCLIST \ 108 | WGL__FUNCLIST_DO(PFNGLBLENDCOLORPROC, BlendColor) \ 109 | WGL__FUNCLIST_DO(PFNGLBLENDEQUATIONPROC, BlendEquation) \ 110 | WGL__FUNCLIST_DO(PFNGLGENBUFFERSPROC, GenBuffers) \ 111 | WGL__FUNCLIST_DO(PFNGLBINDBUFFERPROC, BindBuffer) \ 112 | WGL__FUNCLIST_DO(PFNGLBUFFERDATAPROC, BufferData) \ 113 | WGL__FUNCLIST_DO(PFNGLGETATTRIBLOCATIONPROC, GetAttribLocation) \ 114 | WGL__FUNCLIST_DO(PFNGLACTIVETEXTUREPROC, ActiveTexture) \ 115 | WGL__FUNCLIST_DO(PFNGLCREATESHADERPROC, CreateShader) \ 116 | WGL__FUNCLIST_DO(PFNGLSHADERSOURCEPROC, ShaderSource) \ 117 | WGL__FUNCLIST_DO(PFNGLCOMPILESHADERPROC, CompileShader) \ 118 | WGL__FUNCLIST_DO(PFNGLATTACHSHADERPROC, AttachShader) \ 119 | WGL__FUNCLIST_DO(PFNGLDELETESHADERPROC, DeleteShader) \ 120 | WGL__FUNCLIST_DO(PFNGLGETSHADERIVPROC, GetShaderiv) \ 121 | WGL__FUNCLIST_DO(PFNGLGETSHADERINFOLOGPROC, GetShaderInfoLog) \ 122 | WGL__FUNCLIST_DO(PFNGLCREATEPROGRAMPROC, CreateProgram) \ 123 | WGL__FUNCLIST_DO(PFNGLLINKPROGRAMPROC, LinkProgram) \ 124 | WGL__FUNCLIST_DO(PFNGLGETPROGRAMINFOLOGPROC, GetProgramInfoLog) \ 125 | WGL__FUNCLIST_DO(PFNGLDELETEPROGRAMPROC, DeleteProgram) \ 126 | WGL__FUNCLIST_DO(PFNGLGETPROGRAMIVPROC, GetProgramiv) \ 127 | WGL__FUNCLIST_DO(PFNGLUSEPROGRAMPROC, UseProgram) \ 128 | WGL__FUNCLIST_DO(PFNGLGETUNIFORMLOCATIONPROC, GetUniformLocation) \ 129 | WGL__FUNCLIST_DO(PFNGLUNIFORM1FPROC, Uniform1f) \ 130 | WGL__FUNCLIST_DO(PFNGLUNIFORM2FPROC, Uniform2f) \ 131 | WGL__FUNCLIST_DO(PFNGLUNIFORM1IPROC, Uniform1i) \ 132 | WGL__FUNCLIST_DO(PFNGLUNIFORMMATRIX4FVPROC, UniformMatrix4fv) \ 133 | WGL__FUNCLIST_DO(PFNGLENABLEVERTEXATTRIBARRAYPROC, EnableVertexAttribArray) \ 134 | WGL__FUNCLIST_DO(PFNGLDISABLEVERTEXATTRIBARRAYPROC, DisableVertexAttribArray) \ 135 | WGL__FUNCLIST_DO(PFNGLVERTEXATTRIBPOINTERPROC, VertexAttribPointer) \ 136 | WGL__FUNCLIST_DO(PFNGLGENERATEMIPMAPPROC, GenerateMipmap) \ 137 | WGL__FUNCLIST_DO(PFNGLCOMPRESSEDTEXIMAGE2DPROC, CompressedTexImage2D) \ 138 | 139 | #define WGL__FUNCLIST_DO(T,N) T gl##N = 0; 140 | WGL__FUNCLIST 141 | #undef WGL__FUNCLIST_DO 142 | #endif /* ifdef _WIN32 */ 143 | 144 | static struct { 145 | int textures_count; 146 | int textures_size; 147 | int buffers_count; 148 | int buffers_size; 149 | } stats; 150 | 151 | static void renderPrintMemUsage() { 152 | PRINTF("Render Tc: %u, Ts: %uMiB, Bc: %u, Bs: %uMiB, Total: %uMiB", 153 | (unsigned)stats.textures_count, (unsigned)stats.textures_size >> 20, 154 | (unsigned)stats.buffers_count, (unsigned)stats.buffers_size >> 20, 155 | (stats.buffers_size + stats.textures_size) >> 20); 156 | } 157 | 158 | static GLint render_ShaderCreate(GLenum type, const char *sources[]) { 159 | int n; 160 | GLuint shader = glCreateShader(type); 161 | 162 | for (n = 0; sources[n]; ++n); 163 | 164 | GL_CALL(glShaderSource(shader, n, (const GLchar **)sources, 0)); 165 | GL_CALL(glCompileShader(shader)); 166 | 167 | #ifdef RENDER_ERRORCHECK 168 | { 169 | GLint status; 170 | GL_CALL(glGetShaderiv(shader, GL_COMPILE_STATUS, &status)); 171 | if (status != GL_TRUE) { 172 | char buffer[1024]; 173 | GL_CALL(glGetShaderInfoLog(shader, sizeof(buffer), 0, buffer)); 174 | PRINTF("Shader compilation error: %s", buffer); 175 | GL_CALL(glDeleteShader(shader)); 176 | shader = 0; 177 | } 178 | } 179 | #endif 180 | 181 | return shader; 182 | } 183 | 184 | void renderTextureUpload(RTexture *texture, RTextureUploadParams params) { 185 | int pixel_bits = 0; 186 | GLenum internal, format, type; 187 | 188 | if (texture->gl_name == -1) { 189 | GL_CALL(glGenTextures(1, (GLuint*)&texture->gl_name)); 190 | texture->type_flags = 0; 191 | ++stats.textures_count; 192 | } 193 | 194 | const GLenum binding = (params.type == RTexType_2D) ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP; 195 | const GLint wrap = (params.type == RTexType_2D && params.wrap == RTexWrap_Repeat) 196 | ? GL_REPEAT : GL_CLAMP_TO_EDGE; 197 | 198 | GL_CALL(glBindTexture(binding, texture->gl_name)); 199 | 200 | GLenum upload_binding = binding; 201 | switch (params.type) { 202 | case RTexType_2D: upload_binding = GL_TEXTURE_2D; break; 203 | case RTexType_CubePX: upload_binding = GL_TEXTURE_CUBE_MAP_POSITIVE_X; break; 204 | case RTexType_CubeNX: upload_binding = GL_TEXTURE_CUBE_MAP_NEGATIVE_X; break; 205 | case RTexType_CubePY: upload_binding = GL_TEXTURE_CUBE_MAP_POSITIVE_Y; break; 206 | case RTexType_CubeNY: upload_binding = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y; break; 207 | case RTexType_CubePZ: upload_binding = GL_TEXTURE_CUBE_MAP_POSITIVE_Z; break; 208 | case RTexType_CubeNZ: upload_binding = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; break; 209 | } 210 | 211 | int compressed = 0; 212 | switch (params.format) { 213 | case RTexFormat_RGB565: 214 | pixel_bits = 16; 215 | internal = format = GL_RGB; type = GL_UNSIGNED_SHORT_5_6_5; 216 | break; 217 | #ifdef ATTO_PLATFORM_RPI 218 | case RTexFormat_Compressed_ETC1: 219 | pixel_bits = 4; 220 | internal = GL_ETC1_RGB8_OES; 221 | compressed = 1; 222 | break; 223 | #endif 224 | default: 225 | ATTO_ASSERT(!"Impossible texture format"); 226 | return; 227 | } 228 | 229 | const int image_size = params.width * params.height * pixel_bits / 8; 230 | 231 | if (!compressed) { 232 | GL_CALL(glTexImage2D(upload_binding, params.mip_level < 0 ? 0 : params.mip_level, internal, params.width, params.height, 0, 233 | format, type, params.pixels)); 234 | } else { 235 | GL_CALL(glCompressedTexImage2D(upload_binding, params.mip_level < 0 ? 0 : params.mip_level, internal, params.width, params.height, 236 | 0, image_size, params.pixels)); 237 | } 238 | 239 | stats.textures_size += image_size; 240 | renderPrintMemUsage(); 241 | 242 | if (params.mip_level == -1) 243 | GL_CALL(glGenerateMipmap(binding)); 244 | 245 | GL_CALL(glTexParameteri(binding, GL_TEXTURE_MIN_FILTER, params.mip_level >= -1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR)); 246 | GL_CALL(glTexParameteri(binding, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); 247 | 248 | GL_CALL(glTexParameteri(binding, GL_TEXTURE_WRAP_S, wrap)); 249 | GL_CALL(glTexParameteri(binding, GL_TEXTURE_WRAP_T, wrap)); 250 | 251 | if (params.mip_level < 1) { 252 | texture->width = params.width; 253 | texture->height = params.height; 254 | } 255 | 256 | texture->format = params.format; 257 | texture->type_flags |= params.type; 258 | } 259 | 260 | void renderBufferCreate(RBuffer *buffer, RBufferType type, int size, const void *data) { 261 | switch (type) { 262 | case RBufferType_Vertex: buffer->type = GL_ARRAY_BUFFER; break; 263 | case RBufferType_Index: buffer->type = GL_ELEMENT_ARRAY_BUFFER; break; 264 | default: ASSERT(!"Invalid buffer type"); 265 | } 266 | GL_CALL(glGenBuffers(1, (GLuint*)&buffer->gl_name)); 267 | ++stats.buffers_count; 268 | 269 | GL_CALL(glBindBuffer(buffer->type, (GLuint)buffer->gl_name)); 270 | GL_CALL(glBufferData(buffer->type, size, data, GL_STATIC_DRAW)); 271 | stats.buffers_size += size; 272 | renderPrintMemUsage(); 273 | } 274 | 275 | typedef struct { 276 | const char *name; 277 | int components; 278 | GLenum type; 279 | GLboolean normalize; 280 | int stride; 281 | const void *ptr; 282 | } RAttrib; 283 | 284 | typedef struct { 285 | const char *name; 286 | } RUniform; 287 | 288 | #define RENDER_LIST_ATTRIBS \ 289 | RENDER_DECLARE_ATTRIB(vertex, 3, GL_FLOAT, GL_FALSE) \ 290 | RENDER_DECLARE_ATTRIB(lightmap_uv, 2, GL_FLOAT, GL_FALSE) \ 291 | RENDER_DECLARE_ATTRIB(tex_uv, 2, GL_FLOAT, GL_FALSE) \ 292 | RENDER_DECLARE_ATTRIB(average_color, 3, GL_UNSIGNED_BYTE, GL_TRUE) \ 293 | 294 | // RENDER_DECLARE_ATTRIB(normal, 3, GL_FLOAT) 295 | 296 | static const RAttrib g_attribs[] = { 297 | #define RENDER_DECLARE_ATTRIB(n,c,t,N) \ 298 | {"a_" # n, c, t, N, sizeof(struct BSPModelVertex), (void*)offsetof(struct BSPModelVertex, n)}, 299 | RENDER_LIST_ATTRIBS 300 | #undef RENDER_DECLARE_ATTRIB 301 | }; 302 | 303 | enum RAttribKinds { 304 | #define RENDER_DECLARE_ATTRIB(n,c,t,N) \ 305 | RAttribKind_ ## n, 306 | RENDER_LIST_ATTRIBS 307 | #undef RENDER_DECLARE_ATTRIB 308 | RAttribKind_COUNT 309 | }; 310 | 311 | #define RENDER_LIST_UNIFORMS \ 312 | RENDER_DECLARE_UNIFORM(mvp) \ 313 | RENDER_DECLARE_UNIFORM(far) \ 314 | RENDER_DECLARE_UNIFORM(lightmap) \ 315 | RENDER_DECLARE_UNIFORM(tex0) \ 316 | RENDER_DECLARE_UNIFORM(tex0_size) \ 317 | RENDER_DECLARE_UNIFORM(tex0_scale) \ 318 | RENDER_DECLARE_UNIFORM(tex0_translate) \ 319 | 320 | static const RUniform uniforms[] = { 321 | #define RENDER_DECLARE_UNIFORM(n) {"u_" # n}, 322 | RENDER_LIST_UNIFORMS 323 | #undef RENDER_DECLARE_UNIFORM 324 | }; 325 | 326 | enum RUniformKinds { 327 | #define RENDER_DECLARE_UNIFORM(n) RUniformKind_ ## n, 328 | RENDER_LIST_UNIFORMS 329 | #undef RENDER_DECLARE_UNIFORM 330 | RUniformKind_COUNT 331 | }; 332 | 333 | typedef struct RProgram { 334 | GLint name; 335 | struct { 336 | const char *common, *vertex, *fragment; 337 | } shader_sources; 338 | int attrib_locations[RAttribKind_COUNT]; 339 | int uniform_locations[RUniformKind_COUNT]; 340 | } RProgram; 341 | 342 | static RProgram programs[MShader_COUNT] = { 343 | /* MShader_Unknown */ 344 | {-1, { 345 | /* common */ 346 | "varying vec3 v_pos;\n", 347 | /* vertex */ 348 | "attribute vec3 a_vertex;\n" 349 | "uniform mat4 u_mvp;\n" 350 | "void main() {\n" 351 | "v_pos = a_vertex;\n" 352 | "gl_Position = u_mvp * vec4(a_vertex, 1.);\n" 353 | "}\n", 354 | /* fragment */ 355 | "void main() {\n" 356 | "gl_FragColor = vec4(mod(v_pos,100.)/100., 1.);\n" 357 | "}\n", 358 | }, 359 | { -1 }, { -1 } 360 | }, 361 | /* MShader_LightmappedOnly */ 362 | {-1, { 363 | /*common*/ 364 | "varying vec2 v_lightmap_uv;\n" 365 | "varying vec3 v_color;\n", 366 | /*vertex*/ 367 | "attribute vec3 a_vertex, a_average_color;\n" 368 | "attribute vec2 a_lightmap_uv;\n" 369 | "uniform mat4 u_mvp;\n" 370 | "void main() {\n" 371 | "v_lightmap_uv = a_lightmap_uv;\n" 372 | "v_color = a_average_color;\n" 373 | "gl_Position = u_mvp * vec4(a_vertex, 1.);\n" 374 | "}\n", 375 | /*fragment*/ 376 | "uniform sampler2D u_lightmap;\n" 377 | "void main() {\n" 378 | "gl_FragColor = vec4(v_color * texture2D(u_lightmap, v_lightmap_uv).xyz, 1.);\n" 379 | "}\n" 380 | }, 381 | { -1 }, { -1 } 382 | }, 383 | /* MShader_LightmappedGeneric */ 384 | {-1, { 385 | /*common*/ 386 | "varying vec2 v_lightmap_uv, v_tex_uv;\n", 387 | /*vertex*/ 388 | "attribute vec3 a_vertex;\n" 389 | "attribute vec2 a_lightmap_uv, a_tex_uv;\n" 390 | "uniform mat4 u_mvp;\n" 391 | "void main() {\n" 392 | "v_lightmap_uv = a_lightmap_uv;\n" 393 | "v_tex_uv = a_tex_uv;\n" 394 | "gl_Position = u_mvp * vec4(a_vertex, 1.);\n" 395 | "}\n", 396 | /*fragment*/ 397 | "uniform sampler2D u_lightmap, u_tex0;\n" 398 | "uniform vec2 u_lightmap_size, u_tex0_size;\n" 399 | "void main() {\n" 400 | "vec4 albedo = texture2D(u_tex0, v_tex_uv/u_tex0_size);\n" 401 | "vec3 lm = texture2D(u_lightmap, v_lightmap_uv).xyz;\n" 402 | "vec3 color = albedo.xyz * lm;\n" 403 | "gl_FragColor = vec4(color, 1.);\n" 404 | "}\n" 405 | }, 406 | { -1 }, { -1 } 407 | }, 408 | /* MShader_UnlitGeneric */ 409 | {-1, { /* common */ 410 | "varying vec2 v_uv;\n", 411 | /* vertex */ 412 | "attribute vec3 a_vertex;\n" 413 | "attribute vec2 a_tex_uv;\n" 414 | "uniform mat4 u_mvp;\n" 415 | "uniform float u_far;\n" 416 | "uniform vec2 u_tex0_scale, u_tex0_translate;\n" 417 | "void main() {\n" 418 | "v_uv = a_tex_uv * u_tex0_scale + u_tex0_translate;\n" 419 | "gl_Position = u_mvp * vec4(u_far * .5 * a_vertex, 1.);\n" 420 | "}\n", 421 | /* fragment */ 422 | "uniform sampler2D u_tex0;\n" 423 | "uniform vec2 u_tex0_size;\n" 424 | "void main() {\n" 425 | "gl_FragColor = texture2D(u_tex0, v_uv + vec2(.5) / u_tex0_size);\n" 426 | //"gl_FragColor = texture2D(u_tex0, v_uv);\n" 427 | "}\n", 428 | }, {-1}, {-1}}, 429 | }; 430 | 431 | static struct BSPModelVertex box[] = { 432 | {{ 1.f, -1.f, -1.f}, {0.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 433 | {{ 1.f, 1.f, -1.f}, {0.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 434 | {{ 1.f, 1.f, 1.f}, {0.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 435 | {{ 1.f, 1.f, 1.f}, {0.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 436 | {{ 1.f, -1.f, 1.f}, {0.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 437 | {{ 1.f, -1.f, -1.f}, {0.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 438 | 439 | {{-1.f, -1.f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 440 | {{-1.f, 1.f, 1.f}, {1.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 441 | {{-1.f, 1.f, -1.f}, {1.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 442 | {{-1.f, 1.f, -1.f}, {1.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 443 | {{-1.f, -1.f, -1.f}, {1.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 444 | {{-1.f, -1.f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 445 | 446 | {{ 1.f, -1.f, -1.f}, {2.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 447 | {{ 1.f, -1.f, 1.f}, {2.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 448 | {{-1.f, -1.f, 1.f}, {2.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 449 | {{-1.f, -1.f, 1.f}, {2.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 450 | {{-1.f, -1.f, -1.f}, {2.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 451 | {{ 1.f, -1.f, -1.f}, {2.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 452 | 453 | {{ 1.f, 1.f, 1.f}, {3.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 454 | {{ 1.f, 1.f, -1.f}, {3.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 455 | {{-1.f, 1.f, -1.f}, {3.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 456 | {{-1.f, 1.f, -1.f}, {3.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 457 | {{-1.f, 1.f, 1.f}, {3.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 458 | {{ 1.f, 1.f, 1.f}, {3.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 459 | 460 | {{ 1.f, -1.f, 1.f}, {4.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 461 | {{ 1.f, 1.f, 1.f}, {4.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 462 | {{-1.f, 1.f, 1.f}, {4.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 463 | {{-1.f, 1.f, 1.f}, {4.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 464 | {{-1.f, -1.f, 1.f}, {4.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 465 | {{ 1.f, -1.f, 1.f}, {4.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 466 | 467 | {{-1.f, -1.f, -1.f}, {5.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 468 | {{-1.f, 1.f, -1.f}, {5.f, 0.f}, {1.f, 1.f}, {0, 0, 0}}, 469 | {{ 1.f, 1.f, -1.f}, {5.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 470 | {{ 1.f, 1.f, -1.f}, {5.f, 0.f}, {0.f, 1.f}, {0, 0, 0}}, 471 | {{ 1.f, -1.f, -1.f}, {5.f, 0.f}, {0.f, 0.f}, {0, 0, 0}}, 472 | {{-1.f, -1.f, -1.f}, {5.f, 0.f}, {1.f, 0.f}, {0, 0, 0}}, 473 | }; 474 | 475 | static RBuffer box_buffer; 476 | 477 | static struct { 478 | const RTexture *current_tex0; 479 | 480 | const RProgram *current_program; 481 | struct { 482 | const float *mvp; 483 | float far; 484 | } uniforms; 485 | 486 | struct { 487 | float distance; 488 | const struct BSPModel *model; 489 | } closest_map; 490 | } r; 491 | 492 | static void renderApplyAttribs(const RAttrib *attribs, const RBuffer *buffer, unsigned int vbo_offset) { 493 | for(int i = 0; i < RAttribKind_COUNT; ++i) { 494 | const RAttrib *a = attribs + i; 495 | const int loc = r.current_program->attrib_locations[i]; 496 | if (loc < 0) continue; 497 | GL_CALL(glEnableVertexAttribArray(loc)); 498 | GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, buffer->gl_name)); 499 | GL_CALL(glVertexAttribPointer(loc, a->components, a->type, a->normalize, a->stride, (const char*)a->ptr + vbo_offset * sizeof(struct BSPModelVertex))); 500 | } 501 | } 502 | 503 | static int render_ProgramUse(RProgram *prog) { 504 | if (r.current_program == prog) 505 | return 0; 506 | 507 | if (r.current_program) { 508 | for (int i = 0; i < RAttribKind_COUNT; ++i) { 509 | const int loc = r.current_program->attrib_locations[i]; 510 | if (loc >= 0) 511 | GL_CALL(glDisableVertexAttribArray(loc)); 512 | } 513 | } 514 | 515 | GL_CALL(glUseProgram(prog->name)); 516 | GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_lightmap], 0)); 517 | GL_CALL(glUniform1i(prog->uniform_locations[RUniformKind_tex0], 1)); 518 | 519 | GL_CALL(glUniformMatrix4fv(prog->uniform_locations[RUniformKind_mvp], 1, GL_FALSE, r.uniforms.mvp)); 520 | GL_CALL(glUniform1f(prog->uniform_locations[RUniformKind_far], r.uniforms.far)); 521 | 522 | r.current_program = prog; 523 | r.current_tex0 = NULL; 524 | 525 | return 1; 526 | } 527 | 528 | static int render_ProgramInit(RProgram *prog) { 529 | GLuint program; 530 | GLuint vertex_shader, fragment_shader; 531 | const char *sources[] = { 532 | prog->shader_sources.common, prog->shader_sources.fragment, 0 533 | }; 534 | fragment_shader = render_ShaderCreate(GL_FRAGMENT_SHADER, sources); 535 | if (fragment_shader == 0) 536 | return -1; 537 | 538 | sources[1] = prog->shader_sources.vertex; 539 | vertex_shader = render_ShaderCreate(GL_VERTEX_SHADER, sources); 540 | if (vertex_shader == 0) { 541 | GL_CALL(glDeleteShader(fragment_shader)); 542 | return -2; 543 | } 544 | 545 | program = glCreateProgram(); 546 | GL_CALL(glAttachShader(program, fragment_shader)); 547 | GL_CALL(glAttachShader(program, vertex_shader)); 548 | GL_CALL(glLinkProgram(program)); 549 | 550 | GL_CALL(glDeleteShader(fragment_shader)); 551 | GL_CALL(glDeleteShader(vertex_shader)); 552 | 553 | #ifdef RENDER_ERRORCHECK 554 | { 555 | GLint status; 556 | GL_CALL(glGetProgramiv(program, GL_LINK_STATUS, &status)); 557 | if (status != GL_TRUE) { 558 | char buffer[1024]; 559 | GL_CALL(glGetProgramInfoLog(program, sizeof(buffer), 0, buffer)); 560 | PRINTF("Program linking error: %s", buffer); 561 | GL_CALL(glDeleteProgram(program)); 562 | return -3; 563 | } 564 | } 565 | #endif 566 | 567 | prog->name = program; 568 | 569 | for(int i = 0; i < RAttribKind_COUNT; ++i) { 570 | prog->attrib_locations[i] = glGetAttribLocation(prog->name, g_attribs[i].name); 571 | if (prog->attrib_locations[i] < 0) 572 | PRINTF("Cannot locate attribute %s", g_attribs[i].name); 573 | } 574 | 575 | for(int i = 0; i < RUniformKind_COUNT; ++i) { 576 | prog->uniform_locations[i] = glGetUniformLocation(prog->name, uniforms[i].name); 577 | if (prog->uniform_locations[i] < 0) 578 | PRINTF("Cannot locate uniform %s", uniforms[i].name); 579 | } 580 | 581 | return 0; 582 | } 583 | 584 | int renderInit() { 585 | PRINTF("GL extensions: %s", glGetString(GL_EXTENSIONS)); 586 | #ifdef _WIN32 587 | #define WGL__FUNCLIST_DO(T, N) \ 588 | gl##N = (T)wglGetProcAddress("gl" #N); \ 589 | ASSERT(gl##N); 590 | 591 | WGL__FUNCLIST 592 | #undef WGL__FUNCLIST_DO 593 | #endif 594 | 595 | memset(&stats, 0, sizeof(stats)); 596 | 597 | r.current_program = NULL; 598 | r.current_tex0 = NULL; 599 | r.uniforms.mvp = NULL; 600 | 601 | for (int i = 0; i < MShader_COUNT; ++i) { 602 | if (render_ProgramInit(programs + i) != 0) { 603 | PRINTF("Cannot create program %d", i); 604 | return 0; 605 | } 606 | } 607 | 608 | struct Texture default_texture; 609 | RTextureUploadParams params; 610 | params.type = RTexType_2D; 611 | params.format = RTexFormat_RGB565; 612 | params.width = 2; 613 | params.height = 2; 614 | params.pixels = (uint16_t[]){0xffffu, 0, 0, 0xffffu}; 615 | params.mip_level = -2; 616 | params.wrap = RTexWrap_Clamp; 617 | renderTextureInit(&default_texture.texture); 618 | renderTextureUpload(&default_texture.texture, params); 619 | cachePutTexture("opensource/placeholder", &default_texture); 620 | 621 | { 622 | struct Material default_material; 623 | memset(&default_material, 0, sizeof default_material); 624 | default_material.average_color = aVec3f(0.f, 1.f, 0.f); 625 | default_material.shader = MShader_Unknown; 626 | cachePutMaterial("opensource/placeholder", &default_material); 627 | } 628 | 629 | { 630 | struct Material lightmap_color_material; 631 | memset(&lightmap_color_material, 0, sizeof lightmap_color_material); 632 | lightmap_color_material.average_color = aVec3f(1.f, 1.f, 0.f); 633 | lightmap_color_material.shader = MShader_LightmappedOnly; 634 | cachePutMaterial("opensource/coarse", &lightmap_color_material); 635 | } 636 | 637 | renderBufferCreate(&box_buffer, RBufferType_Vertex, sizeof(box), box); 638 | 639 | GL_CALL(glEnable(GL_DEPTH_TEST)); 640 | GL_CALL(glEnable(GL_CULL_FACE)); 641 | return 1; 642 | } 643 | 644 | static void renderBindTexture(const RTexture *texture, int slot, int norepeat) { 645 | GL_CALL(glActiveTexture(GL_TEXTURE0 + slot)); 646 | GL_CALL(glBindTexture(GL_TEXTURE_2D, texture->gl_name)); 647 | if (norepeat) { 648 | const GLuint wrap = GL_CLAMP_TO_EDGE; 649 | GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap)); 650 | GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap)); 651 | } 652 | } 653 | 654 | static int renderUseMaterial(const Material *m) { 655 | const int program_changed = render_ProgramUse(programs + m->shader); 656 | 657 | if (m->base_texture.texture) { 658 | const RTexture *t = &m->base_texture.texture->texture; 659 | if (t != r.current_tex0) { 660 | renderBindTexture(&m->base_texture.texture->texture, 1, m->shader == MShader_UnlitGeneric); 661 | GL_CALL(glUniform2f(r.current_program->uniform_locations[RUniformKind_tex0_size], (float)t->width, (float)t->height)); 662 | GL_CALL(glUniform2f(r.current_program->uniform_locations[RUniformKind_tex0_scale], m->base_texture.transform.scale.x, m->base_texture.transform.scale.y)); 663 | GL_CALL(glUniform2f(r.current_program->uniform_locations[RUniformKind_tex0_translate], m->base_texture.transform.translate.x, m->base_texture.transform.translate.y)); 664 | r.current_tex0 = t; 665 | } 666 | } 667 | 668 | return program_changed; 669 | } 670 | 671 | static void renderDrawSet(const struct BSPModel *model, const struct BSPDrawSet *drawset) { 672 | unsigned int vbo_offset = 0; 673 | for (int i = 0; i < drawset->draws_count; ++i) { 674 | const struct BSPDraw *draw = drawset->draws + i; 675 | 676 | if (renderUseMaterial(draw->material) || i == 0 || draw->vbo_offset != vbo_offset) { 677 | vbo_offset = draw->vbo_offset; 678 | renderApplyAttribs(g_attribs, &model->vbo, draw->vbo_offset); 679 | } 680 | 681 | GL_CALL(glDrawElements(GL_TRIANGLES, draw->count, GL_UNSIGNED_SHORT, (void*)(sizeof(uint16_t) * draw->start))); 682 | } 683 | } 684 | 685 | static void renderSkybox(const struct Camera *camera, const struct BSPModel *model) { 686 | const struct AMat4f op = aMat4fMul(camera->projection, aMat4f3(camera->orientation, aVec3ff(0))); 687 | r.uniforms.mvp = &op.X.x; 688 | 689 | GL_CALL(glDisable(GL_CULL_FACE)); 690 | for (int i = 0; i < BSPSkyboxDir_COUNT; ++i) { 691 | if (!model->skybox[i] || !model->skybox[i]->base_texture.texture) 692 | continue; 693 | renderUseMaterial(model->skybox[i]); 694 | renderApplyAttribs(g_attribs, &box_buffer, 0); 695 | GL_CALL(glDrawArrays(GL_TRIANGLES, i*6, 6)); 696 | } 697 | GL_CALL(glEnable(GL_CULL_FACE)); 698 | } 699 | 700 | static float aMaxf(float a, float b) { return a > b ? a : b; } 701 | //static float aMinf(float a, float b) { return a < b ? a : b; } 702 | 703 | void renderModelDraw(const RDrawParams *params, const struct BSPModel *model) { 704 | if (!model->detailed.draws_count) return; 705 | 706 | const struct AMat4f mvp = aMat4fMul(params->camera->view_projection, 707 | aMat4fTranslation(params->translation)); 708 | 709 | GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model->ibo.gl_name)); 710 | renderBindTexture(&model->lightmap, 0, 0); 711 | 712 | const struct AVec3f rel_pos = aVec3fSub(params->camera->pos, params->translation); 713 | 714 | r.current_program = NULL; 715 | r.uniforms.mvp = &mvp.X.x; 716 | r.uniforms.far = params->camera->z_far; 717 | 718 | const float distance = 719 | aMaxf(aMaxf( 720 | aMaxf(rel_pos.x - model->aabb.max.x, model->aabb.min.x - rel_pos.x), 721 | aMaxf(rel_pos.y - model->aabb.max.y, model->aabb.min.y - rel_pos.y)), 722 | aMaxf(rel_pos.z - model->aabb.max.z, model->aabb.min.z - rel_pos.z)); 723 | 724 | /* 725 | PRINTF("%f %f %f -> %f", 726 | rel_pos.x, rel_pos.y, rel_pos.z, distance); 727 | */ 728 | 729 | if (distance < r.closest_map.distance) { 730 | r.closest_map.distance = distance; 731 | r.closest_map.model = model; 732 | } 733 | 734 | if (params->selected) { 735 | GL_CALL(glEnable(GL_BLEND)); 736 | GL_CALL(glBlendColor(1, 1, 1, .5f)); 737 | //GL_CALL(glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE)); 738 | GL_CALL(glBlendFunc(GL_ONE, GL_CONSTANT_ALPHA)); 739 | GL_CALL(glBlendEquation(GL_FUNC_ADD)); 740 | } 741 | 742 | if (distance < 0.f) 743 | renderDrawSet(model, &model->detailed); 744 | else 745 | renderDrawSet(model, &model->coarse); 746 | 747 | if (params->selected) { 748 | GL_CALL(glDisable(GL_BLEND)); 749 | } 750 | } 751 | 752 | void renderResize(int w, int h) { 753 | glViewport(0, 0, w, h); 754 | } 755 | 756 | void renderBegin() { 757 | glClearColor(0.f,1.f,0.f,0); 758 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 759 | r.closest_map.distance = 1e9f; 760 | } 761 | 762 | void renderEnd(const struct Camera *camera) { 763 | renderSkybox(camera, r.closest_map.model); 764 | } 765 | -------------------------------------------------------------------------------- /src/render.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "atto/math.h" 3 | 4 | typedef enum { 5 | RTexFormat_RGB565, 6 | #ifdef ATTO_PLATFORM_RPI 7 | RTexFormat_Compressed_ETC1, 8 | #endif 9 | } RTexFormat; 10 | 11 | typedef enum { 12 | RTexType_2D = (1 << 0), 13 | RTexType_CubePX = (1 << 1), 14 | RTexType_CubeNX = (1 << 2), 15 | RTexType_CubePY = (1 << 3), 16 | RTexType_CubeNY = (1 << 4), 17 | RTexType_CubePZ = (1 << 5), 18 | RTexType_CubeNZ = (1 << 6), 19 | } RTexType; 20 | 21 | typedef enum { 22 | RTexWrap_Repeat, 23 | RTexWrap_Clamp 24 | } RTexWrap; 25 | 26 | typedef struct { 27 | int width, height; 28 | RTexFormat format; 29 | int gl_name; 30 | int type_flags; 31 | } RTexture; 32 | 33 | typedef struct { 34 | RTexType type; 35 | int width, height; 36 | RTexFormat format; 37 | const void *pixels; 38 | int mip_level; // -1 means generate; -2 means don't need 39 | RTexWrap wrap; 40 | } RTextureUploadParams; 41 | 42 | #define renderTextureInit(texture_ptr) do { (texture_ptr)->gl_name = -1; } while (0) 43 | void renderTextureUpload(RTexture *texture, RTextureUploadParams params); 44 | 45 | typedef struct { 46 | int gl_name; 47 | int type; 48 | } RBuffer; 49 | 50 | typedef enum { 51 | RBufferType_Vertex, 52 | RBufferType_Index 53 | } RBufferType; 54 | 55 | int renderInit(); 56 | void renderResize(int w, int h); 57 | 58 | void renderBufferCreate(RBuffer *buffer, RBufferType type, int size, const void *data); 59 | 60 | struct BSPModel; 61 | struct Camera; 62 | 63 | void renderBegin(); 64 | 65 | typedef struct { 66 | const struct Camera *camera; 67 | struct AVec3f translation; 68 | int selected; 69 | } RDrawParams; 70 | 71 | void renderModelDraw(const RDrawParams *params, const struct BSPModel *model); 72 | 73 | void renderEnd(const struct Camera *camera); 74 | -------------------------------------------------------------------------------- /src/texture.c: -------------------------------------------------------------------------------- 1 | #include "texture.h" 2 | #include "etcpack.h" 3 | #include "dxt.h" 4 | #include "vtf.h" 5 | #include "cache.h" 6 | #include "collection.h" 7 | #include "mempools.h" 8 | #include "common.h" 9 | 10 | const char *vtfFormatStr(enum VTFImageFormat fmt) { 11 | switch(fmt) { 12 | case VTFImage_None: return "None"; 13 | case VTFImage_RGBA8: return "RGBA8"; 14 | case VTFImage_ABGR8: return "ABGR8"; 15 | case VTFImage_RGB8: return "RGB8"; 16 | case VTFImage_BGR8: return "BGR8"; 17 | case VTFImage_RGB565: return "RGB565"; 18 | case VTFImage_I8: return "I8"; 19 | case VTFImage_IA8: return "IA8"; 20 | case VTFImage_P8: return "P8"; 21 | case VTFImage_A8: return "A8"; 22 | case VTFImage_RGB8_Bluescreen: return "RGB8_Bluescreen"; 23 | case VTFImage_BGR8_Bluescreen: return "BGR8_Bluescreen"; 24 | case VTFImage_ARGB8: return "ARGB8"; 25 | case VTFImage_BGRA8: return "BGRA8"; 26 | case VTFImage_DXT1: return "DXT1"; 27 | case VTFImage_DXT3: return "DXT3"; 28 | case VTFImage_DXT5: return "DXT5"; 29 | case VTFImage_BGRX8: return "BGRX8"; 30 | case VTFImage_BGR565: return "BGR565"; 31 | case VTFImage_BGRX5551: return "BGRX5551"; 32 | case VTFImage_BGRA4: return "BGRA4"; 33 | case VTFImage_DXT1_A1: return "DXT1_A1"; 34 | case VTFImage_BGRA5551: return "BGRA5551"; 35 | case VTFImage_UV8: return "UV8"; 36 | case VTFImage_UVWQ8: return "UVWQ8"; 37 | case VTFImage_RGBA16F: return "RGBA16F"; 38 | case VTFImage_RGBA16: return "RGBA16"; 39 | case VTFImage_UVLX8: return "UVLX8"; 40 | } 41 | return "None"; 42 | } 43 | 44 | static int vtfImageSize(enum VTFImageFormat fmt, int width, int height) { 45 | int pixel_bits = 0; 46 | switch(fmt) { 47 | case VTFImage_None: break; 48 | case VTFImage_RGBA8: 49 | case VTFImage_ABGR8: 50 | case VTFImage_ARGB8: 51 | case VTFImage_BGRA8: 52 | case VTFImage_BGRX8: 53 | case VTFImage_UVWQ8: 54 | case VTFImage_UVLX8: 55 | pixel_bits = 32; 56 | break; 57 | case VTFImage_BGR565: 58 | case VTFImage_BGRX5551: 59 | case VTFImage_BGRA5551: 60 | case VTFImage_BGRA4: 61 | case VTFImage_RGB565: 62 | case VTFImage_UV8: 63 | pixel_bits = 16; 64 | break; 65 | case VTFImage_RGB8: 66 | case VTFImage_BGR8: 67 | case VTFImage_RGB8_Bluescreen: 68 | case VTFImage_BGR8_Bluescreen: 69 | pixel_bits = 24; 70 | break; 71 | case VTFImage_I8: 72 | case VTFImage_IA8: 73 | case VTFImage_P8: 74 | case VTFImage_A8: 75 | pixel_bits = 8; 76 | break; 77 | case VTFImage_DXT3: 78 | case VTFImage_DXT5: 79 | pixel_bits = 4; 80 | /* fall through */ 81 | case VTFImage_DXT1: 82 | case VTFImage_DXT1_A1: 83 | pixel_bits += 4; 84 | width = 4 * ((width + 3) / 4); 85 | height = 4 * ((height + 3) / 4); 86 | break; 87 | case VTFImage_RGBA16F: 88 | case VTFImage_RGBA16: 89 | pixel_bits = 64; 90 | break; 91 | } 92 | return width * height * pixel_bits / 8; 93 | } 94 | 95 | static void textureUnpackDXTto565(uint8_t *src, uint16_t *dst, int width, int height, enum VTFImageFormat format) { 96 | const struct DXTUnpackContext dxt_ctx = { 97 | .width = width, 98 | .height = height, 99 | .packed = src, 100 | .output = dst 101 | }; 102 | 103 | if (format == VTFImage_DXT1) 104 | dxt1Unpack(dxt_ctx); 105 | else 106 | dxt5Unpack(dxt_ctx); 107 | } 108 | 109 | static void textureUnpackBGR8to565(uint8_t *src, uint16_t *dst, int width, int height) { 110 | const int pixels = width * height; 111 | for (int i = 0; i < pixels; ++i, src+=3) { 112 | const int r = (src[0] >> 3); 113 | const int g = (src[1] >> 2); 114 | const int b = (src[2] >> 3); 115 | dst[i] = (uint16_t)((r << 11) | (g << 5) | b); 116 | } 117 | } 118 | 119 | static void textureUnpackBGRX8to565(uint8_t *src, uint16_t *dst, int width, int height) { 120 | const int pixels = width * height; 121 | for (int i = 0; i < pixels; ++i, src+=4) { 122 | const int r = (src[0] & 0xf8); 123 | const int g = (src[1] & 0xfc); 124 | const int b = (src[2] >> 3); 125 | dst[i] = (uint16_t)((r << 8) | (g << 3)| b); 126 | } 127 | } 128 | 129 | static void textureUnpackBGRA8to565(uint8_t *src, uint16_t *dst, int width, int height) { 130 | const int pixels = width * height; 131 | for (int i = 0; i < pixels; ++i, src+=4) { 132 | const int a = src[3] * 8; /* FIXME this is likely HDR and need proper color correction */ 133 | const int r = ((a * src[0]) >> 11); 134 | const int g = ((a * src[1]) >> 10); 135 | const int b = (a * src[2]) >> 11; 136 | 137 | dst[i] = (uint16_t)(((r>31?31:r) << 11) | ((g>63?63:g) << 5) | (b>31?31:b)); 138 | } 139 | } 140 | 141 | /* FIXME: taken from internets: https://gist.github.com/martinkallman/5049614 */ 142 | float float32(uint16_t value) { 143 | const uint32_t result = 144 | (((value & 0x7fffu) << 13) + 0x38000000u) | // mantissa + exponent 145 | ((value & 0x8000u) << 16); // sign 146 | float retval; 147 | memcpy(&retval, &result, sizeof(retval)); 148 | return retval; 149 | } 150 | 151 | static void textureUnpackRGBA16Fto565(const uint16_t *src, uint16_t *dst, int width, int height) { 152 | const int pixels = width * height; 153 | for (int i = 0; i < pixels; ++i, src+=4) { 154 | const float scale = 255.f * 1.5f; 155 | const int rf = (int)(sqrtf(float32(src[0])) * scale) >> 3; 156 | const int gf = (int)(sqrtf(float32(src[1])) * scale) >> 2; 157 | const int bf = (int)(sqrtf(float32(src[2])) * scale) >> 3; 158 | dst[i] = (uint16_t)(((rf>31?31:rf) << 11) | ((gf>63?63:gf) << 5) | (bf>31?31:bf)); 159 | } 160 | } 161 | 162 | static uint16_t *textureUnpackToTemp(struct Stack *tmp, struct IFile *file, size_t cursor, 163 | int width, int height, enum VTFImageFormat format) { 164 | 165 | const int src_texture_size = vtfImageSize(format, width, height); 166 | const int dst_texture_size = sizeof(uint16_t) * width * height; 167 | void *dst_texture = stackAlloc(tmp, dst_texture_size); 168 | if (!dst_texture) { 169 | PRINTF("Cannot allocate %d bytes for texture", dst_texture_size); 170 | return 0; 171 | } 172 | void *src_texture = stackAlloc(tmp, src_texture_size); 173 | if (!src_texture) { 174 | PRINTF("Cannot allocate %d bytes for texture", src_texture_size); 175 | return 0; 176 | } 177 | 178 | if (src_texture_size != (int)file->read(file, cursor, src_texture_size, src_texture)) { 179 | PRINT("Cannot read texture data"); 180 | return 0; 181 | } 182 | 183 | switch (format) { 184 | case VTFImage_DXT1: 185 | case VTFImage_DXT5: 186 | textureUnpackDXTto565(src_texture, dst_texture, width, height, format); 187 | break; 188 | case VTFImage_BGR8: 189 | textureUnpackBGR8to565(src_texture, dst_texture, width, height); 190 | break; 191 | case VTFImage_BGRA8: 192 | textureUnpackBGRA8to565(src_texture, dst_texture, width, height); 193 | break; 194 | case VTFImage_BGRX8: 195 | textureUnpackBGRX8to565(src_texture, dst_texture, width, height); 196 | break; 197 | case VTFImage_RGBA16F: 198 | textureUnpackRGBA16Fto565(src_texture, dst_texture, width, height); 199 | break; 200 | default: 201 | PRINTF("Unsupported texture format %s", vtfFormatStr(format)); 202 | return 0; 203 | } 204 | 205 | stackFreeUpToPosition(tmp, src_texture); 206 | return dst_texture; 207 | } 208 | 209 | static int textureUploadMipmapType(struct Stack *tmp, struct IFile *file, size_t cursor, 210 | const struct VTFHeader *hdr, int miplevel, RTexture *tex, RTexType tex_type) { 211 | for (int mip = hdr->mipmap_count - 1; mip > miplevel; --mip) { 212 | const unsigned int mip_width = hdr->width >> mip; 213 | const unsigned int mip_height = hdr->height >> mip; 214 | const int mip_image_size = vtfImageSize(hdr->hires_format, mip_width, mip_height); 215 | cursor += mip_image_size * hdr->frames; 216 | 217 | /*PRINTF("cur: %d; size: %d, mip: %d, %dx%d", 218 | cursor, mip_image_size, mip, mip_width, mip_height); 219 | */ 220 | } 221 | 222 | void *dst_texture = textureUnpackToTemp(tmp, file, cursor, hdr->width, hdr->height, hdr->hires_format); 223 | if (!dst_texture) { 224 | PRINT("Failed to unpack texture"); 225 | return 0; 226 | } 227 | 228 | #ifdef ATTO_PLATFORM_RPI 229 | { 230 | const uint16_t *p565 = dst_texture; 231 | uint8_t *etc1_data = stackAlloc(tmp, hdr->width * hdr->height / 2); 232 | uint8_t *block = etc1_data; 233 | 234 | // FIXME assumes w and h % 4 == 0 235 | for (int by = 0; by < hdr->height; by += 4) { 236 | for (int bx = 0; bx < hdr->width; bx += 4) { 237 | const uint16_t *bp = p565 + bx + by * hdr->width; 238 | ETC1Color ec[16]; 239 | for (int x = 0; x < 4; ++x) { 240 | for (int y = 0; y < 4; ++y) { 241 | const unsigned p = bp[x + y * hdr->width]; 242 | ec[x*4+y].r = (p & 0xf800u) >> 8; 243 | ec[x*4+y].g = (p & 0x07e0u) >> 3; 244 | ec[x*4+y].b = (p & 0x001fu) << 3; 245 | } 246 | } 247 | 248 | etc1PackBlock(ec, block); 249 | block += 8; 250 | } 251 | } 252 | 253 | const RTextureUploadParams params = { 254 | .type = tex_type, 255 | .width = hdr->width, 256 | .height = hdr->height, 257 | .format = RTexFormat_Compressed_ETC1, 258 | .pixels = etc1_data, 259 | .mip_level = -2,//miplevel, 260 | .wrap = RTexWrap_Repeat 261 | }; 262 | 263 | renderTextureUpload(tex, params); 264 | 265 | stackFreeUpToPosition(tmp, etc1_data); 266 | } 267 | #else 268 | 269 | const RTextureUploadParams params = { 270 | .type = tex_type, 271 | .width = hdr->width, 272 | .height = hdr->height, 273 | .format = RTexFormat_RGB565, 274 | .pixels = dst_texture, 275 | .mip_level = -1,//miplevel, 276 | .wrap = RTexWrap_Repeat 277 | }; 278 | 279 | renderTextureUpload(tex, params); 280 | #endif 281 | 282 | return 1; 283 | } 284 | 285 | static int textureLoad(struct IFile *file, Texture *tex, struct Stack *tmp, RTexType type) { 286 | struct VTFHeader hdr; 287 | size_t cursor = 0; 288 | int retval = 0; 289 | if (file->read(file, 0, sizeof(hdr), &hdr) != sizeof(hdr)) { 290 | PRINT("Cannot read texture"); 291 | return 0; 292 | } 293 | 294 | if (hdr.signature[0] != 'V' || hdr.signature[1] != 'T' || 295 | hdr.signature[2] != 'F' || hdr.signature[3] != '\0') { 296 | PRINT("Invalid file signature"); 297 | return 0; 298 | } 299 | 300 | /* 301 | if (!(hdr.version[0] > 7 || hdr.version[1] > 2)) { 302 | PRINTF("VTF version %d.%d is not supported", hdr.version[0], hdr.version[1]); 303 | return 0; 304 | } 305 | */ 306 | 307 | //PRINTF("Texture: %dx%d, %s", 308 | // hdr.width, hdr.height, vtfFormatStr(hdr.hires_format)); 309 | 310 | /* 311 | if (hdr.hires_format != VTFImage_DXT1 && hdr.hires_format != VTFImage_DXT5 && hdr.hires_format != VTFImage_BGR8) { 312 | PRINTF("Not implemented texture format: %s", vtfFormatStr(hdr.hires_format)); 313 | return 0; 314 | } 315 | */ 316 | 317 | cursor += hdr.header_size; 318 | 319 | /* Compute averaga color from lowres image */ 320 | void *pre_alloc_cursor = stackGetCursor(tmp); 321 | if (hdr.lores_format != VTFImage_DXT1 && hdr.lores_format != VTFImage_DXT5) { 322 | PRINTF("Not implemented lores texture format: %s", vtfFormatStr(hdr.lores_format)); 323 | tex->avg_color = aVec3ff(1.f); 324 | } else { 325 | uint16_t *pixels = textureUnpackToTemp(tmp, file, cursor, hdr.lores_width, hdr.lores_height, hdr.lores_format); 326 | 327 | if (!pixels) { 328 | PRINT("Cannot unpack lowres image"); 329 | return 0; 330 | } 331 | 332 | tex->avg_color = aVec3ff(0); 333 | const int pixels_count = hdr.lores_width * hdr.lores_height; 334 | for (int i = 0; i < pixels_count; ++i) { 335 | tex->avg_color.x += (pixels[i] >> 11); 336 | tex->avg_color.y += (pixels[i] >> 5) & 0x3f; 337 | tex->avg_color.z += (pixels[i] & 0x1f); 338 | } 339 | 340 | tex->avg_color = aVec3fMul(tex->avg_color, 341 | aVec3fMulf(aVec3f(1.f/31.f, 1.f/63.f, 1.f/31.f), 1.f / pixels_count)); 342 | //PRINTF("Average color %f %f %f", tex->avg_color.x, tex->avg_color.y, tex->avg_color.z); 343 | } 344 | 345 | cursor += vtfImageSize(hdr.lores_format, hdr.lores_width, hdr.lores_height); 346 | 347 | /* 348 | PRINTF("Texture lowres: %dx%d, %s; mips %d; header_size: %u", 349 | hdr.lores_width, hdr.lores_height, vtfFormatStr(hdr.lores_format), hdr.mipmap_count, hdr.header_size); 350 | */ 351 | 352 | for (int mip = 0; mip <= 0/*< hdr.mipmap_count*/; ++mip) { 353 | retval = textureUploadMipmapType(tmp, file, cursor, &hdr, mip, &tex->texture, type); 354 | if (retval != 1) 355 | break; 356 | } 357 | stackFreeUpToPosition(tmp, pre_alloc_cursor); 358 | 359 | return retval; 360 | } 361 | 362 | const Texture *textureGet(const char *name, struct ICollection *collection, struct Stack *tmp) { 363 | const Texture *tex = cacheGetTexture(name); 364 | if (tex) return tex; 365 | 366 | struct IFile *texfile; 367 | if (CollectionOpen_Success != collectionChainOpen(collection, name, File_Texture, &texfile)) { 368 | PRINTF("Texture \"%s\" not found", name); 369 | return cacheGetTexture("opensource/placeholder"); 370 | } 371 | 372 | struct Texture localtex; 373 | renderTextureInit(&localtex.texture); 374 | if (textureLoad(texfile, &localtex, tmp, RTexType_2D) == 0) { 375 | PRINTF("Texture \"%s\" found, but could not be loaded", name); 376 | } else { 377 | cachePutTexture(name, &localtex); 378 | tex = cacheGetTexture(name); 379 | } 380 | 381 | texfile->close(texfile); 382 | return tex ? tex : cacheGetTexture("opensource/placeholder"); 383 | } 384 | -------------------------------------------------------------------------------- /src/texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "render.h" 3 | #include "collection.h" 4 | #include "mempools.h" 5 | 6 | typedef struct Texture { 7 | RTexture texture; 8 | struct AVec3f avg_color; 9 | } Texture; 10 | 11 | const Texture *textureGet(const char *name, struct ICollection *collection, struct Stack *tmp); 12 | -------------------------------------------------------------------------------- /src/vbsp.h: -------------------------------------------------------------------------------- 1 | #ifndef VBSP_H__INCLUDED 2 | #define VBSP_H__INCLUDED 3 | 4 | #include 5 | 6 | #ifdef _MSC_VER 7 | #pragma warning(disable:4214) 8 | #endif 9 | 10 | enum { 11 | VBSP_Lump_Entity = 0, 12 | VBSP_Lump_Plane = 1, 13 | VBSP_Lump_TexData = 2, 14 | VBSP_Lump_Vertex = 3, 15 | VBSP_Lump_Visibility = 4, 16 | VBSP_Lump_Node = 5, 17 | VBSP_Lump_TexInfo = 6, 18 | VBSP_Lump_Face = 7, 19 | VBSP_Lump_LightMap = 8, 20 | 21 | VBSP_Lump_Leaf = 10, 22 | 23 | VBSP_Lump_Edge = 12, 24 | VBSP_Lump_Surfedge = 13, 25 | VBSP_Lump_Model = 14, 26 | 27 | VBSP_Lump_LeafFace = 16, 28 | 29 | VBSP_Lump_DispInfo = 26, 30 | 31 | VBSP_Lump_DispVerts = 33, 32 | 33 | VBSP_Lump_PakFile = 40, 34 | 35 | VBSP_Lump_TexDataStringData = 43, 36 | VBSP_Lump_TexDataStringTable = 44, 37 | 38 | VBSP_Lump_LightMapHDR = 53, 39 | 40 | VBSP_Lump_FaceHDR = 58, 41 | 42 | VBSP_Lump_COUNT = 64 43 | }; 44 | 45 | #pragma pack(1) 46 | struct VBSPLumpHeader { 47 | uint32_t file_offset; 48 | uint32_t size; 49 | uint32_t version; 50 | char fourCC[4]; 51 | }; 52 | struct VBSPHeader { 53 | char ident[4]; 54 | uint32_t version; 55 | struct VBSPLumpHeader lump_headers[VBSP_Lump_COUNT]; 56 | uint32_t map_revision; 57 | }; 58 | struct VBSPLumpPlane { float x, y, z, d; }; 59 | struct VBSPLumpTexData { 60 | struct { float x, y, z; } reflectivity; 61 | uint32_t name_string_table_id; 62 | uint32_t width, height; 63 | uint32_t view_width, view_height; 64 | }; 65 | struct VBSPLumpVertex { float x, y, z; }; 66 | struct VBSPLumpVisibility { 67 | uint32_t num_clusters; 68 | uint32_t byte_offsets[8][2]; 69 | }; 70 | struct VBSPLumpNode { 71 | uint32_t plane; 72 | uint32_t children[2]; 73 | int16_t min[3], max[3]; 74 | uint16_t first_face, num_faces; 75 | int16_t area; 76 | int16_t padding_; 77 | }; 78 | struct VBSPLumpTexInfo { 79 | float texture_vecs[2][4]; 80 | float lightmap_vecs[2][4]; 81 | uint32_t flags; 82 | uint32_t texdata; 83 | }; 84 | struct VBSPLumpFace { 85 | uint16_t plane; 86 | uint8_t side, node; 87 | uint32_t first_edge; 88 | int16_t num_edges; 89 | int16_t texinfo; 90 | int16_t dispinfo; 91 | int16_t surface_fog_volume_id; 92 | uint8_t styles[4]; 93 | uint32_t lightmap_offset; 94 | float area; 95 | int32_t lightmap_min[2]; 96 | int32_t lightmap_size[2]; 97 | uint32_t orig_face; 98 | uint16_t num_primitives; 99 | uint16_t first_primitive; 100 | uint32_t lightmap_smoothing_group; 101 | }; 102 | struct VBSPLumpLightMap { 103 | uint8_t r, g, b; 104 | int8_t exponent; 105 | }; 106 | struct VBSPLumpLeaf { 107 | uint32_t contents; 108 | uint16_t cluster; 109 | uint16_t area:9; 110 | uint16_t flags:7; 111 | int16_t min[3], max[3]; 112 | uint16_t first_leafface, num_leaffaces; 113 | uint16_t first_leafbrush, num_leafbrushes; 114 | int16_t water_data_id; 115 | }; 116 | struct VBSPLumpEdge { 117 | uint16_t v[2]; 118 | }; 119 | struct VBSPLumpModel { 120 | struct { float x, y, z; } min; 121 | struct { float x, y, z; } max; 122 | struct { float x, y, z; } origin; 123 | int32_t head_node; 124 | int32_t first_face, num_faces; 125 | }; 126 | struct VBSPLumpDispInfo { 127 | struct { float x, y, z; } start_pos; 128 | int32_t vtx_start; 129 | int32_t tri_start; 130 | int32_t power; 131 | int32_t min_tess; 132 | float smoothing_angle; 133 | int32_t contents; 134 | uint16_t face; 135 | int32_t lightmap_alpha_start; /* not used? */ 136 | int32_t lightmap_sample_position_start; 137 | /* FIXME a mistake here? 138 | struct { 139 | struct { 140 | uint16_t index; 141 | uint8_t orientation; 142 | uint8_t span; 143 | uint8_t neighbor_span; 144 | } sub_neighbors[2]; 145 | } edge_neighbors[4]; 146 | struct { 147 | uint16_t indices[4]; 148 | uint8_t num; 149 | } corner_neighbors[4]; 150 | */ 151 | uint8_t fixme_padding[90]; 152 | uint32_t allowed_verts[10]; 153 | }; 154 | struct VBSPLumpDispVert { 155 | float x, y, z, dist, alpha; 156 | }; 157 | #pragma pack() 158 | 159 | enum VBSPSurfaceFlags { 160 | VBSP_Surface_Light = 0x0001, 161 | VBSP_Surface_Sky2D = 0x0002, 162 | VBSP_Surface_Sky = 0x0004, 163 | VBSP_Surface_NoDraw = 0x0080, 164 | VBSP_Surface_NoLight = 0x0400 165 | }; 166 | 167 | #endif /* ifndef VBSP_H__INCLUDED */ 168 | -------------------------------------------------------------------------------- /src/vmfparser.c: -------------------------------------------------------------------------------- 1 | #include "vmfparser.h" 2 | #include "log.h" 3 | 4 | typedef enum { 5 | VMFTokenType_End, 6 | VMFTokenType_Error, 7 | VMFTokenType_String, 8 | VMFTokenType_Open, 9 | VMFTokenType_Close 10 | } VMFTokenType; 11 | 12 | typedef struct { 13 | VMFTokenType type; 14 | StringView string; 15 | } VMFToken; 16 | 17 | static VMFToken readNextToken(VMFState *state) { 18 | const char *c = state->data.str; 19 | const char * const end = state->data.str + state->data.length; 20 | const char *error_message = NULL; 21 | 22 | #define CHECK_END (end == c || *c == '\0') 23 | #define REPORT_ERROR(msg) \ 24 | do {\ 25 | error_message = msg; \ 26 | token.type = VMFTokenType_Error; \ 27 | goto exit; \ 28 | } while(0) 29 | 30 | VMFToken token; 31 | token.string.str = NULL; 32 | token.string.length = 0; 33 | token.type = VMFTokenType_End; 34 | 35 | for (;;) { 36 | for (;; ++c) { 37 | if (CHECK_END) { 38 | --c; 39 | goto exit; 40 | } 41 | if (!isspace(*c)) 42 | break; 43 | } 44 | 45 | switch(*c) { 46 | case '{': 47 | token.string.str = c; 48 | token.string.length = 1; 49 | token.type = VMFTokenType_Open; 50 | break; 51 | case '}': 52 | token.string.str = c; 53 | token.string.length = 1; 54 | token.type = VMFTokenType_Close; 55 | break; 56 | case '/': 57 | ++c; 58 | if (CHECK_END || *c != '/') 59 | REPORT_ERROR("'/' expected"); 60 | while(!CHECK_END && *c != '\n') ++c; 61 | continue; 62 | case '\"': 63 | token.string.str = ++c; 64 | for (;; ++c) { 65 | if (CHECK_END) 66 | REPORT_ERROR("\" is not closed at the end"); 67 | if (*c == '\"') 68 | break; 69 | } 70 | token.string.length = (int)(c - token.string.str); 71 | token.type = VMFTokenType_String; 72 | break; 73 | 74 | default: 75 | token.string.str = c; 76 | while (!CHECK_END && isgraph(*c)) ++c; 77 | token.string.length = (int)(c - token.string.str); 78 | token.type = VMFTokenType_String; 79 | } /* switch(*c) */ 80 | 81 | break; 82 | } /* forever */ 83 | 84 | exit: 85 | if (error_message) 86 | PRINTF("Parsing error \"%s\" @%d (%.*s)", error_message, 87 | (int)(c - state->data.str), (int)(end - c < 32 ? end - c : 32), c); 88 | //else PRINTF("Token %d, (%.*s)", token.type, PRI_SVV(token.string)); 89 | 90 | state->data.str = c + 1; 91 | state->data.length = (int)(end - c - 1); 92 | return token; 93 | } 94 | 95 | VMFResult vmfParse(VMFState *state) { 96 | VMFKeyValue kv; 97 | VMFAction action = VMFAction_Continue; 98 | 99 | for (;;) { 100 | switch (action) { 101 | case VMFAction_Continue: 102 | break; 103 | case VMFAction_Exit: 104 | return VMFResult_Success; 105 | case VMFAction_SemanticError: 106 | return VMFResult_SemanticError; 107 | } 108 | 109 | kv.key.str = kv.value.str = ""; 110 | kv.key.length = kv.value.length = 0; 111 | 112 | VMFToken token = readNextToken(state); 113 | switch (token.type) { 114 | case VMFTokenType_String: 115 | kv.key = token.string; 116 | break; 117 | case VMFTokenType_Open: 118 | action = state->callback(state, VMFEntryType_SectionOpen, &kv); 119 | continue; 120 | case VMFTokenType_Close: 121 | action = state->callback(state, VMFEntryType_SectionClose, &kv); 122 | continue; 123 | case VMFTokenType_End: 124 | return VMFResult_Success; 125 | default: 126 | PRINTF("Unexpected token %d", token.type); 127 | return VMFResult_SyntaxError; 128 | } 129 | 130 | token = readNextToken(state); 131 | switch (token.type) { 132 | case VMFTokenType_String: 133 | kv.value = token.string; 134 | action = state->callback(state, VMFEntryType_KeyValue, &kv); 135 | continue; 136 | case VMFTokenType_Open: 137 | action = state->callback(state, VMFEntryType_SectionOpen, &kv); 138 | continue; 139 | default: 140 | PRINTF("Unexpected token %d", token.type); 141 | return VMFResult_SyntaxError; 142 | } 143 | } // forever 144 | } // vmfParse 145 | -------------------------------------------------------------------------------- /src/vmfparser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | struct VMFState; 6 | typedef struct VMFState VMFState; 7 | 8 | typedef enum { 9 | VMFEntryType_KeyValue, 10 | VMFEntryType_SectionOpen, 11 | VMFEntryType_SectionClose 12 | } VMFEntryType; 13 | 14 | typedef enum { 15 | VMFAction_Continue, 16 | VMFAction_Exit, 17 | VMFAction_SemanticError 18 | } VMFAction; 19 | 20 | typedef struct { 21 | StringView key, value; 22 | } VMFKeyValue; 23 | 24 | typedef VMFAction (*VMFCallback)(VMFState *state, VMFEntryType entry, const VMFKeyValue *kv); 25 | 26 | struct VMFState { 27 | void *user_data; 28 | StringView data; 29 | VMFCallback callback; 30 | }; 31 | 32 | typedef enum { 33 | VMFResult_Success, 34 | VMFResult_SyntaxError, 35 | VMFResult_SemanticError 36 | } VMFResult; 37 | 38 | VMFResult vmfParse(VMFState *state); 39 | -------------------------------------------------------------------------------- /src/vpk.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #pragma pack(1) 5 | #define VPK_SIGNATURE (0x55aa1234ul) 6 | typedef struct { 7 | uint32_t signature; 8 | uint32_t version; 9 | uint32_t treeSize; 10 | } VPK1Header; 11 | typedef struct { 12 | uint32_t signature; 13 | uint32_t version; 14 | uint32_t treeSize; 15 | uint32_t dontCareSize[4]; 16 | } VPK2Header; 17 | 18 | #define VPK_TERMINATOR (0xffffu) 19 | struct VPKTreeEntry { 20 | uint32_t crc; 21 | uint16_t preloadBytes; 22 | uint16_t archive; 23 | uint32_t archiveOffset; 24 | uint32_t archiveLength; 25 | uint16_t terminator; 26 | }; 27 | 28 | #pragma pack() 29 | -------------------------------------------------------------------------------- /src/vtf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #pragma pack(1) 5 | struct VTFHeader { 6 | char signature[4]; 7 | uint32_t version[2]; 8 | uint32_t header_size; 9 | uint16_t width, height; 10 | uint32_t flags; 11 | uint16_t frames; 12 | uint16_t first_frame; 13 | char padding0[4]; 14 | float reflectivity[3]; 15 | char padding1[4]; 16 | float bump_scale; 17 | uint32_t hires_format; 18 | uint8_t mipmap_count; 19 | uint32_t lores_format; 20 | uint8_t lores_width, lores_height; 21 | }; 22 | 23 | enum VTFImageFormat { 24 | VTFImage_None = -1, 25 | VTFImage_RGBA8 = 0, 26 | VTFImage_ABGR8, 27 | VTFImage_RGB8, 28 | VTFImage_BGR8, 29 | VTFImage_RGB565, 30 | VTFImage_I8, 31 | VTFImage_IA8, 32 | VTFImage_P8, 33 | VTFImage_A8, 34 | VTFImage_RGB8_Bluescreen, 35 | VTFImage_BGR8_Bluescreen, 36 | VTFImage_ARGB8, 37 | VTFImage_BGRA8, 38 | VTFImage_DXT1, 39 | VTFImage_DXT3, 40 | VTFImage_DXT5, 41 | VTFImage_BGRX8, 42 | VTFImage_BGR565, 43 | VTFImage_BGRX5551, 44 | VTFImage_BGRA4, 45 | VTFImage_DXT1_A1, 46 | VTFImage_BGRA5551, 47 | VTFImage_UV8, 48 | VTFImage_UVWQ8, 49 | VTFImage_RGBA16F, 50 | VTFImage_RGBA16, 51 | VTFImage_UVLX8 52 | }; 53 | #pragma pack() 54 | -------------------------------------------------------------------------------- /src/zip.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #pragma pack(1) 5 | 6 | #define ZipEndOfDirectory_Signature 0x06054b50ul 7 | #define ZipFileHeader_Signature 0x02014b50ul 8 | #define ZipLocalFileHeader_Signature 0x04034b50ul 9 | 10 | struct ZipEndOfDirectory { 11 | uint32_t signature; 12 | uint16_t disk; 13 | uint16_t dir_disk; 14 | uint16_t dir_records_num; 15 | uint16_t dir_records_num_total; 16 | uint32_t dir_size; 17 | uint32_t dir_offset; 18 | uint16_t comment_size; 19 | }; 20 | 21 | struct ZipFileHeader { 22 | uint32_t signature; 23 | uint16_t version; 24 | uint16_t extract_version; 25 | uint16_t flags; 26 | uint16_t compression; 27 | uint16_t mod_time, mod_date; 28 | uint32_t crc32; 29 | uint32_t compressed_size; 30 | uint32_t uncompressed_size; 31 | uint16_t filename_length; 32 | uint16_t extra_field_length; 33 | uint16_t file_comment_length; 34 | uint16_t disk; 35 | uint16_t file_attribs; 36 | uint32_t ext_file_attribs; 37 | uint32_t local_offset; 38 | /* 39 | * - [filename_length] file name 40 | * - [extra_field_length] extra field 41 | * - [file_comment_length] file comments 42 | */ 43 | }; 44 | 45 | struct ZipLocalFileHeader { 46 | uint32_t signature; 47 | uint16_t extract_version; 48 | uint16_t flags; 49 | uint16_t compression; 50 | uint16_t mod_time; 51 | uint16_t mod_date; 52 | uint32_t crc32; 53 | uint32_t compressed_size, uncompressed_size; 54 | uint16_t filename_length, extra_field_length; 55 | /* - [filename_length] file name 56 | * - [extra_field_length] extra field 57 | */ 58 | }; 59 | 60 | #pragma pack() 61 | --------------------------------------------------------------------------------