Squashed
This commit is contained in:
commit
b138a33a71
107 changed files with 10966 additions and 0 deletions
17
.clang-format
Executable file
17
.clang-format
Executable file
|
@ -0,0 +1,17 @@
|
|||
UseTab: Always
|
||||
IndentWidth: 8
|
||||
TabWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
BreakBeforeBraces: Allman
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignConsecutiveMacros: true
|
||||
AlignConsecutiveAssignments: true
|
||||
# AlignConsecutiveDeclarations: true # this breaks things even in 13.0 :(
|
||||
AlignTrailingComments: true
|
||||
AlignOperands: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
IndentCaseLabels: false
|
||||
ColumnLimit: 0
|
||||
PointerAlignment: Right
|
||||
# DanglingParenthesis: true # https://reviews.llvm.org/D33029
|
2
.gitattributes
vendored
Executable file
2
.gitattributes
vendored
Executable file
|
@ -0,0 +1,2 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto eol=lf autocrlf=input
|
8
.gitignore
vendored
Executable file
8
.gitignore
vendored
Executable file
|
@ -0,0 +1,8 @@
|
|||
# builds
|
||||
bin/
|
||||
obj/
|
||||
libs/
|
||||
|
||||
# assets
|
||||
assets/*
|
||||
!assets/cfg
|
6
.gitmodules
vendored
Executable file
6
.gitmodules
vendored
Executable file
|
@ -0,0 +1,6 @@
|
|||
[submodule "third_party/include/gs"]
|
||||
path = third_party/include/gs
|
||||
url = https://github.com/MrFrenik/gunslinger
|
||||
[submodule "assets"]
|
||||
path = assets
|
||||
url = https://github.com/nullprop/my_game_assets
|
40
.vscode/c_cpp_properties.json
vendored
Executable file
40
.vscode/c_cpp_properties.json
vendored
Executable file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Win32",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/third_party/include"
|
||||
],
|
||||
"defines": [
|
||||
"_DEBUG",
|
||||
"UNICODE",
|
||||
"_UNICODE"
|
||||
],
|
||||
"windowsSdkVersion": "10.0.18362.0",
|
||||
//"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/VC/Tools/MSVC/14.16.27023/bin/Hostx64/x64/cl.exe",
|
||||
"compilerPath": "C:/msys64/mingw64/bin/gcc.exe",
|
||||
"cStandard": "c99",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "linux-gcc-x64"
|
||||
},
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/third_party/include",
|
||||
"${workspaceFolder}/../android-ndk-r25/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/"
|
||||
],
|
||||
"defines": [
|
||||
"_DEBUG",
|
||||
"UNICODE",
|
||||
"_UNICODE"
|
||||
],
|
||||
"compilerPath": "/usr/bin/gcc",
|
||||
"cStandard": "c99",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "linux-gcc-x64"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
46
.vscode/launch.json
vendored
Normal file
46
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "(gdb) Launch Game",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/game",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/bin",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "(gdb) Launch ModelViewer",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/bin/modelviewer",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/bin",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
32
.vscode/settings.json
vendored
Executable file
32
.vscode/settings.json
vendored
Executable file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"*.h": "c",
|
||||
"xstring": "c",
|
||||
"xutility": "c",
|
||||
"*.m": "c",
|
||||
"random": "c",
|
||||
"array": "c",
|
||||
"string": "c",
|
||||
"string_view": "c",
|
||||
"ranges": "c",
|
||||
"regex": "c",
|
||||
"optional": "c",
|
||||
"istream": "c",
|
||||
"ostream": "c",
|
||||
"system_error": "c",
|
||||
"functional": "c",
|
||||
"tuple": "c",
|
||||
"type_traits": "c",
|
||||
"utility": "c",
|
||||
"*.tcc": "c",
|
||||
"typeinfo": "c",
|
||||
"initializer_list": "c",
|
||||
"iterator": "c",
|
||||
"vector": "c"
|
||||
},
|
||||
"files.eol": "\n",
|
||||
"C_Cpp.dimInactiveRegions": false,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.tabSize": 8,
|
||||
"editor.insertSpaces": false
|
||||
}
|
30
LICENSE
Executable file
30
LICENSE
Executable file
|
@ -0,0 +1,30 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2021, nullprop
|
||||
Copyright (c) 2021, John Jackson
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
117
README.md
Executable file
117
README.md
Executable file
|
@ -0,0 +1,117 @@
|
|||
# my_game
|
||||
|
||||
The source code release of my_game.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Status](#Status)
|
||||
- [License](#License)
|
||||
- [Assets](#Assets)
|
||||
- [Compiling](#Compiling)
|
||||
- [Tools](#Tools)
|
||||
|
||||
## Status
|
||||
|
||||
In development.
|
||||
|
||||

|
||||
|
||||
<p align="center">Testing with Q3 assets.</p>
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE) for the BSD 3-Clause License.
|
||||
|
||||
Some source code in this release is not covered by the BSD 3-Clause License:
|
||||
|
||||
---
|
||||
|
||||
BSP loading and rendering based on work by Krzysztof Kondrak.
|
||||
[https://github.com/kondrak/quake_bsp_vulkan](https://github.com/kondrak/quake_bsp_vulkan)
|
||||
|
||||
```
|
||||
file(s):
|
||||
src/bsp/bsp_loader.c
|
||||
src/bsp/bsp_map.c
|
||||
src/bsp/bsp_patch.c
|
||||
```
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Krzysztof Kondrak
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
All projects contained in `third_party` are copyright of their respective owners.
|
||||
|
||||
[gunslinger](https://github.com/MrFrenik/gunslinger)
|
||||
|
||||
- File(s): `third_party/include/gs/*`
|
||||
- License: [third_party/include/gs/LICENSE](third_party/include/gs/LICENSE)
|
||||
|
||||
## Assets
|
||||
|
||||
// TODO
|
||||
|
||||
## Compiling
|
||||
|
||||
### Windows
|
||||
|
||||
- Install MingW
|
||||
|
||||
```sh
|
||||
./proc/win/mingw_dbg.sh
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
Tested on Ubuntu 22.04.1 LTS (Desktop).
|
||||
|
||||
```sh
|
||||
sudo apt install gcc mesa-common-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libc6-dev-i386
|
||||
./proc/linux/gcc_dbg.sh
|
||||
```
|
||||
|
||||
## Console
|
||||
|
||||
Hitting the console key (default: F1) will allow you to change cvars and run commands. Type `help` for more information.
|
||||
|
||||

|
||||
|
||||
## Config
|
||||
|
||||
The game config is located in `assets/cfg/config.txt`. The game will use values from this file when launching. You can modify any value at runtime via the console. Note that this file does not support custom formatting and will be overwritten when the game exits.
|
||||
|
||||
## Tools
|
||||
|
||||
### ModelViewer
|
||||
|
||||
Tool for checking .md3 models and custom animation.cfg files.
|
||||
Type `help` in console for details.
|
||||
|
||||
```sh
|
||||
cd bin
|
||||
./modelviewer
|
||||
```
|
||||
|
||||

|
1
assets
Submodule
1
assets
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit dc32187d5c499052f18b66c98292adab0477254e
|
BIN
docs/screenshots/console_2022-04-18.png
Normal file
BIN
docs/screenshots/console_2022-04-18.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 KiB |
BIN
docs/screenshots/game_2022-01-05.png
Normal file
BIN
docs/screenshots/game_2022-01-05.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 918 KiB |
BIN
docs/screenshots/modelviewer_2022-03-11.png
Normal file
BIN
docs/screenshots/modelviewer_2022-03-11.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
26
jni/Android.mk
Normal file
26
jni/Android.mk
Normal file
|
@ -0,0 +1,26 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := my_game
|
||||
LOCAL_LDLIBS += -llog -lEGL -lGLESv3 -landroid -lOpenSLES -lnativewindow
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../third_party/include/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/ccd/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/cgltf/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/dr_libs/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/glad/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/glfw/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/KHR/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/miniaudio/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/sg_noise/
|
||||
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../third_party/include/gs/external/stb/
|
||||
LOCAL_CFLAGS += -D__ANDROID__ -std=gnu99 -w -ldl -lm -lGL -lX11 -lXi -pthread
|
||||
LOCAL_SRC_FILES := $(LOCAL_PATH)/../src/main.c
|
||||
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/../src/audio/*.c)
|
||||
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/../src/bsp/*.c)
|
||||
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/../src/entities/*.c)
|
||||
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/../src/game/*.c)
|
||||
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/../src/graphics/*.c)
|
||||
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/../src/shaders/*.c)
|
||||
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/../src/util/*.c)
|
||||
include $(BUILD_SHARED_LIBRARY)
|
2
jni/Application.mk
Normal file
2
jni/Application.mk
Normal file
|
@ -0,0 +1,2 @@
|
|||
APP_PLATFORM := 30
|
||||
APP_DEBUG := true
|
17
proc/android/build.sh
Executable file
17
proc/android/build.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Clean
|
||||
rm -rf libs
|
||||
rm -rf obj
|
||||
|
||||
# TODO: env vars
|
||||
rm -rf ~/AndroidStudioProjects/NativeTest/app/src/main/jniLibs
|
||||
rm -rf ~/AndroidStudioProjects/NativeTest/app/src/main/assets
|
||||
|
||||
cd jni
|
||||
../../android-ndk-r25/ndk-build
|
||||
|
||||
cd ..
|
||||
cp obj/local ~/AndroidStudioProjects/NativeTest/app/src/main/jniLibs -r
|
||||
cp assets ~/AndroidStudioProjects/NativeTest/app/src/main/assets -r
|
||||
cp src/shaders/mobile ~/AndroidStudioProjects/NativeTest/app/src/main/assets/shaders -r
|
52
proc/linux/_gcc_base.sh
Executable file
52
proc/linux/_gcc_base.sh
Executable file
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Clean
|
||||
rm -rf bin
|
||||
mkdir bin
|
||||
|
||||
# Copy assets
|
||||
./proc/win/copy_assets.sh
|
||||
|
||||
cd bin
|
||||
|
||||
flags=(
|
||||
-std=gnu99 -w -ldl -lGL -lX11 -pthread -lXi $1
|
||||
)
|
||||
|
||||
inc=(
|
||||
-I ../third_party/include/
|
||||
)
|
||||
|
||||
libs=(
|
||||
-lm
|
||||
)
|
||||
|
||||
# Build game
|
||||
proj_name=game
|
||||
echo Building ${proj_name}...
|
||||
src=(
|
||||
../src/main.c
|
||||
../src/**/*.c
|
||||
)
|
||||
build_cmd="gcc ${inc[*]} ${src[*]} ${flags[*]} ${libs[*]} -o ${proj_name}"
|
||||
echo ${build_cmd}
|
||||
${build_cmd}
|
||||
|
||||
if [ "$?" -ne "0" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$2" == "game" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build model viewer
|
||||
proj_name=modelviewer
|
||||
echo Building ${proj_name}...
|
||||
src=(
|
||||
../src/model_viewer.c
|
||||
../src/**/*.c
|
||||
)
|
||||
build_cmd="gcc ${inc[*]} ${src[*]} ${flags[*]} ${libs[*]} -o ${proj_name}"
|
||||
echo ${build_cmd}
|
||||
${build_cmd}
|
8
proc/linux/copy_assets.sh
Normal file
8
proc/linux/copy_assets.sh
Normal file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Clean
|
||||
rm -rf ./bin/assets
|
||||
|
||||
# Copy assets
|
||||
cp ./assets/ ./bin/ -r
|
||||
cp ./src/shaders/standard ./bin/assets/shaders -r
|
3
proc/linux/gcc_dbg.sh
Executable file
3
proc/linux/gcc_dbg.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
./proc/linux/_gcc_base.sh "-g -pg -O0" $1
|
3
proc/linux/gcc_rel.sh
Executable file
3
proc/linux/gcc_rel.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
./proc/linux/_gcc_base.sh "-O3" $1
|
5
proc/linux/valgrind.sh
Executable file
5
proc/linux/valgrind.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd bin
|
||||
valgrind --leak-check=full --track-origins=yes ./game 2>&1 | tee valgrind.txt
|
||||
echo "log saved to valgrind.txt"
|
41
proc/osx/gcc_dbg.sh
Executable file
41
proc/osx/gcc_dbg.sh
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/bin/bash
|
||||
|
||||
rm -rf bin
|
||||
mkdir bin
|
||||
cd bin
|
||||
|
||||
proj_name=Game
|
||||
proj_root_dir=$(pwd)/../
|
||||
|
||||
flags=(
|
||||
-std=c99 -x objective-c -O0 -w
|
||||
)
|
||||
|
||||
# Include directories
|
||||
inc=(
|
||||
-I ../third_party/include/
|
||||
)
|
||||
|
||||
# Source files
|
||||
src=(
|
||||
../src/main.c
|
||||
../src/**/*.c
|
||||
)
|
||||
|
||||
fworks=(
|
||||
-framework OpenGL
|
||||
-framework CoreFoundation
|
||||
-framework CoreVideo
|
||||
-framework IOKit
|
||||
-framework Cocoa
|
||||
-framework Carbon
|
||||
)
|
||||
|
||||
# Build
|
||||
echo gcc ${flags[*]} ${fworks[*]} ${inc[*]} ${src[*]} -o ${proj_name}
|
||||
gcc ${flags[*]} ${fworks[*]} ${inc[*]} ${src[*]} -o ${proj_name}
|
||||
|
||||
cd ..
|
||||
|
||||
|
||||
|
41
proc/osx/gcc_rel.sh
Executable file
41
proc/osx/gcc_rel.sh
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/bin/bash
|
||||
|
||||
rm -rf bin
|
||||
mkdir bin
|
||||
cd bin
|
||||
|
||||
proj_name=Game
|
||||
proj_root_dir=$(pwd)/../
|
||||
|
||||
flags=(
|
||||
-std=c99 -x objective-c -O3 -w
|
||||
)
|
||||
|
||||
# Include directories
|
||||
inc=(
|
||||
-I ../third_party/include/
|
||||
)
|
||||
|
||||
# Source files
|
||||
src=(
|
||||
../src/main.c
|
||||
../src/**/*.c
|
||||
)
|
||||
|
||||
fworks=(
|
||||
-framework OpenGL
|
||||
-framework CoreFoundation
|
||||
-framework CoreVideo
|
||||
-framework IOKit
|
||||
-framework Cocoa
|
||||
-framework Carbon
|
||||
)
|
||||
|
||||
# Build
|
||||
echo gcc ${flags[*]} ${fworks[*]} ${inc[*]} ${src[*]} -o ${proj_name}
|
||||
gcc ${flags[*]} ${fworks[*]} ${inc[*]} ${src[*]} -o ${proj_name}
|
||||
|
||||
cd ..
|
||||
|
||||
|
||||
|
59
proc/win/_mingw_base.sh
Normal file
59
proc/win/_mingw_base.sh
Normal file
|
@ -0,0 +1,59 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Clean
|
||||
rm -rf bin
|
||||
mkdir bin
|
||||
|
||||
# Copy assets
|
||||
./proc/win/copy_assets.sh
|
||||
|
||||
cd bin
|
||||
|
||||
flags=(
|
||||
-std=gnu99 -w $1
|
||||
)
|
||||
|
||||
inc=(
|
||||
-I ../third_party/include/
|
||||
)
|
||||
|
||||
libs=(
|
||||
-lopengl32
|
||||
-lkernel32
|
||||
-luser32
|
||||
-lshell32
|
||||
-lgdi32
|
||||
-lWinmm
|
||||
-lAdvapi32
|
||||
-lm
|
||||
)
|
||||
|
||||
# Build game
|
||||
proj_name=game
|
||||
echo Building ${proj_name}...
|
||||
src=(
|
||||
../src/main.c
|
||||
../src/**/*.c
|
||||
)
|
||||
build_cmd="gcc ${inc[*]} ${src[*]} ${flags[*]} ${libs[*]} -o ${proj_name}"
|
||||
echo ${build_cmd}
|
||||
${build_cmd}
|
||||
|
||||
if [ "$?" -ne "0" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$2" == "game" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build model viewer
|
||||
proj_name=modelviewer
|
||||
echo Building ${proj_name}...
|
||||
src=(
|
||||
../src/model_viewer.c
|
||||
../src/**/*.c
|
||||
)
|
||||
build_cmd="gcc ${inc[*]} ${src[*]} ${flags[*]} ${libs[*]} -o ${proj_name}"
|
||||
echo ${build_cmd}
|
||||
${build_cmd}
|
8
proc/win/copy_assets.sh
Normal file
8
proc/win/copy_assets.sh
Normal file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Clean
|
||||
rm -rf ./bin/assets
|
||||
|
||||
# Copy assets
|
||||
cp ./assets/ ./bin/ -r
|
||||
cp ./src/shaders/standard ./bin/assets/shaders -r
|
3
proc/win/mingw_dbg.sh
Executable file
3
proc/win/mingw_dbg.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
./proc/win/_mingw_base.sh "-g -O0" $1
|
3
proc/win/mingw_rel.sh
Executable file
3
proc/win/mingw_rel.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
./proc/win/_mingw_base.sh "-O3" $1
|
194
src/audio/audio_manager.c
Normal file
194
src/audio/audio_manager.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*================================================================
|
||||
* audio/audio_manager.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../game/config.h"
|
||||
#include "../game/console.h"
|
||||
#include "../util/math.h"
|
||||
#include "../util/string.h"
|
||||
#include "audio_manager.h"
|
||||
|
||||
mg_audio_manager_t *g_audio_manager;
|
||||
|
||||
void mg_audio_manager_init()
|
||||
{
|
||||
// Allocate
|
||||
g_audio_manager = gs_malloc_init(mg_audio_manager_t);
|
||||
g_audio_manager->assets = gs_dyn_array_new(mg_audio_asset_t);
|
||||
|
||||
// Player sounds
|
||||
_mg_audio_manager_load("player/jump1.wav", MG_AUDIO_TYPE_EFFECT, false, false, 1.0f);
|
||||
}
|
||||
|
||||
void mg_audio_manager_free()
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_audio_manager->assets); i++)
|
||||
{
|
||||
gs_audio_stop(g_audio_manager->assets[i].instance);
|
||||
}
|
||||
|
||||
gs_dyn_array_free(g_audio_manager->assets);
|
||||
gs_free(g_audio_manager);
|
||||
g_audio_manager = NULL;
|
||||
}
|
||||
|
||||
void mg_audio_manager_play(char *name, float pitch_var)
|
||||
{
|
||||
mg_audio_asset_t *asset = _mg_audio_manager_find(name);
|
||||
if (asset == NULL)
|
||||
{
|
||||
mg_println("WARN: mg_audio_manager_play invalid audio %s", name);
|
||||
return;
|
||||
}
|
||||
|
||||
mg_cvar_t *snd_master = mg_cvar("snd_master");
|
||||
mg_cvar_t *snd_mixer = NULL;
|
||||
switch (asset->type)
|
||||
{
|
||||
case MG_AUDIO_TYPE_EFFECT:
|
||||
snd_mixer = mg_cvar("snd_effect");
|
||||
break;
|
||||
|
||||
case MG_AUDIO_TYPE_MUSIC:
|
||||
snd_mixer = mg_cvar("snd_music");
|
||||
break;
|
||||
|
||||
case MG_AUDIO_TYPE_AMBIENT:
|
||||
snd_mixer = mg_cvar("snd_ambient");
|
||||
break;
|
||||
|
||||
default:
|
||||
mg_println("WARN: mg_audio_manager_play invalid type %d", asset->type);
|
||||
snd_mixer = snd_master;
|
||||
break;
|
||||
}
|
||||
|
||||
float pitch = mg_cvar("cl_timescale")->value.f;
|
||||
if (pitch_var != 0.0f)
|
||||
{
|
||||
pitch += (float)rand_range(-512, 512) * pitch_var / 512.0f;
|
||||
}
|
||||
|
||||
gs_audio_instance_decl_t data = gs_audio_get_instance_data(asset->instance);
|
||||
data.pitch = pitch;
|
||||
gs_audio_set_instance_data(asset->instance, data);
|
||||
|
||||
// FIXME: playing instance doesn't work here
|
||||
if (asset->loop)
|
||||
{
|
||||
gs_audio_play(asset->instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// gs_audio_play(asset->instance);
|
||||
gs_audio_play_source_with_pitch(asset->source, snd_master->value.f * snd_mixer->value.f * asset->volume, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
void mg_audio_manager_stop(char *name)
|
||||
{
|
||||
mg_audio_asset_t *asset = _mg_audio_manager_find(name);
|
||||
if (asset == NULL)
|
||||
{
|
||||
mg_println("WARN: mg_audio_manager_stop invalid audio %s", name);
|
||||
return;
|
||||
}
|
||||
|
||||
gs_audio_stop(asset->instance);
|
||||
}
|
||||
|
||||
void mg_audio_manager_pause(char *name)
|
||||
{
|
||||
mg_audio_asset_t *asset = _mg_audio_manager_find(name);
|
||||
if (asset == NULL)
|
||||
{
|
||||
mg_println("WARN: mg_audio_manager_pause invalid audio %s", name);
|
||||
return;
|
||||
}
|
||||
|
||||
gs_audio_pause(asset->instance);
|
||||
}
|
||||
|
||||
void mg_audio_manager_restart(char *name)
|
||||
{
|
||||
mg_audio_asset_t *asset = _mg_audio_manager_find(name);
|
||||
if (asset == NULL)
|
||||
{
|
||||
mg_println("WARN: mg_audio_manager_restart invalid audio %s", name);
|
||||
return;
|
||||
}
|
||||
|
||||
gs_audio_restart(asset->instance);
|
||||
}
|
||||
|
||||
bool mg_audio_manager_is_playing(char *name)
|
||||
{
|
||||
mg_audio_asset_t *asset = _mg_audio_manager_find(name);
|
||||
if (asset == NULL)
|
||||
{
|
||||
mg_println("WARN: mg_audio_manager_is_playing invalid audio %s", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return gs_audio_is_playing(asset->instance);
|
||||
}
|
||||
|
||||
void _mg_audio_manager_load(char *filename, mg_audio_type type, bool loop, bool persistent, float volume)
|
||||
{
|
||||
char *path = mg_append_string("assets/sound/", filename);
|
||||
|
||||
mg_audio_asset_t asset = (mg_audio_asset_t){
|
||||
.source = gs_audio_load_from_file(path),
|
||||
.type = type,
|
||||
.loop = loop,
|
||||
.persistent = persistent,
|
||||
.name = filename,
|
||||
.volume = volume,
|
||||
};
|
||||
|
||||
if (!gs_handle_is_valid(asset.source))
|
||||
{
|
||||
gs_free(path);
|
||||
return;
|
||||
}
|
||||
|
||||
asset.instance = gs_audio_instance_create(
|
||||
&(gs_audio_instance_decl_t){
|
||||
.src = asset.source,
|
||||
.persistent = persistent,
|
||||
.loop = loop,
|
||||
.volume = volume,
|
||||
});
|
||||
|
||||
if (!gs_handle_is_valid(asset.instance))
|
||||
{
|
||||
gs_free(path);
|
||||
return;
|
||||
}
|
||||
|
||||
gs_free(path);
|
||||
gs_dyn_array_push(g_audio_manager->assets, asset);
|
||||
}
|
||||
|
||||
mg_audio_asset_t *_mg_audio_manager_find(char *name)
|
||||
{
|
||||
mg_audio_asset_t *asset = NULL;
|
||||
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_audio_manager->assets); i++)
|
||||
{
|
||||
if (strcmp(g_audio_manager->assets[i].name, name) == 0)
|
||||
{
|
||||
asset = &g_audio_manager->assets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
51
src/audio/audio_manager.h
Normal file
51
src/audio/audio_manager.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*================================================================
|
||||
* audio/audio_manager.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_AUDIO_MANAGER_H
|
||||
#define MG_AUDIO_MANAGER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
typedef enum mg_audio_type
|
||||
{
|
||||
MG_AUDIO_TYPE_EFFECT,
|
||||
MG_AUDIO_TYPE_MUSIC,
|
||||
MG_AUDIO_TYPE_AMBIENT,
|
||||
MG_AUDIO_TYPE_COUNT,
|
||||
} mg_audio_type;
|
||||
|
||||
typedef struct mg_audio_asset_t
|
||||
{
|
||||
gs_handle(gs_audio_source_t) source;
|
||||
gs_handle(gs_audio_instance_t) instance;
|
||||
mg_audio_type type;
|
||||
bool loop;
|
||||
bool persistent;
|
||||
char *name;
|
||||
float volume;
|
||||
} mg_audio_asset_t;
|
||||
|
||||
typedef struct mg_audio_manager_t
|
||||
{
|
||||
gs_dyn_array(mg_audio_asset_t) assets;
|
||||
} mg_audio_manager_t;
|
||||
|
||||
void mg_audio_manager_init();
|
||||
void mg_audio_manager_free();
|
||||
void mg_audio_manager_play(char *name, float pitch_var);
|
||||
void mg_audio_manager_stop(char *name);
|
||||
void mg_audio_manager_pause(char *name);
|
||||
void mg_audio_manager_restart(char *name);
|
||||
bool mg_audio_manager_is_playing(char *name);
|
||||
void _mg_audio_manager_load(char *filename, mg_audio_type type, bool loop, bool persistent, float volume);
|
||||
mg_audio_asset_t *_mg_audio_manager_find(char *name);
|
||||
|
||||
extern mg_audio_manager_t *g_audio_manager;
|
||||
|
||||
#endif // MG_AUDIO_MANAGER_H
|
181
src/bsp/bsp_entity.c
Normal file
181
src/bsp/bsp_entity.c
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_entity.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "bsp_entity.h"
|
||||
#include "../game/console.h"
|
||||
|
||||
bsp_entity_t bsp_entity_from_string(char *content)
|
||||
{
|
||||
uint32_t sz = gs_string_length(content) + 1;
|
||||
|
||||
bsp_entity_t ent = {
|
||||
.slot_map = gs_slot_map_new(char *, char *),
|
||||
.content = gs_malloc(sz),
|
||||
};
|
||||
|
||||
memset(ent.content, 0, sz);
|
||||
gs_util_string_remove_character(content, ent.content, sz, '\n');
|
||||
|
||||
_bsp_entity_load_keys(&ent);
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
void bsp_entity_free(bsp_entity_t *ent)
|
||||
{
|
||||
for (
|
||||
gs_slot_map_iter it = gs_slot_map_iter_new(ent->slot_map);
|
||||
gs_slot_map_iter_valid(ent->slot_map, it);
|
||||
gs_slot_map_iter_advance(ent->slot_map, it))
|
||||
{
|
||||
char *k = gs_slot_map_iter_getk(ent->slot_map, it);
|
||||
char *v = gs_slot_map_iter_get(ent->slot_map, it);
|
||||
|
||||
gs_free(k);
|
||||
gs_free(v);
|
||||
k = NULL;
|
||||
v = NULL;
|
||||
}
|
||||
|
||||
gs_slot_map_free(ent->slot_map);
|
||||
gs_free(ent->content);
|
||||
}
|
||||
|
||||
bool bsp_entity_has_key(bsp_entity_t *ent, char *key)
|
||||
{
|
||||
if (ent->slot_map == NULL) return false;
|
||||
|
||||
for (
|
||||
gs_slot_map_iter it = gs_slot_map_iter_new(ent->slot_map);
|
||||
gs_slot_map_iter_valid(ent->slot_map, it);
|
||||
gs_slot_map_iter_advance(ent->slot_map, it))
|
||||
{
|
||||
char *k = gs_slot_map_iter_getk(ent->slot_map, it);
|
||||
if (strcmp(key, k) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
char *bsp_entity_get_value(bsp_entity_t *ent, char *key)
|
||||
{
|
||||
if (ent->slot_map == NULL) return NULL;
|
||||
|
||||
for (
|
||||
gs_slot_map_iter it = gs_slot_map_iter_new(ent->slot_map);
|
||||
gs_slot_map_iter_valid(ent->slot_map, it);
|
||||
gs_slot_map_iter_advance(ent->slot_map, it))
|
||||
{
|
||||
char *k = gs_slot_map_iter_getk(ent->slot_map, it);
|
||||
if (strcmp(key, k) == 0)
|
||||
{
|
||||
return gs_slot_map_iter_get(ent->slot_map, it);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void bsp_entity_print(bsp_entity_t *ent)
|
||||
{
|
||||
mg_println("====================================");
|
||||
mg_println("| BSP ENTITY ");
|
||||
mg_println("| ----------------------------------");
|
||||
|
||||
for (
|
||||
gs_slot_map_iter it = gs_slot_map_iter_new(ent->slot_map);
|
||||
gs_slot_map_iter_valid(ent->slot_map, it);
|
||||
gs_slot_map_iter_advance(ent->slot_map, it))
|
||||
{
|
||||
char *k = gs_slot_map_iter_getk(ent->slot_map, it);
|
||||
char *v = gs_slot_map_iter_get(ent->slot_map, it);
|
||||
|
||||
mg_println("| %s: %s", k, v);
|
||||
}
|
||||
|
||||
mg_println("====================================");
|
||||
}
|
||||
|
||||
void _bsp_entity_load_keys(bsp_entity_t *ent)
|
||||
{
|
||||
#define KEY_MAX_SIZE 128
|
||||
#define VAL_MAX_SIZE 128
|
||||
|
||||
char state = 0;
|
||||
char key[KEY_MAX_SIZE] = {0};
|
||||
char value[VAL_MAX_SIZE] = {0};
|
||||
uint32_t key_index = 0;
|
||||
uint32_t value_index = 0;
|
||||
|
||||
char c = 0;
|
||||
for (size_t i = 0; i < gs_string_length(ent->content); i++)
|
||||
{
|
||||
c = ent->content[i];
|
||||
|
||||
if (c == '\"')
|
||||
{
|
||||
if (state == 0)
|
||||
{
|
||||
// Begin key
|
||||
key_index = 0;
|
||||
}
|
||||
else if (state == 1)
|
||||
{
|
||||
// End key
|
||||
gs_assert(key_index < KEY_MAX_SIZE);
|
||||
key[key_index] = '\0';
|
||||
}
|
||||
else if (state == 2)
|
||||
{
|
||||
// Begin value
|
||||
value_index = 0;
|
||||
}
|
||||
else if (state == 3)
|
||||
{
|
||||
// End value, add to map
|
||||
gs_assert(value_index < VAL_MAX_SIZE);
|
||||
value[value_index] = '\0';
|
||||
|
||||
uint32_t key_len = gs_string_length(key) + 1;
|
||||
uint32_t value_len = gs_string_length(value) + 1;
|
||||
|
||||
char *k = gs_malloc(key_len);
|
||||
memcpy(k, key, key_len);
|
||||
char *v = gs_malloc(value_len);
|
||||
memcpy(v, value, value_len);
|
||||
|
||||
gs_slot_map_insert(ent->slot_map, k, v);
|
||||
}
|
||||
|
||||
state++;
|
||||
if (state >= 4)
|
||||
state = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state == 1)
|
||||
{
|
||||
// Reading key
|
||||
gs_assert(key_index < KEY_MAX_SIZE);
|
||||
key[key_index] = c;
|
||||
key_index++;
|
||||
}
|
||||
else if (state == 3)
|
||||
{
|
||||
// Reading value
|
||||
gs_assert(value_index < VAL_MAX_SIZE);
|
||||
value[value_index] = c;
|
||||
value_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
src/bsp/bsp_entity.h
Normal file
24
src/bsp/bsp_entity.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_entity.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef BSP_ENTITY_H
|
||||
#define BSP_ENTITY_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "bsp_types.h"
|
||||
|
||||
bsp_entity_t bsp_entity_from_string(char *content);
|
||||
void bsp_entity_free(bsp_entity_t *ent);
|
||||
bool bsp_entity_has_key(bsp_entity_t *ent, char *key);
|
||||
char *bsp_entity_get_value(bsp_entity_t *ent, char *key);
|
||||
void bsp_entity_print(bsp_entity_t *ent);
|
||||
void _bsp_entity_load_keys(bsp_entity_t *ent);
|
||||
|
||||
#endif // BSP_ENTITY_H
|
184
src/bsp/bsp_loader.c
Normal file
184
src/bsp/bsp_loader.c
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_loader.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* Copyright (c) 2018 Krzysztof Kondrak
|
||||
*
|
||||
* See README.md for license.
|
||||
* ================================
|
||||
|
||||
Handles BSP map loading from file.
|
||||
NOTE: only supports version 46.
|
||||
=================================================================*/
|
||||
|
||||
#include "bsp_loader.h"
|
||||
#include "../game/console.h"
|
||||
|
||||
// shorthand util for failing during BSP load
|
||||
b32 _load_bsp_fail(gs_byte_buffer_t *buffer, char *msg)
|
||||
{
|
||||
mg_println("load_bsp() failed: %s", msg);
|
||||
gs_byte_buffer_free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// load BSP from file
|
||||
b32 load_bsp(char *filename, bsp_map_t *map)
|
||||
{
|
||||
mg_println("load_bsp() loading: '%s'", filename);
|
||||
|
||||
if (!gs_platform_file_exists(filename))
|
||||
{
|
||||
mg_println("load_bsp() failed: file not found '%s'", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
gs_byte_buffer_t buffer = gs_byte_buffer_new();
|
||||
gs_byte_buffer_read_from_file(&buffer, filename);
|
||||
|
||||
// read header
|
||||
if (!_load_bsp_header(&buffer, &map->header))
|
||||
return _load_bsp_fail(&buffer, "failed to read header");
|
||||
|
||||
// validate header
|
||||
if (memcmp(map->header.magic, "IBSP", 4) != 0 || map->header.version != 46)
|
||||
return _load_bsp_fail(&buffer, "invalid header");
|
||||
|
||||
// read entity lump
|
||||
if (!_load_entity_lump(&buffer, map))
|
||||
return _load_bsp_fail(&buffer, "failed to read entity lumps");
|
||||
|
||||
// generic lumps
|
||||
uint32_t *counts[] = {
|
||||
&map->textures.count,
|
||||
&map->planes.count,
|
||||
&map->nodes.count,
|
||||
&map->leaves.count,
|
||||
&map->leaf_faces.count,
|
||||
&map->leaf_brushes.count,
|
||||
&map->models.count,
|
||||
&map->brushes.count,
|
||||
&map->brush_sides.count,
|
||||
&map->vertices.count,
|
||||
&map->indices.count,
|
||||
&map->effects.count,
|
||||
&map->faces.count,
|
||||
&map->lightmaps.count,
|
||||
&map->lightvols.count,
|
||||
};
|
||||
void **datas[] = {
|
||||
&map->textures.data,
|
||||
&map->planes.data,
|
||||
&map->nodes.data,
|
||||
&map->leaves.data,
|
||||
&map->leaf_faces.data,
|
||||
&map->leaf_brushes.data,
|
||||
&map->models.data,
|
||||
&map->brushes.data,
|
||||
&map->brush_sides.data,
|
||||
&map->vertices.data,
|
||||
&map->indices.data,
|
||||
&map->effects.data,
|
||||
&map->faces.data,
|
||||
&map->lightmaps.data,
|
||||
&map->lightvols.data,
|
||||
};
|
||||
uint32_t sizes[] = {
|
||||
sizeof(bsp_texture_lump_t),
|
||||
sizeof(bsp_plane_lump_t),
|
||||
sizeof(bsp_node_lump_t),
|
||||
sizeof(bsp_leaf_lump_t),
|
||||
sizeof(bsp_leaf_face_lump_t),
|
||||
sizeof(bsp_leaf_brush_lump_t),
|
||||
sizeof(bsp_model_lump_t),
|
||||
sizeof(bsp_brush_lump_t),
|
||||
sizeof(bsp_brush_side_lump_t),
|
||||
sizeof(bsp_vert_lump_t),
|
||||
sizeof(bsp_index_lump_t),
|
||||
sizeof(bsp_effect_lump_t),
|
||||
sizeof(bsp_face_lump_t),
|
||||
sizeof(bsp_lightmap_lump_t),
|
||||
sizeof(bsp_lightvol_lump_t),
|
||||
};
|
||||
|
||||
// read generics
|
||||
uint32_t start = BSP_LUMP_TYPE_TEXTURES;
|
||||
uint32_t end = BSP_LUMP_TYPE_LIGHTVOLS;
|
||||
for (size_t i = start; i <= end; i++)
|
||||
{
|
||||
if (!_load_lump(&buffer, map, counts[i - start], datas[i - start], i, sizes[i - start]))
|
||||
{
|
||||
gs_byte_buffer_free(&buffer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// read visdata lump
|
||||
if (!_load_visdata_lump(&buffer, map))
|
||||
return _load_bsp_fail(&buffer, "failed to read visdata lump");
|
||||
|
||||
map->valid = map->faces.count > 0;
|
||||
gs_byte_buffer_free(&buffer);
|
||||
|
||||
// Get map name from filepath
|
||||
char *name = mg_get_filename_from_path(filename);
|
||||
size_t sz = strlen(name) + 1;
|
||||
map->name = gs_malloc(sz);
|
||||
memcpy(map->name, name, sz);
|
||||
|
||||
mg_println("load_bsp() done loading '%s'", map->name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
b32 _load_bsp_header(gs_byte_buffer_t *buffer, bsp_header_t *header)
|
||||
{
|
||||
gs_byte_buffer_read(buffer, bsp_header_t, header);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
b32 _load_lump(gs_byte_buffer_t *buffer, bsp_map_t *map, uint32_t *count, void **data, bsp_lump_types type, uint32_t lump_size)
|
||||
{
|
||||
uint32_t size = map->header.dir_entries[type].length;
|
||||
|
||||
*count = size / lump_size;
|
||||
*data = gs_malloc(size);
|
||||
|
||||
buffer->position = map->header.dir_entries[type].offset;
|
||||
|
||||
if (size > 0)
|
||||
gs_byte_buffer_read_bulk(buffer, data, size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
b32 _load_entity_lump(gs_byte_buffer_t *buffer, bsp_map_t *map)
|
||||
{
|
||||
gs_assert(map->header.dir_entries[BSP_LUMP_TYPE_ENTITIES].length > 0);
|
||||
int32_t size = map->header.dir_entries[BSP_LUMP_TYPE_ENTITIES].length;
|
||||
|
||||
// not sure if the lump is null terminated,
|
||||
// malloc extra byte for \0 at the end.
|
||||
map->entity_lump.ents = gs_malloc(size + 1);
|
||||
|
||||
buffer->position = map->header.dir_entries[BSP_LUMP_TYPE_ENTITIES].offset;
|
||||
gs_byte_buffer_read_bulk(buffer, &map->entity_lump.ents, size);
|
||||
map->entity_lump.ents[size] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
b32 _load_visdata_lump(gs_byte_buffer_t *buffer, bsp_map_t *map)
|
||||
{
|
||||
gs_assert(map->header.dir_entries[BSP_LUMP_TYPE_VISDATA].length > 0);
|
||||
buffer->position = map->header.dir_entries[BSP_LUMP_TYPE_VISDATA].offset;
|
||||
gs_byte_buffer_read(buffer, int32_t, &map->visdata.num_vecs);
|
||||
gs_byte_buffer_read(buffer, int32_t, &map->visdata.size_vecs);
|
||||
|
||||
int32_t size = map->visdata.num_vecs * map->visdata.size_vecs;
|
||||
map->visdata.vecs = gs_malloc(size);
|
||||
gs_byte_buffer_read_bulk(buffer, &map->visdata.vecs, size);
|
||||
|
||||
return true;
|
||||
}
|
25
src/bsp/bsp_loader.h
Normal file
25
src/bsp/bsp_loader.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_loader.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef BSP_LOADER_H
|
||||
#define BSP_LOADER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../util/string.h"
|
||||
#include "bsp_types.h"
|
||||
|
||||
b32 _load_bsp_fail(gs_byte_buffer_t *buffer, char *msg);
|
||||
b32 load_bsp(char *filename, bsp_map_t *map);
|
||||
b32 _load_bsp_header(gs_byte_buffer_t *buffer, bsp_header_t *header);
|
||||
b32 _load_lump(gs_byte_buffer_t *buffer, bsp_map_t *map, uint32_t *count, void **data, bsp_lump_types type, uint32_t lump_size);
|
||||
b32 _load_entity_lump(gs_byte_buffer_t *buffer, bsp_map_t *map);
|
||||
b32 _load_visdata_lump(gs_byte_buffer_t *buffer, bsp_map_t *map);
|
||||
|
||||
#endif // BSP_LOADER_H
|
1057
src/bsp/bsp_map.c
Normal file
1057
src/bsp/bsp_map.c
Normal file
File diff suppressed because it is too large
Load diff
41
src/bsp/bsp_map.h
Normal file
41
src/bsp/bsp_map.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_map.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef BSP_MAP_H
|
||||
#define BSP_MAP_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
#include <gs/util/gs_idraw.h>
|
||||
|
||||
#include "../graphics/types.h"
|
||||
#include "../util/math.h"
|
||||
#include "../util/string.h"
|
||||
#include "bsp_entity.h"
|
||||
#include "bsp_patch.h"
|
||||
#include "bsp_types.h"
|
||||
|
||||
void bsp_map_init(bsp_map_t *map);
|
||||
void _bsp_load_entities(bsp_map_t *map);
|
||||
void _bsp_load_textures(bsp_map_t *map);
|
||||
void _bsp_load_lightmaps(bsp_map_t *map);
|
||||
void _bsp_load_lightvols(bsp_map_t *map);
|
||||
void _bsp_create_patch(bsp_map_t *map, bsp_face_lump_t face);
|
||||
void _bsp_map_create_buffers(bsp_map_t *map);
|
||||
void bsp_map_update(bsp_map_t *map, gs_camera_t *cam, const gs_vec2 fb);
|
||||
void bsp_map_render_immediate(bsp_map_t *map, gs_immediate_draw_t *gsi, gs_camera_t *cam);
|
||||
void bsp_map_render(bsp_map_t *map, gs_camera_t *cam, gs_handle(gs_graphics_renderpass_t) rp, gs_command_buffer_t *cb, const gs_vec2 fb);
|
||||
void bsp_map_find_spawn_point(bsp_map_t *map, gs_vec3 *position, float32_t *yaw);
|
||||
void bsp_map_free(bsp_map_t *map);
|
||||
int32_t _bsp_find_camera_leaf(bsp_map_t *map, gs_vec3 view_position);
|
||||
void _bsp_calculate_visible_faces(bsp_map_t *map, int32_t leaf, gs_camera_t *cam, const gs_vec2 fb);
|
||||
bool32_t _bsp_cluster_visible(bsp_map_t *map, int32_t view_cluster, int32_t test_cluster);
|
||||
bsp_lightvol_lump_t bsp_get_lightvol(bsp_map_t *map, gs_vec3 position, gs_vec3 *center);
|
||||
mg_renderer_light_t bsp_sample_lightvol(bsp_map_t *map, gs_vec3 position);
|
||||
|
||||
#endif // BSP_MAP_H
|
99
src/bsp/bsp_patch.c
Normal file
99
src/bsp/bsp_patch.c
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_patch.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* Copyright (c) 2018 Krzysztof Kondrak
|
||||
*
|
||||
* See README.md for license.
|
||||
* ================================
|
||||
|
||||
Handles BSP patch tesselation.
|
||||
=================================================================*/
|
||||
|
||||
#include "bsp_patch.h"
|
||||
|
||||
// Tesselate quadratic patch to wanted level
|
||||
void bsp_quadratic_patch_tesselate(bsp_quadratic_patch_t *patch)
|
||||
{
|
||||
gs_dyn_array_reserve(patch->vertices, (patch->tesselation + 1) * (patch->tesselation + 1));
|
||||
gs_dyn_array_head(patch->vertices)->size = (patch->tesselation + 1) * (patch->tesselation + 1);
|
||||
|
||||
// Sample (tesselation + 1)^2 points
|
||||
// for our vertices from the bezier patch.
|
||||
for (size_t i = 0; i <= patch->tesselation; i++)
|
||||
{
|
||||
// How far into the row are we?
|
||||
float32_t a = (float32_t)i / patch->tesselation;
|
||||
float32_t b = 1.0f - a;
|
||||
|
||||
// Sample each row into temp variable
|
||||
bsp_vert_lump_t temp[3];
|
||||
for (size_t j = 0; j < 3; j++)
|
||||
{
|
||||
int32_t k = j * 3;
|
||||
|
||||
// cp[k + 0] * (b * b) +
|
||||
// cp[k + 1] * (2 * b * b) +
|
||||
// cp[k + 2] * (a * a)
|
||||
temp[j] = bsp_vert_lump_add(
|
||||
bsp_vert_lump_mul(patch->control_points[k + 0], (b * b)),
|
||||
bsp_vert_lump_add(
|
||||
bsp_vert_lump_mul(patch->control_points[k + 1], (2 * b * a)),
|
||||
bsp_vert_lump_mul(patch->control_points[k + 2], (a * a))));
|
||||
}
|
||||
|
||||
// Sample each column using row values
|
||||
for (size_t j = 0; j <= patch->tesselation; j++)
|
||||
{
|
||||
// How far into the column are we?
|
||||
a = (float32_t)j / patch->tesselation;
|
||||
b = 1.0f - a;
|
||||
|
||||
// temp[k + 0] * (b * b) +
|
||||
// temp[k + 1] * (2 * b * b) +
|
||||
// temp[k + 2] * (a * a)
|
||||
bsp_vert_lump_t val = bsp_vert_lump_add(
|
||||
bsp_vert_lump_mul(temp[0], b * b),
|
||||
bsp_vert_lump_add(
|
||||
bsp_vert_lump_mul(temp[1], 2 * b * a),
|
||||
bsp_vert_lump_mul(temp[2], a * a)));
|
||||
|
||||
gs_dyn_array_set_data_i(&patch->vertices, &val, sizeof(bsp_vert_lump_t), i * (patch->tesselation + 1) + j);
|
||||
}
|
||||
}
|
||||
|
||||
// Triangulate
|
||||
gs_dyn_array_reserve(patch->indices, patch->tesselation * patch->tesselation * 6);
|
||||
for (size_t row = 0; row < patch->tesselation; row++)
|
||||
{
|
||||
for (size_t col = 0; col < patch->tesselation; col++)
|
||||
{
|
||||
gs_dyn_array_push(patch->indices, (row + 1) * (patch->tesselation + 1) + col);
|
||||
gs_dyn_array_push(patch->indices, (row + 0) * (patch->tesselation + 1) + col);
|
||||
gs_dyn_array_push(patch->indices, (row + 1) * (patch->tesselation + 1) + col + 1);
|
||||
|
||||
gs_dyn_array_push(patch->indices, (row + 1) * (patch->tesselation + 1) + col + 1);
|
||||
gs_dyn_array_push(patch->indices, (row + 0) * (patch->tesselation + 1) + col);
|
||||
gs_dyn_array_push(patch->indices, (row + 0) * (patch->tesselation + 1) + col + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bsp_quadratic_patch_free(bsp_quadratic_patch_t *patch)
|
||||
{
|
||||
gs_dyn_array_free(patch->vertices);
|
||||
patch->vertices = NULL;
|
||||
gs_dyn_array_free(patch->indices);
|
||||
patch->indices = NULL;
|
||||
}
|
||||
|
||||
void bsp_patch_free(bsp_patch_t *patch)
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(patch->quadratic_patches); i++)
|
||||
{
|
||||
bsp_quadratic_patch_free(&patch->quadratic_patches[i]);
|
||||
}
|
||||
|
||||
gs_dyn_array_free(patch->quadratic_patches);
|
||||
patch->quadratic_patches = NULL;
|
||||
}
|
42
src/bsp/bsp_patch.h
Normal file
42
src/bsp/bsp_patch.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_patch.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef BSP_PATCH_H
|
||||
#define BSP_PATCH_H
|
||||
|
||||
#include "bsp_types.h"
|
||||
|
||||
// Helpers for vertex lump math
|
||||
static inline bsp_vert_lump_t bsp_vert_lump_mul(bsp_vert_lump_t lump, float32_t mul)
|
||||
{
|
||||
bsp_vert_lump_t result = {
|
||||
.position = gs_vec3_scale(lump.position, mul),
|
||||
.tex_coord = gs_vec2_scale(lump.tex_coord, mul),
|
||||
.lm_coord = gs_vec2_scale(lump.lm_coord, mul),
|
||||
.normal = lump.normal,
|
||||
.color = lump.color};
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline bsp_vert_lump_t bsp_vert_lump_add(bsp_vert_lump_t a, bsp_vert_lump_t b)
|
||||
{
|
||||
bsp_vert_lump_t result = {
|
||||
.position = gs_vec3_add(a.position, b.position),
|
||||
.tex_coord = gs_vec2_add(a.tex_coord, b.tex_coord),
|
||||
.lm_coord = gs_vec2_add(a.lm_coord, b.lm_coord),
|
||||
.normal = a.normal,
|
||||
.color = a.color};
|
||||
return result;
|
||||
}
|
||||
|
||||
void bsp_quadratic_patch_tesselate(bsp_quadratic_patch_t *patch);
|
||||
void bsp_quadratic_patch_free(bsp_quadratic_patch_t *patch);
|
||||
void bsp_patch_free(bsp_patch_t *patch);
|
||||
|
||||
#endif // BSP_PATCH_H
|
295
src/bsp/bsp_trace.c
Normal file
295
src/bsp/bsp_trace.c
Normal file
|
@ -0,0 +1,295 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_trace.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
BSP brush tracing for collisions.
|
||||
Based on post by Nathan Ostgard:
|
||||
http://www.devmaster.net/articles/quake3collision/
|
||||
=================================================================*/
|
||||
|
||||
#include "bsp_trace.h"
|
||||
|
||||
void bsp_trace_ray(bsp_trace_t *trace, gs_vec3 start, gs_vec3 end, int32_t content_mask)
|
||||
{
|
||||
trace->type = RAY;
|
||||
trace->radius = 0;
|
||||
_bsp_trace(trace, start, end, content_mask);
|
||||
}
|
||||
|
||||
void bsp_trace_sphere(bsp_trace_t *trace, gs_vec3 start, gs_vec3 end, float32_t radius, int32_t content_mask)
|
||||
{
|
||||
trace->type = SPHERE;
|
||||
trace->radius = radius;
|
||||
_bsp_trace(trace, start, end, content_mask);
|
||||
}
|
||||
|
||||
void bsp_trace_box(bsp_trace_t *trace, gs_vec3 start, gs_vec3 end, gs_vec3 mins, gs_vec3 maxs, int32_t content_mask)
|
||||
{
|
||||
if (gs_vec3_len2(mins) < BSP_TRACE_EPSILON && gs_vec3_len2(maxs) < BSP_TRACE_EPSILON)
|
||||
{
|
||||
// Treat as a ray
|
||||
bsp_trace_ray(trace, start, end, content_mask);
|
||||
return;
|
||||
}
|
||||
|
||||
trace->type = BOX;
|
||||
trace->radius = 0;
|
||||
trace->mins = mins;
|
||||
trace->maxs = maxs;
|
||||
trace->extents = gs_v3(
|
||||
gs_max(-trace->mins.x, trace->maxs.x),
|
||||
gs_max(-trace->mins.y, trace->maxs.y),
|
||||
gs_max(-trace->mins.z, trace->maxs.z));
|
||||
|
||||
_bsp_trace(trace, start, end, content_mask);
|
||||
}
|
||||
|
||||
void _bsp_trace(bsp_trace_t *trace, gs_vec3 start, gs_vec3 end, int32_t content_mask)
|
||||
{
|
||||
gs_assert(trace->map != NULL);
|
||||
|
||||
trace->start_solid = false;
|
||||
trace->all_solid = false;
|
||||
trace->fraction = 1.0f;
|
||||
|
||||
// Walk through the BSP tree
|
||||
_bsp_trace_check_node(trace, 0, 0.0f, 1.0f, start, end, content_mask);
|
||||
|
||||
if (trace->fraction == 1.0f)
|
||||
{
|
||||
// Nothing blocked the trace
|
||||
trace->end = end;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Collided with something
|
||||
// start + fraction * (end - start);
|
||||
trace->end = gs_vec3_add(start, gs_vec3_scale(gs_vec3_sub(end, start), trace->fraction));
|
||||
}
|
||||
}
|
||||
|
||||
void _bsp_trace_check_node(bsp_trace_t *trace, int32_t node_index, float32_t start_fraction, float32_t end_fraction, gs_vec3 start, gs_vec3 end, int32_t content_mask)
|
||||
{
|
||||
if (node_index < 0)
|
||||
{
|
||||
// This is a leaf
|
||||
bsp_leaf_lump_t leaf = trace->map->leaves.data[-(node_index + 1)];
|
||||
int32_t brush_index;
|
||||
bsp_brush_lump_t brush;
|
||||
for (size_t i = 0; i < leaf.num_leaf_brushes; i++)
|
||||
{
|
||||
brush_index = trace->map->leaf_brushes.data[leaf.first_leaf_brush + i].brush;
|
||||
brush = trace->map->brushes.data[brush_index];
|
||||
if (brush.num_brush_sides > 0 && (trace->map->textures.data[brush.texture].contents & content_mask) != 0)
|
||||
{
|
||||
_bsp_trace_check_brush(trace, brush, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't have to do anything else for leaves
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a node
|
||||
bsp_node_lump_t node = trace->map->nodes.data[node_index];
|
||||
bsp_plane_lump_t plane = trace->map->planes.data[node.plane];
|
||||
|
||||
float32_t offset = 0;
|
||||
float32_t start_distance = gs_vec3_dot(start, plane.normal) - plane.dist;
|
||||
float32_t end_distance = gs_vec3_dot(end, plane.normal) - plane.dist;
|
||||
|
||||
if (trace->type == SPHERE)
|
||||
{
|
||||
offset = trace->radius;
|
||||
}
|
||||
else if (trace->type == BOX)
|
||||
{
|
||||
// Dot product but we want the absolute values
|
||||
offset = fabsf(trace->extents.x * plane.normal.x) +
|
||||
fabsf(trace->extents.y * plane.normal.y) +
|
||||
fabsf(trace->extents.z * plane.normal.z);
|
||||
}
|
||||
|
||||
if (start_distance >= offset && end_distance >= offset)
|
||||
{
|
||||
// Both points are in front of the plane,
|
||||
// check the front child.
|
||||
_bsp_trace_check_node(trace, node.children[0], start_fraction, end_fraction, start, end, content_mask);
|
||||
}
|
||||
else if (start_distance < -offset && end_distance < -offset)
|
||||
{
|
||||
// Both points are behind the plane,
|
||||
// check back child.
|
||||
_bsp_trace_check_node(trace, node.children[1], start_fraction, end_fraction, start, end, content_mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The line crosses through the splitting plane.
|
||||
int32_t side;
|
||||
float32_t fraction1;
|
||||
float32_t fraction2;
|
||||
float32_t middle_fraction;
|
||||
gs_vec3 middle;
|
||||
|
||||
// STEP 1: Split the segment into two.
|
||||
if (start_distance < end_distance)
|
||||
{
|
||||
side = 1; // back
|
||||
float32_t inverse_distance = 1.0f / (start_distance - end_distance);
|
||||
fraction1 = (start_distance - offset + BSP_TRACE_EPSILON) * inverse_distance;
|
||||
fraction2 = (start_distance + offset + BSP_TRACE_EPSILON) * inverse_distance;
|
||||
}
|
||||
else if (end_distance < start_distance)
|
||||
{
|
||||
side = 0; // front
|
||||
float32_t inverse_distance = 1.0f / (start_distance - end_distance);
|
||||
fraction1 = (start_distance + offset + BSP_TRACE_EPSILON) * inverse_distance;
|
||||
fraction2 = (start_distance - offset - BSP_TRACE_EPSILON) * inverse_distance;
|
||||
}
|
||||
else
|
||||
{
|
||||
side = 0; // front
|
||||
fraction1 = 1.0f;
|
||||
fraction2 = 0.0f;
|
||||
}
|
||||
|
||||
// STEP 2: Make sure the numbers are valid.
|
||||
fraction1 = fminf(1.0f, fmaxf(0.0f, fraction1));
|
||||
fraction2 = fminf(1.0f, fmaxf(0.0f, fraction2));
|
||||
|
||||
// STEP 3: Calculate the middle point for the first side
|
||||
middle_fraction = start_fraction + (end_fraction - start_fraction) * fraction1;
|
||||
middle = gs_vec3_add(start, gs_vec3_scale(gs_vec3_sub(end, start), fraction1));
|
||||
|
||||
// STEP 4: Check the first side
|
||||
_bsp_trace_check_node(trace, node.children[side], start_fraction, middle_fraction, start, middle, content_mask);
|
||||
|
||||
// STEP 5: Calculate the middle point for the second side
|
||||
middle_fraction = start_fraction + (end_fraction - start_fraction) * fraction2;
|
||||
middle = gs_vec3_add(start, gs_vec3_scale(gs_vec3_sub(end, start), fraction2));
|
||||
|
||||
// STEP 6: Check the second side
|
||||
_bsp_trace_check_node(trace, node.children[1 - side], middle_fraction, end_fraction, middle, end, content_mask);
|
||||
}
|
||||
}
|
||||
|
||||
void _bsp_trace_check_brush(bsp_trace_t *trace, bsp_brush_lump_t brush, gs_vec3 start, gs_vec3 end)
|
||||
{
|
||||
float32_t start_fraction = -1.0f;
|
||||
float end_fraction = 1.0f;
|
||||
bool32_t starts_out = false;
|
||||
bool32_t ends_out = false;
|
||||
|
||||
bsp_brush_side_lump_t brush_side;
|
||||
bsp_brush_side_lump_t clip_brush_side;
|
||||
bsp_plane_lump_t plane;
|
||||
bsp_plane_lump_t clip_plane;
|
||||
gs_vec3 offset = gs_v3(0, 0, 0);
|
||||
float32_t start_distance = 0;
|
||||
float32_t end_distance = 0;
|
||||
float32_t fraction = 0;
|
||||
|
||||
for (size_t i = 0; i < brush.num_brush_sides; i++)
|
||||
{
|
||||
brush_side = trace->map->brush_sides.data[brush.first_brush_side + i];
|
||||
plane = trace->map->planes.data[brush_side.plane];
|
||||
offset = gs_v3(0, 0, 0);
|
||||
|
||||
if (trace->type == BOX)
|
||||
{
|
||||
for (size_t j = 0; j < 3; j++)
|
||||
{
|
||||
if (plane.normal.xyz[j] < 0)
|
||||
{
|
||||
offset.xyz[j] = trace->maxs.xyz[j];
|
||||
}
|
||||
else
|
||||
{
|
||||
offset.xyz[j] = trace->mins.xyz[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: offset should be zero if not BOX,
|
||||
// and radius should be zero if not SPHERE.
|
||||
start_distance = gs_vec3_dot(gs_vec3_add(start, offset), plane.normal) - (plane.dist + trace->radius);
|
||||
end_distance = gs_vec3_dot(gs_vec3_add(end, offset), plane.normal) - (plane.dist + trace->radius);
|
||||
|
||||
// Don't reset these to false,
|
||||
// we are outside the brush if in front of any plane.
|
||||
if (start_distance > 0)
|
||||
starts_out = true;
|
||||
if (end_distance > 0)
|
||||
ends_out = true;
|
||||
|
||||
// Make sure the trace isn't completely on one side of the plane.
|
||||
// Make sure end_distance can't get too close to the plane.
|
||||
if (start_distance > 0 && (end_distance >= BSP_TRACE_EPSILON || end_distance >= start_distance))
|
||||
{
|
||||
// Both are in front of the plane, outside of the brush
|
||||
return;
|
||||
}
|
||||
if (start_distance <= 0 && end_distance <= 0)
|
||||
{
|
||||
// Both are behind this plane, check the next one
|
||||
continue;
|
||||
}
|
||||
|
||||
if (start_distance > end_distance)
|
||||
{
|
||||
// The line is entering the plane
|
||||
fraction = (start_distance - BSP_TRACE_EPSILON) / (start_distance - end_distance);
|
||||
if (fraction < 0)
|
||||
{
|
||||
fraction = 0;
|
||||
}
|
||||
if (fraction > start_fraction)
|
||||
{
|
||||
start_fraction = fraction;
|
||||
clip_plane = plane;
|
||||
clip_brush_side = brush_side;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The line is leaving the plane
|
||||
fraction = (start_distance + BSP_TRACE_EPSILON) / (start_distance - end_distance);
|
||||
if (fraction > 1.0f)
|
||||
{
|
||||
fraction = 1.0f;
|
||||
}
|
||||
if (fraction < end_fraction)
|
||||
{
|
||||
end_fraction = fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: What if we start inside but end outside a previous brush,
|
||||
// and start outside but end inside the current one?
|
||||
// start_solid will be true but all_solid will be false despite ending inside.
|
||||
if (!starts_out)
|
||||
{
|
||||
trace->start_solid = true;
|
||||
if (!ends_out)
|
||||
{
|
||||
trace->all_solid = true;
|
||||
trace->fraction = 0;
|
||||
trace->contents = trace->map->textures.data[brush.texture].contents;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (start_fraction < end_fraction)
|
||||
{
|
||||
if (start_fraction > -1.0f && start_fraction < trace->fraction)
|
||||
{
|
||||
trace->fraction = fmaxf(0.0f, start_fraction);
|
||||
trace->normal = clip_plane.normal;
|
||||
trace->contents = trace->map->textures.data[brush.texture].contents;
|
||||
trace->surface_flags = trace->map->textures.data[clip_brush_side.texture].flags;
|
||||
}
|
||||
}
|
||||
}
|
50
src/bsp/bsp_trace.h
Normal file
50
src/bsp/bsp_trace.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_trace.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Types for BSP tracing.
|
||||
=================================================================*/
|
||||
|
||||
#ifndef BSP_TRACE_H
|
||||
#define BSP_TRACE_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "bsp_types.h"
|
||||
|
||||
#define BSP_TRACE_EPSILON 0.125f
|
||||
|
||||
typedef enum bsp_trace_type
|
||||
{
|
||||
RAY,
|
||||
SPHERE,
|
||||
BOX,
|
||||
} bsp_trace_type;
|
||||
|
||||
typedef struct bsp_trace_t
|
||||
{
|
||||
bsp_map_t *map;
|
||||
bsp_trace_type type;
|
||||
float32_t fraction;
|
||||
float32_t radius;
|
||||
bool32_t start_solid;
|
||||
bool32_t all_solid;
|
||||
gs_vec3 end;
|
||||
gs_vec3 normal;
|
||||
gs_vec3 mins;
|
||||
gs_vec3 maxs;
|
||||
gs_vec3 extents;
|
||||
int32_t contents;
|
||||
int32_t surface_flags;
|
||||
} bsp_trace_t;
|
||||
|
||||
void bsp_trace_ray(bsp_trace_t *trace, gs_vec3 start, gs_vec3 end, int32_t content_mask);
|
||||
void bsp_trace_sphere(bsp_trace_t *trace, gs_vec3 start, gs_vec3 end, float32_t radius, int32_t content_mask);
|
||||
void bsp_trace_box(bsp_trace_t *trace, gs_vec3 start, gs_vec3 end, gs_vec3 mins, gs_vec3 maxs, int32_t content_mask);
|
||||
void _bsp_trace(bsp_trace_t *trace, gs_vec3 start, gs_vec3 end, int32_t content_mask);
|
||||
void _bsp_trace_check_node(bsp_trace_t *trace, int32_t node_index, float32_t start_fraction, float32_t end_fraction, gs_vec3 start, gs_vec3 end, int32_t content_mask);
|
||||
void _bsp_trace_check_brush(bsp_trace_t *trace, bsp_brush_lump_t brush, gs_vec3 start, gs_vec3 end);
|
||||
|
||||
#endif // BSP_TRACE_H
|
428
src/bsp/bsp_types.h
Normal file
428
src/bsp/bsp_types.h
Normal file
|
@ -0,0 +1,428 @@
|
|||
/*================================================================
|
||||
* bsp/bsp_types.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
BSP data types.
|
||||
|
||||
Based on document by Kekoa Proudfoot:
|
||||
http://www.mralligator.com/q3/
|
||||
=================================================================*/
|
||||
|
||||
#ifndef BSP_TYPES_H
|
||||
#define BSP_TYPES_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
/*========
|
||||
// ENUMS
|
||||
=========*/
|
||||
|
||||
typedef enum bsp_content
|
||||
{
|
||||
BSP_CONTENT_UNKNOWN = 0,
|
||||
BSP_CONTENT_CONTENTS_SOLID = 1,
|
||||
BSP_CONTENT_CONTENTS_LAVA = 8,
|
||||
BSP_CONTENT_CONTENTS_SLIME = 16,
|
||||
BSP_CONTENT_CONTENTS_WATER = 32,
|
||||
BSP_CONTENT_CONTENTS_FOG = 64,
|
||||
BSP_CONTENT_CONTENTS_MONSTER = 128,
|
||||
BSP_CONTENT_CONTENTS_PLAYERCLIP = 256,
|
||||
BSP_CONTENT_CONTENTS_MONSTERCLIP = 512,
|
||||
} bsp_content;
|
||||
|
||||
typedef enum bsp_face_type
|
||||
{
|
||||
BSP_FACE_TYPE_POLYGON = 1,
|
||||
BSP_FACE_TYPE_PATCH,
|
||||
BSP_FACE_TYPE_MESH,
|
||||
BSP_FACE_TYPE_BILLBOARD,
|
||||
BSP_FACE_TYPE_COUNT
|
||||
} bsp_face_type;
|
||||
|
||||
typedef enum bsp_render_flags
|
||||
{
|
||||
SHOW_WIREFRAME = 1 << 0,
|
||||
SHOW_LIGHTMAPS = 1 << 1,
|
||||
USE_LIGHTMAPS = 1 << 2,
|
||||
ALPHA_TEST = 1 << 3,
|
||||
SKIP_MISSING_TEX = 1 << 4,
|
||||
SKIP_PVS = 1 << 5,
|
||||
SKIP_FC = 1 << 6,
|
||||
MULTISAMPLING = 1 << 7
|
||||
} bsp_render_flags;
|
||||
|
||||
typedef enum bsp_lump_types
|
||||
{
|
||||
BSP_LUMP_TYPE_ENTITIES = 0,
|
||||
BSP_LUMP_TYPE_TEXTURES,
|
||||
BSP_LUMP_TYPE_PLANES,
|
||||
BSP_LUMP_TYPE_NODES,
|
||||
BSP_LUMP_TYPE_LEAVES,
|
||||
BSP_LUMP_TYPE_LEAF_FACES,
|
||||
BSP_LUMP_TYPE_LEAF_BRUSHES,
|
||||
BSP_LUMP_TYPE_MODELS,
|
||||
BSP_LUMP_TYPE_BRUSHES,
|
||||
BSP_LUMP_TYPE_BRUSH_SIDES,
|
||||
BSP_LUMP_TYPE_VERTICES,
|
||||
BSP_LUMP_TYPE_INDICES,
|
||||
BSP_LUMP_TYPE_EFFECTS,
|
||||
BSP_LUMP_TYPE_FACES,
|
||||
BSP_LUMP_TYPE_LIGHTMAPS,
|
||||
BSP_LUMP_TYPE_LIGHTVOLS,
|
||||
BSP_LUMP_TYPE_VISDATA,
|
||||
BSP_LUMP_TYPE_COUNT
|
||||
} bsp_lump_types;
|
||||
|
||||
/*==========
|
||||
// STRUCTS
|
||||
===========*/
|
||||
|
||||
typedef struct bsp_stats_t
|
||||
{
|
||||
uint32_t total_vertices;
|
||||
uint32_t total_indices;
|
||||
uint32_t total_faces;
|
||||
uint32_t total_patches;
|
||||
uint32_t culled_leaves_pvs;
|
||||
uint32_t culled_leaves_frustum;
|
||||
uint32_t visible_leaves;
|
||||
uint32_t visible_vertices;
|
||||
uint32_t visible_indices;
|
||||
uint32_t visible_faces;
|
||||
uint32_t visible_patches;
|
||||
uint32_t total_textures;
|
||||
uint32_t loaded_textures;
|
||||
uint32_t models;
|
||||
int32_t current_leaf;
|
||||
} bsp_stats_t;
|
||||
|
||||
typedef struct bsp_face_renderable_t
|
||||
{
|
||||
int32_t type;
|
||||
int32_t index;
|
||||
uint32_t first_ibo_index;
|
||||
uint32_t num_ibo_indices;
|
||||
bool visible;
|
||||
} bsp_face_renderable_t;
|
||||
|
||||
/*
|
||||
typedef struct bsp_leaf_renderable_t
|
||||
{
|
||||
int32_t vis_cluster;
|
||||
int32_t first_face;
|
||||
int32_t num_faces;
|
||||
gs_vec3 mins;
|
||||
gs_vec3 maxs;
|
||||
} bsp_leaf_renderable_t;
|
||||
*/
|
||||
|
||||
typedef struct bsp_dir_entry_t
|
||||
{
|
||||
int32_t offset;
|
||||
int32_t length;
|
||||
} bsp_dir_entry_t;
|
||||
|
||||
typedef struct bsp_entity_lump_t
|
||||
{
|
||||
char *ents;
|
||||
} bsp_entity_lump_t;
|
||||
|
||||
typedef struct bsp_texture_lump_t
|
||||
{
|
||||
char name[64];
|
||||
int32_t flags;
|
||||
int32_t contents;
|
||||
} bsp_texture_lump_t;
|
||||
|
||||
typedef struct bsp_plane_lump_t
|
||||
{
|
||||
gs_vec3 normal;
|
||||
float32_t dist;
|
||||
} bsp_plane_lump_t;
|
||||
|
||||
typedef struct bsp_node_lump_t
|
||||
{
|
||||
int32_t plane;
|
||||
int32_t children[2];
|
||||
int32_t mins[3];
|
||||
int32_t maxs[3];
|
||||
} bsp_node_lump_t;
|
||||
|
||||
typedef struct bsp_leaf_lump_t
|
||||
{
|
||||
int32_t cluster;
|
||||
int32_t area;
|
||||
int32_t mins[3];
|
||||
int32_t maxs[3];
|
||||
int32_t first_leaf_face;
|
||||
int32_t num_leaf_faces;
|
||||
int32_t first_leaf_brush;
|
||||
int32_t num_leaf_brushes;
|
||||
} bsp_leaf_lump_t;
|
||||
|
||||
typedef struct bsp_leaf_face_lump_t
|
||||
{
|
||||
int32_t face;
|
||||
} bsp_leaf_face_lump_t;
|
||||
|
||||
typedef struct bsp_leaf_brush_lump_t
|
||||
{
|
||||
int32_t brush;
|
||||
} bsp_leaf_brush_lump_t;
|
||||
|
||||
typedef struct bsp_model_lump_t
|
||||
{
|
||||
gs_vec3 mins;
|
||||
gs_vec3 maxs;
|
||||
int32_t first_face;
|
||||
int32_t num_faces;
|
||||
int32_t first_brush;
|
||||
int32_t num_brushes;
|
||||
} bsp_model_lump_t;
|
||||
|
||||
typedef struct bsp_brush_lump_t
|
||||
{
|
||||
int32_t first_brush_side;
|
||||
int32_t num_brush_sides;
|
||||
int32_t texture;
|
||||
} bsp_brush_lump_t;
|
||||
|
||||
typedef struct bsp_brush_side_lump_t
|
||||
{
|
||||
int32_t plane;
|
||||
int32_t texture;
|
||||
} bsp_brush_side_lump_t;
|
||||
|
||||
typedef struct bsp_vert_lump_t
|
||||
{
|
||||
gs_vec3 position;
|
||||
gs_vec2 tex_coord;
|
||||
gs_vec2 lm_coord;
|
||||
gs_vec3 normal;
|
||||
gs_color_t color;
|
||||
} bsp_vert_lump_t;
|
||||
|
||||
typedef struct bsp_index_lump_t
|
||||
{
|
||||
int32_t offset;
|
||||
} bsp_index_lump_t;
|
||||
|
||||
typedef struct bsp_effect_lump_t
|
||||
{
|
||||
char name[64];
|
||||
int32_t brush;
|
||||
int32_t unknown;
|
||||
} bsp_effect_lump_t;
|
||||
|
||||
typedef struct bsp_face_lump_t
|
||||
{
|
||||
int32_t texture;
|
||||
int32_t effect;
|
||||
int32_t type;
|
||||
int32_t first_vertex;
|
||||
int32_t num_vertices;
|
||||
int32_t first_index;
|
||||
int32_t num_indices;
|
||||
int32_t lm_index;
|
||||
// gs_vec2i lm_start;
|
||||
// gs_vec2i lm_size;
|
||||
// gs_vec2i lm_origin;
|
||||
int32_t lm_start[2];
|
||||
int32_t lm_size[2];
|
||||
int32_t lm_origin[3];
|
||||
gs_vec3 lm_vecs[2];
|
||||
gs_vec3 normal;
|
||||
// gs_vec2i size;
|
||||
int32_t size[2];
|
||||
} bsp_face_lump_t;
|
||||
|
||||
typedef struct bsp_lightmap_lump_t
|
||||
{
|
||||
char map[128 * 128 * 3];
|
||||
} bsp_lightmap_lump_t;
|
||||
|
||||
typedef struct bsp_lightvol_lump_t
|
||||
{
|
||||
uint8_t ambient[3];
|
||||
uint8_t directional[3];
|
||||
uint8_t dir[2];
|
||||
} bsp_lightvol_lump_t;
|
||||
|
||||
typedef struct bsp_visdata_lump_t
|
||||
{
|
||||
int32_t num_vecs;
|
||||
int32_t size_vecs;
|
||||
char *vecs;
|
||||
} bsp_visdata_lump_t;
|
||||
|
||||
typedef struct bsp_quadratic_patch_t
|
||||
{
|
||||
int32_t tesselation;
|
||||
bsp_vert_lump_t control_points[9];
|
||||
gs_dyn_array(bsp_vert_lump_t) vertices;
|
||||
gs_dyn_array(uint16_t) indices;
|
||||
} bsp_quadratic_patch_t;
|
||||
|
||||
typedef struct bsp_patch_t
|
||||
{
|
||||
int32_t texture_idx;
|
||||
int32_t lightmap_idx;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
gs_dyn_array(bsp_quadratic_patch_t) quadratic_patches;
|
||||
} bsp_patch_t;
|
||||
|
||||
typedef struct bsp_entity_t
|
||||
{
|
||||
char *content;
|
||||
gs_slot_map(char *, char *) slot_map;
|
||||
} bsp_entity_t;
|
||||
|
||||
typedef struct bsp_header_t
|
||||
{
|
||||
char magic[4];
|
||||
int32_t version;
|
||||
bsp_dir_entry_t dir_entries[BSP_LUMP_TYPE_COUNT];
|
||||
} bsp_header_t;
|
||||
|
||||
typedef struct bsp_map_t
|
||||
{
|
||||
/*==== File data ====*/
|
||||
|
||||
bsp_header_t header;
|
||||
bsp_entity_lump_t entity_lump;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_texture_lump_t *data;
|
||||
} textures;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_plane_lump_t *data;
|
||||
} planes;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_node_lump_t *data;
|
||||
} nodes;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_leaf_lump_t *data;
|
||||
} leaves;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_leaf_face_lump_t *data;
|
||||
} leaf_faces;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_leaf_brush_lump_t *data;
|
||||
} leaf_brushes;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_model_lump_t *data;
|
||||
} models;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_brush_lump_t *data;
|
||||
} brushes;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_brush_side_lump_t *data;
|
||||
} brush_sides;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_vert_lump_t *data;
|
||||
} vertices;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_index_lump_t *data;
|
||||
} indices;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_effect_lump_t *data;
|
||||
} effects;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_face_lump_t *data;
|
||||
} faces;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_lightmap_lump_t *data;
|
||||
} lightmaps;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
bsp_lightvol_lump_t *data;
|
||||
} lightvols;
|
||||
|
||||
bsp_visdata_lump_t visdata;
|
||||
|
||||
/*==== Runtime data ====*/
|
||||
|
||||
char *name;
|
||||
bool32_t valid;
|
||||
bsp_stats_t stats;
|
||||
gs_dyn_array(bsp_patch_t) patches;
|
||||
gs_dyn_array(bsp_face_renderable_t) render_faces;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
gs_asset_texture_t **data;
|
||||
} texture_assets;
|
||||
|
||||
struct
|
||||
{
|
||||
uint32_t count;
|
||||
gs_handle(gs_graphics_texture_t) * data;
|
||||
} lightmap_textures;
|
||||
|
||||
gs_handle(gs_graphics_texture_t) missing_texture;
|
||||
gs_handle(gs_graphics_texture_t) missing_lm_texture;
|
||||
|
||||
int32_t previous_leaf;
|
||||
|
||||
gs_dyn_array(bsp_entity_t) entities;
|
||||
|
||||
gs_handle(gs_graphics_vertex_buffer_t) bsp_graphics_vbo;
|
||||
gs_handle(gs_graphics_index_buffer_t) bsp_graphics_ibo;
|
||||
gs_handle(gs_graphics_pipeline_t) bsp_graphics_pipe;
|
||||
gs_handle(gs_graphics_pipeline_t) bsp_graphics_wire_pipe;
|
||||
gs_handle(gs_graphics_uniform_t) bsp_graphics_u_proj;
|
||||
gs_handle(gs_graphics_uniform_t) bsp_graphics_u_tex;
|
||||
gs_handle(gs_graphics_uniform_t) bsp_graphics_u_lm;
|
||||
gs_handle(gs_graphics_uniform_t) bsp_graphics_u_color;
|
||||
gs_dyn_array(uint32_t) bsp_graphics_index_arr;
|
||||
gs_dyn_array(bsp_vert_lump_t) bsp_graphics_vert_arr;
|
||||
} bsp_map_t;
|
||||
|
||||
#endif // BSP_TYPES_H
|
291
src/entities/entity.h
Normal file
291
src/entities/entity.h
Normal file
|
@ -0,0 +1,291 @@
|
|||
/*================================================================
|
||||
* entities/entity.h
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
Common functions for entities.
|
||||
BSP movement, acceleration, etc.
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_ENTITY_H
|
||||
#define MG_ENTITY_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../bsp/bsp_trace.h"
|
||||
#include "../game/game_manager.h"
|
||||
#include "../graphics/renderer.h"
|
||||
|
||||
typedef struct mg_entity_t
|
||||
{
|
||||
uint32_t id;
|
||||
gs_vqs transform;
|
||||
gs_vec3 velocity;
|
||||
gs_vec3 mins;
|
||||
gs_vec3 maxs;
|
||||
} mg_entity_t;
|
||||
|
||||
typedef struct mg_model_entity_t
|
||||
{
|
||||
mg_entity_t ent;
|
||||
mg_model_t *model;
|
||||
uint32_t renderable_id;
|
||||
} mg_model_entity_t;
|
||||
|
||||
static inline void mg_ent_init(mg_entity_t *ent, gs_vqs transform)
|
||||
{
|
||||
ent->transform = transform;
|
||||
}
|
||||
|
||||
static inline bool mg_model_ent_init(mg_model_entity_t *ent, gs_vqs transform, char *modelpath, char *shader)
|
||||
{
|
||||
mg_ent_init(&ent->ent, transform);
|
||||
ent->model = mg_model_manager_find_or_load(modelpath, shader);
|
||||
if (ent->model == NULL) return false;
|
||||
ent->renderable_id = mg_renderer_create_renderable(*ent->model, &ent->ent.transform);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void mg_model_ent_free(mg_model_entity_t *ent)
|
||||
{
|
||||
mg_renderer_remove_renderable(ent->renderable_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply friction to entity.
|
||||
*/
|
||||
static inline void mg_ent_friction(gs_vec3 *velocity, const float stop_speed, const float friction, const float delta_time)
|
||||
{
|
||||
float vel2 = gs_vec3_len2(*velocity);
|
||||
if (vel2 < GS_EPSILON)
|
||||
{
|
||||
*velocity = gs_v3(0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
float vel = sqrtf(vel2);
|
||||
float loss = fmaxf(vel, stop_speed) * friction * delta_time;
|
||||
float frac = fmaxf(0, vel - loss) / vel;
|
||||
|
||||
*velocity = gs_vec3_scale(*velocity, frac);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accelerate entity with wished move direction.
|
||||
*/
|
||||
static inline void mg_ent_accelerate(gs_vec3 *velocity, const gs_vec3 wish_move, const float move_speed, const float acceleration, const float max_vel, const float delta_time)
|
||||
{
|
||||
// Velocity projected on to wished direction,
|
||||
// positive if in the same direction.
|
||||
float proj_vel = gs_vec3_dot(*velocity, wish_move);
|
||||
|
||||
// The max amount to change by,
|
||||
// won't accelerate past move_speed.
|
||||
float change = move_speed - proj_vel;
|
||||
if (change <= 0) return;
|
||||
|
||||
// The actual acceleration
|
||||
float accel = acceleration * move_speed * delta_time;
|
||||
|
||||
// Clamp to max change
|
||||
if (accel > change) accel = change;
|
||||
|
||||
*velocity = gs_vec3_add(*velocity, gs_vec3_scale(wish_move, accel));
|
||||
|
||||
// Clamp to max vel per axis
|
||||
velocity->x = fminf(max_vel, fmaxf(velocity->x, -max_vel));
|
||||
velocity->y = fminf(max_vel, fmaxf(velocity->y, -max_vel));
|
||||
velocity->z = fminf(max_vel, fmaxf(velocity->z, -max_vel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide and step through the BSP.
|
||||
* Will try to remain on ground when stepping down if grounded=true.
|
||||
* Returns false if stuck.
|
||||
*/
|
||||
static inline bool mg_ent_slidemove(
|
||||
gs_vqs *transform,
|
||||
gs_vec3 *velocity,
|
||||
const gs_vec3 mins,
|
||||
const gs_vec3 maxs,
|
||||
const float max_step_height,
|
||||
const bool grounded,
|
||||
const int32_t content_mask,
|
||||
const float delta_time)
|
||||
{
|
||||
uint16_t current_iter = 0;
|
||||
uint16_t max_iter = 10;
|
||||
gs_vec3 start;
|
||||
gs_vec3 end;
|
||||
bsp_trace_t trace = {.map = g_game_manager->map};
|
||||
float32_t prev_frac;
|
||||
gs_vec3 prev_normal;
|
||||
float dt = delta_time;
|
||||
|
||||
while (dt > 0)
|
||||
{
|
||||
if (gs_vec3_len2(*velocity) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Sanity check for infinite loops,
|
||||
// shouldn't really happen.
|
||||
if (current_iter >= max_iter)
|
||||
{
|
||||
break;
|
||||
}
|
||||
current_iter++;
|
||||
|
||||
start = transform->position;
|
||||
end = gs_vec3_add(start, gs_vec3_scale(*velocity, dt));
|
||||
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
start,
|
||||
end,
|
||||
mins,
|
||||
maxs,
|
||||
content_mask);
|
||||
|
||||
if (trace.start_solid)
|
||||
{
|
||||
// Stuck in a solid, try to get out
|
||||
if (trace.all_solid)
|
||||
{
|
||||
// Proper stuck
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_println(
|
||||
"WARN: slidemove start solid but not stuck: [%f, %f, %f] -> [%f, %f, %f]",
|
||||
transform->position.x,
|
||||
transform->position.y,
|
||||
transform->position.z,
|
||||
trace.end.x,
|
||||
trace.end.y,
|
||||
trace.end.z);
|
||||
transform->position = trace.end;
|
||||
*velocity = gs_v3(0, 0, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (trace.fraction > 0)
|
||||
{
|
||||
// Move as far as we can
|
||||
transform->position = trace.end;
|
||||
}
|
||||
|
||||
bool is_done = false;
|
||||
gs_vec3 end_pos = trace.end;
|
||||
|
||||
if (trace.fraction == 1.0f)
|
||||
{
|
||||
// Moved all the way
|
||||
dt = 0;
|
||||
is_done = true;
|
||||
goto step_down;
|
||||
}
|
||||
|
||||
dt -= dt * trace.fraction;
|
||||
|
||||
// Colliding with something, check if we can move further
|
||||
// by stepping up or down before clipping velocity to the plane.
|
||||
gs_vec3 hit_normal = trace.normal;
|
||||
|
||||
step_up:
|
||||
// TODO
|
||||
// Known issues:
|
||||
// Hugging a wall stops player from going up stairs.
|
||||
// The forward trace will collide with the wall if velocity
|
||||
// is angled towards it.
|
||||
// Potential solution: If forward trace collides with anything,
|
||||
// project velocity to normal and try again?
|
||||
|
||||
// Check above
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
end_pos,
|
||||
gs_vec3_add(end_pos, gs_v3(0, 0, max_step_height)),
|
||||
mins,
|
||||
maxs,
|
||||
content_mask);
|
||||
if (trace.fraction <= 0)
|
||||
goto step_down;
|
||||
|
||||
// Check forward
|
||||
gs_vec3 horizontal_vel = *velocity;
|
||||
horizontal_vel.z = 0;
|
||||
horizontal_vel = gs_vec3_scale(horizontal_vel, dt);
|
||||
// Player can be epsilon from an edge on either axis,
|
||||
// and will need to go epsilon over it for downwards trace to hit anything.
|
||||
// Scaling both axes to a min of 2 * epsilon (if not 0) allows player to
|
||||
// step up stairs even if moving at a very shallow angle towards them.
|
||||
// While this can make the player move a very tiny distance further,
|
||||
// it's imperceptible in testing and preferred to getting stuck on a stair.
|
||||
if (fabs(horizontal_vel.x) > GS_EPSILON && fabs(horizontal_vel.x) < BSP_TRACE_EPSILON * 2.0f)
|
||||
horizontal_vel.x = (horizontal_vel.x / fabs(horizontal_vel.x)) * BSP_TRACE_EPSILON * 2.0f;
|
||||
if (fabs(horizontal_vel.y) > GS_EPSILON && fabs(horizontal_vel.y) < BSP_TRACE_EPSILON * 2.0f)
|
||||
horizontal_vel.y = (horizontal_vel.y / fabs(horizontal_vel.y)) * BSP_TRACE_EPSILON * 2.0f;
|
||||
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
trace.end,
|
||||
gs_vec3_add(trace.end, horizontal_vel),
|
||||
mins,
|
||||
maxs,
|
||||
content_mask);
|
||||
if (trace.fraction <= 0)
|
||||
goto step_down;
|
||||
|
||||
float forward_frac = trace.fraction;
|
||||
|
||||
// Move down
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
trace.end,
|
||||
gs_vec3_add(trace.end, gs_v3(0, 0, -max_step_height)),
|
||||
mins,
|
||||
maxs,
|
||||
content_mask);
|
||||
if (trace.fraction == 1.0f || trace.end.z <= end_pos.z || trace.normal.z <= 0.7f)
|
||||
goto step_down;
|
||||
|
||||
transform->position = trace.end;
|
||||
dt -= dt * forward_frac;
|
||||
continue;
|
||||
|
||||
step_down:
|
||||
// We're already going 'forward' as much as we can,
|
||||
// try to stick to the floor if grounded before.
|
||||
if (!grounded)
|
||||
goto slide;
|
||||
|
||||
// Move down
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
end_pos,
|
||||
gs_vec3_add(end_pos, gs_v3(0, 0, -max_step_height)),
|
||||
mins,
|
||||
maxs,
|
||||
content_mask);
|
||||
if (trace.fraction == 1.0f || trace.end.z >= end_pos.z || trace.normal.z <= 0.7f)
|
||||
goto slide;
|
||||
|
||||
transform->position = trace.end;
|
||||
continue;
|
||||
|
||||
slide:
|
||||
if (is_done) break; // if getting here from frac = 1.0 -> step_down
|
||||
|
||||
// Can't step, slide along the plane, modify velocity for next loop
|
||||
*velocity = mg_clip_velocity(*velocity, hit_normal, 1.001f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // MG_ENTITY_H
|
72
src/entities/entity_manager.c
Normal file
72
src/entities/entity_manager.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*================================================================
|
||||
* entities/entity_manager.c
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "entity_manager.h"
|
||||
#include "../game/time_manager.h"
|
||||
|
||||
mg_entity_manager_t *g_entity_manager;
|
||||
|
||||
void mg_entity_manager_init()
|
||||
{
|
||||
g_entity_manager = gs_malloc_init(mg_entity_manager_t);
|
||||
g_entity_manager->ent_funcs = gs_slot_array_new(mg_entity_func_wrapper_t);
|
||||
}
|
||||
|
||||
void mg_entity_manager_free()
|
||||
{
|
||||
for (
|
||||
gs_slot_array_iter it = gs_slot_array_iter_new(g_entity_manager->ent_funcs);
|
||||
gs_slot_array_iter_valid(g_entity_manager->ent_funcs, it);
|
||||
gs_slot_array_iter_advance(g_entity_manager->ent_funcs, it))
|
||||
{
|
||||
mg_entity_funcs_t ent = gs_slot_array_iter_get(g_entity_manager->ent_funcs, it);
|
||||
if (ent.free_func != NULL)
|
||||
{
|
||||
ent.free_func(ent.entity);
|
||||
}
|
||||
}
|
||||
gs_slot_array_free(g_entity_manager->ent_funcs);
|
||||
}
|
||||
|
||||
void mg_entity_manager_update()
|
||||
{
|
||||
double dt = g_time_manager->delta;
|
||||
for (
|
||||
gs_slot_array_iter it = gs_slot_array_iter_new(g_entity_manager->ent_funcs);
|
||||
gs_slot_array_iter_valid(g_entity_manager->ent_funcs, it);
|
||||
gs_slot_array_iter_advance(g_entity_manager->ent_funcs, it))
|
||||
{
|
||||
mg_entity_funcs_t ent = gs_slot_array_iter_get(g_entity_manager->ent_funcs, it);
|
||||
if (ent.update_func != NULL)
|
||||
{
|
||||
ent.update_func(ent.entity, dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mg_entity_manager_add_entity(mg_entity_t *entity, void (*update_func)(void *, double), void (*free_func)(void *))
|
||||
{
|
||||
mg_entity_funcs_t ent_funcs = {
|
||||
.entity = entity,
|
||||
.update_func = update_func,
|
||||
.free_func = free_func,
|
||||
};
|
||||
uint32_t id = gs_slot_array_insert(g_entity_manager->ent_funcs, ent_funcs);
|
||||
mg_entity_funcs_t *f = gs_slot_array_getp(g_entity_manager->ent_funcs, id);
|
||||
f->entity->id = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
void mg_entity_manager_remove_entity(const uint32_t id)
|
||||
{
|
||||
if (gs_slot_array_handle_valid(g_entity_manager->ent_funcs, id))
|
||||
{
|
||||
gs_slot_array_erase(g_entity_manager->ent_funcs, id);
|
||||
}
|
||||
}
|
37
src/entities/entity_manager.h
Normal file
37
src/entities/entity_manager.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*================================================================
|
||||
* entities/entity_manager.h
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_ENTITY_MANAGER_H
|
||||
#define MG_ENTITY_MANAGER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "entity.h"
|
||||
|
||||
typedef struct mg_entity_funcs_t
|
||||
{
|
||||
mg_entity_t *entity;
|
||||
void (*update_func)(void *, double);
|
||||
void (*free_func)(void *);
|
||||
} mg_entity_funcs_t;
|
||||
|
||||
typedef struct mg_entity_manager_t
|
||||
{
|
||||
gs_slot_array(mg_entity_funcs_t) ent_funcs;
|
||||
} mg_entity_manager_t;
|
||||
|
||||
void mg_entity_manager_init();
|
||||
void mg_entity_manager_free();
|
||||
void mg_entity_manager_update();
|
||||
uint32_t mg_entity_manager_add_entity(mg_entity_t *entity, void (*update_func)(void *, double), void (*free_func)(void *));
|
||||
void mg_entity_manager_remove_entity(const uint32_t id);
|
||||
|
||||
extern mg_entity_manager_t *g_entity_manager;
|
||||
|
||||
#endif // MG_ENTITY_MANAGER_H
|
404
src/entities/monster.c
Normal file
404
src/entities/monster.c
Normal file
|
@ -0,0 +1,404 @@
|
|||
/*================================================================
|
||||
* entities/monster.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "monster.h"
|
||||
#include "../audio/audio_manager.h"
|
||||
#include "../bsp/bsp_trace.h"
|
||||
#include "../game/config.h"
|
||||
#include "../game/console.h"
|
||||
#include "../game/game_manager.h"
|
||||
#include "../game/time_manager.h"
|
||||
#include "../graphics/ui_manager.h"
|
||||
#include "../util/math.h"
|
||||
#include "../util/transform.h"
|
||||
#include "entity.h"
|
||||
#include <gs/util/gs_idraw.h>
|
||||
|
||||
mg_monster_t *mg_monster_new(const char *model_path, const gs_vec3 mins, const gs_vec3 maxs)
|
||||
{
|
||||
mg_monster_t *monster = gs_malloc_init(mg_monster_t);
|
||||
|
||||
mg_cvar_t *vid_width = mg_cvar("vid_width");
|
||||
mg_cvar_t *vid_height = mg_cvar("vid_height");
|
||||
|
||||
*monster = (mg_monster_t){
|
||||
.transform = gs_vqs_default(),
|
||||
.health = 100,
|
||||
.eye_pos = gs_v3(0, 0, maxs.z - MG_MONSTER_EYE_OFFSET),
|
||||
.mins = gs_v3(mins.x, mins.y, mins.z),
|
||||
.maxs = gs_v3(maxs.x, maxs.y, maxs.z),
|
||||
.last_ground_time = 0,
|
||||
.height = maxs.z,
|
||||
.crouch_height = maxs.z * 0.5f,
|
||||
};
|
||||
|
||||
monster->model = mg_model_manager_find(model_path);
|
||||
if (monster->model == NULL)
|
||||
{
|
||||
gs_assert(_mg_model_manager_load(model_path, "basic"));
|
||||
monster->model = mg_model_manager_find(model_path);
|
||||
}
|
||||
monster->model_id = mg_renderer_create_renderable(*monster->model, &monster->transform);
|
||||
monster->renderable = mg_renderer_get_renderable(monster->model_id);
|
||||
|
||||
return monster;
|
||||
}
|
||||
|
||||
void mg_monster_free(mg_monster_t *monster)
|
||||
{
|
||||
gs_free(monster);
|
||||
}
|
||||
|
||||
void mg_monster_update(mg_monster_t *monster)
|
||||
{
|
||||
// TODO: time manager, pausing
|
||||
if (g_ui_manager->show_cursor) return;
|
||||
if (g_game_manager->map == NULL || !g_game_manager->map->valid) return;
|
||||
|
||||
double dt = g_time_manager->delta;
|
||||
double pt = g_time_manager->time;
|
||||
|
||||
_mg_monster_think(monster, pt);
|
||||
_mg_monster_check_floor(monster);
|
||||
|
||||
// Handle jump and gravity
|
||||
if (monster->grounded)
|
||||
{
|
||||
if (monster->wish_jump)
|
||||
{
|
||||
_mg_monster_do_jump(monster);
|
||||
}
|
||||
else
|
||||
{
|
||||
monster->velocity.z = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// gravity
|
||||
monster->velocity = gs_vec3_add(monster->velocity, gs_vec3_scale(MG_AXIS_DOWN, MG_MONSTER_GRAVITY * dt));
|
||||
}
|
||||
|
||||
// Crouching
|
||||
if (monster->wish_crouch)
|
||||
{
|
||||
_mg_monster_crouch(monster, dt);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mg_monster_uncrouch(monster, dt);
|
||||
}
|
||||
|
||||
// Update velocity
|
||||
if (monster->grounded)
|
||||
{
|
||||
mg_ent_friction(&monster->velocity, MG_MONSTER_STOP_SPEED, MG_MONSTER_FRICTION, dt);
|
||||
}
|
||||
|
||||
float move_speed = 0;
|
||||
float move_accel = 0;
|
||||
|
||||
if (monster->grounded)
|
||||
{
|
||||
if (monster->crouched)
|
||||
{
|
||||
|
||||
move_speed = mg_lerp(MG_MONSTER_MOVE_SPEED, MG_MONSTER_CROUCH_MOVE_SPEED, monster->crouch_fraction);
|
||||
move_accel = MG_MONSTER_ACCEL;
|
||||
}
|
||||
else
|
||||
{
|
||||
move_speed = MG_MONSTER_MOVE_SPEED;
|
||||
move_accel = MG_MONSTER_ACCEL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
move_speed = MG_MONSTER_AIR_MOVE_SPEED;
|
||||
move_accel = MG_MONSTER_AIR_ACCEL;
|
||||
}
|
||||
|
||||
mg_ent_accelerate(
|
||||
&monster->velocity,
|
||||
monster->wish_move,
|
||||
move_speed,
|
||||
move_accel,
|
||||
MG_MONSTER_MAX_VEL,
|
||||
dt);
|
||||
|
||||
// Move
|
||||
if (!mg_ent_slidemove(
|
||||
&monster->transform,
|
||||
&monster->velocity,
|
||||
monster->mins,
|
||||
monster->maxs,
|
||||
MG_MONSTER_STEP_HEIGHT,
|
||||
monster->grounded,
|
||||
BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_MONSTERCLIP,
|
||||
dt))
|
||||
{
|
||||
// FIXME: this just makes you fly up walls...
|
||||
// _mg_monster_unstuck(monster);
|
||||
}
|
||||
|
||||
// Check out of map bounds
|
||||
uint32_t leaf_index = g_game_manager->map->stats.current_leaf;
|
||||
int32_t cluster_index = g_game_manager->map->leaves.data[leaf_index].cluster;
|
||||
if (cluster_index < 0)
|
||||
{
|
||||
mg_println(
|
||||
"WARN: monster in invalid leaf, reset to last valid pos: [%f, %f, %f]",
|
||||
monster->last_valid_pos.x,
|
||||
monster->last_valid_pos.y,
|
||||
monster->last_valid_pos.z);
|
||||
monster->transform.position = monster->last_valid_pos;
|
||||
monster->velocity = gs_v3(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void _mg_monster_think(mg_monster_t *monster, double platform_time)
|
||||
{
|
||||
if (platform_time - monster->last_think_time < MG_MONSTER_THINK_INTERVAL) return;
|
||||
|
||||
monster->last_think_time = platform_time;
|
||||
|
||||
// just move towards player for now
|
||||
monster->wish_move = gs_v3(0, 0, 0);
|
||||
if (g_game_manager->player != NULL)
|
||||
{
|
||||
gs_vec3 d = gs_vec3_sub(g_game_manager->player->transform.position, monster->transform.position);
|
||||
d.z = 0;
|
||||
d = gs_vec3_norm(d);
|
||||
monster->transform.rotation = gs_quat_look_rotation(d, MG_AXIS_UP);
|
||||
monster->wish_move = d;
|
||||
}
|
||||
}
|
||||
|
||||
void _mg_monster_do_jump(mg_monster_t *monster)
|
||||
{
|
||||
monster->velocity.z = MG_MONSTER_JUMP_SPEED;
|
||||
monster->grounded = false;
|
||||
monster->has_jumped = true;
|
||||
if (g_audio_manager != NULL)
|
||||
mg_audio_manager_play("monster/jump1.wav", 0.03f);
|
||||
}
|
||||
|
||||
void _mg_monster_check_floor(mg_monster_t *monster)
|
||||
{
|
||||
bsp_trace_t trace = {.map = g_game_manager->map};
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
monster->transform.position,
|
||||
gs_vec3_add(monster->transform.position, gs_vec3_scale(MG_AXIS_DOWN, 2.0f)),
|
||||
monster->mins,
|
||||
monster->maxs,
|
||||
BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_MONSTERCLIP);
|
||||
|
||||
// Don't set grounded if moving up fast enough.
|
||||
// (Sliding up a ramp or jumping to a higher level floor)
|
||||
// TODO: relative velocity between monster and floor if moving
|
||||
float relative_velocity = monster->velocity.z;
|
||||
|
||||
if (trace.fraction < 1.0f && trace.normal.z > 0.7f && relative_velocity < MG_MONSTER_SLIDE_LIMIT)
|
||||
{
|
||||
monster->grounded = true;
|
||||
monster->has_jumped = false;
|
||||
monster->ground_normal = trace.normal;
|
||||
monster->last_ground_time = g_time_manager->time;
|
||||
|
||||
uint32_t leaf_index = g_game_manager->map->stats.current_leaf;
|
||||
int32_t cluster_index = g_game_manager->map->leaves.data[leaf_index].cluster;
|
||||
if (cluster_index >= 0)
|
||||
{
|
||||
monster->last_valid_pos = monster->transform.position;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
monster->grounded = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Can always crouch
|
||||
void _mg_monster_crouch(mg_monster_t *monster, float delta_time)
|
||||
{
|
||||
if (monster->crouch_fraction == 1.0f) return;
|
||||
|
||||
float32_t crouch_time = MG_MONSTER_CROUCH_TIME;
|
||||
if (!monster->grounded) crouch_time = MG_MONSTER_CROUCH_TIME_AIR;
|
||||
|
||||
if (crouch_time > 0.0f)
|
||||
{
|
||||
float32_t prev_fraction = monster->crouch_fraction;
|
||||
monster->crouch_fraction += delta_time / crouch_time;
|
||||
if (monster->crouch_fraction > 1.0f) monster->crouch_fraction = 1.0f;
|
||||
|
||||
monster->crouched = true;
|
||||
monster->maxs.z = monster->height - monster->crouch_fraction * (monster->height - monster->crouch_height);
|
||||
monster->eye_pos.z = monster->maxs.z - MG_MONSTER_EYE_OFFSET;
|
||||
|
||||
// Pull feet up if not on ground
|
||||
if (!monster->grounded)
|
||||
{
|
||||
monster->transform.position.z += (monster->height - monster->crouch_height) * 0.5f * (monster->crouch_fraction - prev_fraction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
monster->crouched = true;
|
||||
monster->crouch_fraction = 1.0f;
|
||||
monster->maxs.z = monster->crouch_height;
|
||||
monster->eye_pos.z = monster->maxs.z - MG_MONSTER_EYE_OFFSET;
|
||||
|
||||
// Pull feet up if not on ground
|
||||
if (!monster->grounded)
|
||||
{
|
||||
monster->transform.position.z += (monster->height - monster->crouch_height) * 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Can uncrouch if enough room
|
||||
void _mg_monster_uncrouch(mg_monster_t *monster, float delta_time)
|
||||
{
|
||||
if (monster->crouch_fraction == 0.0f) return;
|
||||
|
||||
bsp_trace_t trace = {.map = g_game_manager->map};
|
||||
gs_vec3 origin = monster->transform.position;
|
||||
bool32_t grounded = monster->grounded;
|
||||
|
||||
if (!grounded)
|
||||
{
|
||||
// Feet will go down if in air, check how much room below monster.
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
monster->transform.position,
|
||||
gs_vec3_scale(mg_get_down(monster->transform.rotation), (monster->height - monster->crouch_height) * 0.5f * monster->crouch_fraction),
|
||||
monster->mins,
|
||||
monster->maxs,
|
||||
BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_MONSTERCLIP);
|
||||
|
||||
origin = trace.end;
|
||||
grounded = trace.fraction < 1.0f && trace.normal.z > 0.7f;
|
||||
}
|
||||
|
||||
// Check above from potential new position.
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
origin,
|
||||
gs_vec3_add(origin, gs_vec3_scale(mg_get_up(monster->transform.rotation), (monster->height - monster->crouch_height) * monster->crouch_fraction)),
|
||||
monster->mins, monster->maxs,
|
||||
BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_MONSTERCLIP);
|
||||
|
||||
if (trace.fraction == 1.0f)
|
||||
{
|
||||
// Got room to uncrouch
|
||||
float32_t uncrouch_time = MG_MONSTER_UNCROUCH_TIME;
|
||||
if (!monster->grounded) uncrouch_time = MG_MONSTER_UNCROUCH_TIME_AIR;
|
||||
|
||||
if (uncrouch_time > 0.0f)
|
||||
{
|
||||
float32_t prev_fraction = monster->crouch_fraction;
|
||||
monster->crouch_fraction -= delta_time / uncrouch_time;
|
||||
if (monster->crouch_fraction < 0.0f) monster->crouch_fraction = 0.0f;
|
||||
|
||||
monster->crouched = monster->crouch_fraction != 0.0f;
|
||||
monster->maxs.z = monster->height - monster->crouch_fraction * (monster->height - monster->crouch_height);
|
||||
monster->eye_pos.z = monster->maxs.z - MG_MONSTER_EYE_OFFSET;
|
||||
monster->transform.position.z -= (monster->transform.position.z - origin.z) * (monster->crouch_fraction - prev_fraction);
|
||||
|
||||
if (monster->crouched == 0.0f)
|
||||
{
|
||||
monster->grounded = grounded;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
monster->crouched = false;
|
||||
monster->crouch_fraction = 0.0f;
|
||||
monster->maxs.z = monster->height;
|
||||
monster->eye_pos.z = monster->maxs.z - MG_MONSTER_EYE_OFFSET;
|
||||
monster->transform.position.z = origin.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _mg_monster_unstuck(mg_monster_t *monster)
|
||||
{
|
||||
monster->velocity = gs_v3(0, 0, 0);
|
||||
|
||||
// Directions to attempt to teleport to,
|
||||
// in listed order.
|
||||
gs_vec3 directions[6] = {
|
||||
MG_AXIS_UP,
|
||||
MG_AXIS_FORWARD,
|
||||
MG_AXIS_RIGHT,
|
||||
MG_AXIS_BACKWARD,
|
||||
MG_AXIS_LEFT,
|
||||
MG_AXIS_DOWN,
|
||||
};
|
||||
|
||||
float distance = 0.0f;
|
||||
float increment = 64.0f;
|
||||
float max_distance = increment * 10;
|
||||
uint32_t dir = 0;
|
||||
gs_vec3 start = gs_v3(0, 0, 0);
|
||||
gs_vec3 end = gs_v3(0, 0, 0);
|
||||
bsp_trace_t trace = {.map = g_game_manager->map};
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Sweep monster aabb by 1 unit
|
||||
start = gs_vec3_add(monster->transform.position, gs_vec3_scale(directions[dir], distance));
|
||||
end = gs_vec3_add(start, directions[dir]);
|
||||
bsp_trace_box(&trace, start, end, monster->mins, monster->maxs, BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_MONSTERCLIP);
|
||||
|
||||
if (trace.fraction > 0.0f && !trace.all_solid)
|
||||
{
|
||||
// Trace ends in a valid position.
|
||||
// Trace back towards start so we move
|
||||
// the minimum distance to get unstuck.
|
||||
gs_vec3 valid_pos = trace.end;
|
||||
bsp_trace_box(&trace, trace.end, monster->transform.position, monster->mins, monster->maxs, BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_MONSTERCLIP);
|
||||
if (trace.fraction < 1.0f && !trace.all_solid)
|
||||
{
|
||||
valid_pos = trace.end;
|
||||
}
|
||||
|
||||
mg_println(
|
||||
"WARN: monster stuck in solid at [%f, %f, %f], freeing to [%f, %f, %f].",
|
||||
monster->transform.position.x,
|
||||
monster->transform.position.y,
|
||||
monster->transform.position.z,
|
||||
valid_pos.x,
|
||||
valid_pos.y,
|
||||
valid_pos.z);
|
||||
monster->transform.position = valid_pos;
|
||||
break;
|
||||
}
|
||||
|
||||
// Swap to the next direction,
|
||||
// increment distance if looped through all.
|
||||
dir++;
|
||||
if (dir >= 6)
|
||||
{
|
||||
dir = 0;
|
||||
distance += increment;
|
||||
if (distance > max_distance)
|
||||
{
|
||||
mg_println(
|
||||
"WARN: monster stuck in solid at [%f, %f, %f], could not unstuck.",
|
||||
monster->transform.position.x,
|
||||
monster->transform.position.y,
|
||||
monster->transform.position.z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
75
src/entities/monster.h
Normal file
75
src/entities/monster.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*================================================================
|
||||
* entities/monster.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_MONSTER_H
|
||||
#define MG_MONSTER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../bsp/bsp_map.h"
|
||||
#include "../graphics/model_manager.h"
|
||||
#include "../graphics/renderer.h"
|
||||
|
||||
#define MG_MONSTER_EYE_OFFSET 4.0f
|
||||
#define MG_MONSTER_MOVE_SPEED 240.0f
|
||||
#define MG_MONSTER_CROUCH_MOVE_SPEED 160.0f
|
||||
#define MG_MONSTER_ACCEL 10.0f
|
||||
#define MG_MONSTER_AIR_MOVE_SPEED 30.0f
|
||||
#define MG_MONSTER_AIR_ACCEL 50.0f
|
||||
#define MG_MONSTER_FRICTION 8.0f
|
||||
#define MG_MONSTER_JUMP_SPEED 250.0f
|
||||
#define MG_MONSTER_SLIDE_LIMIT 150.0f
|
||||
#define MG_MONSTER_STOP_SPEED 100.0f
|
||||
#define MG_MONSTER_MAX_VEL 3500.0f
|
||||
#define MG_MONSTER_CROUCH_TIME 0.1f
|
||||
#define MG_MONSTER_UNCROUCH_TIME 0.1f
|
||||
// Crouch transitions feel weird in air
|
||||
#define MG_MONSTER_CROUCH_TIME_AIR 0.0f
|
||||
#define MG_MONSTER_UNCROUCH_TIME_AIR 0.0f
|
||||
#define MG_MONSTER_STEP_HEIGHT 16.0f
|
||||
#define MG_MONSTER_THINK_INTERVAL 0.5f
|
||||
#define MG_MONSTER_GRAVITY 100.0f
|
||||
|
||||
typedef struct mg_monster_t
|
||||
{
|
||||
gs_vqs transform;
|
||||
int32_t health;
|
||||
gs_vec3 velocity;
|
||||
gs_vec3 wish_move;
|
||||
gs_vec3 mins;
|
||||
gs_vec3 maxs;
|
||||
gs_vec3 eye_pos;
|
||||
gs_vec3 ground_normal;
|
||||
bool32_t wish_jump;
|
||||
bool32_t wish_crouch;
|
||||
bool32_t crouched;
|
||||
bool32_t grounded;
|
||||
bool32_t has_jumped;
|
||||
float32_t crouch_fraction;
|
||||
gs_vec3 last_valid_pos;
|
||||
double last_ground_time;
|
||||
double last_think_time;
|
||||
mg_model_t *model;
|
||||
uint32_t model_id;
|
||||
mg_renderable_t *renderable;
|
||||
float height;
|
||||
float crouch_height;
|
||||
} mg_monster_t;
|
||||
|
||||
mg_monster_t *mg_monster_new(const char *model_path, const gs_vec3 mins, const gs_vec3 maxs);
|
||||
void mg_monster_free(mg_monster_t *monster);
|
||||
void mg_monster_update(mg_monster_t *monster);
|
||||
void _mg_monster_unstuck(mg_monster_t *monster);
|
||||
void _mg_monster_think(mg_monster_t *monster, double platform_time);
|
||||
void _mg_monster_uncrouch(mg_monster_t *monster, float delta_time);
|
||||
void _mg_monster_crouch(mg_monster_t *monster, float delta_time);
|
||||
void _mg_monster_do_jump(mg_monster_t *monster);
|
||||
void _mg_monster_check_floor(mg_monster_t *monster);
|
||||
|
||||
#endif // MG_MONSTER_H
|
487
src/entities/player.c
Normal file
487
src/entities/player.c
Normal file
|
@ -0,0 +1,487 @@
|
|||
/*================================================================
|
||||
* entities/player.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "player.h"
|
||||
#include "../audio/audio_manager.h"
|
||||
#include "../bsp/bsp_trace.h"
|
||||
#include "../game/config.h"
|
||||
#include "../game/console.h"
|
||||
#include "../game/game_manager.h"
|
||||
#include "../game/time_manager.h"
|
||||
#include "../graphics/model_manager.h"
|
||||
#include "../graphics/renderer.h"
|
||||
#include "../graphics/ui_manager.h"
|
||||
#include "../util/camera.h"
|
||||
#include "../util/math.h"
|
||||
#include "entity.h"
|
||||
|
||||
#include <gs/util/gs_idraw.h>
|
||||
|
||||
mg_player_t *mg_player_new()
|
||||
{
|
||||
mg_player_t *player = gs_malloc_init(mg_player_t);
|
||||
|
||||
mg_cvar_t *vid_width = mg_cvar("vid_width");
|
||||
mg_cvar_t *vid_height = mg_cvar("vid_height");
|
||||
|
||||
*player = (mg_player_t){
|
||||
.transform = gs_vqs_default(),
|
||||
.camera = {
|
||||
.cam = {
|
||||
.aspect_ratio = (float)vid_width->value.i / vid_height->value.i,
|
||||
.far_plane = 10000.0f,
|
||||
.fov = mg_cvar("r_fov")->value.i,
|
||||
.near_plane = 0.1f,
|
||||
.proj_type = GS_PROJECTION_TYPE_PERSPECTIVE,
|
||||
},
|
||||
},
|
||||
.viewmodel_camera = {
|
||||
.aspect_ratio = (float)vid_width->value.i / vid_height->value.i,
|
||||
.far_plane = 1000.0f,
|
||||
.fov = mg_cvar("r_viewmodel_fov")->value.i,
|
||||
.near_plane = 0.1f,
|
||||
.proj_type = GS_PROJECTION_TYPE_PERSPECTIVE,
|
||||
},
|
||||
.health = 100,
|
||||
.eye_pos = gs_v3(0, 0, MG_PLAYER_HEIGHT - MG_PLAYER_EYE_OFFSET),
|
||||
.mins = gs_v3(-MG_PLAYER_HALF_WIDTH, -MG_PLAYER_HALF_WIDTH, 0),
|
||||
.maxs = gs_v3(MG_PLAYER_HALF_WIDTH, MG_PLAYER_HALF_WIDTH, MG_PLAYER_HEIGHT),
|
||||
.last_ground_time = 0,
|
||||
.weapon_current = -1,
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < MG_WEAPON_COUNT; i++)
|
||||
{
|
||||
player->weapons[i] = mg_weapon_create(i);
|
||||
mg_renderer_set_hidden(player->weapons[i]->renderable_id, true);
|
||||
mg_renderer_set_model_type(player->weapons[i]->renderable_id, MG_MODEL_VIEWMODEL);
|
||||
}
|
||||
|
||||
_mg_player_camera_update(player);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
void mg_player_free(mg_player_t *player)
|
||||
{
|
||||
for (size_t i = 0; i < MG_WEAPON_COUNT; i++)
|
||||
{
|
||||
mg_weapon_free(player->weapons[i]);
|
||||
}
|
||||
|
||||
gs_free(player);
|
||||
}
|
||||
|
||||
void mg_player_update(mg_player_t *player)
|
||||
{
|
||||
// TODO: time manager, pausing
|
||||
if (g_ui_manager->show_cursor) return;
|
||||
if (g_game_manager->map == NULL || !g_game_manager->map->valid) return;
|
||||
|
||||
double dt = g_time_manager->delta;
|
||||
double pt = g_time_manager->time;
|
||||
|
||||
_mg_player_check_floor(player);
|
||||
|
||||
// Handle jump and gravity
|
||||
if (player->grounded)
|
||||
{
|
||||
if (player->wish_jump)
|
||||
{
|
||||
_mg_player_do_jump(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
player->velocity.z = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// gravity
|
||||
player->velocity = gs_vec3_add(player->velocity, gs_vec3_scale(MG_AXIS_DOWN, MG_PLAYER_GRAVITY * dt));
|
||||
|
||||
// coyote
|
||||
if (player->wish_jump && !player->has_jumped && pt - player->last_ground_time <= MG_PLAYER_COYOTE_TIME)
|
||||
{
|
||||
_mg_player_do_jump(player);
|
||||
}
|
||||
}
|
||||
|
||||
// Crouching
|
||||
if (player->wish_crouch)
|
||||
{
|
||||
_mg_player_crouch(player, dt);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mg_player_uncrouch(player, dt);
|
||||
}
|
||||
|
||||
// Update velocity
|
||||
if (player->grounded)
|
||||
{
|
||||
mg_ent_friction(&player->velocity, MG_PLAYER_STOP_SPEED, MG_PLAYER_FRICTION, dt);
|
||||
}
|
||||
|
||||
float move_speed = 0;
|
||||
float move_accel = 0;
|
||||
|
||||
if (player->grounded)
|
||||
{
|
||||
if (player->crouched)
|
||||
{
|
||||
|
||||
move_speed = mg_lerp(MG_PLAYER_MOVE_SPEED, MG_PLAYER_CROUCH_MOVE_SPEED, player->crouch_fraction);
|
||||
move_accel = MG_PLAYER_ACCEL;
|
||||
}
|
||||
else
|
||||
{
|
||||
move_speed = MG_PLAYER_MOVE_SPEED;
|
||||
move_accel = MG_PLAYER_ACCEL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
move_speed = MG_PLAYER_AIR_MOVE_SPEED;
|
||||
move_accel = MG_PLAYER_AIR_ACCEL;
|
||||
}
|
||||
|
||||
mg_ent_accelerate(
|
||||
&player->velocity,
|
||||
player->wish_move,
|
||||
move_speed,
|
||||
move_accel,
|
||||
MG_PLAYER_MAX_VEL,
|
||||
dt);
|
||||
|
||||
// Move
|
||||
if (!mg_ent_slidemove(
|
||||
&player->transform,
|
||||
&player->velocity,
|
||||
player->mins,
|
||||
player->maxs,
|
||||
MG_PLAYER_STEP_HEIGHT,
|
||||
player->grounded,
|
||||
BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_PLAYERCLIP,
|
||||
dt))
|
||||
{
|
||||
// FIXME: this just makes you fly up walls...
|
||||
// _mg_player_unstuck(player);
|
||||
}
|
||||
|
||||
_mg_player_camera_update(player);
|
||||
|
||||
// Check out of map bounds
|
||||
uint32_t leaf_index = g_game_manager->map->stats.current_leaf;
|
||||
int32_t cluster_index = g_game_manager->map->leaves.data[leaf_index].cluster;
|
||||
if (cluster_index < 0)
|
||||
{
|
||||
mg_println(
|
||||
"WARN: player in invalid leaf, reset to last valid pos: [%f, %f, %f]",
|
||||
player->last_valid_pos.x,
|
||||
player->last_valid_pos.y,
|
||||
player->last_valid_pos.z);
|
||||
player->transform.position = player->last_valid_pos;
|
||||
player->velocity = gs_v3(0, 0, 0);
|
||||
}
|
||||
|
||||
if (player->wish_shoot)
|
||||
{
|
||||
_mg_player_shoot(player);
|
||||
}
|
||||
}
|
||||
|
||||
void mg_player_switch_weapon(mg_player_t *player, int32_t slot)
|
||||
{
|
||||
if (slot >= MG_WEAPON_COUNT)
|
||||
{
|
||||
gs_println("WARN: mg_player_switch_weapon: slot %d higher than max %d", slot, MG_WEAPON_COUNT - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (player->weapon_current >= 0)
|
||||
{
|
||||
mg_renderer_set_hidden(player->weapons[player->weapon_current]->renderable_id, true);
|
||||
}
|
||||
|
||||
player->weapon_current = slot;
|
||||
|
||||
if (player->weapon_current >= 0)
|
||||
{
|
||||
mg_renderer_set_hidden(player->weapons[player->weapon_current]->renderable_id, false);
|
||||
}
|
||||
}
|
||||
|
||||
void _mg_player_do_jump(mg_player_t *player)
|
||||
{
|
||||
player->velocity.z = MG_PLAYER_JUMP_SPEED;
|
||||
player->grounded = false;
|
||||
player->has_jumped = true;
|
||||
if (g_audio_manager != NULL)
|
||||
mg_audio_manager_play("player/jump1.wav", 0.03f);
|
||||
}
|
||||
|
||||
void _mg_player_check_floor(mg_player_t *player)
|
||||
{
|
||||
bsp_trace_t trace = {.map = g_game_manager->map};
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
player->transform.position,
|
||||
gs_vec3_add(player->transform.position, gs_vec3_scale(MG_AXIS_DOWN, 2.0f)),
|
||||
player->mins,
|
||||
player->maxs,
|
||||
BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_PLAYERCLIP);
|
||||
|
||||
// Don't set grounded if moving up fast enough.
|
||||
// (Sliding up a ramp or jumping to a higher level floor)
|
||||
// TODO: relative velocity between player and floor if moving
|
||||
float relative_velocity = player->velocity.z;
|
||||
|
||||
if (trace.fraction < 1.0f && trace.normal.z > 0.7f && relative_velocity < MG_PLAYER_SLIDE_LIMIT)
|
||||
{
|
||||
player->grounded = true;
|
||||
player->has_jumped = false;
|
||||
player->ground_normal = trace.normal;
|
||||
player->last_ground_time = g_time_manager->time;
|
||||
|
||||
uint32_t leaf_index = g_game_manager->map->stats.current_leaf;
|
||||
int32_t cluster_index = g_game_manager->map->leaves.data[leaf_index].cluster;
|
||||
if (cluster_index >= 0)
|
||||
{
|
||||
player->last_valid_pos = player->transform.position;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
player->grounded = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _mg_player_camera_update(mg_player_t *player)
|
||||
{
|
||||
player->camera.cam.transform = gs_vqs_absolute_transform(
|
||||
&(gs_vqs){
|
||||
.position = player->eye_pos,
|
||||
.rotation = gs_quat_mul(
|
||||
gs_quat_angle_axis(gs_deg2rad(-player->camera.pitch), MG_AXIS_RIGHT),
|
||||
gs_quat_angle_axis(gs_deg2rad(player->camera.roll), MG_AXIS_FORWARD)),
|
||||
.scale = gs_v3(1.0f, 1.0f, 1.0f),
|
||||
},
|
||||
&player->transform);
|
||||
|
||||
player->viewmodel_camera.transform = player->camera.cam.transform;
|
||||
|
||||
if (player->weapon_current >= 0)
|
||||
{
|
||||
mg_weapon_t *weapon = player->weapons[player->weapon_current];
|
||||
weapon->transform = gs_vqs_absolute_transform(
|
||||
&(gs_vqs){
|
||||
.position = gs_v3(
|
||||
mg_cvar("r_viewmodel_pos_x")->value.f,
|
||||
mg_cvar("r_viewmodel_pos_y")->value.f,
|
||||
mg_cvar("r_viewmodel_pos_z")->value.f),
|
||||
.rotation = gs_quat_default(),
|
||||
.scale = gs_v3(
|
||||
weapon->view_scale.x * mg_cvar("r_viewmodel_scale_x")->value.f,
|
||||
weapon->view_scale.y * mg_cvar("r_viewmodel_scale_y")->value.f,
|
||||
weapon->view_scale.z * mg_cvar("r_viewmodel_scale_z")->value.f),
|
||||
},
|
||||
&player->camera.cam.transform);
|
||||
}
|
||||
}
|
||||
|
||||
// Can always crouch
|
||||
void _mg_player_crouch(mg_player_t *player, float delta_time)
|
||||
{
|
||||
if (player->crouch_fraction == 1.0f) return;
|
||||
|
||||
float32_t crouch_time = MG_PLAYER_CROUCH_TIME;
|
||||
if (!player->grounded) crouch_time = MG_PLAYER_CROUCH_TIME_AIR;
|
||||
|
||||
if (crouch_time > 0.0f)
|
||||
{
|
||||
float32_t prev_fraction = player->crouch_fraction;
|
||||
player->crouch_fraction += delta_time / crouch_time;
|
||||
if (player->crouch_fraction > 1.0f) player->crouch_fraction = 1.0f;
|
||||
|
||||
player->crouched = true;
|
||||
player->maxs.z = MG_PLAYER_HEIGHT - player->crouch_fraction * (MG_PLAYER_HEIGHT - MG_PLAYER_CROUCH_HEIGHT);
|
||||
player->eye_pos.z = player->maxs.z - MG_PLAYER_EYE_OFFSET;
|
||||
|
||||
// Pull feet up if not on ground
|
||||
if (!player->grounded)
|
||||
{
|
||||
player->transform.position.z += (MG_PLAYER_HEIGHT - MG_PLAYER_CROUCH_HEIGHT) * 0.5f * (player->crouch_fraction - prev_fraction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
player->crouched = true;
|
||||
player->crouch_fraction = 1.0f;
|
||||
player->maxs.z = MG_PLAYER_CROUCH_HEIGHT;
|
||||
player->eye_pos.z = player->maxs.z - MG_PLAYER_EYE_OFFSET;
|
||||
|
||||
// Pull feet up if not on ground
|
||||
if (!player->grounded)
|
||||
{
|
||||
player->transform.position.z += (MG_PLAYER_HEIGHT - MG_PLAYER_CROUCH_HEIGHT) * 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Can uncrouch if enough room
|
||||
void _mg_player_uncrouch(mg_player_t *player, float delta_time)
|
||||
{
|
||||
if (player->crouch_fraction == 0.0f) return;
|
||||
|
||||
bsp_trace_t trace = {.map = g_game_manager->map};
|
||||
gs_vec3 origin = player->transform.position;
|
||||
bool32_t grounded = player->grounded;
|
||||
|
||||
if (!grounded)
|
||||
{
|
||||
// Feet will go down if in air, check how much room below player.
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
player->transform.position,
|
||||
gs_vec3_scale(mg_get_down(player->transform.rotation), (MG_PLAYER_HEIGHT - MG_PLAYER_CROUCH_HEIGHT) * 0.5f * player->crouch_fraction),
|
||||
player->mins,
|
||||
player->maxs,
|
||||
BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_PLAYERCLIP);
|
||||
|
||||
origin = trace.end;
|
||||
grounded = trace.fraction < 1.0f && trace.normal.z > 0.7f;
|
||||
}
|
||||
|
||||
// Check above from potential new position.
|
||||
bsp_trace_box(
|
||||
&trace,
|
||||
origin,
|
||||
gs_vec3_add(origin, gs_vec3_scale(mg_get_up(player->transform.rotation), (MG_PLAYER_HEIGHT - MG_PLAYER_CROUCH_HEIGHT) * player->crouch_fraction)),
|
||||
player->mins, player->maxs,
|
||||
BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_PLAYERCLIP);
|
||||
|
||||
if (trace.fraction == 1.0f)
|
||||
{
|
||||
// Got room to uncrouch
|
||||
float32_t uncrouch_time = MG_PLAYER_UNCROUCH_TIME;
|
||||
if (!player->grounded) uncrouch_time = MG_PLAYER_UNCROUCH_TIME_AIR;
|
||||
|
||||
if (uncrouch_time > 0.0f)
|
||||
{
|
||||
float32_t prev_fraction = player->crouch_fraction;
|
||||
player->crouch_fraction -= delta_time / uncrouch_time;
|
||||
if (player->crouch_fraction < 0.0f) player->crouch_fraction = 0.0f;
|
||||
|
||||
player->crouched = player->crouch_fraction != 0.0f;
|
||||
player->maxs.z = MG_PLAYER_HEIGHT - player->crouch_fraction * (MG_PLAYER_HEIGHT - MG_PLAYER_CROUCH_HEIGHT);
|
||||
player->eye_pos.z = player->maxs.z - MG_PLAYER_EYE_OFFSET;
|
||||
player->transform.position.z -= (player->transform.position.z - origin.z) * (player->crouch_fraction - prev_fraction);
|
||||
|
||||
if (player->crouched == 0.0f)
|
||||
{
|
||||
player->grounded = grounded;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
player->crouched = false;
|
||||
player->crouch_fraction = 0.0f;
|
||||
player->maxs.z = MG_PLAYER_HEIGHT;
|
||||
player->eye_pos.z = player->maxs.z - MG_PLAYER_EYE_OFFSET;
|
||||
player->transform.position.z = origin.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _mg_player_unstuck(mg_player_t *player)
|
||||
{
|
||||
player->velocity = gs_v3(0, 0, 0);
|
||||
|
||||
// Directions to attempt to teleport to,
|
||||
// in listed order.
|
||||
gs_vec3 directions[6] = {
|
||||
MG_AXIS_UP,
|
||||
MG_AXIS_FORWARD,
|
||||
MG_AXIS_RIGHT,
|
||||
MG_AXIS_BACKWARD,
|
||||
MG_AXIS_LEFT,
|
||||
MG_AXIS_DOWN,
|
||||
};
|
||||
|
||||
float distance = 0.0f;
|
||||
float increment = 64.0f;
|
||||
float max_distance = increment * 10;
|
||||
uint32_t dir = 0;
|
||||
gs_vec3 start = gs_v3(0, 0, 0);
|
||||
gs_vec3 end = gs_v3(0, 0, 0);
|
||||
bsp_trace_t trace = {.map = g_game_manager->map};
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Sweep player aabb by 1 unit
|
||||
start = gs_vec3_add(player->transform.position, gs_vec3_scale(directions[dir], distance));
|
||||
end = gs_vec3_add(start, directions[dir]);
|
||||
bsp_trace_box(&trace, start, end, player->mins, player->maxs, BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_PLAYERCLIP);
|
||||
|
||||
if (trace.fraction > 0.0f && !trace.all_solid)
|
||||
{
|
||||
// Trace ends in a valid position.
|
||||
// Trace back towards start so we move
|
||||
// the minimum distance to get unstuck.
|
||||
gs_vec3 valid_pos = trace.end;
|
||||
bsp_trace_box(&trace, trace.end, player->transform.position, player->mins, player->maxs, BSP_CONTENT_CONTENTS_SOLID | BSP_CONTENT_CONTENTS_PLAYERCLIP);
|
||||
if (trace.fraction < 1.0f && !trace.all_solid)
|
||||
{
|
||||
valid_pos = trace.end;
|
||||
}
|
||||
|
||||
mg_println(
|
||||
"WARN: player stuck in solid at [%f, %f, %f], freeing to [%f, %f, %f].",
|
||||
player->transform.position.x,
|
||||
player->transform.position.y,
|
||||
player->transform.position.z,
|
||||
valid_pos.x,
|
||||
valid_pos.y,
|
||||
valid_pos.z);
|
||||
player->transform.position = valid_pos;
|
||||
break;
|
||||
}
|
||||
|
||||
// Swap to the next direction,
|
||||
// increment distance if looped through all.
|
||||
dir++;
|
||||
if (dir >= 6)
|
||||
{
|
||||
dir = 0;
|
||||
distance += increment;
|
||||
if (distance > max_distance)
|
||||
{
|
||||
mg_println(
|
||||
"WARN: player stuck in solid at [%f, %f, %f], could not unstuck.",
|
||||
player->transform.position.x,
|
||||
player->transform.position.y,
|
||||
player->transform.position.z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _mg_player_shoot(mg_player_t *player)
|
||||
{
|
||||
if (player->weapon_current < 0 || player->weapon_current >= MG_WEAPON_COUNT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mg_weapon_t *weapon = player->weapons[player->weapon_current];
|
||||
mg_weapon_shoot_result result = mg_weapon_shoot(weapon, player->camera.cam.transform);
|
||||
// TODO: shoot anim, out of ammo sound
|
||||
}
|
86
src/entities/player.h
Normal file
86
src/entities/player.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*================================================================
|
||||
* entities/player.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_PLAYER_H
|
||||
#define MG_PLAYER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../bsp/bsp_map.h"
|
||||
#include "weapon.h"
|
||||
|
||||
#define MG_PLAYER_HEIGHT 64.0f
|
||||
#define MG_PLAYER_CROUCH_HEIGHT 32.0f
|
||||
#define MG_PLAYER_EYE_OFFSET 4.0f
|
||||
#define MG_PLAYER_HALF_WIDTH 16.0f
|
||||
#define MG_PLAYER_MOVE_SPEED 320.0f
|
||||
#define MG_PLAYER_CROUCH_MOVE_SPEED 160.0f
|
||||
#define MG_PLAYER_ACCEL 10.0f
|
||||
#define MG_PLAYER_AIR_MOVE_SPEED 30.0f
|
||||
#define MG_PLAYER_AIR_ACCEL 50.0f
|
||||
#define MG_PLAYER_FRICTION 8.0f
|
||||
#define MG_PLAYER_JUMP_SPEED 250.0f
|
||||
#define MG_PLAYER_SLIDE_LIMIT 150.0f
|
||||
#define MG_PLAYER_STOP_SPEED 100.0f
|
||||
#define MG_PLAYER_MAX_VEL 3500.0f
|
||||
#define MG_PLAYER_CROUCH_TIME 0.1f
|
||||
#define MG_PLAYER_UNCROUCH_TIME 0.1f
|
||||
// Crouch transitions feel weird in air
|
||||
#define MG_PLAYER_CROUCH_TIME_AIR 0.0f
|
||||
#define MG_PLAYER_UNCROUCH_TIME_AIR 0.0f
|
||||
#define MG_PLAYER_STEP_HEIGHT 16.0f
|
||||
#define MG_PLAYER_COYOTE_TIME 0.2f
|
||||
#define MG_PLAYER_GRAVITY 800.0f
|
||||
|
||||
typedef struct mg_player_camera_t
|
||||
{
|
||||
float32_t pitch;
|
||||
float32_t roll;
|
||||
gs_camera_t cam;
|
||||
} mg_player_camera_t;
|
||||
|
||||
typedef struct mg_player_t
|
||||
{
|
||||
gs_vqs transform;
|
||||
mg_player_camera_t camera;
|
||||
gs_camera_t viewmodel_camera;
|
||||
float32_t yaw;
|
||||
int32_t health;
|
||||
gs_vec3 velocity;
|
||||
gs_vec3 wish_move;
|
||||
gs_vec3 mins;
|
||||
gs_vec3 maxs;
|
||||
gs_vec3 eye_pos;
|
||||
gs_vec3 ground_normal;
|
||||
bool32_t wish_jump;
|
||||
bool32_t wish_crouch;
|
||||
bool32_t crouched;
|
||||
bool32_t grounded;
|
||||
bool32_t has_jumped;
|
||||
bool32_t wish_shoot;
|
||||
float32_t crouch_fraction;
|
||||
gs_vec3 last_valid_pos;
|
||||
int32_t weapon_current;
|
||||
mg_weapon_t *weapons[MG_WEAPON_COUNT];
|
||||
double last_ground_time;
|
||||
} mg_player_t;
|
||||
|
||||
mg_player_t *mg_player_new();
|
||||
void mg_player_free(mg_player_t *player);
|
||||
void mg_player_update(mg_player_t *player);
|
||||
void mg_player_switch_weapon(mg_player_t *player, int32_t slot);
|
||||
void _mg_player_unstuck(mg_player_t *player);
|
||||
void _mg_player_uncrouch(mg_player_t *player, float delta_time);
|
||||
void _mg_player_crouch(mg_player_t *player, float delta_time);
|
||||
void _mg_player_camera_update(mg_player_t *player);
|
||||
void _mg_player_do_jump(mg_player_t *player);
|
||||
void _mg_player_check_floor(mg_player_t *player);
|
||||
void _mg_player_shoot(mg_player_t *player);
|
||||
|
||||
#endif // MG_PLAYER_H
|
100
src/entities/rocket.c
Normal file
100
src/entities/rocket.c
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*================================================================
|
||||
* entities/rocket.c
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "rocket.h"
|
||||
#include "../bsp/bsp_trace.h"
|
||||
#include "../game/console.h"
|
||||
#include "../game/time_manager.h"
|
||||
#include "../util/transform.h"
|
||||
#include "entity_manager.h"
|
||||
|
||||
mg_rocket_t *mg_rocket_new(gs_vqs transform)
|
||||
{
|
||||
mg_rocket_t *rocket = gs_malloc_init(mg_rocket_t);
|
||||
gs_assert(mg_model_ent_init(&rocket->mdl_ent, transform, "projectiles/rocket.md3", "basic"));
|
||||
rocket->mdl_ent.ent.velocity = gs_vec3_scale(mg_get_forward(transform.rotation), MG_ROCKET_SPEED);
|
||||
rocket->life_time = MG_ROCKET_LIFE;
|
||||
rocket->start_time = g_time_manager->time;
|
||||
rocket->hidden = true;
|
||||
rocket->trail = mg_rocket_trail_new(&rocket->mdl_ent.ent.transform);
|
||||
mg_renderer_set_hidden(rocket->mdl_ent.renderable_id, true);
|
||||
mg_entity_manager_add_entity(rocket, mg_rocket_update, mg_rocket_free);
|
||||
return rocket;
|
||||
}
|
||||
|
||||
void mg_rocket_free(mg_rocket_t *rocket)
|
||||
{
|
||||
mg_model_ent_free(&rocket->mdl_ent);
|
||||
gs_free(rocket);
|
||||
rocket = NULL;
|
||||
}
|
||||
|
||||
void mg_rocket_update(mg_rocket_t *rocket, double dt)
|
||||
{
|
||||
rocket->life_time -= dt;
|
||||
if (rocket->life_time <= 0)
|
||||
{
|
||||
_mg_rocket_explode(rocket);
|
||||
return;
|
||||
}
|
||||
|
||||
double time = g_time_manager->time;
|
||||
if (rocket->hidden && time - rocket->start_time >= MG_ROCKET_HIDE_TIME)
|
||||
{
|
||||
rocket->hidden = false;
|
||||
mg_renderer_set_hidden(rocket->mdl_ent.renderable_id, false);
|
||||
}
|
||||
|
||||
gs_vec3 current_pos = rocket->mdl_ent.ent.transform.position;
|
||||
gs_vec3 new_pos = gs_vec3_add(
|
||||
current_pos,
|
||||
gs_vec3_scale(rocket->mdl_ent.ent.velocity, dt));
|
||||
|
||||
bsp_trace_t trace = {0};
|
||||
trace.map = g_game_manager->map;
|
||||
bsp_trace_ray(&trace, current_pos, new_pos, BSP_CONTENT_CONTENTS_SOLID);
|
||||
|
||||
if (trace.start_solid)
|
||||
{
|
||||
_mg_rocket_remove(rocket);
|
||||
return;
|
||||
}
|
||||
|
||||
if (trace.fraction < 1.0)
|
||||
{
|
||||
// Pull away from surface a bit so explosion can see entities better
|
||||
gs_vec3 pull = gs_vec3_scale(trace.normal, 8.0f);
|
||||
rocket->mdl_ent.ent.transform.position = gs_vec3_add(
|
||||
trace.end,
|
||||
pull);
|
||||
_mg_rocket_explode(rocket);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: trace against entities
|
||||
|
||||
rocket->mdl_ent.ent.transform.position = new_pos;
|
||||
|
||||
// TODO: travel sound at pos
|
||||
}
|
||||
|
||||
void _mg_rocket_remove(mg_rocket_t *rocket)
|
||||
{
|
||||
mg_rocket_trail_detach(rocket->trail);
|
||||
mg_entity_manager_remove_entity(rocket->mdl_ent.ent.id);
|
||||
mg_rocket_free(rocket);
|
||||
}
|
||||
|
||||
void _mg_rocket_explode(mg_rocket_t *rocket)
|
||||
{
|
||||
// TODO: sphere iter nearby ents + trace + damage
|
||||
// TODO: explosion sound at pos
|
||||
// TODO: explosion fx
|
||||
_mg_rocket_remove(rocket);
|
||||
}
|
35
src/entities/rocket.h
Normal file
35
src/entities/rocket.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*================================================================
|
||||
* entities/rocket.h
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_ROCKET_H
|
||||
#define MG_ROCKET_H
|
||||
|
||||
#include "../fx/rocket_trail.h"
|
||||
#include "entity.h"
|
||||
|
||||
#define MG_ROCKET_SPEED 800.0
|
||||
#define MG_ROCKET_LIFE 10.0
|
||||
#define MG_ROCKET_HIDE_TIME 0.025
|
||||
|
||||
typedef struct mg_rocket_t
|
||||
{
|
||||
mg_model_entity_t mdl_ent;
|
||||
bool hidden;
|
||||
double life_time;
|
||||
double start_time;
|
||||
mg_rocket_trail_t *trail;
|
||||
} mg_rocket_t;
|
||||
|
||||
mg_rocket_t *mg_rocket_new(gs_vqs transform);
|
||||
void mg_rocket_free(mg_rocket_t *rocket);
|
||||
void mg_rocket_update(mg_rocket_t *rocket, double dt);
|
||||
void _mg_rocket_remove(mg_rocket_t *rocket);
|
||||
void _mg_rocket_explode(mg_rocket_t *rocket);
|
||||
|
||||
#endif // MG_ROCKET_H
|
109
src/entities/weapon.c
Normal file
109
src/entities/weapon.c
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*================================================================
|
||||
* entities/weapon.c
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "weapon.h"
|
||||
#include "../game/console.h"
|
||||
#include "../game/time_manager.h"
|
||||
#include "../graphics/renderer.h"
|
||||
#include "../util/transform.h"
|
||||
#include "rocket.h"
|
||||
|
||||
mg_weapon_t *mg_weapon_create(mg_weapon_type type)
|
||||
{
|
||||
mg_weapon_t *weapon = gs_malloc_init(mg_weapon_t);
|
||||
weapon->type = type;
|
||||
|
||||
// TODO: weapon def files
|
||||
switch (type)
|
||||
{
|
||||
case MG_WEAPON_MACHINE_GUN:
|
||||
weapon->model = mg_model_manager_find_or_load("weapons/machine_gun.md3", "basic");
|
||||
weapon->view_scale = gs_v3(1.0f, 1.0f, 1.0f);
|
||||
weapon->uses_ammo = true;
|
||||
weapon->ammo_type = MG_AMMO_BULLET;
|
||||
weapon->ammo_current = 50;
|
||||
weapon->ammo_max = 50;
|
||||
weapon->shoot_interval = 0.1;
|
||||
break;
|
||||
|
||||
case MG_WEAPON_ROCKET_LAUNCHER:
|
||||
weapon->model = mg_model_manager_find_or_load("weapons/rocket_launcher.md3", "basic");
|
||||
weapon->view_scale = gs_v3(0.6f, 0.6f, 0.6f);
|
||||
weapon->uses_ammo = true;
|
||||
weapon->ammo_type = MG_AMMO_ROCKET;
|
||||
weapon->ammo_current = 10;
|
||||
weapon->ammo_max = 10;
|
||||
weapon->shoot_interval = 1.0;
|
||||
break;
|
||||
|
||||
default:
|
||||
mg_println("mg_weapon_create: unknown weapon type %d", type);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (weapon->model == NULL)
|
||||
{
|
||||
mg_println("mg_weapon_create: invalid model for type %d", type);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
weapon->transform = gs_vqs_default();
|
||||
weapon->renderable_id = mg_renderer_create_renderable(*weapon->model, &weapon->transform);
|
||||
|
||||
return weapon;
|
||||
}
|
||||
|
||||
void mg_weapon_free(mg_weapon_t *weapon)
|
||||
{
|
||||
mg_renderer_remove_renderable(weapon->renderable_id);
|
||||
gs_free(weapon);
|
||||
}
|
||||
|
||||
mg_weapon_shoot_result mg_weapon_shoot(mg_weapon_t *weapon, gs_vqs origin)
|
||||
{
|
||||
if (weapon->ammo_current <= 0)
|
||||
{
|
||||
return MG_WEAPON_RESULT_NO_AMMO;
|
||||
}
|
||||
|
||||
double time = g_time_manager->time;
|
||||
|
||||
if (time - weapon->last_shot < weapon->shoot_interval)
|
||||
{
|
||||
return MG_WEAPON_RESULT_COOLDOWN;
|
||||
}
|
||||
|
||||
weapon->ammo_current--;
|
||||
weapon->last_shot = time;
|
||||
|
||||
switch (weapon->type)
|
||||
{
|
||||
case MG_WEAPON_MACHINE_GUN:
|
||||
// TODO: trace
|
||||
break;
|
||||
|
||||
case MG_WEAPON_ROCKET_LAUNCHER:
|
||||
mg_rocket_new(gs_vqs_absolute_transform(
|
||||
&(gs_vqs){
|
||||
.position = gs_vec3_scale(MG_AXIS_DOWN, 8.0f),
|
||||
.rotation = gs_quat_default(),
|
||||
.scale = gs_v3(1.0f, 1.0f, 1.0f),
|
||||
},
|
||||
&origin));
|
||||
break;
|
||||
|
||||
default:
|
||||
mg_println("mg_weapon_create: unknown weapon type %d", weapon->type);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// TODO: sound
|
||||
|
||||
return MG_WEAPON_RESULT_SHOT;
|
||||
}
|
58
src/entities/weapon.h
Normal file
58
src/entities/weapon.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*================================================================
|
||||
* entities/weapon.h
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_WEAPON_H
|
||||
#define MG_WEAPON_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../graphics/model_manager.h"
|
||||
|
||||
typedef enum mg_ammo_type
|
||||
{
|
||||
MG_AMMO_BULLET,
|
||||
MG_AMMO_ROCKET,
|
||||
MG_AMMO_COUNT,
|
||||
} mg_ammo_type;
|
||||
|
||||
typedef enum mg_weapon_type
|
||||
{
|
||||
MG_WEAPON_MACHINE_GUN,
|
||||
MG_WEAPON_ROCKET_LAUNCHER,
|
||||
MG_WEAPON_COUNT,
|
||||
} mg_weapon_type;
|
||||
|
||||
typedef enum mg_weapon_shoot_result
|
||||
{
|
||||
MG_WEAPON_RESULT_SHOT,
|
||||
MG_WEAPON_RESULT_COOLDOWN,
|
||||
MG_WEAPON_RESULT_NO_AMMO,
|
||||
MG_WEAPON_RESULT_COUNT,
|
||||
} mg_weapon_shoot_result;
|
||||
|
||||
typedef struct mg_weapon_t
|
||||
{
|
||||
mg_weapon_type type;
|
||||
mg_model_t *model;
|
||||
gs_vqs transform;
|
||||
gs_vec3 view_scale;
|
||||
uint32_t renderable_id;
|
||||
bool uses_ammo;
|
||||
mg_ammo_type ammo_type;
|
||||
uint16_t ammo_current;
|
||||
uint16_t ammo_max;
|
||||
double shoot_interval;
|
||||
double last_shot;
|
||||
} mg_weapon_t;
|
||||
|
||||
mg_weapon_t *mg_weapon_create(mg_weapon_type type);
|
||||
void mg_weapon_free(mg_weapon_t *weapon);
|
||||
mg_weapon_shoot_result mg_weapon_shoot(mg_weapon_t *weapon, gs_vqs origin);
|
||||
|
||||
#endif // MG_WEAPON_H
|
77
src/fx/rocket_trail.c
Normal file
77
src/fx/rocket_trail.c
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*================================================================
|
||||
* fx/rocket_trail.c
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "rocket_trail.h"
|
||||
#include "../entities/entity_manager.h"
|
||||
#include "../game/time_manager.h"
|
||||
|
||||
mg_rocket_trail_t *mg_rocket_trail_new(gs_vqs *rocket_transform)
|
||||
{
|
||||
mg_rocket_trail_t *trail = gs_malloc_init(mg_rocket_trail_t);
|
||||
gs_assert(mg_model_ent_init(&trail->mdl_ent_fire, *rocket_transform, "fx/rocket_flame.md3", "basic"));
|
||||
gs_assert(mg_model_ent_init(&trail->mdl_ent_smoke, *rocket_transform, "fx/rocket_smoke.md3", "basic"));
|
||||
mg_entity_manager_add_entity(&trail->mdl_ent_fire.ent, mg_rocket_trail_update, mg_rocket_trail_free);
|
||||
trail->rocket_transform = rocket_transform;
|
||||
trail->attached = true;
|
||||
mg_renderer_play_animation(trail->mdl_ent_fire.renderable_id, "BURN");
|
||||
mg_renderer_play_animation(trail->mdl_ent_smoke.renderable_id, "BURN");
|
||||
return trail;
|
||||
}
|
||||
|
||||
void mg_rocket_trail_free(mg_rocket_trail_t *trail)
|
||||
{
|
||||
mg_model_ent_free(&trail->mdl_ent_fire);
|
||||
mg_model_ent_free(&trail->mdl_ent_smoke);
|
||||
gs_free(trail);
|
||||
trail = NULL;
|
||||
}
|
||||
|
||||
void mg_rocket_trail_update(mg_rocket_trail_t *trail, double dt)
|
||||
{
|
||||
if (!trail->attached)
|
||||
{
|
||||
double frac = (g_time_manager->time - trail->detach_time) / MG_ROCKET_TRAIL_FADE_TIME;
|
||||
if (frac >= 1.0)
|
||||
{
|
||||
mg_rocket_trail_remove(trail);
|
||||
return;
|
||||
}
|
||||
float alpha = gs_max(0, 1.0 - frac);
|
||||
// TODO: set renderables override alpha
|
||||
return;
|
||||
}
|
||||
|
||||
trail->mdl_ent_fire.ent.transform = gs_vqs_absolute_transform(
|
||||
&(gs_vqs){
|
||||
.position = gs_v3(0.0f, 0.0f, 0.0f),
|
||||
.rotation = gs_quat_default(),
|
||||
.scale = gs_v3(1.0f, 1.0f, 1.0f),
|
||||
},
|
||||
trail->rocket_transform);
|
||||
trail->mdl_ent_smoke.ent.transform = gs_vqs_absolute_transform(
|
||||
&(gs_vqs){
|
||||
.position = gs_v3(0.0f, 0.0f, 0.0f),
|
||||
.rotation = gs_quat_default(),
|
||||
.scale = gs_v3(1.0f, 1.0f, 1.0f),
|
||||
},
|
||||
trail->rocket_transform);
|
||||
}
|
||||
|
||||
void mg_rocket_trail_remove(mg_rocket_trail_t *trail)
|
||||
{
|
||||
mg_entity_manager_remove_entity(trail->mdl_ent_fire.ent.id);
|
||||
mg_rocket_trail_free(trail);
|
||||
}
|
||||
|
||||
void mg_rocket_trail_detach(mg_rocket_trail_t *trail)
|
||||
{
|
||||
trail->attached = false;
|
||||
trail->rocket_transform = NULL;
|
||||
trail->detach_time = g_time_manager->time;
|
||||
}
|
34
src/fx/rocket_trail.h
Normal file
34
src/fx/rocket_trail.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*================================================================
|
||||
* fx/rocket_trail.h
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_ROCKET_TRAIL_H
|
||||
#define MG_ROCKET_TRAIL_H
|
||||
|
||||
#define MG_ROCKET_TRAIL_FADE_TIME 0.1
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../entities/entity.h"
|
||||
|
||||
typedef struct mg_rocket_trail_t
|
||||
{
|
||||
mg_model_entity_t mdl_ent_fire; // first for casting from entity manager
|
||||
mg_model_entity_t mdl_ent_smoke;
|
||||
gs_vqs *rocket_transform;
|
||||
bool attached;
|
||||
double detach_time;
|
||||
} mg_rocket_trail_t;
|
||||
|
||||
mg_rocket_trail_t *mg_rocket_trail_new(gs_vqs *rocket_transform);
|
||||
void mg_rocket_trail_free(mg_rocket_trail_t *trail);
|
||||
void mg_rocket_trail_update(mg_rocket_trail_t *trail, double dt);
|
||||
void mg_rocket_trail_remove(mg_rocket_trail_t *trail);
|
||||
void mg_rocket_trail_detach(mg_rocket_trail_t *trail);
|
||||
|
||||
#endif // MG_ROCKET_TRAIL_H
|
261
src/game/config.c
Normal file
261
src/game/config.c
Normal file
|
@ -0,0 +1,261 @@
|
|||
/*================================================================
|
||||
* util/config.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Handle config files.
|
||||
=================================================================*/
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
mg_config_t *g_config;
|
||||
|
||||
void mg_config_init()
|
||||
{
|
||||
g_config = gs_malloc(sizeof(mg_config_t));
|
||||
g_config->cvars = gs_dyn_array_new(mg_cvar_t);
|
||||
|
||||
mg_cvar_new("vid_fullscreen", MG_CONFIG_TYPE_INT, 0);
|
||||
mg_cvar_new("vid_width", MG_CONFIG_TYPE_INT, 800);
|
||||
mg_cvar_new("vid_height", MG_CONFIG_TYPE_INT, 600);
|
||||
#ifdef __ANDROID__
|
||||
mg_cvar_new("vid_max_fps", MG_CONFIG_TYPE_INT, 60);
|
||||
#else
|
||||
mg_cvar_new("vid_max_fps", MG_CONFIG_TYPE_INT, 240);
|
||||
#endif
|
||||
mg_cvar_new("vid_vsync", MG_CONFIG_TYPE_INT, 0);
|
||||
|
||||
mg_cvar_new("snd_master", MG_CONFIG_TYPE_FLOAT, 0.1f);
|
||||
mg_cvar_new("snd_effect", MG_CONFIG_TYPE_FLOAT, 0.6f);
|
||||
mg_cvar_new("snd_music", MG_CONFIG_TYPE_FLOAT, 1.0f);
|
||||
mg_cvar_new("snd_ambient", MG_CONFIG_TYPE_FLOAT, 1.0f);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
mg_cvar_new("r_fov", MG_CONFIG_TYPE_INT, 90);
|
||||
#else
|
||||
mg_cvar_new("r_fov", MG_CONFIG_TYPE_INT, 115);
|
||||
#endif
|
||||
mg_cvar_new("r_barrel_enabled", MG_CONFIG_TYPE_INT, 1);
|
||||
mg_cvar_new("r_barrel_strength", MG_CONFIG_TYPE_FLOAT, 0.5f);
|
||||
mg_cvar_new("r_barrel_cyl_ratio", MG_CONFIG_TYPE_FLOAT, 1.0f);
|
||||
mg_cvar_new("r_filter_mip", MG_CONFIG_TYPE_INT, 1);
|
||||
#ifdef __ANDROID__
|
||||
mg_cvar_new("r_filter", MG_CONFIG_TYPE_INT, 1);
|
||||
mg_cvar_new("r_mips", MG_CONFIG_TYPE_INT, 1);
|
||||
#else
|
||||
mg_cvar_new("r_filter", MG_CONFIG_TYPE_INT, 0);
|
||||
mg_cvar_new("r_mips", MG_CONFIG_TYPE_INT, 0);
|
||||
#endif
|
||||
mg_cvar_new("r_wireframe", MG_CONFIG_TYPE_INT, 0);
|
||||
|
||||
mg_cvar_new("r_viewmodel_fov", MG_CONFIG_TYPE_INT, 65);
|
||||
mg_cvar_new("r_viewmodel_pos_x", MG_CONFIG_TYPE_FLOAT, 0.0f);
|
||||
mg_cvar_new("r_viewmodel_pos_y", MG_CONFIG_TYPE_FLOAT, 15.0f);
|
||||
mg_cvar_new("r_viewmodel_pos_z", MG_CONFIG_TYPE_FLOAT, -10.0f);
|
||||
mg_cvar_new("r_viewmodel_scale_x", MG_CONFIG_TYPE_FLOAT, 1.0f);
|
||||
mg_cvar_new("r_viewmodel_scale_y", MG_CONFIG_TYPE_FLOAT, 1.0f);
|
||||
mg_cvar_new("r_viewmodel_scale_z", MG_CONFIG_TYPE_FLOAT, 1.0f);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
mg_cvar_new("cl_sensitivity", MG_CONFIG_TYPE_FLOAT, 5.0f);
|
||||
#else
|
||||
mg_cvar_new("cl_sensitivity", MG_CONFIG_TYPE_FLOAT, 2.0f);
|
||||
#endif
|
||||
|
||||
mg_cvar_new("cl_timescale", MG_CONFIG_TYPE_FLOAT, 1.0f);
|
||||
|
||||
mg_cvar_new_str("stringtest", MG_CONFIG_TYPE_STRING, "Sandvich make me strong!");
|
||||
|
||||
// Load config if exists
|
||||
if (gs_platform_file_exists("assets/cfg/config.txt"))
|
||||
{
|
||||
_mg_config_load("assets/cfg/config.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_println("WARN: missing assets/cfg/config.txt, saving default");
|
||||
_mg_config_save("assets/cfg/config.txt");
|
||||
}
|
||||
|
||||
mg_cmd_new("cvars", "Shows available cvars", &mg_config_print, NULL, 0);
|
||||
}
|
||||
|
||||
void mg_config_free()
|
||||
{
|
||||
_mg_config_save("assets/cfg/config.txt");
|
||||
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_config->cvars); i++)
|
||||
{
|
||||
if (g_config->cvars->type == MG_CONFIG_TYPE_STRING)
|
||||
{
|
||||
gs_free(g_config->cvars[i].value.s);
|
||||
g_config->cvars[i].value.s = NULL;
|
||||
}
|
||||
}
|
||||
gs_dyn_array_free(g_config->cvars);
|
||||
|
||||
gs_free(g_config);
|
||||
g_config = NULL;
|
||||
}
|
||||
|
||||
mg_cvar_t *mg_config_get(char *name)
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_config->cvars); i++)
|
||||
{
|
||||
if (strcmp(g_config->cvars[i].name, name) == 0)
|
||||
{
|
||||
return &g_config->cvars[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mg_config_print()
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_config->cvars); i++)
|
||||
{
|
||||
mg_cvar_print(&g_config->cvars[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Load config from file
|
||||
void _mg_config_load(char *filepath)
|
||||
{
|
||||
if (!gs_platform_file_exists(filepath))
|
||||
{
|
||||
mg_println("WARN: failed to read config file %s", filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
mg_println("Loading config from '%s'", filepath);
|
||||
|
||||
char *file_data = gs_platform_read_file_contents(filepath, "r", NULL);
|
||||
|
||||
char *line;
|
||||
char *line_ptr;
|
||||
char *token;
|
||||
u8 num_line = 0;
|
||||
line = strtok_r(file_data, "\r\n", &line_ptr);
|
||||
while (line != NULL)
|
||||
{
|
||||
num_line++;
|
||||
// gs_println("line %d: %s", num_line, line);
|
||||
|
||||
// Empty line
|
||||
if (line[0] == '\n')
|
||||
{
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Comment
|
||||
if (line[0] == '/' && line[1] == '/')
|
||||
{
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
token = strtok(line, " ");
|
||||
if (!token)
|
||||
{
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
}
|
||||
|
||||
mg_cvar_t *cvar = mg_cvar(token);
|
||||
if (cvar == NULL)
|
||||
{
|
||||
mg_println("WARN: _mg_config_load unknown cvar name %s", token);
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cvar->type == MG_CONFIG_TYPE_STRING)
|
||||
{
|
||||
token = strtok(NULL, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
token = strtok(NULL, " ");
|
||||
}
|
||||
|
||||
if (!token)
|
||||
{
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (cvar->type)
|
||||
{
|
||||
case MG_CONFIG_TYPE_INT:
|
||||
cvar->value.i = strtol(token, (char **)NULL, 10);
|
||||
break;
|
||||
|
||||
case MG_CONFIG_TYPE_FLOAT:
|
||||
cvar->value.f = strtof(token, (char **)NULL);
|
||||
break;
|
||||
|
||||
case MG_CONFIG_TYPE_STRING:
|
||||
memset(cvar->value.s, 0, MG_CVAR_STR_LEN);
|
||||
memcpy(cvar->value.s, token, gs_min(MG_CVAR_STR_LEN - 1, gs_string_length(token)));
|
||||
break;
|
||||
|
||||
default:
|
||||
mg_println("WARN: _mg_config_load unknown cvar type %d", cvar->type);
|
||||
break;
|
||||
}
|
||||
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
}
|
||||
|
||||
gs_free(file_data);
|
||||
mg_println("Config loaded");
|
||||
}
|
||||
|
||||
// Save current config to filepath.
|
||||
// User formatting and replacing values is a hassle,
|
||||
// let's just dump all cvars and overwrite.
|
||||
void _mg_config_save(char *filepath)
|
||||
{
|
||||
mg_println("Saving config to '%s'", filepath);
|
||||
|
||||
FILE *file = gs_platform_open_file(filepath, "w");
|
||||
if (file == NULL)
|
||||
{
|
||||
mg_println("WARN: _mg_config_save couldn't save config to '%s'", filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
char line[128];
|
||||
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_config->cvars); i++)
|
||||
{
|
||||
mg_cvar_t cvar = g_config->cvars[i];
|
||||
gs_fprintf(file, "%s ", g_config->cvars[i].name);
|
||||
switch (g_config->cvars[i].type)
|
||||
{
|
||||
case MG_CONFIG_TYPE_INT:
|
||||
gs_fprintf(file, "%d\n", g_config->cvars[i].value.i);
|
||||
break;
|
||||
|
||||
case MG_CONFIG_TYPE_FLOAT:
|
||||
gs_fprintf(file, "%f\n", g_config->cvars[i].value.f);
|
||||
break;
|
||||
|
||||
case MG_CONFIG_TYPE_STRING:
|
||||
gs_fprintf(file, "%s\n", g_config->cvars[i].value.s);
|
||||
break;
|
||||
|
||||
default:
|
||||
mg_println("WARN: _mg_config_save unknown cvar type %d", g_config->cvars[i].type);
|
||||
gs_fprintf(file, "%d\n", g_config->cvars[i].value.i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fflush(file);
|
||||
fclose(file);
|
||||
mg_println("Config saved");
|
||||
}
|
121
src/game/config.h
Normal file
121
src/game/config.h
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*================================================================
|
||||
* game/config.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Config types.
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_CONFIG_H
|
||||
#define MG_CONFIG_H
|
||||
|
||||
#include "console.h"
|
||||
#include <gs/gs.h>
|
||||
|
||||
#define MG_CVAR_STR_LEN 64
|
||||
|
||||
typedef enum mg_cvar_type
|
||||
{
|
||||
MG_CONFIG_TYPE_INT,
|
||||
MG_CONFIG_TYPE_FLOAT,
|
||||
MG_CONFIG_TYPE_STRING,
|
||||
MG_CONFIG_TYPE_COUNT,
|
||||
} mg_cvar_type;
|
||||
|
||||
typedef struct mg_cvar_t
|
||||
{
|
||||
char name[MG_CVAR_STR_LEN];
|
||||
mg_cvar_type type;
|
||||
union
|
||||
{
|
||||
float32_t f;
|
||||
int32_t i;
|
||||
char *s;
|
||||
} value;
|
||||
} mg_cvar_t;
|
||||
|
||||
typedef struct mg_config_t
|
||||
{
|
||||
gs_dyn_array(mg_cvar_t) cvars;
|
||||
} mg_config_t;
|
||||
|
||||
void mg_config_init();
|
||||
void mg_config_free();
|
||||
mg_cvar_t *mg_config_get(char *name);
|
||||
void mg_config_print();
|
||||
void _mg_config_load(char *filepath);
|
||||
void _mg_config_save(char *filepath);
|
||||
|
||||
extern mg_config_t *g_config;
|
||||
|
||||
#define mg_cvar_new(n, t, v) \
|
||||
{ \
|
||||
mg_cvar_t cvar = (mg_cvar_t){.name = n, .type = t, .value = 0}; \
|
||||
if (t == MG_CONFIG_TYPE_INT) \
|
||||
{ \
|
||||
cvar.value.i = v; \
|
||||
} \
|
||||
else if (t == MG_CONFIG_TYPE_FLOAT) \
|
||||
{ \
|
||||
cvar.value.f = v; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
gs_assert(false); \
|
||||
} \
|
||||
gs_dyn_array_push(g_config->cvars, cvar); \
|
||||
}
|
||||
|
||||
// painful to make into a single macro because of float -> char* casting
|
||||
#define mg_cvar_new_str(n, t, v) \
|
||||
{ \
|
||||
mg_cvar_t cvar = (mg_cvar_t){.name = n, .type = t, .value = {0}}; \
|
||||
cvar.value.s = gs_malloc(MG_CVAR_STR_LEN); \
|
||||
memset(cvar.value.s, 0, MG_CVAR_STR_LEN); \
|
||||
memcpy(cvar.value.s, v, gs_min(MG_CVAR_STR_LEN - 1, gs_string_length(v))); \
|
||||
gs_dyn_array_push(g_config->cvars, cvar); \
|
||||
}
|
||||
|
||||
#define mg_cvar(n) \
|
||||
mg_config_get(n)
|
||||
|
||||
#define mg_cvar_print(cvar) \
|
||||
{ \
|
||||
switch ((cvar)->type) \
|
||||
{ \
|
||||
default: \
|
||||
case MG_CONFIG_TYPE_STRING: \
|
||||
mg_println("%s = %s", (cvar)->name, (cvar)->value.s); \
|
||||
break; \
|
||||
\
|
||||
case MG_CONFIG_TYPE_FLOAT: \
|
||||
mg_println("%s = %f", (cvar)->name, (cvar)->value.f); \
|
||||
break; \
|
||||
\
|
||||
case MG_CONFIG_TYPE_INT: \
|
||||
mg_println("%s = %d", (cvar)->name, (cvar)->value.i); \
|
||||
break; \
|
||||
}; \
|
||||
}
|
||||
|
||||
#define mg_cvar_set(cvar, str) \
|
||||
{ \
|
||||
switch ((cvar)->type) \
|
||||
{ \
|
||||
default: \
|
||||
case MG_CONFIG_TYPE_STRING: \
|
||||
memcpy((cvar)->value.s, str, gs_string_length(str) + 1); \
|
||||
break; \
|
||||
\
|
||||
case MG_CONFIG_TYPE_FLOAT: \
|
||||
(cvar)->value.f = atof(str); \
|
||||
break; \
|
||||
\
|
||||
case MG_CONFIG_TYPE_INT: \
|
||||
(cvar)->value.i = atoi(str); \
|
||||
break; \
|
||||
}; \
|
||||
}
|
||||
|
||||
#endif // MG_CONFIG_H
|
242
src/game/console.c
Normal file
242
src/game/console.c
Normal file
|
@ -0,0 +1,242 @@
|
|||
#include "console.h"
|
||||
#include "config.h"
|
||||
|
||||
mg_console_t *g_console;
|
||||
|
||||
void mg_console_init()
|
||||
{
|
||||
g_console = gs_malloc(sizeof(mg_console_t));
|
||||
for (size_t i = 0; i < MG_CON_LINES; i++)
|
||||
{
|
||||
g_console->output[i] = NULL;
|
||||
}
|
||||
for (size_t i = 0; i < MG_CON_HIST; i++)
|
||||
{
|
||||
g_console->input[i] = NULL;
|
||||
}
|
||||
g_console->commands = gs_dyn_array_new(mg_cmd_t);
|
||||
|
||||
mg_cmd_new("clear", "Clears the console", &mg_console_clear, NULL, 0);
|
||||
mg_cmd_new("help", "Shows available commands", &mg_console_help, NULL, 0);
|
||||
mg_cmd_new("exit", "Exits the game", &gs_quit, NULL, 0);
|
||||
}
|
||||
|
||||
void mg_console_free()
|
||||
{
|
||||
for (size_t i = 0; i < MG_CON_LINES; i++)
|
||||
{
|
||||
gs_free(g_console->output[i]);
|
||||
}
|
||||
for (size_t i = 0; i < MG_CON_HIST; i++)
|
||||
{
|
||||
gs_free(g_console->input[i]);
|
||||
}
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_console->commands); i++)
|
||||
{
|
||||
// gs_free(g_console->commands[i].name);
|
||||
// gs_free(g_console->commands[i].help);
|
||||
if (g_console->commands[i].argt != NULL)
|
||||
{
|
||||
gs_free(g_console->commands[i].argt);
|
||||
}
|
||||
}
|
||||
gs_dyn_array_free(g_console->commands);
|
||||
gs_free(g_console);
|
||||
}
|
||||
|
||||
void mg_console_println(const char *text)
|
||||
{
|
||||
size_t sz = gs_string_length(text) + 1;
|
||||
char *target = mg_console_get_last(g_console->output, MG_CON_LINES, sz);
|
||||
if (target != NULL)
|
||||
{
|
||||
memcpy(target, text, sz);
|
||||
}
|
||||
}
|
||||
|
||||
void mg_console_input(const char *text)
|
||||
{
|
||||
// Store in input history
|
||||
size_t sz = gs_string_length(text) + 1;
|
||||
char *target = mg_console_get_last(g_console->input, MG_CON_HIST, sz);
|
||||
if (target != NULL)
|
||||
{
|
||||
memcpy(target, text, sz);
|
||||
}
|
||||
|
||||
// Store in output history, add prefix
|
||||
size_t tmp_sz = sz + 2;
|
||||
char *tmp = gs_malloc(tmp_sz);
|
||||
tmp[0] = '>';
|
||||
tmp[1] = ' ';
|
||||
memcpy(&tmp[2], text, sz);
|
||||
target = mg_console_get_last(g_console->output, MG_CON_LINES, tmp_sz);
|
||||
if (target != NULL)
|
||||
{
|
||||
memcpy(target, tmp, tmp_sz);
|
||||
}
|
||||
gs_free(tmp);
|
||||
|
||||
char *token = strtok(text, " ");
|
||||
if (!token)
|
||||
{
|
||||
mg_println("ERR: Failed to tokenize console input %s", text);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle commands
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_console->commands); i++)
|
||||
{
|
||||
mg_cmd_t cmd = g_console->commands[i];
|
||||
if (strcmp(text, cmd.name) == 0)
|
||||
{
|
||||
if (cmd.argc == 0)
|
||||
{
|
||||
mg_console_run(cmd, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
void **argv = gs_malloc(cmd.argc * sizeof(void *));
|
||||
for (size_t j = 0; j < cmd.argc; j++)
|
||||
{
|
||||
token = strtok(NULL, " ");
|
||||
if (!token)
|
||||
{
|
||||
mg_println("ERR: Not enough arguments for command '%s'. Expected %d.", cmd.name, cmd.argc);
|
||||
for (size_t k = 0; k < j; k++)
|
||||
{
|
||||
gs_free(argv[k]);
|
||||
}
|
||||
|
||||
gs_free(argv);
|
||||
return;
|
||||
}
|
||||
mg_cmd_arg_type ttt = cmd.argt[j];
|
||||
switch (cmd.argt[j])
|
||||
{
|
||||
default:
|
||||
case MG_CMD_ARG_STRING:
|
||||
sz = gs_string_length(token) + 1;
|
||||
argv[j] = gs_malloc(sz);
|
||||
memcpy(argv[j], token, sz);
|
||||
break;
|
||||
|
||||
case MG_CMD_ARG_INT:
|
||||
argv[j] = gs_malloc(sizeof(int));
|
||||
*(int *)argv[j] = atoi(token);
|
||||
break;
|
||||
|
||||
case MG_CMD_ARG_FLOAT:
|
||||
argv[j] = gs_malloc(sizeof(float));
|
||||
*(float *)argv[j] = atof(token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mg_console_run(cmd, argv);
|
||||
|
||||
for (size_t j = 0; j < cmd.argc; j++)
|
||||
{
|
||||
gs_free(argv[j]);
|
||||
}
|
||||
gs_free(argv);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cvars
|
||||
mg_cvar_t *cvar = mg_cvar(token);
|
||||
if (cvar)
|
||||
{
|
||||
token = strtok(NULL, " ");
|
||||
if (token)
|
||||
{
|
||||
mg_cvar_set(cvar, token);
|
||||
}
|
||||
mg_cvar_print(cvar);
|
||||
return;
|
||||
}
|
||||
|
||||
mg_println("Unknown command or cvar '%s'. Type 'help' to show available commands.", text);
|
||||
}
|
||||
|
||||
void mg_console_run(const mg_cmd_t cmd, void **argv)
|
||||
{
|
||||
if (cmd.func == NULL)
|
||||
{
|
||||
mg_println("ERR: Command '%s' doesn't have a function", cmd.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Too lazy for vargs, shouldn't need more than this...
|
||||
if (cmd.argc == 0)
|
||||
{
|
||||
(cmd.func)(NULL);
|
||||
}
|
||||
else if (cmd.argc == 1)
|
||||
{
|
||||
void (*tmp)(void *) = cmd.func;
|
||||
(tmp)(argv[0]);
|
||||
}
|
||||
else if (cmd.argc == 2)
|
||||
{
|
||||
void (*tmp)(void *, void *) = cmd.func;
|
||||
(tmp)(argv[0], argv[1]);
|
||||
}
|
||||
else if (cmd.argc == 3)
|
||||
{
|
||||
void (*tmp)(void *, void *, void *) = cmd.func;
|
||||
(tmp)(argv[0], argv[1], argv[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_println("WARN: not unpacking arguments for command '%s'", cmd.name);
|
||||
(cmd.func)(argv);
|
||||
}
|
||||
}
|
||||
|
||||
char *mg_console_get_last(char **container, size_t container_len, size_t sz)
|
||||
{
|
||||
if (container[0] != NULL)
|
||||
{
|
||||
// Container is full, shift all lines by 1
|
||||
char *tmp = container[container_len - 1];
|
||||
memcpy(&container[1], &container[0], sizeof(char *) * container_len - 1);
|
||||
container[0] = gs_realloc(tmp, sz);
|
||||
return container[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find first unused line
|
||||
for (size_t i = container_len - 1; i >= 0; i--)
|
||||
{
|
||||
if (container[i] == NULL)
|
||||
{
|
||||
container[i] = gs_malloc(sz);
|
||||
return container[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mg_console_clear()
|
||||
{
|
||||
for (size_t i = 0; i < MG_CON_LINES; i++)
|
||||
{
|
||||
gs_free(g_console->output[i]);
|
||||
g_console->output[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void mg_console_help()
|
||||
{
|
||||
mg_println("help:");
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_console->commands); i++)
|
||||
{
|
||||
mg_println(" '%s' - %s", g_console->commands[i].name, g_console->commands[i].help);
|
||||
}
|
||||
}
|
68
src/game/console.h
Normal file
68
src/game/console.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
#ifndef MG_CONSOLE_H
|
||||
#define MG_CONSOLE_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#define MG_CON_LINES 512
|
||||
#define MG_CON_HIST 64
|
||||
|
||||
typedef enum mg_cmd_arg_type
|
||||
{
|
||||
MG_CMD_ARG_STRING,
|
||||
MG_CMD_ARG_INT,
|
||||
MG_CMD_ARG_FLOAT,
|
||||
MG_CMD_ARG_COUNT,
|
||||
} mg_cmd_arg_type;
|
||||
|
||||
typedef struct mg_cmd_t
|
||||
{
|
||||
char *name;
|
||||
char *help;
|
||||
void (*func)(void **);
|
||||
mg_cmd_arg_type *argt;
|
||||
uint8_t argc;
|
||||
} mg_cmd_t;
|
||||
|
||||
typedef struct mg_console_t
|
||||
{
|
||||
char *output[MG_CON_LINES];
|
||||
char *input[MG_CON_HIST];
|
||||
gs_dyn_array(mg_cmd_t) commands;
|
||||
} mg_console_t;
|
||||
|
||||
void mg_console_init();
|
||||
void mg_console_free();
|
||||
|
||||
void mg_console_println(const char *text);
|
||||
void mg_console_input(const char *text);
|
||||
void mg_console_run(const mg_cmd_t cmd, void **argv);
|
||||
char *mg_console_get_last(char **container, size_t container_len, size_t sz);
|
||||
|
||||
void mg_console_clear();
|
||||
void mg_console_help();
|
||||
|
||||
extern mg_console_t *g_console;
|
||||
|
||||
#define mg_println(__FMT, ...) \
|
||||
{ \
|
||||
gs_printf(__FMT, ##__VA_ARGS__); \
|
||||
gs_printf("\n"); \
|
||||
\
|
||||
char *tmp = gs_malloc(1024); \
|
||||
gs_snprintf(tmp, 1024, __FMT, ##__VA_ARGS__); \
|
||||
if (g_console != NULL) mg_console_println(tmp); \
|
||||
gs_free(tmp); \
|
||||
}
|
||||
|
||||
#define mg_cmd_new(n, h, f, t, c) \
|
||||
{ \
|
||||
mg_cmd_t cmd = (mg_cmd_t){.name = n, .help = h, .func = f, .argt = NULL, .argc = c}; \
|
||||
if (c > 0) \
|
||||
{ \
|
||||
cmd.argt = gs_malloc(c * sizeof(mg_cmd_arg_type)); \
|
||||
memcpy(cmd.argt, t, c * sizeof(mg_cmd_arg_type)); \
|
||||
} \
|
||||
gs_dyn_array_push(g_console->commands, cmd); \
|
||||
}
|
||||
|
||||
#endif // MG_CONSOLE_H
|
307
src/game/game_manager.c
Normal file
307
src/game/game_manager.c
Normal file
|
@ -0,0 +1,307 @@
|
|||
#include "game_manager.h"
|
||||
#include "../graphics/renderer.h"
|
||||
#include "../graphics/ui_manager.h"
|
||||
#include "../util/transform.h"
|
||||
#include "config.h"
|
||||
#include "console.h"
|
||||
#include "monster_manager.h"
|
||||
#include "time_manager.h"
|
||||
|
||||
mg_game_manager_t *g_game_manager;
|
||||
|
||||
void mg_game_manager_init()
|
||||
{
|
||||
g_game_manager = gs_malloc_init(mg_game_manager_t);
|
||||
g_game_manager->player = mg_player_new();
|
||||
|
||||
mg_game_manager_load_map("assets/maps/q3dm1.bsp");
|
||||
mg_game_manager_spawn_player();
|
||||
|
||||
mg_monster_manager_init();
|
||||
|
||||
mg_cmd_arg_type types[] = {MG_CMD_ARG_STRING};
|
||||
mg_cmd_new("map", "Load map", &mg_game_manager_load_map, (mg_cmd_arg_type *)types, 1);
|
||||
mg_cmd_new("spawn", "Spawn player", &mg_game_manager_spawn_player, NULL, 0);
|
||||
}
|
||||
|
||||
void mg_game_manager_free()
|
||||
{
|
||||
mg_monster_manager_free();
|
||||
|
||||
mg_player_free(g_game_manager->player);
|
||||
g_game_manager->player = NULL;
|
||||
bsp_map_free(g_game_manager->map);
|
||||
g_game_manager->map = NULL;
|
||||
|
||||
gs_free(g_game_manager);
|
||||
g_game_manager = NULL;
|
||||
}
|
||||
|
||||
void mg_game_manager_update()
|
||||
{
|
||||
if (g_ui_manager->console_open)
|
||||
{
|
||||
mg_game_manager_input_console();
|
||||
}
|
||||
else if (g_ui_manager->menu_open)
|
||||
{
|
||||
mg_game_manager_input_menu();
|
||||
}
|
||||
else if (g_game_manager->player)
|
||||
{
|
||||
mg_game_manager_input_alive();
|
||||
mg_player_update(g_game_manager->player);
|
||||
mg_monster_manager_update();
|
||||
}
|
||||
|
||||
mg_game_manager_input_general();
|
||||
}
|
||||
|
||||
void mg_game_manager_load_map(char *filename)
|
||||
{
|
||||
if (!gs_platform_file_exists(filename))
|
||||
{
|
||||
mg_println("mg_game_manager_load_map() failed: file not found '%s'", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_game_manager->map != NULL)
|
||||
{
|
||||
bsp_map_free(g_game_manager->map);
|
||||
g_game_manager->map = NULL;
|
||||
}
|
||||
|
||||
g_game_manager->map = gs_malloc_init(bsp_map_t);
|
||||
load_bsp(filename, g_game_manager->map);
|
||||
|
||||
if (g_game_manager->map->valid)
|
||||
{
|
||||
bsp_map_init(g_game_manager->map);
|
||||
mg_game_manager_spawn_player();
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_println("Failed to load map %s", filename);
|
||||
bsp_map_free(g_game_manager->map);
|
||||
g_game_manager->map = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void mg_game_manager_spawn_player()
|
||||
{
|
||||
if (g_game_manager->map->valid)
|
||||
{
|
||||
g_game_manager->player->velocity = gs_v3(0, 0, 0);
|
||||
g_game_manager->player->camera.pitch = 0;
|
||||
bsp_map_find_spawn_point(g_game_manager->map, &g_game_manager->player->transform.position, &g_game_manager->player->yaw);
|
||||
g_game_manager->player->last_valid_pos = g_game_manager->player->transform.position;
|
||||
g_game_manager->player->yaw -= 90;
|
||||
g_renderer->cam = &g_game_manager->player->camera.cam;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
mg_player_input_t mg_game_manager_get_input()
|
||||
{
|
||||
mg_player_input_t input = {0};
|
||||
|
||||
// Testing
|
||||
input.move.x += 1.0f;
|
||||
|
||||
if (gs_platform_touch_down(0))
|
||||
{
|
||||
input.delta_aim = gs_vec2_scale(gs_platform_touch_deltav(0), mg_cvar("cl_sensitivity")->value.f * 0.022f);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
#else
|
||||
mg_player_input_t mg_game_manager_get_input()
|
||||
{
|
||||
double dt = g_time_manager->unscaled_delta;
|
||||
mg_player_input_t input = {0};
|
||||
|
||||
input.delta_aim = gs_vec2_scale(gs_platform_mouse_deltav(), mg_cvar("cl_sensitivity")->value.f * 0.022f);
|
||||
|
||||
f32 scroll_x, scroll_y;
|
||||
gs_platform_mouse_wheel(&scroll_x, &scroll_y);
|
||||
|
||||
if (gs_platform_key_down(GS_KEYCODE_UP))
|
||||
input.delta_aim.y -= 150.0f * dt;
|
||||
if (gs_platform_key_down(GS_KEYCODE_DOWN))
|
||||
input.delta_aim.y += 150.0f * dt;
|
||||
if (gs_platform_key_down(GS_KEYCODE_RIGHT))
|
||||
input.delta_aim.x += 150.0f * dt;
|
||||
if (gs_platform_key_down(GS_KEYCODE_LEFT))
|
||||
input.delta_aim.x -= 150.0f * dt;
|
||||
|
||||
if (gs_platform_key_down(GS_KEYCODE_W))
|
||||
input.move.x += 1.0f;
|
||||
if (gs_platform_key_down(GS_KEYCODE_S))
|
||||
input.move.x -= 1.0f;
|
||||
if (gs_platform_key_down(GS_KEYCODE_D))
|
||||
input.move.y += 1.0f;
|
||||
if (gs_platform_key_down(GS_KEYCODE_A))
|
||||
input.move.y -= 1.0f;
|
||||
|
||||
if (gs_platform_key_down(GS_KEYCODE_SPACE))
|
||||
input.jump = true;
|
||||
if (gs_platform_key_down(GS_KEYCODE_LEFT_CONTROL))
|
||||
input.crouch = true;
|
||||
|
||||
if (gs_platform_mouse_down(GS_MOUSE_LBUTTON))
|
||||
input.shoot = true;
|
||||
|
||||
input.wish_slot = -1;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_1))
|
||||
input.wish_slot = 0;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_2))
|
||||
input.wish_slot = 1;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_3))
|
||||
input.wish_slot = 2;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_4))
|
||||
input.wish_slot = 3;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_5))
|
||||
input.wish_slot = 4;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_6))
|
||||
input.wish_slot = 5;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_7))
|
||||
input.wish_slot = 6;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_8))
|
||||
input.wish_slot = 7;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_9))
|
||||
input.wish_slot = 8;
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_0))
|
||||
input.wish_slot = 9;
|
||||
|
||||
// TODO: weapon scroll
|
||||
// if (scroll_y > 0)
|
||||
// ...
|
||||
// else if (scroll_y < 0)
|
||||
// ...
|
||||
|
||||
return input;
|
||||
}
|
||||
#endif
|
||||
|
||||
void mg_game_manager_input_alive()
|
||||
{
|
||||
gs_platform_t *platform = gs_subsystem(platform);
|
||||
|
||||
// Reset
|
||||
g_game_manager->player->wish_move = gs_v3(0, 0, 0);
|
||||
g_game_manager->player->wish_jump = false;
|
||||
g_game_manager->player->wish_crouch = false;
|
||||
|
||||
mg_player_input_t input = mg_game_manager_get_input();
|
||||
|
||||
// Rotate
|
||||
g_game_manager->player->camera.pitch = gs_clamp(g_game_manager->player->camera.pitch + input.delta_aim.y, -90.0f, 90.0f);
|
||||
g_game_manager->player->yaw = fmodf(g_game_manager->player->yaw - input.delta_aim.x, 360.0f);
|
||||
g_game_manager->player->transform.rotation = gs_quat_angle_axis(gs_deg2rad(g_game_manager->player->yaw), MG_AXIS_UP);
|
||||
|
||||
// Move dir
|
||||
if (input.move.x != 0)
|
||||
g_game_manager->player->wish_move = gs_vec3_add(g_game_manager->player->wish_move, gs_vec3_scale(mg_get_forward(g_game_manager->player->transform.rotation), input.move.x));
|
||||
if (input.move.y != 0)
|
||||
g_game_manager->player->wish_move = gs_vec3_add(g_game_manager->player->wish_move, gs_vec3_scale(mg_get_right(g_game_manager->player->transform.rotation), input.move.y));
|
||||
|
||||
g_game_manager->player->wish_move.z = 0;
|
||||
g_game_manager->player->wish_move = gs_vec3_norm(g_game_manager->player->wish_move);
|
||||
|
||||
// Actions
|
||||
g_game_manager->player->wish_jump = input.jump;
|
||||
g_game_manager->player->wish_crouch = input.crouch;
|
||||
g_game_manager->player->wish_shoot = input.shoot;
|
||||
|
||||
if (input.wish_slot >= 0)
|
||||
{
|
||||
mg_player_switch_weapon(g_game_manager->player, input.wish_slot);
|
||||
}
|
||||
|
||||
// TODO: platform
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_ESC))
|
||||
{
|
||||
g_ui_manager->menu_open = true;
|
||||
}
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_F))
|
||||
{
|
||||
mg_monster_manager_spawn_monster(
|
||||
gs_vec3_add(g_game_manager->player->transform.position, gs_vec3_scale(mg_get_forward(g_game_manager->player->transform.rotation), 250.0f)),
|
||||
"cube.md3");
|
||||
}
|
||||
}
|
||||
|
||||
void mg_game_manager_input_console()
|
||||
{
|
||||
f32 scroll_x, scroll_y;
|
||||
gs_platform_mouse_wheel(&scroll_x, &scroll_y);
|
||||
|
||||
if (gs_platform_key_down(GS_KEYCODE_LSHIFT))
|
||||
{
|
||||
scroll_x = scroll_y;
|
||||
scroll_y = 0;
|
||||
}
|
||||
|
||||
if (scroll_y != 0)
|
||||
{
|
||||
g_ui_manager->console_scroll_y += scroll_y < 0 ? -4 : 4;
|
||||
g_ui_manager->console_scroll_y = gs_clamp(g_ui_manager->console_scroll_y, 0, MG_CON_LINES - 1);
|
||||
}
|
||||
if (scroll_x != 0)
|
||||
{
|
||||
g_ui_manager->console_scroll_x += scroll_x < 0 ? -4 : 4;
|
||||
g_ui_manager->console_scroll_x = gs_min(g_ui_manager->console_scroll_x, 0);
|
||||
}
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_ENTER))
|
||||
{
|
||||
mg_console_input(g_ui_manager->console_input);
|
||||
memset(g_ui_manager->console_input, 0, 256);
|
||||
}
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_ESC))
|
||||
{
|
||||
g_ui_manager->console_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
void mg_game_manager_input_menu()
|
||||
{
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_ESC))
|
||||
{
|
||||
g_ui_manager->menu_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
void mg_game_manager_input_general()
|
||||
{
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_F1))
|
||||
{
|
||||
g_ui_manager->console_open = !g_ui_manager->console_open;
|
||||
g_ui_manager->console_scroll_y = 0;
|
||||
g_ui_manager->console_scroll_x = 0;
|
||||
}
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_F2))
|
||||
{
|
||||
g_ui_manager->debug_open = !g_ui_manager->debug_open;
|
||||
}
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_F3))
|
||||
{
|
||||
mg_cvar_t *fs = mg_cvar("vid_fullscreen");
|
||||
fs->value.i = !fs->value.i;
|
||||
}
|
||||
}
|
41
src/game/game_manager.h
Normal file
41
src/game/game_manager.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef MG_GAME_MANAGER_H
|
||||
#define MG_GAME_MANAGER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../bsp/bsp_loader.h"
|
||||
#include "../bsp/bsp_map.h"
|
||||
#include "../entities/player.h"
|
||||
|
||||
typedef struct mg_game_manager_t
|
||||
{
|
||||
bsp_map_t *map;
|
||||
mg_player_t *player;
|
||||
} mg_game_manager_t;
|
||||
|
||||
typedef struct mg_player_input_t
|
||||
{
|
||||
gs_vec2 delta_aim;
|
||||
gs_vec2 move;
|
||||
bool jump;
|
||||
bool crouch;
|
||||
bool shoot;
|
||||
int32_t wish_slot;
|
||||
} mg_player_input_t;
|
||||
|
||||
void mg_game_manager_init();
|
||||
void mg_game_manager_free();
|
||||
void mg_game_manager_update();
|
||||
|
||||
void mg_game_manager_load_map(char *filename);
|
||||
void mg_game_manager_spawn_player();
|
||||
|
||||
mg_player_input_t mg_game_manager_get_input();
|
||||
void mg_game_manager_input_alive();
|
||||
void mg_game_manager_input_console();
|
||||
void mg_game_manager_input_menu();
|
||||
void mg_game_manager_input_general();
|
||||
|
||||
extern mg_game_manager_t *g_game_manager;
|
||||
|
||||
#endif // MG_GAME_MANAGER_H
|
53
src/game/monster_manager.c
Normal file
53
src/game/monster_manager.c
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include "monster_manager.h"
|
||||
#include "../entities/monster.h"
|
||||
#include "../graphics/renderer.h"
|
||||
#include "../graphics/ui_manager.h"
|
||||
#include "../util/transform.h"
|
||||
#include "config.h"
|
||||
#include "console.h"
|
||||
|
||||
mg_monster_manager_t *g_monster_manager;
|
||||
|
||||
void mg_monster_manager_init()
|
||||
{
|
||||
g_monster_manager = gs_malloc_init(mg_monster_manager_t);
|
||||
g_monster_manager->monsters = gs_dyn_array_new(mg_monster_t *);
|
||||
|
||||
// TODO
|
||||
// mg_cmd_arg_type types[] = {MG_CMD_ARG_STRING};
|
||||
// mg_cmd_new("monster", "Spawn monster", &mg_monster_manager_spawn_monster, (mg_cmd_arg_type *)types, 1);
|
||||
}
|
||||
|
||||
void mg_monster_manager_free()
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_monster_manager->monsters); i++)
|
||||
{
|
||||
mg_monster_free(g_monster_manager->monsters[i]);
|
||||
g_monster_manager->monsters[i] = NULL;
|
||||
}
|
||||
|
||||
gs_dyn_array_free(g_monster_manager->monsters);
|
||||
gs_free(g_monster_manager);
|
||||
g_monster_manager = NULL;
|
||||
}
|
||||
|
||||
void mg_monster_manager_update()
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_monster_manager->monsters); i++)
|
||||
{
|
||||
mg_monster_update(g_monster_manager->monsters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool mg_monster_manager_spawn_monster(const gs_vec3 pos, const char *model_path)
|
||||
{
|
||||
mg_monster_t *mon = mg_monster_new(model_path, gs_v3(-16.0f, -16.0f, 0), gs_v3(16.0f, 16.0f, 64.0f));
|
||||
if (mon)
|
||||
{
|
||||
mon->transform.position = pos;
|
||||
mon->last_valid_pos = pos;
|
||||
gs_dyn_array_push(g_monster_manager->monsters, mon);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
21
src/game/monster_manager.h
Normal file
21
src/game/monster_manager.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef MG_MONSTER_MANAGER_H
|
||||
#define MG_MONSTER_MANAGER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "../entities/monster.h"
|
||||
|
||||
typedef struct mg_monster_manager_t
|
||||
{
|
||||
gs_dyn_array(mg_monster_t *) monsters;
|
||||
} mg_monster_manager_t;
|
||||
|
||||
void mg_monster_manager_init();
|
||||
void mg_monster_manager_free();
|
||||
void mg_monster_manager_update();
|
||||
|
||||
bool mg_monster_manager_spawn_monster(const gs_vec3 pos, const char *model_path);
|
||||
|
||||
extern mg_monster_manager_t *g_monster_manager;
|
||||
|
||||
#endif // MG_MONSTER_MANAGER_H
|
132
src/game/time_manager.c
Normal file
132
src/game/time_manager.c
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*================================================================
|
||||
* game/time_manager.c
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
-
|
||||
=================================================================*/
|
||||
|
||||
#include "time_manager.h"
|
||||
#include "config.h"
|
||||
|
||||
mg_time_manager_t *g_time_manager;
|
||||
|
||||
void mg_time_manager_init()
|
||||
{
|
||||
g_time_manager = gs_malloc_init(mg_time_manager_t);
|
||||
|
||||
// Avoid possible division by 0 elsewhere
|
||||
g_time_manager->delta = DBL_MIN;
|
||||
g_time_manager->unscaled_delta = DBL_MIN;
|
||||
g_time_manager->unscaled_time = DBL_MIN;
|
||||
g_time_manager->time = DBL_MIN;
|
||||
}
|
||||
|
||||
void mg_time_manager_free()
|
||||
{
|
||||
gs_free(g_time_manager);
|
||||
}
|
||||
|
||||
void mg_time_manager_update_start()
|
||||
{
|
||||
g_time_manager->_update_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->unscaled_delta = gs_platform_delta_time();
|
||||
g_time_manager->delta = g_time_manager->unscaled_delta * mg_cvar("cl_timescale")->value.f;
|
||||
g_time_manager->unscaled_time += g_time_manager->unscaled_delta;
|
||||
g_time_manager->time += g_time_manager->delta;
|
||||
}
|
||||
|
||||
void mg_time_manager_update_end()
|
||||
{
|
||||
g_time_manager->_update_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->update = g_time_manager->_update_end - g_time_manager->_update_start;
|
||||
}
|
||||
|
||||
void mg_time_manager_render_start()
|
||||
{
|
||||
g_time_manager->_render_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
}
|
||||
|
||||
void mg_time_manager_render_end()
|
||||
{
|
||||
g_time_manager->_render_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->render = g_time_manager->_render_end - g_time_manager->_render_start;
|
||||
}
|
||||
|
||||
void mg_time_manager_bsp_start()
|
||||
{
|
||||
g_time_manager->_bsp_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
}
|
||||
|
||||
void mg_time_manager_bsp_end()
|
||||
{
|
||||
g_time_manager->_bsp_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->bsp = g_time_manager->_bsp_end - g_time_manager->_bsp_start;
|
||||
}
|
||||
|
||||
void mg_time_manager_vis_start()
|
||||
{
|
||||
g_time_manager->_vis_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
}
|
||||
|
||||
void mg_time_manager_vis_end()
|
||||
{
|
||||
g_time_manager->_vis_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->vis = g_time_manager->_vis_end - g_time_manager->_vis_start;
|
||||
}
|
||||
|
||||
void mg_time_manager_models_start()
|
||||
{
|
||||
g_time_manager->_models_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
}
|
||||
|
||||
void mg_time_manager_models_end()
|
||||
{
|
||||
g_time_manager->_models_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->models = g_time_manager->_models_end - g_time_manager->_models_start;
|
||||
}
|
||||
|
||||
void mg_time_manager_viewmodel_start()
|
||||
{
|
||||
g_time_manager->_viewmodel_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
}
|
||||
|
||||
void mg_time_manager_viewmodel_end()
|
||||
{
|
||||
g_time_manager->_viewmodel_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->viewmodel = g_time_manager->_viewmodel_end - g_time_manager->_viewmodel_start;
|
||||
}
|
||||
|
||||
void mg_time_manager_post_start()
|
||||
{
|
||||
g_time_manager->_post_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
}
|
||||
|
||||
void mg_time_manager_post_end()
|
||||
{
|
||||
g_time_manager->_post_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->post = g_time_manager->_post_end - g_time_manager->_post_start;
|
||||
}
|
||||
|
||||
void mg_time_manager_ui_start()
|
||||
{
|
||||
g_time_manager->_ui_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
}
|
||||
|
||||
void mg_time_manager_ui_end()
|
||||
{
|
||||
g_time_manager->_ui_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->ui = g_time_manager->_ui_end - g_time_manager->_ui_start;
|
||||
}
|
||||
|
||||
void mg_time_manager_submit_start()
|
||||
{
|
||||
g_time_manager->_submit_start = gs_platform_elapsed_time() / 1000.0f;
|
||||
}
|
||||
|
||||
void mg_time_manager_submit_end()
|
||||
{
|
||||
g_time_manager->_submit_end = gs_platform_elapsed_time() / 1000.0f;
|
||||
g_time_manager->submit = g_time_manager->_submit_end - g_time_manager->_submit_start;
|
||||
}
|
76
src/game/time_manager.h
Normal file
76
src/game/time_manager.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*================================================================
|
||||
* game/time_manager.h
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
-
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_TIME_MANAGER_H
|
||||
#define MG_TIME_MANAGER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
typedef struct mg_time_manager_t
|
||||
{
|
||||
double delta; // seconds
|
||||
double unscaled_delta; // seconds
|
||||
double time; // seconds
|
||||
double unscaled_time; // seconds
|
||||
|
||||
double update; // seconds
|
||||
double render; // seconds
|
||||
double bsp; // seconds
|
||||
double vis; // seconds
|
||||
double models; // seconds
|
||||
double viewmodel; // seconds
|
||||
double post; // seconds
|
||||
double ui; // seconds
|
||||
double submit; // seconds
|
||||
|
||||
double _update_start; // seconds
|
||||
double _update_end; // seconds
|
||||
double _render_start; // seconds
|
||||
double _render_end; // seconds
|
||||
double _bsp_start; // seconds
|
||||
double _bsp_end; // seconds
|
||||
double _vis_start; // seconds
|
||||
double _vis_end; // seconds
|
||||
double _models_start; // seconds
|
||||
double _models_end; // seconds
|
||||
double _viewmodel_start; // seconds
|
||||
double _viewmodel_end; // seconds
|
||||
double _post_start; // seconds
|
||||
double _post_end; // seconds
|
||||
double _ui_start; // seconds
|
||||
double _ui_end; // seconds
|
||||
double _submit_start; // seconds
|
||||
double _submit_end; // seconds
|
||||
} mg_time_manager_t;
|
||||
|
||||
void mg_time_manager_init();
|
||||
void mg_time_manager_free();
|
||||
// TODO: macro these
|
||||
void mg_time_manager_update_start();
|
||||
void mg_time_manager_update_end();
|
||||
void mg_time_manager_render_start();
|
||||
void mg_time_manager_render_end();
|
||||
void mg_time_manager_bsp_start();
|
||||
void mg_time_manager_bsp_end();
|
||||
void mg_time_manager_vis_start();
|
||||
void mg_time_manager_vis_end();
|
||||
void mg_time_manager_models_start();
|
||||
void mg_time_manager_models_end();
|
||||
void mg_time_manager_viewmodel_start();
|
||||
void mg_time_manager_viewmodel_end();
|
||||
void mg_time_manager_post_start();
|
||||
void mg_time_manager_post_end();
|
||||
void mg_time_manager_ui_start();
|
||||
void mg_time_manager_ui_end();
|
||||
void mg_time_manager_submit_start();
|
||||
void mg_time_manager_submit_end();
|
||||
|
||||
extern mg_time_manager_t *g_time_manager;
|
||||
|
||||
#endif // MG_TIME_MANAGER_H
|
349
src/graphics/model.c
Normal file
349
src/graphics/model.c
Normal file
|
@ -0,0 +1,349 @@
|
|||
/*================================================================
|
||||
* graphics/model.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Q3 MD3 version 15 loading + modified animation.cfg.
|
||||
=================================================================*/
|
||||
|
||||
#include "model.h"
|
||||
#include "../game/console.h"
|
||||
#include "../util/render.h"
|
||||
#include "../util/string.h"
|
||||
#include "../util/transform.h"
|
||||
#include "texture_manager.h"
|
||||
|
||||
// shorthand util for failing during MD3 load
|
||||
b32 _mg_load_md3_fail(gs_byte_buffer_t *buffer, char *msg)
|
||||
{
|
||||
mg_println("mg_load_md3() failed: %s", msg);
|
||||
gs_byte_buffer_free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mg_load_md3(char *filename, md3_t *model)
|
||||
{
|
||||
mg_println("mg_load_md3() loading: '%s'", filename);
|
||||
|
||||
if (!gs_platform_file_exists(filename))
|
||||
{
|
||||
mg_println("mg_load_md3() failed: file not found '%s'", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
gs_byte_buffer_t buffer = gs_byte_buffer_new();
|
||||
gs_byte_buffer_read_from_file(&buffer, filename);
|
||||
|
||||
// read header
|
||||
gs_byte_buffer_read(&buffer, md3_header_t, &model->header);
|
||||
|
||||
// validate header
|
||||
if (memcmp(model->header.magic, MD3_MAGIC, 4) != 0 || model->header.version != MD3_VERSION)
|
||||
return _mg_load_md3_fail(&buffer, "invalid header");
|
||||
|
||||
// read frames
|
||||
size_t sz = sizeof(md3_frame_t) * model->header.num_frames;
|
||||
model->frames = gs_malloc(sz);
|
||||
buffer.position = model->header.off_frames;
|
||||
gs_byte_buffer_read_bulk(&buffer, &model->frames, sz);
|
||||
|
||||
// read tags
|
||||
sz = sizeof(md3_tag_t) * model->header.num_tags;
|
||||
model->tags = gs_malloc(sz);
|
||||
buffer.position = model->header.off_tags;
|
||||
gs_byte_buffer_read_bulk(&buffer, &model->tags, sz);
|
||||
|
||||
// read surfaces
|
||||
sz = sizeof(md3_surface_t) * model->header.num_surfaces;
|
||||
model->surfaces = gs_malloc(sz);
|
||||
memset(model->surfaces, 0, sz);
|
||||
buffer.position = model->header.off_surfaces;
|
||||
for (size_t i = 0; i < model->header.num_surfaces; i++)
|
||||
{
|
||||
// Offset to the start of this surface
|
||||
uint32_t off_start = buffer.position;
|
||||
|
||||
md3_surface_t *surf = &model->surfaces[i];
|
||||
|
||||
surf->magic = gs_malloc(4);
|
||||
gs_byte_buffer_read_bulk(&buffer, &surf->magic, 4);
|
||||
|
||||
// validate magic
|
||||
if (memcmp(surf->magic, MD3_MAGIC, 4) != 0)
|
||||
return _mg_load_md3_fail(&buffer, "invalid magic in surface");
|
||||
|
||||
surf->name = gs_malloc(64);
|
||||
gs_byte_buffer_read_bulk(&buffer, &surf->name, 64);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->flags);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->num_frames);
|
||||
|
||||
// validate frames
|
||||
if (surf->num_frames != model->header.num_frames)
|
||||
return _mg_load_md3_fail(&buffer, "invalid number of frames in surface");
|
||||
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->num_shaders);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->num_verts);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->num_tris);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->off_tris);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->off_shaders);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->off_texcoords);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->off_verts);
|
||||
gs_byte_buffer_read(&buffer, int32_t, &surf->off_end);
|
||||
|
||||
// not supported, could use missing tex
|
||||
if (surf->num_shaders <= 0)
|
||||
return _mg_load_md3_fail(&buffer, "no shaders in surface");
|
||||
|
||||
// shaders
|
||||
sz = sizeof(md3_shader_t) * surf->num_shaders;
|
||||
surf->shaders = gs_malloc(sz);
|
||||
buffer.position = off_start + surf->off_shaders;
|
||||
gs_byte_buffer_read_bulk(&buffer, &surf->shaders, sz);
|
||||
|
||||
// triangles
|
||||
sz = sizeof(md3_triangle_t) * surf->num_tris;
|
||||
surf->triangles = gs_malloc(sz);
|
||||
buffer.position = off_start + surf->off_tris;
|
||||
gs_byte_buffer_read_bulk(&buffer, &surf->triangles, sz);
|
||||
|
||||
// texcoords
|
||||
sz = sizeof(md3_texcoord_t) * surf->num_shaders * surf->num_verts;
|
||||
surf->texcoords = gs_malloc(sz);
|
||||
buffer.position = off_start + surf->off_texcoords;
|
||||
gs_byte_buffer_read_bulk(&buffer, &surf->texcoords, sz);
|
||||
|
||||
// vertices
|
||||
sz = sizeof(md3_texcoord_t) * surf->num_verts * surf->num_frames;
|
||||
surf->vertices = gs_malloc(sz);
|
||||
buffer.position = off_start + surf->off_verts;
|
||||
gs_byte_buffer_read_bulk(&buffer, &surf->vertices, sz);
|
||||
|
||||
// Renderable vertices
|
||||
surf->render_vertices = gs_malloc(sizeof(mg_md3_render_vertex_t) * surf->num_verts * surf->num_frames);
|
||||
for (size_t j = 0; j < surf->num_verts * surf->num_frames; j++)
|
||||
{
|
||||
// TODO: multiple shaders?
|
||||
int16_t shader_index = 0;
|
||||
surf->render_vertices[j].position.x = surf->vertices[j].x / MD3_SCALE;
|
||||
surf->render_vertices[j].position.y = surf->vertices[j].y / MD3_SCALE;
|
||||
surf->render_vertices[j].position.z = surf->vertices[j].z / MD3_SCALE;
|
||||
|
||||
surf->render_vertices[j].normal = mg_int16_to_vec3(surf->vertices[j].normal);
|
||||
|
||||
// Loop texcoords for each frame
|
||||
size_t texcoord_index = ((shader_index + 1) * j) % (surf->num_shaders * surf->num_verts);
|
||||
surf->render_vertices[j].texcoord.x = surf->texcoords[texcoord_index].u;
|
||||
surf->render_vertices[j].texcoord.y = surf->texcoords[texcoord_index].v;
|
||||
}
|
||||
|
||||
// Vertex buffers
|
||||
surf->vbos = gs_malloc(sizeof(gs_handle_gs_graphics_vertex_buffer_t) * surf->num_frames);
|
||||
for (size_t j = 0; j < surf->num_frames; j++)
|
||||
{
|
||||
gs_graphics_vertex_buffer_desc_t vdesc = gs_default_val();
|
||||
vdesc.data = surf->render_vertices + j * surf->num_verts;
|
||||
vdesc.size = sizeof(mg_md3_render_vertex_t) * surf->num_verts;
|
||||
surf->vbos[j] = gs_graphics_vertex_buffer_create(&vdesc);
|
||||
}
|
||||
|
||||
// Index buffer
|
||||
gs_graphics_index_buffer_desc_t idesc = gs_default_val();
|
||||
idesc.data = surf->triangles;
|
||||
idesc.size = sizeof(md3_triangle_t) * surf->num_tris;
|
||||
surf->ibo = gs_graphics_index_buffer_create(&idesc);
|
||||
|
||||
// Textures
|
||||
surf->textures = gs_malloc(sizeof(gs_asset_texture_t *) * surf->num_shaders);
|
||||
for (size_t j = 0; j < surf->num_shaders; j++)
|
||||
{
|
||||
// FIXME: why do shader names start with null?
|
||||
// \0odels/players/...
|
||||
if (surf->shaders[j].name[0] == '\0') surf->shaders[j].name[0] = 'm';
|
||||
|
||||
surf->textures[j] = mg_texture_manager_get(surf->shaders[j].name);
|
||||
}
|
||||
|
||||
// Seek to next surface
|
||||
buffer.position = off_start + surf->off_end;
|
||||
}
|
||||
|
||||
// Animations from <model>_animation.cfg
|
||||
model->animations = gs_dyn_array_new(mg_md3_animation_t);
|
||||
char *model_path = mg_path_remove_ext(filename);
|
||||
char *cfg_path = gs_malloc(gs_string_length(model_path) + 15);
|
||||
memset(cfg_path, '\0', sizeof(cfg_path));
|
||||
strcat(cfg_path, model_path);
|
||||
strcat(cfg_path, "_animation.cfg");
|
||||
|
||||
if (gs_platform_file_exists(cfg_path))
|
||||
{
|
||||
mg_println("mg_load_md3(): loading animations from '%s'", cfg_path);
|
||||
|
||||
char *file_data = gs_platform_read_file_contents(cfg_path, "r", NULL);
|
||||
|
||||
char *line;
|
||||
char *line_ptr;
|
||||
char *token;
|
||||
u8 num_parts = 0;
|
||||
u8 num_line = 0;
|
||||
line = strtok_r(file_data, "\r\n", &line_ptr);
|
||||
while (line != NULL)
|
||||
{
|
||||
num_line++;
|
||||
// gs_println("line %d: %s", num_line, line);
|
||||
|
||||
if (strlen(line) < 2)
|
||||
{
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Empty line
|
||||
if (line[0] == '\n' || (line[0] == '\r' && line[1] == '\n'))
|
||||
{
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Comment
|
||||
if (line[0] == '/' && line[1] == '/')
|
||||
{
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
mg_md3_animation_t anim = (mg_md3_animation_t){};
|
||||
|
||||
// Parse values delimited by space:
|
||||
// first frame, num frames, loop, frames per second, name
|
||||
num_parts = 0;
|
||||
token = strtok(line, " ");
|
||||
while (token)
|
||||
{
|
||||
switch (num_parts)
|
||||
{
|
||||
case 0:
|
||||
anim.first_frame = strtol(token, (char **)NULL, 10);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
anim.num_frames = strtol(token, (char **)NULL, 10);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
anim.loop = strtol(token, (char **)NULL, 10);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
anim.fps = strtol(token, (char **)NULL, 10);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
strcat(anim.name, token);
|
||||
// Remove new line at the end
|
||||
for (size_t i = 0; i < 16; i++)
|
||||
{
|
||||
if (anim.name[i] == '\r')
|
||||
{
|
||||
anim.name[i] = '\0';
|
||||
continue;
|
||||
}
|
||||
if (anim.name[i] == '\n')
|
||||
{
|
||||
anim.name[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
mg_println("WARN: animation config line %zu has too many arguments", num_line);
|
||||
break;
|
||||
}
|
||||
|
||||
num_parts++;
|
||||
|
||||
token = strtok(NULL, " ");
|
||||
}
|
||||
|
||||
// Check we got all
|
||||
if (num_parts < 5)
|
||||
{
|
||||
mg_println("WARN: animation config line %zu has too few arguments", num_line);
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
mg_println(" name: %s, fs: %d, fn: %d, fps: %d, loop: %d", anim.name, anim.first_frame, anim.num_frames, anim.fps, anim.loop);
|
||||
|
||||
gs_dyn_array_push(model->animations, anim);
|
||||
line = strtok_r(NULL, "\r\n", &line_ptr);
|
||||
}
|
||||
|
||||
gs_free(file_data);
|
||||
mg_println("Config loaded");
|
||||
}
|
||||
else
|
||||
{
|
||||
mg_println("mg_load_md3(): no animation.cfg for model '%s' (%s)", filename, cfg_path);
|
||||
}
|
||||
|
||||
gs_free(model_path);
|
||||
gs_free(cfg_path);
|
||||
|
||||
gs_byte_buffer_free(&buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mg_free_md3(md3_t *model)
|
||||
{
|
||||
for (size_t i = 0; i < model->header.num_surfaces; i++)
|
||||
{
|
||||
gs_graphics_index_buffer_destroy(model->surfaces[i].ibo);
|
||||
|
||||
if (model->surfaces[i].vbos != NULL)
|
||||
{
|
||||
for (size_t j = 0; j < model->surfaces[i].num_frames; j++)
|
||||
{
|
||||
gs_graphics_vertex_buffer_destroy(model->surfaces[i].vbos[j]);
|
||||
}
|
||||
}
|
||||
|
||||
gs_free(model->surfaces[i].magic);
|
||||
gs_free(model->surfaces[i].name);
|
||||
gs_free(model->surfaces[i].shaders);
|
||||
gs_free(model->surfaces[i].triangles);
|
||||
gs_free(model->surfaces[i].texcoords);
|
||||
gs_free(model->surfaces[i].vertices);
|
||||
gs_free(model->surfaces[i].render_vertices);
|
||||
gs_free(model->surfaces[i].vbos);
|
||||
// contents will be freed by texture manager
|
||||
gs_free(model->surfaces[i].textures);
|
||||
|
||||
model->surfaces[i].magic = NULL;
|
||||
model->surfaces[i].name = NULL;
|
||||
model->surfaces[i].shaders = NULL;
|
||||
model->surfaces[i].triangles = NULL;
|
||||
model->surfaces[i].texcoords = NULL;
|
||||
model->surfaces[i].vertices = NULL;
|
||||
model->surfaces[i].render_vertices = NULL;
|
||||
model->surfaces[i].vbos = NULL;
|
||||
model->surfaces[i].textures = NULL;
|
||||
}
|
||||
|
||||
gs_free(model->frames);
|
||||
gs_free(model->tags);
|
||||
gs_free(model->surfaces);
|
||||
|
||||
model->frames = NULL;
|
||||
model->tags = NULL;
|
||||
model->surfaces = NULL;
|
||||
|
||||
gs_dyn_array_free(model->animations);
|
||||
|
||||
gs_free(model);
|
||||
model = NULL;
|
||||
}
|
132
src/graphics/model.h
Normal file
132
src/graphics/model.h
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*================================================================
|
||||
* graphics/model.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Q3 MD3 version 15 loading + modified animation.cfg.
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MODEL_H
|
||||
#define MODEL_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#define MD3_MAGIC "IDP3"
|
||||
#define MD3_VERSION 15
|
||||
#define MD3_SCALE 64.0f
|
||||
|
||||
typedef struct mg_md3_animation_t
|
||||
{
|
||||
int32_t first_frame;
|
||||
int32_t num_frames;
|
||||
bool32_t loop;
|
||||
int32_t fps;
|
||||
char name[16];
|
||||
} mg_md3_animation_t;
|
||||
|
||||
typedef struct md3_header_t
|
||||
{
|
||||
char magic[4];
|
||||
int32_t version;
|
||||
char name[64];
|
||||
int32_t flags;
|
||||
int32_t num_frames;
|
||||
int32_t num_tags;
|
||||
int32_t num_surfaces;
|
||||
int32_t num_skins;
|
||||
int32_t off_frames;
|
||||
int32_t off_tags;
|
||||
int32_t off_surfaces;
|
||||
int32_t off_end;
|
||||
} md3_header_t;
|
||||
|
||||
typedef struct md3_frame_t
|
||||
{
|
||||
gs_vec3 bounds_min;
|
||||
gs_vec3 bounds_max;
|
||||
gs_vec3 origin;
|
||||
float32_t radius;
|
||||
char name[16];
|
||||
} md3_frame_t;
|
||||
|
||||
typedef struct md3_tag_t
|
||||
{
|
||||
char name[64];
|
||||
gs_vec3 origin;
|
||||
gs_vec3 forward;
|
||||
gs_vec3 right;
|
||||
gs_vec3 up;
|
||||
} md3_tag_t;
|
||||
|
||||
typedef struct md3_shader_t
|
||||
{
|
||||
char name[64];
|
||||
int32_t index;
|
||||
} md3_shader_t;
|
||||
|
||||
typedef struct md3_triangle_t
|
||||
{
|
||||
int32_t indices[3];
|
||||
} md3_triangle_t;
|
||||
|
||||
typedef struct md3_texcoord_t
|
||||
{
|
||||
float32_t u;
|
||||
float32_t v;
|
||||
} md3_texcoord_t;
|
||||
|
||||
typedef struct md3_vertex_t
|
||||
{
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t z;
|
||||
int16_t normal;
|
||||
} md3_vertex_t;
|
||||
|
||||
// Helper struct used for rendering
|
||||
typedef struct mg_md3_render_vertex_t
|
||||
{
|
||||
gs_vec3 position;
|
||||
gs_vec3 normal;
|
||||
gs_vec2 texcoord;
|
||||
} mg_md3_render_vertex_t;
|
||||
|
||||
typedef struct md3_surface_t
|
||||
{
|
||||
char *magic;
|
||||
char *name;
|
||||
int32_t flags;
|
||||
int32_t num_frames;
|
||||
int32_t num_shaders;
|
||||
int32_t num_verts;
|
||||
int32_t num_tris;
|
||||
int32_t off_tris;
|
||||
int32_t off_shaders;
|
||||
int32_t off_texcoords;
|
||||
int32_t off_verts;
|
||||
int32_t off_end;
|
||||
md3_shader_t *shaders;
|
||||
md3_triangle_t *triangles;
|
||||
md3_texcoord_t *texcoords;
|
||||
md3_vertex_t *vertices;
|
||||
mg_md3_render_vertex_t *render_vertices;
|
||||
gs_handle_gs_graphics_vertex_buffer_t *vbos;
|
||||
gs_handle_gs_graphics_index_buffer_t ibo;
|
||||
gs_asset_texture_t **textures;
|
||||
} md3_surface_t;
|
||||
|
||||
typedef struct md3_t
|
||||
{
|
||||
md3_header_t header;
|
||||
md3_frame_t *frames;
|
||||
md3_tag_t *tags;
|
||||
md3_surface_t *surfaces;
|
||||
gs_dyn_array(mg_md3_animation_t) animations;
|
||||
} md3_t;
|
||||
|
||||
b32 _mg_load_md3_fail(gs_byte_buffer_t *buffer, char *msg);
|
||||
bool mg_load_md3(char *filename, md3_t *model);
|
||||
void mg_free_md3(md3_t *model);
|
||||
|
||||
#endif // MODEL_H
|
99
src/graphics/model_manager.c
Normal file
99
src/graphics/model_manager.c
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*================================================================
|
||||
* graphics/model_manager.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "model_manager.h"
|
||||
#include "../game/console.h"
|
||||
#include "../util/string.h"
|
||||
|
||||
mg_model_manager_t *g_model_manager;
|
||||
|
||||
void mg_model_manager_init()
|
||||
{
|
||||
// Allocate
|
||||
g_model_manager = gs_malloc_init(mg_model_manager_t);
|
||||
g_model_manager->models = gs_dyn_array_new(mg_model_t);
|
||||
|
||||
// Test
|
||||
_mg_model_manager_load("players/sarge/head.md3", "basic");
|
||||
_mg_model_manager_load("players/sarge/upper.md3", "basic");
|
||||
_mg_model_manager_load("players/sarge/lower.md3", "basic");
|
||||
}
|
||||
|
||||
void mg_model_manager_free()
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_model_manager->models); i++)
|
||||
{
|
||||
mg_free_md3(g_model_manager->models[i].data);
|
||||
}
|
||||
|
||||
gs_dyn_array_free(g_model_manager->models);
|
||||
|
||||
gs_free(g_model_manager);
|
||||
g_model_manager = NULL;
|
||||
}
|
||||
|
||||
mg_model_t *mg_model_manager_find(const char *filename)
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_model_manager->models); i++)
|
||||
{
|
||||
if (strcmp(filename, g_model_manager->models[i].filename) == 0)
|
||||
{
|
||||
return &g_model_manager->models[i];
|
||||
}
|
||||
}
|
||||
|
||||
mg_println("WARN: mg_model_manager_find invalid model %s", filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mg_model_t *mg_model_manager_find_or_load(const char *filename, const char *shader)
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_model_manager->models); i++)
|
||||
{
|
||||
if (strcmp(filename, g_model_manager->models[i].filename) == 0)
|
||||
{
|
||||
return &g_model_manager->models[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (_mg_model_manager_load(filename, shader))
|
||||
{
|
||||
return &gs_dyn_array_back(g_model_manager->models);
|
||||
}
|
||||
|
||||
mg_println("WARN: mg_model_manager_find_or_load invalid model %s", filename);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool _mg_model_manager_load(const char *filename, const char *shader)
|
||||
{
|
||||
char *path = mg_append_string("assets/models/", filename);
|
||||
|
||||
md3_t *data = gs_malloc_init(md3_t);
|
||||
if (!mg_load_md3(path, data))
|
||||
{
|
||||
mg_println("WARN: _mg_model_manager_load failed, model %s", filename);
|
||||
mg_free_md3(data);
|
||||
gs_free(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
mg_model_t model = {
|
||||
.filename = filename,
|
||||
.shader = shader,
|
||||
.data = data,
|
||||
};
|
||||
|
||||
gs_dyn_array_push(g_model_manager->models, model);
|
||||
|
||||
mg_println("Model: Loaded %s", filename);
|
||||
gs_free(path);
|
||||
return true;
|
||||
}
|
37
src/graphics/model_manager.h
Normal file
37
src/graphics/model_manager.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*================================================================
|
||||
* graphics/model_manager.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_MODEL_MANAGER_H
|
||||
#define MG_MODEL_MANAGER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
#include "model.h"
|
||||
|
||||
typedef struct mg_model_t
|
||||
{
|
||||
char *filename;
|
||||
char *shader;
|
||||
md3_t *data;
|
||||
} mg_model_t;
|
||||
|
||||
typedef struct mg_model_manager_t
|
||||
{
|
||||
gs_dyn_array(mg_model_t) models;
|
||||
} mg_model_manager_t;
|
||||
|
||||
void mg_model_manager_init();
|
||||
void mg_model_manager_free();
|
||||
mg_model_t *mg_model_manager_find(const char *filename);
|
||||
mg_model_t *mg_model_manager_find_or_load(const char *filename, const char *shader);
|
||||
bool _mg_model_manager_load(const char *filename, const char *shader);
|
||||
|
||||
extern mg_model_manager_t *g_model_manager;
|
||||
|
||||
#endif // MG_MODEL_MANAGER_H
|
1168
src/graphics/renderer.c
Normal file
1168
src/graphics/renderer.c
Normal file
File diff suppressed because it is too large
Load diff
110
src/graphics/renderer.h
Normal file
110
src/graphics/renderer.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*================================================================
|
||||
* graphics/renderer.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_RENDERER_H
|
||||
#define MG_RENDERER_H
|
||||
|
||||
// clang-format off
|
||||
#include <gs/gs.h>
|
||||
#include <gs/util/gs_idraw.h>
|
||||
#include <gs/util/gs_gui.h>
|
||||
// clang-format on
|
||||
|
||||
#include "../bsp/bsp_map.h"
|
||||
#include "../entities/player.h"
|
||||
#include "model_manager.h"
|
||||
#include "types.h"
|
||||
|
||||
typedef enum mg_model_type
|
||||
{
|
||||
MG_MODEL_WORLD,
|
||||
MG_MODEL_VIEWMODEL,
|
||||
MG_MODEL_COUNT,
|
||||
} mg_model_type;
|
||||
|
||||
// Renderable instance of a model
|
||||
typedef struct mg_renderable_t
|
||||
{
|
||||
uint32_t id;
|
||||
bool hidden;
|
||||
mg_model_type type;
|
||||
gs_vqs *transform;
|
||||
gs_mat4 u_view;
|
||||
mg_model_t model;
|
||||
mg_md3_animation_t *current_animation;
|
||||
int32_t frame;
|
||||
double prev_frame_time;
|
||||
} mg_renderable_t;
|
||||
|
||||
typedef struct mg_renderer_t
|
||||
{
|
||||
gs_command_buffer_t cb;
|
||||
gs_immediate_draw_t gsi;
|
||||
gs_gui_context_t gui;
|
||||
gs_camera_t *cam;
|
||||
gs_slot_array(mg_renderable_t) renderables;
|
||||
gs_handle(gs_graphics_pipeline_t) pipe;
|
||||
gs_handle(gs_graphics_pipeline_t) viewmodel_pipe;
|
||||
gs_handle(gs_graphics_pipeline_t) wire_pipe;
|
||||
gs_handle(gs_graphics_pipeline_t) post_pipe;
|
||||
gs_dyn_array(gs_handle(gs_graphics_shader_t)) shaders;
|
||||
gs_dyn_array(char *) shader_names;
|
||||
gs_dyn_array(char *) shader_sources_vert;
|
||||
gs_dyn_array(char *) shader_sources_frag;
|
||||
gs_vec2 fb_size;
|
||||
int32_t *screen_indices;
|
||||
gs_vec2 *screen_vertices;
|
||||
bool32_t offscreen_cleared;
|
||||
gs_handle(gs_graphics_vertex_buffer_t) screen_vbo;
|
||||
gs_handle(gs_graphics_index_buffer_t) screen_ibo;
|
||||
gs_handle(gs_graphics_renderpass_t) offscreen_rp;
|
||||
gs_handle(gs_graphics_framebuffer_t) offscreen_fbo;
|
||||
gs_handle(gs_graphics_texture_t) offscreen_rt;
|
||||
gs_handle(gs_graphics_texture_t) offscreen_dt;
|
||||
gs_handle(gs_graphics_renderpass_t) viewmodel_rp;
|
||||
gs_handle(gs_graphics_framebuffer_t) viewmodel_fbo;
|
||||
gs_handle(gs_graphics_texture_t) viewmodel_rt;
|
||||
gs_handle(gs_graphics_texture_t) viewmodel_dt;
|
||||
gs_handle(gs_graphics_uniform_t) u_proj;
|
||||
gs_handle(gs_graphics_uniform_t) u_view;
|
||||
gs_handle(gs_graphics_uniform_t) u_light;
|
||||
gs_handle(gs_graphics_uniform_t) u_tex;
|
||||
gs_handle(gs_graphics_uniform_t) u_tex_vm;
|
||||
gs_handle(gs_graphics_uniform_t) u_color;
|
||||
gs_handle(gs_graphics_uniform_t) u_barrel_enabled;
|
||||
gs_handle(gs_graphics_uniform_t) u_barrel_strength;
|
||||
gs_handle(gs_graphics_uniform_t) u_barrel_height;
|
||||
gs_handle(gs_graphics_uniform_t) u_barrel_aspect;
|
||||
gs_handle(gs_graphics_uniform_t) u_barrel_cyl_ratio;
|
||||
gs_handle(gs_graphics_texture_t) missing_texture;
|
||||
float clear_color[4];
|
||||
float clear_color_overlay[4];
|
||||
} mg_renderer_t;
|
||||
|
||||
void mg_renderer_init(uint32_t window_handle);
|
||||
void mg_renderer_update();
|
||||
void mg_renderer_free();
|
||||
uint32_t mg_renderer_create_renderable(mg_model_t model, gs_vqs *transform);
|
||||
void mg_renderer_remove_renderable(uint32_t renderable_id);
|
||||
mg_renderable_t *mg_renderer_get_renderable(uint32_t renderable_id);
|
||||
gs_handle(gs_graphics_shader_t) mg_renderer_get_shader(char *name);
|
||||
bool32_t mg_renderer_play_animation(uint32_t id, char *name);
|
||||
void mg_renderer_set_hidden(uint32_t id, bool hidden);
|
||||
void mg_renderer_set_model_type(uint32_t id, mg_model_type type);
|
||||
void _mg_renderer_resize(const gs_vec2 fb);
|
||||
void _mg_renderer_models_pass();
|
||||
void _mg_renderer_viewmodel_pass();
|
||||
void _mg_renderer_post_pass();
|
||||
void _mg_renderer_immediate_pass();
|
||||
void _mg_renderer_draw_debug_overlay();
|
||||
void _mg_renderer_load_shader(char *name);
|
||||
|
||||
extern mg_renderer_t *g_renderer;
|
||||
|
||||
#endif // MG_RENDERER_H
|
181
src/graphics/texture_manager.c
Normal file
181
src/graphics/texture_manager.c
Normal file
|
@ -0,0 +1,181 @@
|
|||
/*================================================================
|
||||
* graphics/texture_manager.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Loading and storing texture pointers by filename.
|
||||
=================================================================*/
|
||||
|
||||
#include "texture_manager.h"
|
||||
#include "../game/config.h"
|
||||
#include "../game/console.h"
|
||||
#include "../util/string.h"
|
||||
|
||||
mg_texture_manager_t *g_texture_manager;
|
||||
|
||||
void mg_texture_manager_init()
|
||||
{
|
||||
g_texture_manager = gs_malloc_init(mg_texture_manager_t);
|
||||
g_texture_manager->textures = gs_dyn_array_new(mg_texture_t);
|
||||
|
||||
g_texture_manager->tex_filter = mg_cvar("r_filter")->value.i + 1;
|
||||
g_texture_manager->mip_filter = mg_cvar("r_filter_mip")->value.i + 1;
|
||||
g_texture_manager->num_mips = mg_cvar("r_mips")->value.i;
|
||||
}
|
||||
|
||||
void mg_texture_manager_free()
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_texture_manager->textures); i++)
|
||||
{
|
||||
gs_graphics_texture_destroy(g_texture_manager->textures[i].asset->hndl);
|
||||
g_texture_manager->textures[i].asset->hndl = gs_handle_invalid(gs_graphics_texture_t);
|
||||
gs_free(g_texture_manager->textures[i].asset);
|
||||
gs_free(g_texture_manager->textures[i].filename);
|
||||
}
|
||||
|
||||
gs_dyn_array_free(g_texture_manager->textures);
|
||||
|
||||
gs_free(g_texture_manager);
|
||||
g_texture_manager = NULL;
|
||||
}
|
||||
|
||||
void mg_texture_manager_set_filter(gs_graphics_texture_filtering_type tex, gs_graphics_texture_filtering_type mip, int num_mips)
|
||||
{
|
||||
mg_println("mg_texture_manager_set_filter: reloading textures");
|
||||
g_texture_manager->tex_filter = tex;
|
||||
g_texture_manager->mip_filter = mip;
|
||||
g_texture_manager->num_mips = num_mips;
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_texture_manager->textures); i++)
|
||||
{
|
||||
if (
|
||||
g_texture_manager->textures[i].asset->desc.min_filter == tex &&
|
||||
g_texture_manager->textures[i].asset->desc.mag_filter == tex &&
|
||||
g_texture_manager->textures[i].asset->desc.mip_filter == mip &&
|
||||
g_texture_manager->textures[i].asset->desc.num_mips == num_mips)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
gs_graphics_texture_destroy(g_texture_manager->textures[i].asset->hndl);
|
||||
gs_assert(
|
||||
_mg_texture_manager_load(
|
||||
g_texture_manager->textures[i].filename,
|
||||
g_texture_manager->textures[i].asset));
|
||||
}
|
||||
}
|
||||
|
||||
// Get texture pointer, load from file if required.
|
||||
// Returns NULL on failure.
|
||||
gs_asset_texture_t *mg_texture_manager_get(char *path)
|
||||
{
|
||||
// Strip any extensions from path
|
||||
char *filename = mg_path_remove_ext(path);
|
||||
|
||||
gs_asset_texture_t *asset = _mg_texture_manager_find(filename);
|
||||
if (asset != NULL)
|
||||
{
|
||||
gs_free(filename);
|
||||
return asset;
|
||||
}
|
||||
|
||||
asset = gs_malloc_init(gs_asset_texture_t);
|
||||
bool32_t success = _mg_texture_manager_load(filename, asset);
|
||||
if (!success)
|
||||
{
|
||||
gs_free(asset);
|
||||
gs_free(filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mg_texture_t tex = (mg_texture_t){
|
||||
.asset = asset,
|
||||
.filename = filename,
|
||||
};
|
||||
gs_dyn_array_push(g_texture_manager->textures, tex);
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
// Load gs_asset_texture from a file.
|
||||
bool32_t _mg_texture_manager_load(char *name, gs_asset_texture_t *asset)
|
||||
{
|
||||
// Supported extensions
|
||||
char extensions[2][5] = {
|
||||
".jpg",
|
||||
".tga",
|
||||
};
|
||||
|
||||
bool32_t success = false;
|
||||
|
||||
// bsp textures already start with 'textures/'
|
||||
char *path = mg_append_string("assets/", name);
|
||||
|
||||
// Name with extension
|
||||
size_t malloc_sz = strlen(path) + 5;
|
||||
char *filename = gs_malloc(malloc_sz);
|
||||
memset(filename, 0, malloc_sz);
|
||||
strcat(filename, path);
|
||||
strcat(filename, extensions[0]);
|
||||
|
||||
for (size_t i = 0; i < 2; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
strcpy(filename + strlen(filename) - 4, extensions[i]);
|
||||
}
|
||||
|
||||
if (gs_platform_file_exists(filename))
|
||||
{
|
||||
success = gs_asset_texture_load_from_file(
|
||||
filename,
|
||||
asset,
|
||||
&(gs_graphics_texture_desc_t){
|
||||
.format = GS_GRAPHICS_TEXTURE_FORMAT_RGBA8,
|
||||
.min_filter = g_texture_manager->tex_filter,
|
||||
.mag_filter = g_texture_manager->tex_filter,
|
||||
.mip_filter = g_texture_manager->mip_filter,
|
||||
.num_mips = g_texture_manager->num_mips,
|
||||
},
|
||||
false,
|
||||
false);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
mg_println("WARN: _mg_texture_manager_load could not load texture: %s, failed to open existing file %s", name, filename);
|
||||
}
|
||||
}
|
||||
else if (i == 1)
|
||||
{
|
||||
mg_println("WARN: _mg_texture_manager_load could not load texture: %s, file not found", name);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
mg_println("_mg_texture_manager_load: Loaded texture: %s", name);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
asset->hndl = gs_handle_invalid(gs_graphics_texture_t);
|
||||
}
|
||||
}
|
||||
|
||||
gs_free(filename);
|
||||
gs_free(path);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
gs_asset_texture_t *_mg_texture_manager_find(char *filename)
|
||||
{
|
||||
for (size_t i = 0; i < gs_dyn_array_size(g_texture_manager->textures); i++)
|
||||
{
|
||||
if (strcmp(g_texture_manager->textures[i].filename, filename) == 0)
|
||||
{
|
||||
return g_texture_manager->textures[i].asset;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
38
src/graphics/texture_manager.h
Normal file
38
src/graphics/texture_manager.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*================================================================
|
||||
* graphics/texture_manager.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_TEXTURE_MANAGER_H
|
||||
#define MG_TEXTURE_MANAGER_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
typedef struct mg_texture_t
|
||||
{
|
||||
char *filename;
|
||||
gs_asset_texture_t *asset;
|
||||
} mg_texture_t;
|
||||
|
||||
typedef struct mg_texture_manager_t
|
||||
{
|
||||
gs_graphics_texture_filtering_type tex_filter;
|
||||
gs_graphics_texture_filtering_type mip_filter;
|
||||
int num_mips;
|
||||
gs_dyn_array(mg_texture_t) textures;
|
||||
} mg_texture_manager_t;
|
||||
|
||||
void mg_texture_manager_init();
|
||||
void mg_texture_manager_free();
|
||||
void mg_texture_manager_set_filter(gs_graphics_texture_filtering_type tex, gs_graphics_texture_filtering_type mip, int num_mips);
|
||||
gs_asset_texture_t *mg_texture_manager_get(char *path);
|
||||
bool32_t _mg_texture_manager_load(char *path, gs_asset_texture_t *asset);
|
||||
gs_asset_texture_t *_mg_texture_manager_find(char *filename);
|
||||
|
||||
extern mg_texture_manager_t *g_texture_manager;
|
||||
|
||||
#endif // MG_TEXTURE_MANAGER_H
|
22
src/graphics/types.h
Normal file
22
src/graphics/types.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*================================================================
|
||||
* graphics/types.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_GFX_TYPES_H
|
||||
#define MG_GFX_TYPES_H
|
||||
|
||||
#include <gs/gs.h>
|
||||
|
||||
typedef struct mg_renderer_light_t
|
||||
{
|
||||
gs_vec3 ambient;
|
||||
gs_vec3 directional;
|
||||
gs_vec3 direction;
|
||||
} mg_renderer_light_t;
|
||||
|
||||
#endif // MG_GFX_TYPES_H
|
640
src/graphics/ui_manager.c
Normal file
640
src/graphics/ui_manager.c
Normal file
|
@ -0,0 +1,640 @@
|
|||
/*================================================================
|
||||
* graphics/ui_manager.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#include "ui_manager.h"
|
||||
#include "../game/console.h"
|
||||
#include "../game/game_manager.h"
|
||||
#include "../game/time_manager.h"
|
||||
#include "../util/render.h"
|
||||
#include "renderer.h"
|
||||
|
||||
mg_ui_manager_t *g_ui_manager;
|
||||
|
||||
void mg_ui_manager_init()
|
||||
{
|
||||
// Allocate
|
||||
g_ui_manager = gs_malloc_init(mg_ui_manager_t);
|
||||
|
||||
// Load fonts
|
||||
#ifdef __ANDROID__
|
||||
gs_asset_font_load_from_file("./assets/fonts/PixeloidSans.otf", &g_ui_manager->fonts[GUI_FONT_SMALL], 28);
|
||||
gs_asset_font_load_from_file("./assets/fonts/PixeloidSans.otf", &g_ui_manager->fonts[GUI_FONT_MEDIUM], 36);
|
||||
gs_asset_font_load_from_file("./assets/fonts/PixeloidSans.otf", &g_ui_manager->fonts[GUI_FONT_LARGE], 48);
|
||||
#else
|
||||
gs_asset_font_load_from_file("./assets/fonts/PixeloidSans.otf", &g_ui_manager->fonts[GUI_FONT_SMALL], 14);
|
||||
gs_asset_font_load_from_file("./assets/fonts/PixeloidSans.otf", &g_ui_manager->fonts[GUI_FONT_MEDIUM], 18);
|
||||
gs_asset_font_load_from_file("./assets/fonts/PixeloidSans.otf", &g_ui_manager->fonts[GUI_FONT_LARGE], 24);
|
||||
#endif
|
||||
|
||||
// Default style sheet
|
||||
gs_gui_style_element_t panel_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_SMALL]},
|
||||
{.type = GS_GUI_STYLE_PADDING_TOP, .value = 20},
|
||||
{.type = GS_GUI_STYLE_COLOR_BORDER, .color = gs_color(0, 0, 0, 0)},
|
||||
{.type = GS_GUI_STYLE_COLOR_BACKGROUND, .color = gs_color(0, 0, 0, 0)},
|
||||
};
|
||||
|
||||
gs_gui_style_element_t button_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_SMALL]},
|
||||
{.type = GS_GUI_STYLE_ALIGN_CONTENT, .value = GS_GUI_ALIGN_CENTER},
|
||||
{.type = GS_GUI_STYLE_JUSTIFY_CONTENT, .value = GS_GUI_JUSTIFY_CENTER},
|
||||
{.type = GS_GUI_STYLE_WIDTH, .value = 200},
|
||||
{.type = GS_GUI_STYLE_HEIGHT, .value = 50},
|
||||
{.type = GS_GUI_STYLE_MARGIN_LEFT, .value = 0},
|
||||
{.type = GS_GUI_STYLE_MARGIN_TOP, .value = 10},
|
||||
{.type = GS_GUI_STYLE_MARGIN_BOTTOM, .value = 0},
|
||||
{.type = GS_GUI_STYLE_MARGIN_RIGHT, .value = 20},
|
||||
{.type = GS_GUI_STYLE_SHADOW_X, .value = 1},
|
||||
{.type = GS_GUI_STYLE_SHADOW_Y, .value = 1},
|
||||
{.type = GS_GUI_STYLE_COLOR_SHADOW, .color = gs_color(146, 146, 146, 200)},
|
||||
{.type = GS_GUI_STYLE_COLOR_BORDER, .color = GS_COLOR_BLACK},
|
||||
{.type = GS_GUI_STYLE_BORDER_WIDTH, .value = 2},
|
||||
{.type = GS_GUI_STYLE_COLOR_CONTENT, .color = gs_color(67, 67, 67, 255)},
|
||||
{.type = GS_GUI_STYLE_COLOR_BACKGROUND, .color = gs_color(198, 198, 198, 255)},
|
||||
};
|
||||
|
||||
gs_gui_style_element_t button_hover_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_SMALL]},
|
||||
{.type = GS_GUI_STYLE_COLOR_BACKGROUND, .color = gs_color(168, 168, 168, 255)},
|
||||
};
|
||||
|
||||
gs_gui_style_element_t button_focus_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_SMALL]},
|
||||
{.type = GS_GUI_STYLE_COLOR_CONTENT, .color = gs_color(255, 255, 255, 255)},
|
||||
{.type = GS_GUI_STYLE_COLOR_BACKGROUND, .color = gs_color(49, 174, 31, 255)},
|
||||
};
|
||||
|
||||
gs_gui_style_element_t label_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_SMALL]},
|
||||
{.type = GS_GUI_STYLE_ALIGN_CONTENT, .value = GS_GUI_ALIGN_CENTER},
|
||||
{.type = GS_GUI_STYLE_JUSTIFY_CONTENT, .value = GS_GUI_JUSTIFY_END},
|
||||
};
|
||||
|
||||
gs_gui_style_element_t text_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_SMALL]},
|
||||
{.type = GS_GUI_STYLE_ALIGN_CONTENT, .value = GS_GUI_ALIGN_CENTER},
|
||||
{.type = GS_GUI_STYLE_JUSTIFY_CONTENT, .value = GS_GUI_JUSTIFY_START},
|
||||
};
|
||||
|
||||
g_ui_manager->default_style_sheet = gs_gui_style_sheet_create(
|
||||
&g_renderer->gui,
|
||||
&(gs_gui_style_sheet_desc_t){
|
||||
.button = {
|
||||
.all = {button_style, sizeof(button_style)},
|
||||
.hover = {button_hover_style, sizeof(button_hover_style)},
|
||||
.focus = {button_focus_style, sizeof(button_focus_style)},
|
||||
},
|
||||
.panel = {
|
||||
.all = {panel_style, sizeof(panel_style)},
|
||||
},
|
||||
.label = {
|
||||
.all = {label_style, sizeof(label_style)},
|
||||
},
|
||||
.text = {
|
||||
.all = {text_style, sizeof(text_style)},
|
||||
},
|
||||
});
|
||||
|
||||
// Console style sheet
|
||||
gs_gui_style_element_t console_text_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_SMALL]},
|
||||
{.type = GS_GUI_STYLE_ALIGN_CONTENT, .value = GS_GUI_ALIGN_START},
|
||||
{.type = GS_GUI_STYLE_JUSTIFY_CONTENT, .value = GS_GUI_JUSTIFY_START},
|
||||
};
|
||||
|
||||
g_ui_manager->console_style_sheet = gs_gui_style_sheet_create(
|
||||
&g_renderer->gui,
|
||||
&(gs_gui_style_sheet_desc_t){
|
||||
.panel = {
|
||||
.all = {panel_style, sizeof(panel_style)},
|
||||
},
|
||||
.text = {
|
||||
.all = {console_text_style, sizeof(console_text_style)},
|
||||
},
|
||||
});
|
||||
|
||||
// Dialogue style sheet
|
||||
gs_gui_style_element_t dialogue_text_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_MEDIUM]},
|
||||
{.type = GS_GUI_STYLE_ALIGN_CONTENT, .value = GS_GUI_ALIGN_START},
|
||||
{.type = GS_GUI_STYLE_JUSTIFY_CONTENT, .value = GS_GUI_JUSTIFY_START},
|
||||
};
|
||||
|
||||
gs_gui_style_element_t dialogue_panel_style[] = {
|
||||
{.type = GS_GUI_STYLE_FONT, .font = &g_ui_manager->fonts[GUI_FONT_MEDIUM]},
|
||||
{.type = GS_GUI_STYLE_COLOR_BORDER, .color = gs_color(0, 0, 0, 0)},
|
||||
{.type = GS_GUI_STYLE_COLOR_BACKGROUND, .color = gs_color(0, 0, 0, 0)},
|
||||
};
|
||||
|
||||
g_ui_manager->dialogue_style_sheet = gs_gui_style_sheet_create(
|
||||
&g_renderer->gui,
|
||||
&(gs_gui_style_sheet_desc_t){
|
||||
.panel = {
|
||||
.all = {dialogue_panel_style, sizeof(dialogue_panel_style)},
|
||||
},
|
||||
.text = {
|
||||
.all = {dialogue_text_style, sizeof(dialogue_text_style)},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void mg_ui_manager_free()
|
||||
{
|
||||
for (size_t i = 0; i < GUI_FONT_COUNT; i++)
|
||||
{
|
||||
if (g_ui_manager->fonts[i].font_info)
|
||||
{
|
||||
gs_free(g_ui_manager->fonts[i].font_info);
|
||||
g_ui_manager->fonts[i].font_info = NULL;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < GS_GRAPHICS_TEXTURE_DATA_MAX; j++)
|
||||
{
|
||||
if (g_ui_manager->fonts[i].texture.desc.data[j])
|
||||
{
|
||||
gs_free(g_ui_manager->fonts[i].texture.desc.data[j]);
|
||||
g_ui_manager->fonts[i].texture.desc.data[j] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (gs_handle_is_valid(g_ui_manager->fonts[i].texture.hndl))
|
||||
{
|
||||
gs_graphics_texture_destroy(g_ui_manager->fonts[i].texture.hndl);
|
||||
g_ui_manager->fonts[i].texture.hndl = gs_handle_invalid(gs_graphics_texture_t);
|
||||
}
|
||||
}
|
||||
|
||||
mg_ui_manager_clear_text();
|
||||
gs_slot_array_free(g_ui_manager->texts);
|
||||
|
||||
gs_free(g_ui_manager->current_dialogue.content);
|
||||
gs_free(g_ui_manager);
|
||||
g_ui_manager = NULL;
|
||||
}
|
||||
|
||||
void mg_ui_manager_render(gs_vec2 fbs, bool32_t clear)
|
||||
{
|
||||
mg_time_manager_ui_start();
|
||||
|
||||
bool show_cursor_prev = g_ui_manager->show_cursor;
|
||||
|
||||
g_ui_manager->show_cursor = g_ui_manager->menu_open || g_ui_manager->console_open;
|
||||
|
||||
if (g_ui_manager->show_cursor != show_cursor_prev)
|
||||
{
|
||||
if (g_ui_manager->show_cursor)
|
||||
{
|
||||
gs_platform_lock_mouse(gs_platform_main_window(), false);
|
||||
gs_platform_mouse_set_position(gs_platform_main_window(), fbs.x * 0.5f, fbs.y * 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
gs_platform_lock_mouse(gs_platform_main_window(), true);
|
||||
}
|
||||
}
|
||||
|
||||
// Begin new frame for gui
|
||||
gs_gui_begin(&g_renderer->gui, g_renderer->fb_size);
|
||||
{
|
||||
// Set style sheet
|
||||
gs_gui_set_style_sheet(&g_renderer->gui, &g_ui_manager->default_style_sheet);
|
||||
|
||||
if (gs_gui_window_begin_ex(
|
||||
&g_renderer->gui,
|
||||
"#root",
|
||||
gs_gui_rect(0, 0, 0, 0),
|
||||
NULL,
|
||||
NULL,
|
||||
GS_GUI_OPT_NOFRAME |
|
||||
GS_GUI_OPT_NOTITLE |
|
||||
GS_GUI_OPT_NOMOVE |
|
||||
GS_GUI_OPT_FULLSCREEN |
|
||||
GS_GUI_OPT_NORESIZE |
|
||||
GS_GUI_OPT_NODOCK |
|
||||
GS_GUI_OPT_NOBRINGTOFRONT))
|
||||
{
|
||||
gs_gui_container_t *root = gs_gui_get_current_container(&g_renderer->gui);
|
||||
_mg_ui_manager_text_overlay(fbs, root);
|
||||
_mg_ui_manager_dialogue_window(fbs, root);
|
||||
_mg_ui_manager_menu_window(fbs, root);
|
||||
_mg_ui_manager_debug_overlay(fbs, root);
|
||||
_mg_ui_manager_console_window(fbs, root);
|
||||
|
||||
gs_gui_window_end(&g_renderer->gui);
|
||||
}
|
||||
|
||||
// End gui frame
|
||||
gs_gui_end(&g_renderer->gui);
|
||||
|
||||
// Do rendering
|
||||
gs_graphics_renderpass_begin(&g_renderer->cb, (gs_handle(gs_graphics_renderpass_t)){0});
|
||||
if (clear)
|
||||
{
|
||||
gs_graphics_clear_desc_t clear = (gs_graphics_clear_desc_t){
|
||||
.actions = &(gs_graphics_clear_action_t){
|
||||
.color = {
|
||||
g_renderer->clear_color[0],
|
||||
g_renderer->clear_color[1],
|
||||
g_renderer->clear_color[2],
|
||||
g_renderer->clear_color[3],
|
||||
},
|
||||
},
|
||||
};
|
||||
gs_graphics_clear(&g_renderer->cb, &clear);
|
||||
}
|
||||
gs_gui_render(&g_renderer->gui, &g_renderer->cb);
|
||||
gs_graphics_renderpass_end(&g_renderer->cb);
|
||||
}
|
||||
|
||||
mg_time_manager_ui_end();
|
||||
}
|
||||
|
||||
void mg_ui_manager_set_dialogue(const char *text, float32_t duration)
|
||||
{
|
||||
gs_free(g_ui_manager->current_dialogue.content);
|
||||
|
||||
mg_ui_dialogue_t diag = {
|
||||
.content = gs_malloc(gs_string_length(text) + 1),
|
||||
.duration = duration,
|
||||
._start_time = g_time_manager->time,
|
||||
};
|
||||
memcpy(diag.content, text, gs_string_length(text) + 1);
|
||||
|
||||
g_ui_manager->current_dialogue = diag;
|
||||
g_ui_manager->dialogue_open = true;
|
||||
}
|
||||
|
||||
// Use sz > 0 to reserve a longer string for future calls to mg_ui_manager_update_text.
|
||||
uint32_t mg_ui_manager_add_text(const char *text, const gs_vec2 pos, size_t sz)
|
||||
{
|
||||
if (sz <= 0) sz = gs_string_length(text) + 1;
|
||||
|
||||
mg_ui_text_t t = {
|
||||
.content = gs_malloc(sz),
|
||||
.pos = pos,
|
||||
.sz = sz,
|
||||
};
|
||||
memcpy(t.content, text, sz);
|
||||
return gs_slot_array_insert(g_ui_manager->texts, t);
|
||||
}
|
||||
|
||||
void mg_ui_manager_update_text(const uint32_t id, const char *text)
|
||||
{
|
||||
if (gs_slot_array_handle_valid(g_ui_manager->texts, id))
|
||||
{
|
||||
mg_ui_text_t t = gs_slot_array_get(g_ui_manager->texts, id);
|
||||
size_t sz_old = t.sz;
|
||||
size_t sz_new = gs_string_length(text) + 1;
|
||||
if (sz_new > sz_old)
|
||||
{
|
||||
mg_println("WARN: mg_ui_manager_update_text new text size larger than old (%zu > %zu)", sz_new, sz_old);
|
||||
memcpy(t.content, text, sz_old);
|
||||
// Ensure null-terminated since we are clipping new string
|
||||
memset(t.content + sz_old - 1, '\0', 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(t.content, text, sz_new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mg_ui_manager_remove_text(const uint32_t id)
|
||||
{
|
||||
if (gs_slot_array_handle_valid(g_ui_manager->texts, id))
|
||||
{
|
||||
mg_ui_text_t t = gs_slot_array_get(g_ui_manager->texts, id);
|
||||
gs_free(t.content);
|
||||
gs_slot_array_erase(g_ui_manager->texts, id);
|
||||
}
|
||||
}
|
||||
|
||||
void mg_ui_manager_clear_text()
|
||||
{
|
||||
for (
|
||||
gs_slot_array_iter it = gs_slot_array_iter_new(g_ui_manager->texts);
|
||||
gs_slot_array_iter_valid(g_ui_manager->texts, it);
|
||||
gs_slot_array_iter_advance(g_ui_manager->texts, it))
|
||||
{
|
||||
mg_ui_text_t text = gs_slot_array_iter_get(g_ui_manager->texts, it);
|
||||
gs_free(text.content);
|
||||
}
|
||||
gs_slot_array_clear(g_ui_manager->texts);
|
||||
}
|
||||
|
||||
void _mg_ui_manager_text_overlay(gs_vec2 fbs, gs_gui_container_t *root)
|
||||
{
|
||||
if (gs_slot_array_size(g_ui_manager->texts) <= 0) return;
|
||||
|
||||
gs_gui_set_style_sheet(&g_renderer->gui, &g_ui_manager->console_style_sheet);
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&root->body, fbs.x, fbs.y, 0, 0, GS_GUI_LAYOUT_ANCHOR_TOPLEFT), 0);
|
||||
gs_gui_panel_begin_ex(&g_renderer->gui, "#texts", NULL, GS_GUI_OPT_NOSCROLL);
|
||||
{
|
||||
gs_gui_container_t *cnt = gs_gui_get_current_container(&g_renderer->gui);
|
||||
gs_gui_rect_t next = {};
|
||||
|
||||
for (
|
||||
gs_slot_array_iter it = gs_slot_array_iter_new(g_ui_manager->texts);
|
||||
gs_slot_array_iter_valid(g_ui_manager->texts, it);
|
||||
gs_slot_array_iter_advance(g_ui_manager->texts, it))
|
||||
{
|
||||
mg_ui_text_t text = gs_slot_array_iter_get(g_ui_manager->texts, it);
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&cnt->body, fbs.x, fbs.y, text.pos.x, text.pos.y, GS_GUI_LAYOUT_ANCHOR_TOPLEFT), 0);
|
||||
next = gs_gui_layout_next(&g_renderer->gui);
|
||||
gs_gui_draw_control_text(&g_renderer->gui, text.content, next, &g_ui_manager->console_style_sheet.styles[GS_GUI_ELEMENT_TEXT][GS_GUI_ELEMENT_STATE_DEFAULT], 0x00);
|
||||
}
|
||||
}
|
||||
gs_gui_panel_end(&g_renderer->gui);
|
||||
}
|
||||
|
||||
void _mg_ui_manager_debug_overlay(gs_vec2 fbs, gs_gui_container_t *root)
|
||||
{
|
||||
if (!g_ui_manager->debug_open) return;
|
||||
|
||||
char tmp[64];
|
||||
|
||||
gs_gui_set_style_sheet(&g_renderer->gui, &g_ui_manager->console_style_sheet);
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&root->body, fbs.x, fbs.y, 0, 0, GS_GUI_LAYOUT_ANCHOR_TOPLEFT), 0);
|
||||
gs_gui_panel_begin_ex(&g_renderer->gui, "#debug", NULL, GS_GUI_OPT_NOSCROLL);
|
||||
{
|
||||
gs_gui_container_t *cnt = gs_gui_get_current_container(&g_renderer->gui);
|
||||
gs_gui_rect_t next = {};
|
||||
|
||||
float tmp_y = 5;
|
||||
const float tmp_pad = 3;
|
||||
gs_gui_style_t *style = &g_ui_manager->console_style_sheet.styles[GS_GUI_ELEMENT_TEXT][GS_GUI_ELEMENT_STATE_DEFAULT];
|
||||
float32_t line_height = gs_asset_font_max_height(style->font);
|
||||
#define DRAW_TMP(POS_X, POS_Y) \
|
||||
{ \
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&cnt->body, fbs.x, fbs.y, POS_X, POS_Y, GS_GUI_LAYOUT_ANCHOR_TOPLEFT), 0); \
|
||||
next = gs_gui_layout_next(&g_renderer->gui); \
|
||||
gs_gui_draw_control_text(&g_renderer->gui, tmp, next, style, 0x00); \
|
||||
tmp_y += line_height + tmp_pad; \
|
||||
}
|
||||
|
||||
// draw fps
|
||||
sprintf(
|
||||
tmp,
|
||||
"fps: %d",
|
||||
(int)gs_round(1.0f / g_time_manager->unscaled_delta));
|
||||
DRAW_TMP(5, tmp_y)
|
||||
|
||||
// draw times
|
||||
sprintf(tmp, "game:");
|
||||
DRAW_TMP(5, tmp_y)
|
||||
|
||||
sprintf(tmp, "update: %.2fms", g_time_manager->update * 1000.0);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
|
||||
sprintf(tmp, "render: %.2fms", g_time_manager->render * 1000.0);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
|
||||
sprintf(tmp, "bsp:");
|
||||
DRAW_TMP(15, tmp_y)
|
||||
sprintf(tmp, "vis: %.2fms", g_time_manager->vis * 1000.0);
|
||||
DRAW_TMP(20, tmp_y)
|
||||
sprintf(tmp, "render: %.2fms", g_time_manager->bsp * 1000.0);
|
||||
DRAW_TMP(20, tmp_y)
|
||||
|
||||
sprintf(tmp, "models: %.2fms", g_time_manager->models * 1000.0);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
sprintf(tmp, "viewmodel: %.2fms", g_time_manager->viewmodel * 1000.0);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
sprintf(tmp, "post: %.2fms", g_time_manager->post * 1000.0);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
sprintf(tmp, "ui: %.2fms", g_time_manager->ui * 1000.0);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
sprintf(tmp, "submit: %.2fms", g_time_manager->submit * 1000.0);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
|
||||
sprintf(tmp, "gs:");
|
||||
DRAW_TMP(5, tmp_y)
|
||||
sprintf(tmp, "update: %.2fms", gs_platform_time()->update);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "render: %.2fms", gs_platform_time()->render);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "wait: %.2fms", gs_platform_time()->frame - gs_platform_time()->update - gs_platform_time()->render);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
|
||||
// draw map stats
|
||||
if (g_game_manager->map != NULL && g_game_manager->map->valid)
|
||||
{
|
||||
sprintf(tmp, "map: %s", g_game_manager->map->name);
|
||||
DRAW_TMP(5, tmp_y)
|
||||
sprintf(tmp, "tris: %zu/%zu", g_game_manager->map->stats.visible_indices / 3, g_game_manager->map->stats.total_indices / 3);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "faces: %zu/%zu", g_game_manager->map->stats.visible_faces, g_game_manager->map->stats.total_faces);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "patches: %zu/%zu", g_game_manager->map->stats.visible_patches, g_game_manager->map->stats.total_patches);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "leaf: %zu, cluster: %d", g_game_manager->map->stats.current_leaf, g_game_manager->map->leaves.data[g_game_manager->map->stats.current_leaf].cluster);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "leaves:");
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "total: %zu", g_game_manager->map->leaves.count);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
sprintf(tmp, "pvs culled: %zu", g_game_manager->map->stats.culled_leaves_pvs);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
sprintf(tmp, "frustum culled: %zu", g_game_manager->map->stats.culled_leaves_frustum);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
sprintf(tmp, "visible: %zu", g_game_manager->map->stats.visible_leaves);
|
||||
DRAW_TMP(15, tmp_y)
|
||||
}
|
||||
|
||||
// draw player stats
|
||||
if (g_game_manager != NULL && g_game_manager->player != NULL)
|
||||
{
|
||||
sprintf(tmp, "player:");
|
||||
DRAW_TMP(5, tmp_y)
|
||||
sprintf(tmp, "pos: [%f, %f, %f]", g_game_manager->player->transform.position.x, g_game_manager->player->transform.position.y, g_game_manager->player->transform.position.z);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "ang: [%f, %f, %f]", g_game_manager->player->yaw, g_game_manager->player->camera.pitch, g_game_manager->player->camera.roll);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "vel: [%f, %f, %f]", g_game_manager->player->velocity.x, g_game_manager->player->velocity.y, g_game_manager->player->velocity.z);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
sprintf(tmp, "vel_abs: %f, h: %f", gs_vec3_len(g_game_manager->player->velocity), gs_vec3_len(gs_v3(g_game_manager->player->velocity.x, g_game_manager->player->velocity.y, 0)));
|
||||
DRAW_TMP(10, tmp_y)
|
||||
}
|
||||
else if (g_renderer->cam)
|
||||
{
|
||||
// use renderer camera directly
|
||||
sprintf(tmp, "camera:");
|
||||
DRAW_TMP(5, tmp_y)
|
||||
sprintf(tmp, "pos: [%f, %f, %f]", g_renderer->cam->transform.position.x, g_renderer->cam->transform.position.y, g_renderer->cam->transform.position.z);
|
||||
DRAW_TMP(10, tmp_y)
|
||||
// TODO: yaw/pitch/roll conversion
|
||||
// sprintf(tmp, "ang: [%f, %f, %f]", gs_rad2deg(g_renderer->cam->transform.rotation.x), gs_rad2deg(g_renderer->cam->transform.rotation.y), gs_rad2deg(g_renderer->cam->transform.rotation.z));
|
||||
// DRAW_TMP(10, tmp_y)
|
||||
}
|
||||
}
|
||||
gs_gui_panel_end(&g_renderer->gui);
|
||||
}
|
||||
|
||||
void _mg_ui_manager_console_window(gs_vec2 fbs, gs_gui_container_t *root)
|
||||
{
|
||||
if (!g_ui_manager->console_open) return;
|
||||
|
||||
char tmp[64];
|
||||
|
||||
gs_gui_set_style_sheet(&g_renderer->gui, &g_ui_manager->console_style_sheet);
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&root->body, fbs.x, fbs.y, 0, 0, GS_GUI_LAYOUT_ANCHOR_TOPLEFT), 0);
|
||||
gs_gui_panel_begin_ex(&g_renderer->gui, "#console", NULL, GS_GUI_OPT_NOSCROLL);
|
||||
{
|
||||
gs_gui_container_t *con = gs_gui_get_current_container(&g_renderer->gui);
|
||||
gs_gui_rect_t next = {};
|
||||
|
||||
// draw background
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&con->body, fbs.x, fbs.y, 0, 0, GS_GUI_LAYOUT_ANCHOR_TOPLEFT), 0);
|
||||
next = gs_gui_layout_next(&g_renderer->gui);
|
||||
gs_gui_draw_rect(&g_renderer->gui, next, gs_color(0, 0, 0, 200));
|
||||
gs_gui_rect_t bg = g_renderer->gui.last_rect;
|
||||
|
||||
#define DRAW_CON(TEXT, POS_X, POS_Y) \
|
||||
{ \
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&con->body, fbs.x, fbs.y, POS_X, POS_Y, GS_GUI_LAYOUT_ANCHOR_TOPLEFT), 0); \
|
||||
next = gs_gui_layout_next(&g_renderer->gui); \
|
||||
gs_gui_draw_control_text(&g_renderer->gui, TEXT, next, &g_ui_manager->console_style_sheet.styles[GS_GUI_ELEMENT_TEXT][GS_GUI_ELEMENT_STATE_DEFAULT], 0x00); \
|
||||
}
|
||||
|
||||
gs_asset_font_t *font = g_ui_manager->console_style_sheet.styles[GS_GUI_ELEMENT_TEXT][GS_GUI_ELEMENT_STATE_DEFAULT].font;
|
||||
float32_t line_height = gs_asset_font_max_height(font);
|
||||
float32_t char_width = gs_asset_font_text_dimensions(font, "W", 1).x;
|
||||
|
||||
// Draw output
|
||||
int32_t line_num = 0;
|
||||
int32_t input_height = line_height + 16;
|
||||
int32_t line_offset = input_height + line_height + 4;
|
||||
for (size_t i = 0; i < MG_CON_LINES; i++)
|
||||
{
|
||||
if (g_console->output[i] == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line_num >= g_ui_manager->console_scroll_y)
|
||||
{
|
||||
DRAW_CON(
|
||||
g_console->output[i],
|
||||
g_ui_manager->console_scroll_x * char_width,
|
||||
fbs.y - line_offset);
|
||||
line_offset += line_height;
|
||||
}
|
||||
|
||||
line_num++;
|
||||
}
|
||||
|
||||
// Draw input
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&con->body, fbs.x, input_height, 0, 0, GS_GUI_LAYOUT_ANCHOR_BOTTOMLEFT), 0);
|
||||
gs_gui_panel_begin_ex(&g_renderer->gui, "#console-input", NULL, GS_GUI_OPT_NOSCROLL);
|
||||
{
|
||||
gs_gui_layout_row(&g_renderer->gui, 1, (int[]){-1}, input_height);
|
||||
gs_gui_textbox(&g_renderer->gui, g_ui_manager->console_input, 256);
|
||||
}
|
||||
gs_gui_panel_end(&g_renderer->gui);
|
||||
}
|
||||
gs_gui_panel_end(&g_renderer->gui);
|
||||
}
|
||||
|
||||
void _mg_ui_manager_dialogue_window(gs_vec2 fbs, gs_gui_container_t *root)
|
||||
{
|
||||
if (!g_ui_manager->dialogue_open) return;
|
||||
|
||||
mg_ui_dialogue_t diag = g_ui_manager->current_dialogue;
|
||||
|
||||
gs_gui_set_style_sheet(&g_renderer->gui, &g_ui_manager->dialogue_style_sheet);
|
||||
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&root->body, fbs.x, fbs.y, 0, 0, GS_GUI_LAYOUT_ANCHOR_CENTER), 0);
|
||||
gs_gui_panel_begin_ex(&g_renderer->gui, "#dialogue", NULL, GS_GUI_OPT_NOSCROLL);
|
||||
{
|
||||
gs_gui_container_t *dialogue = gs_gui_get_current_container(&g_renderer->gui);
|
||||
|
||||
// split text to lines
|
||||
uint32_t max_width = 600;
|
||||
int32_t offset_y = -50;
|
||||
gs_asset_font_t *font = g_ui_manager->dialogue_style_sheet.styles[GS_GUI_ELEMENT_TEXT][GS_GUI_ELEMENT_STATE_DEFAULT].font;
|
||||
float32_t line_height = gs_asset_font_max_height(font);
|
||||
uint32_t *num_lines = gs_malloc_init(uint32_t);
|
||||
char **lines = gs_malloc(sizeof(char *) * 64);
|
||||
mg_text_to_lines(font, diag.content, max_width, lines, num_lines);
|
||||
float32_t pad_y = 1.0f * line_height;
|
||||
float32_t pad_x = 2.0f * gs_asset_font_text_dimensions(font, " ", -1).x;
|
||||
float32_t height = (*num_lines) * line_height;
|
||||
|
||||
// draw background
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&dialogue->body, max_width + 2.0f * pad_x, height + 2.0f * pad_y, 0, offset_y, GS_GUI_LAYOUT_ANCHOR_BOTTOMCENTER), 0);
|
||||
gs_gui_rect_t next = gs_gui_layout_next(&g_renderer->gui);
|
||||
gs_gui_draw_rect(&g_renderer->gui, next, gs_color(0, 0, 0, 100));
|
||||
gs_gui_rect_t bg = g_renderer->gui.last_rect;
|
||||
|
||||
// draw lines
|
||||
for (size_t i = 0; i < *num_lines; i++)
|
||||
{
|
||||
float32_t magic = -0.4f * line_height; // texts are too low by roughly this much, TODO: why?
|
||||
float32_t off_y = i * line_height + pad_y + magic;
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&dialogue->body, max_width, line_height, bg.x + pad_x, bg.y + off_y, GS_GUI_LAYOUT_ANCHOR_TOPLEFT), 0);
|
||||
gs_gui_rect_t next = gs_gui_layout_next(&g_renderer->gui);
|
||||
gs_gui_draw_control_text(&g_renderer->gui, lines[i], next, &g_ui_manager->dialogue_style_sheet.styles[GS_GUI_ELEMENT_TEXT][GS_GUI_ELEMENT_STATE_DEFAULT], 0x00);
|
||||
|
||||
gs_free(lines[i]);
|
||||
}
|
||||
|
||||
gs_free(lines);
|
||||
gs_free(num_lines);
|
||||
}
|
||||
gs_gui_panel_end(&g_renderer->gui);
|
||||
|
||||
double pt = g_time_manager->time;
|
||||
if (diag.duration > 0 && pt - diag._start_time > diag.duration)
|
||||
g_ui_manager->dialogue_open = false;
|
||||
}
|
||||
|
||||
void _mg_ui_manager_menu_window(gs_vec2 fbs, gs_gui_container_t *root)
|
||||
{
|
||||
if (!g_ui_manager->menu_open) return;
|
||||
|
||||
gs_gui_set_style_sheet(&g_renderer->gui, &g_ui_manager->default_style_sheet);
|
||||
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&root->body, fbs.x, fbs.y, 0, 0, GS_GUI_LAYOUT_ANCHOR_CENTER), 0);
|
||||
gs_gui_panel_begin_ex(&g_renderer->gui, "#menu", NULL, GS_GUI_OPT_NOSCROLL);
|
||||
{
|
||||
gs_gui_container_t *menu = gs_gui_get_current_container(&g_renderer->gui);
|
||||
|
||||
// buttons panel
|
||||
gs_gui_layout_set_next(&g_renderer->gui, gs_gui_layout_anchor(&menu->body, 500, 500, 0, 0, GS_GUI_LAYOUT_ANCHOR_BOTTOMCENTER), 0);
|
||||
gs_gui_panel_begin_ex(&g_renderer->gui, "#buttons", NULL, GS_GUI_OPT_NOSCROLL);
|
||||
{
|
||||
gs_gui_layout_row(&g_renderer->gui, 1, (int[]){-1}, 0);
|
||||
_mg_ui_manager_custom_button("Test 1");
|
||||
_mg_ui_manager_custom_button("Test 2");
|
||||
_mg_ui_manager_custom_button("Test 3");
|
||||
if (_mg_ui_manager_custom_button("Exit Game"))
|
||||
gs_quit();
|
||||
}
|
||||
gs_gui_panel_end(&g_renderer->gui);
|
||||
}
|
||||
gs_gui_panel_end(&g_renderer->gui);
|
||||
}
|
||||
|
||||
// Simple custom button command that allows us to render some inner highlights and shadows
|
||||
bool _mg_ui_manager_custom_button(const char *str)
|
||||
{
|
||||
gs_gui_context_t *ctx = &g_renderer->gui;
|
||||
bool ret = gs_gui_button(ctx, str);
|
||||
gs_gui_rect_t rect = ctx->last_rect;
|
||||
|
||||
// Draw inner shadows/highlights over button
|
||||
gs_color_t hc = GS_COLOR_WHITE, sc = gs_color(85, 85, 85, 255);
|
||||
int32_t w = 2;
|
||||
gs_gui_draw_rect(ctx, gs_gui_rect(rect.x + w, rect.y, rect.w - 2 * w, w), hc);
|
||||
gs_gui_draw_rect(ctx, gs_gui_rect(rect.x + w, rect.y + rect.h - w, rect.w - 2 * w, w), sc);
|
||||
gs_gui_draw_rect(ctx, gs_gui_rect(rect.x, rect.y, w, rect.h), hc);
|
||||
gs_gui_draw_rect(ctx, gs_gui_rect(rect.x + rect.w - w, rect.y, w, rect.h), sc);
|
||||
|
||||
return ret;
|
||||
}
|
90
src/graphics/ui_manager.h
Normal file
90
src/graphics/ui_manager.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*================================================================
|
||||
* graphics/ui_manager.h
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
...
|
||||
=================================================================*/
|
||||
|
||||
#ifndef MG_UI_MANAGER_H
|
||||
#define MG_UI_MANAGER_H
|
||||
|
||||
// clang-format off
|
||||
#include <gs/gs.h>
|
||||
#include <gs/util/gs_idraw.h>
|
||||
#include <gs/util/gs_gui.h>
|
||||
// clang-format on
|
||||
|
||||
#include "model.h"
|
||||
|
||||
enum
|
||||
{
|
||||
GUI_STYLE_ROOT = 0x00,
|
||||
GUI_STYLE_COUNT
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
GUI_FONT_SMALL = 0x00,
|
||||
GUI_FONT_MEDIUM,
|
||||
GUI_FONT_LARGE,
|
||||
GUI_FONT_COUNT
|
||||
};
|
||||
|
||||
typedef struct mg_ui_dialogue_t
|
||||
{
|
||||
char *content;
|
||||
float32_t duration;
|
||||
double _start_time;
|
||||
} mg_ui_dialogue_t;
|
||||
|
||||
typedef struct mg_ui_text_t
|
||||
{
|
||||
char *content;
|
||||
gs_vec2 pos;
|
||||
size_t sz;
|
||||
} mg_ui_text_t;
|
||||
|
||||
typedef struct mg_ui_manager_t
|
||||
{
|
||||
gs_gui_style_t styles[GUI_STYLE_COUNT][GS_GUI_ELEMENT_STATE_COUNT];
|
||||
gs_asset_font_t fonts[GUI_FONT_COUNT];
|
||||
gs_gui_style_sheet_t default_style_sheet;
|
||||
gs_gui_style_sheet_t console_style_sheet;
|
||||
gs_gui_style_sheet_t dialogue_style_sheet;
|
||||
mg_ui_dialogue_t current_dialogue;
|
||||
bool dialogue_open;
|
||||
bool menu_open;
|
||||
bool debug_open;
|
||||
bool console_open;
|
||||
bool show_cursor;
|
||||
int console_scroll_y;
|
||||
int console_scroll_x;
|
||||
char console_input[256];
|
||||
gs_slot_array(mg_ui_text_t) texts;
|
||||
} mg_ui_manager_t;
|
||||
|
||||
void mg_ui_manager_init();
|
||||
void mg_ui_manager_free();
|
||||
|
||||
void mg_ui_manager_render(gs_vec2 fbs, bool32_t clear);
|
||||
|
||||
void mg_ui_manager_set_dialogue(const char *text, float32_t duration);
|
||||
|
||||
uint32_t mg_ui_manager_add_text(const char *text, const gs_vec2 pos, size_t sz);
|
||||
void mg_ui_manager_update_text(const uint32_t id, const char *text);
|
||||
void mg_ui_manager_remove_text(const uint32_t id);
|
||||
void mg_ui_manager_clear_text();
|
||||
|
||||
void _mg_ui_manager_text_overlay(gs_vec2 fbs, gs_gui_container_t *root);
|
||||
void _mg_ui_manager_debug_overlay(gs_vec2 fbs, gs_gui_container_t *root);
|
||||
void _mg_ui_manager_console_window(gs_vec2 fbs, gs_gui_container_t *root);
|
||||
void _mg_ui_manager_dialogue_window(gs_vec2 fbs, gs_gui_container_t *root);
|
||||
void _mg_ui_manager_menu_window(gs_vec2 fbs, gs_gui_container_t *root);
|
||||
|
||||
bool _mg_ui_manager_custom_button(const char *str);
|
||||
|
||||
extern mg_ui_manager_t *g_ui_manager;
|
||||
|
||||
#endif // MG_UI_MANAGER_H
|
204
src/main.c
Normal file
204
src/main.c
Normal file
|
@ -0,0 +1,204 @@
|
|||
/*================================================================
|
||||
* main.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
The main entry point of my_game.
|
||||
=================================================================*/
|
||||
|
||||
#define GS_IMPL
|
||||
#include <gs/gs.h>
|
||||
#define GS_IMMEDIATE_DRAW_IMPL
|
||||
#include <gs/util/gs_idraw.h>
|
||||
#define GS_GUI_IMPL
|
||||
#include <gs/util/gs_gui.h>
|
||||
|
||||
#include "audio/audio_manager.h"
|
||||
#include "bsp/bsp_loader.h"
|
||||
#include "bsp/bsp_map.h"
|
||||
#include "entities/entity_manager.h"
|
||||
#include "entities/player.h"
|
||||
#include "game/config.h"
|
||||
#include "game/console.h"
|
||||
#include "game/game_manager.h"
|
||||
#include "game/time_manager.h"
|
||||
#include "graphics/model_manager.h"
|
||||
#include "graphics/renderer.h"
|
||||
#include "graphics/texture_manager.h"
|
||||
#include "graphics/ui_manager.h"
|
||||
|
||||
void app_init()
|
||||
{
|
||||
// Init managers, free in app_shutdown if adding here
|
||||
mg_config_init();
|
||||
mg_time_manager_init();
|
||||
mg_audio_manager_init();
|
||||
mg_texture_manager_init();
|
||||
mg_model_manager_init();
|
||||
mg_renderer_init(gs_platform_main_window());
|
||||
mg_entity_manager_init();
|
||||
mg_ui_manager_init();
|
||||
mg_game_manager_init();
|
||||
|
||||
// Lock mouse at start by default
|
||||
// gs_platform_lock_mouse(gs_platform_main_window(), true);
|
||||
|
||||
#ifndef __ANDROID__
|
||||
if (glfwRawMouseMotionSupported())
|
||||
{
|
||||
glfwSetInputMode(gs_platform_raw_window_handle(gs_platform_main_window()), GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||
}
|
||||
#endif
|
||||
|
||||
// - - - -
|
||||
// MD3 testing
|
||||
mg_model_t *testmodel = mg_model_manager_find("players/sarge/head.md3");
|
||||
gs_vqs *testmodel_transform = gs_malloc_init(gs_vqs);
|
||||
testmodel_transform->position = gs_v3(660.0f, 778.0f, -10.0f);
|
||||
testmodel_transform->rotation = gs_quat_from_euler(0.0f, 0.0f, 0.0f);
|
||||
testmodel_transform->scale = gs_v3(1.0f, 1.0f, 1.0f);
|
||||
mg_renderer_create_renderable(*testmodel, testmodel_transform);
|
||||
|
||||
mg_model_t *testmodel_1 = mg_model_manager_find("players/sarge/upper.md3");
|
||||
gs_vqs *testmodel_transform_1 = gs_malloc_init(gs_vqs);
|
||||
testmodel_transform_1->position = gs_v3(660.0f, 748.0f, -10.0f);
|
||||
testmodel_transform_1->rotation = gs_quat_from_euler(0.0f, 0.0f, 0.0f);
|
||||
testmodel_transform_1->scale = gs_v3(1.0f, 1.0f, 1.0f);
|
||||
uint32_t id_1 = mg_renderer_create_renderable(*testmodel_1, testmodel_transform_1);
|
||||
|
||||
mg_model_t *testmodel_2 = mg_model_manager_find("players/sarge/lower.md3");
|
||||
gs_vqs *testmodel_transform_2 = gs_malloc_init(gs_vqs);
|
||||
testmodel_transform_2->position = gs_v3(660.0f, 718.0f, -10.0f);
|
||||
testmodel_transform_2->rotation = gs_quat_from_euler(0.0f, 0.0f, 0.0f);
|
||||
testmodel_transform_2->scale = gs_v3(1.0f, 1.0f, 1.0f);
|
||||
uint32_t id_2 = mg_renderer_create_renderable(*testmodel_2, testmodel_transform_2);
|
||||
|
||||
mg_model_t *testmodel_3 = mg_model_manager_find_or_load("weapons/rocket_launcher.md3", "basic");
|
||||
gs_vqs *testmodel_transform_3 = gs_malloc_init(gs_vqs);
|
||||
testmodel_transform_3->position = gs_v3(660.0f, 680.0f, -10.0f);
|
||||
testmodel_transform_3->rotation = gs_quat_from_euler(0.0f, 0.0f, 0.0f);
|
||||
testmodel_transform_3->scale = gs_v3(1.0f, 1.0f, 1.0f);
|
||||
uint32_t id_3 = mg_renderer_create_renderable(*testmodel_3, testmodel_transform_3);
|
||||
|
||||
mg_model_t *testmodel_4 = mg_model_manager_find_or_load("weapons/machine_gun.md3", "basic");
|
||||
gs_vqs *testmodel_transform_4 = gs_malloc_init(gs_vqs);
|
||||
testmodel_transform_4->position = gs_v3(660.0f, 600.0f, -10.0f);
|
||||
testmodel_transform_4->rotation = gs_quat_from_euler(0.0f, 0.0f, 0.0f);
|
||||
testmodel_transform_4->scale = gs_v3(1.0f, 1.0f, 1.0f);
|
||||
uint32_t id_4 = mg_renderer_create_renderable(*testmodel_4, testmodel_transform_4);
|
||||
|
||||
mg_renderer_play_animation(id_1, "TORSO_GESTURE");
|
||||
mg_renderer_play_animation(id_2, "LEGS_WALK");
|
||||
// - - - -
|
||||
|
||||
// UI test
|
||||
mg_ui_manager_set_dialogue(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
-1);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
g_ui_manager->debug_open = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_update()
|
||||
{
|
||||
mg_time_manager_update_start();
|
||||
uint32_t main_window = gs_platform_main_window();
|
||||
|
||||
#ifndef __ANDROID__
|
||||
gs_vec2 window_size = gs_platform_window_sizev(main_window);
|
||||
bool32_t is_fullscreen = gs_platform_window_fullscreen(main_window);
|
||||
mg_cvar_t *vid_width = mg_cvar("vid_width");
|
||||
mg_cvar_t *vid_height = mg_cvar("vid_height");
|
||||
mg_cvar_t *vid_fullscreen = mg_cvar("vid_fullscreen");
|
||||
if (window_size.x != vid_width->value.i || window_size.y != vid_height->value.i)
|
||||
{
|
||||
gs_platform_set_window_size(main_window, vid_width->value.i, vid_height->value.i);
|
||||
}
|
||||
if (is_fullscreen != vid_fullscreen->value.i)
|
||||
{
|
||||
gs_platform_set_window_fullscreen(main_window, vid_fullscreen->value.i);
|
||||
}
|
||||
#endif
|
||||
|
||||
gs_platform_t *platform = gs_subsystem(platform);
|
||||
|
||||
mg_cvar_t *vid_max_fps = mg_cvar("vid_max_fps");
|
||||
if (platform->time.max_fps != vid_max_fps->value.i)
|
||||
{
|
||||
gs_platform_set_frame_rate(vid_max_fps->value.i);
|
||||
}
|
||||
|
||||
mg_cvar_t *r_filter = mg_cvar("r_filter");
|
||||
mg_cvar_t *r_filter_mip = mg_cvar("r_filter_mip");
|
||||
mg_cvar_t *r_mips = mg_cvar("r_mips");
|
||||
if (
|
||||
g_texture_manager->tex_filter != r_filter->value.i + 1 ||
|
||||
g_texture_manager->mip_filter != r_filter_mip->value.i + 1 ||
|
||||
g_texture_manager->num_mips != r_mips->value.i)
|
||||
{
|
||||
mg_texture_manager_set_filter(
|
||||
r_filter->value.i + 1,
|
||||
r_filter_mip->value.i + 1,
|
||||
r_mips->value.i);
|
||||
}
|
||||
|
||||
#ifndef __ANDROID__
|
||||
// If click, then lock again (in case lost)
|
||||
if (gs_platform_mouse_pressed(GS_MOUSE_LBUTTON) && !gs_platform_mouse_locked() && !g_ui_manager->show_cursor)
|
||||
{
|
||||
gs_platform_lock_mouse(main_window, true);
|
||||
if (glfwRawMouseMotionSupported())
|
||||
{
|
||||
glfwSetInputMode(gs_platform_raw_window_handle(main_window), GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
mg_game_manager_update();
|
||||
mg_entity_manager_update();
|
||||
|
||||
mg_time_manager_update_end();
|
||||
|
||||
mg_renderer_update();
|
||||
}
|
||||
|
||||
void app_shutdown()
|
||||
{
|
||||
mg_game_manager_free();
|
||||
mg_entity_manager_free();
|
||||
mg_renderer_free();
|
||||
mg_ui_manager_free();
|
||||
mg_model_manager_free();
|
||||
mg_texture_manager_free();
|
||||
mg_audio_manager_free();
|
||||
mg_time_manager_free();
|
||||
mg_config_free();
|
||||
mg_console_free();
|
||||
}
|
||||
|
||||
gs_app_desc_t gs_main(int32_t argc, char **argv)
|
||||
{
|
||||
mg_console_init();
|
||||
|
||||
return (gs_app_desc_t){
|
||||
.init = app_init,
|
||||
.update = app_update,
|
||||
.shutdown = app_shutdown,
|
||||
.window_title = "Game",
|
||||
.window_flags = 0,
|
||||
.window_width = 800,
|
||||
.window_height = 600,
|
||||
.enable_vsync = 0,
|
||||
.frame_rate = 60,
|
||||
};
|
||||
}
|
||||
|
||||
// "call DEBUG_fc()" in gdb when hitting breakpoint to free cursor on Linux
|
||||
void DEBUG_fc()
|
||||
{
|
||||
gs_platform_lock_mouse(gs_platform_main_window(), false);
|
||||
}
|
398
src/model_viewer.c
Normal file
398
src/model_viewer.c
Normal file
|
@ -0,0 +1,398 @@
|
|||
/*================================================================
|
||||
* model_viewer.c
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
The main entry point of my_game model viewer.
|
||||
=================================================================*/
|
||||
|
||||
#define GS_IMPL
|
||||
#include <gs/gs.h>
|
||||
#define GS_IMMEDIATE_DRAW_IMPL
|
||||
#include <gs/util/gs_idraw.h>
|
||||
#define GS_GUI_IMPL
|
||||
#include <gs/util/gs_gui.h>
|
||||
|
||||
#include "bsp/bsp_loader.h"
|
||||
#include "bsp/bsp_map.h"
|
||||
#include "entities/player.h"
|
||||
#include "game/config.h"
|
||||
#include "game/console.h"
|
||||
#include "game/time_manager.h"
|
||||
#include "graphics/model_manager.h"
|
||||
#include "graphics/renderer.h"
|
||||
#include "graphics/texture_manager.h"
|
||||
#include "graphics/ui_manager.h"
|
||||
#include "util/transform.h"
|
||||
|
||||
gs_camera_t *camera = NULL;
|
||||
float32_t camera_yaw = 0;
|
||||
float32_t camera_pitch = 0;
|
||||
gs_vqs *model_transform = NULL;
|
||||
char *model_path = NULL;
|
||||
uint32_t model_id = GS_SLOT_ARRAY_INVALID_HANDLE;
|
||||
mg_renderable_t *renderable = NULL;
|
||||
uint32_t animation_index = 0;
|
||||
uint32_t animation_count = 0;
|
||||
bool32_t animation_paused = false;
|
||||
bool32_t animation_loop = true;
|
||||
uint32_t text_fps = GS_SLOT_ARRAY_INVALID_HANDLE;
|
||||
uint32_t text_animation = GS_SLOT_ARRAY_INVALID_HANDLE;
|
||||
uint32_t text_anim_fps = GS_SLOT_ARRAY_INVALID_HANDLE;
|
||||
uint32_t text_anim_frame = GS_SLOT_ARRAY_INVALID_HANDLE;
|
||||
uint32_t text_anim_pause = GS_SLOT_ARRAY_INVALID_HANDLE;
|
||||
uint32_t text_anim_loop = GS_SLOT_ARRAY_INVALID_HANDLE;
|
||||
bool valid = false;
|
||||
|
||||
void app_init()
|
||||
{
|
||||
camera = gs_malloc_init(gs_camera_t);
|
||||
*camera = gs_camera_perspective();
|
||||
camera->near_plane = 0.1f;
|
||||
camera->far_plane = 10000.0f;
|
||||
camera->transform.position = gs_v3(0, -50.0f, 0);
|
||||
|
||||
// Init managers, free in app_shutdown if adding here
|
||||
mg_time_manager_init();
|
||||
mg_texture_manager_init();
|
||||
mg_model_manager_init();
|
||||
mg_renderer_init(gs_platform_main_window());
|
||||
mg_ui_manager_init();
|
||||
|
||||
g_renderer->cam = camera;
|
||||
|
||||
// Lock mouse at start by default
|
||||
gs_platform_lock_mouse(gs_platform_main_window(), true);
|
||||
if (glfwRawMouseMotionSupported())
|
||||
{
|
||||
glfwSetInputMode(gs_platform_raw_window_handle(gs_platform_main_window()), GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void load_model(char *filename)
|
||||
{
|
||||
valid = false;
|
||||
if (model_id != GS_SLOT_ARRAY_INVALID_HANDLE)
|
||||
{
|
||||
mg_renderer_remove_renderable(model_id);
|
||||
gs_free(model_transform);
|
||||
}
|
||||
|
||||
if (model_path) gs_free(model_path);
|
||||
|
||||
size_t sz = gs_string_length(filename) + 1;
|
||||
model_path = gs_malloc(sz);
|
||||
memcpy(model_path, filename, sz);
|
||||
|
||||
mg_model_t *model = mg_model_manager_find(model_path);
|
||||
if (model == NULL)
|
||||
{
|
||||
if (!_mg_model_manager_load(model_path, "basic")) return;
|
||||
model = mg_model_manager_find(model_path);
|
||||
}
|
||||
model_transform = gs_malloc_init(gs_vqs);
|
||||
model_transform->position = gs_v3(0, 0, 0);
|
||||
model_transform->rotation = gs_quat_from_euler(0.0f, 0.0f, 0.0f);
|
||||
model_transform->scale = gs_v3(1.0f, 1.0f, 1.0f);
|
||||
model_id = mg_renderer_create_renderable(*model, model_transform);
|
||||
renderable = mg_renderer_get_renderable(model_id);
|
||||
animation_count = gs_dyn_array_size(renderable->model.data->animations);
|
||||
|
||||
mg_ui_manager_clear_text();
|
||||
|
||||
char tmp[256];
|
||||
|
||||
int32_t text_y = 5;
|
||||
int32_t text_h = 15;
|
||||
text_fps = mg_ui_manager_add_text("FPS: 0", gs_v2(5, text_y), 32);
|
||||
|
||||
int32_t num_surfaces = renderable->model.data->header.num_surfaces;
|
||||
int32_t num_verts = 0;
|
||||
int32_t num_tris = 0;
|
||||
for (size_t i = 0; i < num_surfaces; i++)
|
||||
{
|
||||
md3_surface_t surf = renderable->model.data->surfaces[i];
|
||||
num_verts += surf.num_verts;
|
||||
num_tris += surf.num_tris;
|
||||
}
|
||||
|
||||
text_y += text_h;
|
||||
sprintf(tmp, "Model: %s", model_path);
|
||||
mg_ui_manager_add_text(tmp, gs_v2(5, (text_y += text_h)), 0);
|
||||
sprintf(tmp, "Surfaces: %d", num_surfaces);
|
||||
mg_ui_manager_add_text(tmp, gs_v2(10, (text_y += text_h)), 32);
|
||||
sprintf(tmp, "Verts: %d", num_verts);
|
||||
mg_ui_manager_add_text(tmp, gs_v2(10, (text_y += text_h)), 32);
|
||||
sprintf(tmp, "Tris: %d", num_tris);
|
||||
mg_ui_manager_add_text(tmp, gs_v2(10, (text_y += text_h)), 32);
|
||||
|
||||
text_y += text_h;
|
||||
text_animation = mg_ui_manager_add_text("Animation: None", gs_v2(5, (text_y += text_h)), 64);
|
||||
text_anim_fps = mg_ui_manager_add_text("Anim FPS: 0", gs_v2(10, (text_y += text_h)), 32);
|
||||
text_anim_frame = mg_ui_manager_add_text("Frame: 0 / 0", gs_v2(10, (text_y += text_h)), 32);
|
||||
sprintf(tmp, "Pause: %d", animation_paused);
|
||||
text_anim_pause = mg_ui_manager_add_text(tmp, gs_v2(10, (text_y += text_h)), 0);
|
||||
sprintf(tmp, "Loop: %d", animation_loop);
|
||||
text_anim_loop = mg_ui_manager_add_text(tmp, gs_v2(10, (text_y += text_h)), 0);
|
||||
|
||||
text_y += text_h;
|
||||
mg_ui_manager_add_text("Controls:", gs_v2(5, (text_y += text_h)), 0);
|
||||
mg_ui_manager_add_text("WASD - Move", gs_v2(10, (text_y += text_h)), 0);
|
||||
mg_ui_manager_add_text("Mouse - Look", gs_v2(10, (text_y += text_h)), 0);
|
||||
mg_ui_manager_add_text("P - Pause", gs_v2(10, (text_y += text_h)), 0);
|
||||
mg_ui_manager_add_text("L - Loop", gs_v2(10, (text_y += text_h)), 0);
|
||||
mg_ui_manager_add_text("R - Restart animation", gs_v2(10, (text_y += text_h)), 0);
|
||||
mg_ui_manager_add_text("Up/Down - Next/Prev animation", gs_v2(10, (text_y += text_h)), 0);
|
||||
mg_ui_manager_add_text("Left/Right - Skip frame", gs_v2(10, (text_y += text_h)), 0);
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
void app_update()
|
||||
{
|
||||
mg_time_manager_update_start();
|
||||
double delta_time = g_time_manager->delta;
|
||||
double plat_time = g_time_manager->time;
|
||||
char tmp[64];
|
||||
|
||||
// If click, then lock again (in case lost)
|
||||
if (gs_platform_mouse_pressed(GS_MOUSE_LBUTTON) && !gs_platform_mouse_locked() && !g_ui_manager->show_cursor)
|
||||
{
|
||||
gs_platform_lock_mouse(gs_platform_main_window(), true);
|
||||
if (glfwRawMouseMotionSupported())
|
||||
{
|
||||
glfwSetInputMode(gs_platform_raw_window_handle(gs_platform_main_window()), GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_ui_manager->console_open)
|
||||
{
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_ESC))
|
||||
{
|
||||
g_ui_manager->console_open = false;
|
||||
}
|
||||
|
||||
f32 scroll_x, scroll_y;
|
||||
gs_platform_mouse_wheel(&scroll_x, &scroll_y);
|
||||
|
||||
if (gs_platform_key_down(GS_KEYCODE_LSHIFT))
|
||||
{
|
||||
scroll_x = scroll_y;
|
||||
scroll_y = 0;
|
||||
}
|
||||
|
||||
if (scroll_y != 0)
|
||||
{
|
||||
g_ui_manager->console_scroll_y += scroll_y < 0 ? -4 : 4;
|
||||
g_ui_manager->console_scroll_y = gs_clamp(g_ui_manager->console_scroll_y, 0, MG_CON_LINES - 1);
|
||||
}
|
||||
if (scroll_x != 0)
|
||||
{
|
||||
g_ui_manager->console_scroll_x += scroll_x < 0 ? -4 : 4;
|
||||
g_ui_manager->console_scroll_x = gs_min(g_ui_manager->console_scroll_x, 0);
|
||||
}
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_ENTER))
|
||||
{
|
||||
mg_console_input(g_ui_manager->console_input);
|
||||
memset(g_ui_manager->console_input, 0, 256);
|
||||
}
|
||||
}
|
||||
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_F1)) g_ui_manager->console_open = !g_ui_manager->console_open;
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_F2)) g_ui_manager->debug_open = !g_ui_manager->debug_open;
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
mg_time_manager_update_end();
|
||||
mg_renderer_update();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_ui_manager->console_open)
|
||||
{
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_ESC))
|
||||
{
|
||||
gs_quit();
|
||||
}
|
||||
|
||||
gs_vec3 wish_move = gs_v3(0, 0, 0);
|
||||
gs_vec2 dp = gs_vec2_scale(gs_platform_mouse_deltav(), 2.0f * 0.022f);
|
||||
|
||||
camera_pitch = gs_clamp(camera_pitch - dp.y, -90.0f, 90.0f);
|
||||
camera_yaw = fmodf(camera_yaw - dp.x, 360.0f);
|
||||
camera->transform.rotation = gs_quat_mul(
|
||||
gs_quat_angle_axis(gs_deg2rad(camera_yaw), MG_AXIS_UP),
|
||||
gs_quat_angle_axis(gs_deg2rad(camera_pitch), MG_AXIS_RIGHT));
|
||||
|
||||
if (gs_platform_key_down(GS_KEYCODE_W))
|
||||
wish_move = gs_vec3_add(wish_move, mg_get_forward(camera->transform.rotation));
|
||||
if (gs_platform_key_down(GS_KEYCODE_S))
|
||||
wish_move = gs_vec3_add(wish_move, mg_get_backward(camera->transform.rotation));
|
||||
if (gs_platform_key_down(GS_KEYCODE_D))
|
||||
wish_move = gs_vec3_add(wish_move, mg_get_right(camera->transform.rotation));
|
||||
if (gs_platform_key_down(GS_KEYCODE_A))
|
||||
wish_move = gs_vec3_add(wish_move, mg_get_left(camera->transform.rotation));
|
||||
if (gs_platform_key_down(GS_KEYCODE_SPACE))
|
||||
wish_move = gs_vec3_add(wish_move, MG_AXIS_UP);
|
||||
if (gs_platform_key_down(GS_KEYCODE_LEFT_CONTROL))
|
||||
wish_move = gs_vec3_add(wish_move, MG_AXIS_DOWN);
|
||||
|
||||
wish_move = gs_vec3_norm(wish_move);
|
||||
camera->transform.position = gs_vec3_add(camera->transform.position, gs_vec3_scale(wish_move, 200.0f * delta_time));
|
||||
|
||||
// Play next animation
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_UP) && animation_count > 0)
|
||||
{
|
||||
animation_index++;
|
||||
if (animation_index >= animation_count)
|
||||
animation_index = 0;
|
||||
|
||||
mg_renderer_play_animation(model_id, renderable->model.data->animations[animation_index].name);
|
||||
if (renderable->current_animation != NULL)
|
||||
{
|
||||
renderable->current_animation->loop = animation_loop;
|
||||
if (animation_paused)
|
||||
renderable->prev_frame_time = DBL_MAX;
|
||||
|
||||
sprintf(tmp, "Animation: %s", renderable->current_animation->name);
|
||||
mg_ui_manager_update_text(text_animation, tmp);
|
||||
sprintf(tmp, "Anim FPS: %d", renderable->current_animation->fps);
|
||||
mg_ui_manager_update_text(text_anim_fps, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// Play previous animation
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_DOWN) && animation_count > 0)
|
||||
{
|
||||
animation_index--;
|
||||
// underflow
|
||||
if (animation_index >= animation_count)
|
||||
animation_index = animation_count - 1;
|
||||
|
||||
mg_renderer_play_animation(model_id, renderable->model.data->animations[animation_index].name);
|
||||
if (renderable->current_animation != NULL)
|
||||
{
|
||||
renderable->current_animation->loop = animation_loop;
|
||||
if (animation_paused)
|
||||
renderable->prev_frame_time = DBL_MAX;
|
||||
|
||||
sprintf(tmp, "Animation: %s", renderable->current_animation->name);
|
||||
mg_ui_manager_update_text(text_animation, tmp);
|
||||
sprintf(tmp, "Anim FPS: %d", renderable->current_animation->fps);
|
||||
mg_ui_manager_update_text(text_anim_fps, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle pause
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_P))
|
||||
{
|
||||
animation_paused = !animation_paused;
|
||||
// Set prev_frame_time to future so animation wont progress
|
||||
if (animation_paused)
|
||||
renderable->prev_frame_time = DBL_MAX;
|
||||
else
|
||||
renderable->prev_frame_time = plat_time;
|
||||
|
||||
sprintf(tmp, "Pause: %d", animation_paused);
|
||||
mg_ui_manager_update_text(text_anim_pause, tmp);
|
||||
}
|
||||
|
||||
// Restart animation
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_R))
|
||||
{
|
||||
if (renderable->current_animation != NULL)
|
||||
{
|
||||
renderable->frame = renderable->current_animation->first_frame;
|
||||
renderable->prev_frame_time = plat_time;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle loop
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_L))
|
||||
{
|
||||
animation_loop = !animation_loop;
|
||||
if (renderable->current_animation != NULL)
|
||||
{
|
||||
renderable->current_animation->loop = animation_loop;
|
||||
}
|
||||
sprintf(tmp, "Loop: %d", animation_loop);
|
||||
mg_ui_manager_update_text(text_anim_loop, tmp);
|
||||
}
|
||||
|
||||
// Frame skip forwards
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_RIGHT) && renderable->current_animation != NULL)
|
||||
{
|
||||
renderable->frame++;
|
||||
if (renderable->frame >= renderable->current_animation->first_frame + renderable->current_animation->num_frames)
|
||||
{
|
||||
if (animation_loop)
|
||||
renderable->frame = renderable->current_animation->first_frame;
|
||||
else
|
||||
renderable->frame = renderable->current_animation->first_frame + renderable->current_animation->num_frames - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Frame skip backwards
|
||||
if (gs_platform_key_pressed(GS_KEYCODE_LEFT) && renderable->current_animation != NULL)
|
||||
{
|
||||
renderable->frame--;
|
||||
if (renderable->frame < renderable->current_animation->first_frame)
|
||||
{
|
||||
if (animation_loop)
|
||||
renderable->frame = renderable->current_animation->first_frame + renderable->current_animation->num_frames - 1;
|
||||
else
|
||||
renderable->frame = renderable->current_animation->first_frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sprintf(tmp, "FPS: %d", (int)gs_round(1.0f / g_time_manager->unscaled_delta));
|
||||
mg_ui_manager_update_text(text_fps, tmp);
|
||||
|
||||
sprintf(tmp, "Frame: %d / %d",
|
||||
renderable->current_animation != NULL ? renderable->frame - renderable->current_animation->first_frame + 1 : 0,
|
||||
renderable->current_animation != NULL ? renderable->current_animation->num_frames : 0);
|
||||
mg_ui_manager_update_text(text_anim_frame, tmp);
|
||||
|
||||
mg_time_manager_update_end();
|
||||
|
||||
mg_renderer_update();
|
||||
}
|
||||
|
||||
void app_shutdown()
|
||||
{
|
||||
mg_renderer_free();
|
||||
mg_ui_manager_free();
|
||||
mg_model_manager_free();
|
||||
mg_texture_manager_free();
|
||||
gs_free(camera);
|
||||
gs_free(model_transform);
|
||||
gs_free(model_path);
|
||||
|
||||
mg_time_manager_free();
|
||||
mg_config_free();
|
||||
mg_console_free();
|
||||
}
|
||||
|
||||
gs_app_desc_t gs_main(int32_t argc, char **argv)
|
||||
{
|
||||
mg_console_init();
|
||||
mg_config_init();
|
||||
|
||||
mg_cmd_arg_type args[] = {MG_CMD_ARG_STRING};
|
||||
mg_cmd_new("model", "Load a model. Example: 'model assets/models/players/sarge/upper.md3'", &load_model, (mg_cmd_arg_type *)args, 1);
|
||||
|
||||
return (gs_app_desc_t){
|
||||
.init = app_init,
|
||||
.update = app_update,
|
||||
.shutdown = app_shutdown,
|
||||
.window_title = "ModelViewer",
|
||||
.window_flags = 0,
|
||||
.window_width = 800,
|
||||
.window_height = 600,
|
||||
.enable_vsync = 1,
|
||||
.frame_rate = 60,
|
||||
};
|
||||
}
|
37
src/shaders/mobile/basic_fs.glsl
Normal file
37
src/shaders/mobile/basic_fs.glsl
Normal file
|
@ -0,0 +1,37 @@
|
|||
#version 300 es
|
||||
|
||||
in mediump vec3 v_normal;
|
||||
in mediump vec2 v_texcoord;
|
||||
|
||||
struct Light
|
||||
{
|
||||
mediump vec3 ambient;
|
||||
mediump vec3 directional;
|
||||
mediump vec3 direction;
|
||||
};
|
||||
|
||||
uniform Light u_light;
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out mediump vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 albedo = texture(u_tex, v_texcoord);
|
||||
|
||||
// magic values for the look I want
|
||||
mediump float directional_strength = 2.4;
|
||||
mediump float ambient_strength = 1.0;
|
||||
mediump float gamma = 1.1;
|
||||
|
||||
// Directional
|
||||
mediump float d = dot(v_normal, u_light.direction);
|
||||
mediump float light_dot = max(0.0, d);
|
||||
mediump vec3 lighting = u_light.directional * light_dot * directional_strength;
|
||||
|
||||
// Ambient
|
||||
lighting += u_light.ambient * ambient_strength;
|
||||
|
||||
frag_color = albedo * vec4(lighting, 1.0);
|
||||
frag_color.rgb = pow(frag_color.rgb, vec3(1.0 / gamma));
|
||||
}
|
12
src/shaders/mobile/basic_unlit_fs.glsl
Normal file
12
src/shaders/mobile/basic_unlit_fs.glsl
Normal file
|
@ -0,0 +1,12 @@
|
|||
#version 300 es
|
||||
|
||||
in mediump vec2 v_texcoord;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out mediump vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
frag_color = texture(u_tex, v_texcoord);
|
||||
}
|
15
src/shaders/mobile/basic_unlit_vs.glsl
Normal file
15
src/shaders/mobile/basic_unlit_vs.glsl
Normal file
|
@ -0,0 +1,15 @@
|
|||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
layout(location = 1) in vec2 a_texcoord;
|
||||
|
||||
uniform mat4 u_proj;
|
||||
uniform mat4 u_view;
|
||||
|
||||
out vec2 v_texcoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
v_texcoord = a_texcoord;
|
||||
gl_Position = u_proj * u_view * vec4(a_pos, 1.0);
|
||||
}
|
19
src/shaders/mobile/basic_vs.glsl
Normal file
19
src/shaders/mobile/basic_vs.glsl
Normal file
|
@ -0,0 +1,19 @@
|
|||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
layout(location = 1) in vec3 a_normal;
|
||||
layout(location = 2) in vec2 a_texcoord;
|
||||
|
||||
uniform mat4 u_proj;
|
||||
uniform mat4 u_view;
|
||||
|
||||
out vec3 v_normal;
|
||||
out vec2 v_texcoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
v_normal = -normalize(mat3(u_view) * a_normal);
|
||||
v_texcoord = a_texcoord;
|
||||
|
||||
gl_Position = u_proj * u_view * vec4(a_pos, 1.0);
|
||||
}
|
23
src/shaders/mobile/bsp_fs.glsl
Normal file
23
src/shaders/mobile/bsp_fs.glsl
Normal file
|
@ -0,0 +1,23 @@
|
|||
#version 300 es
|
||||
|
||||
in mediump vec2 tex_coord;
|
||||
in mediump vec2 lm_coord;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
uniform sampler2D u_lm;
|
||||
|
||||
out mediump vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 tex = texture(u_tex, tex_coord);
|
||||
mediump vec4 lm = texture(u_lm, lm_coord);
|
||||
|
||||
// magic values for the look I want
|
||||
mediump float lm_strength = 2.8;
|
||||
mediump float gamma = 1.15;
|
||||
|
||||
frag_color = tex * lm * lm_strength;
|
||||
frag_color.rgb = pow(frag_color.rgb, vec3(1.0/gamma));
|
||||
frag_color.a = 1.0;
|
||||
}
|
19
src/shaders/mobile/bsp_vs.glsl
Normal file
19
src/shaders/mobile/bsp_vs.glsl
Normal file
|
@ -0,0 +1,19 @@
|
|||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
layout(location = 1) in vec2 a_tex_coord;
|
||||
layout(location = 2) in vec2 a_lm_coord;
|
||||
layout(location = 3) in vec3 a_normal;
|
||||
layout(location = 4) in vec4 a_color;
|
||||
|
||||
uniform mat4 u_proj;
|
||||
|
||||
out vec2 tex_coord;
|
||||
out vec2 lm_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_proj * vec4(a_pos, 1.0);
|
||||
tex_coord = a_tex_coord;
|
||||
lm_coord = a_lm_coord;
|
||||
}
|
10
src/shaders/mobile/bsp_wireframe_fs.glsl
Normal file
10
src/shaders/mobile/bsp_wireframe_fs.glsl
Normal file
|
@ -0,0 +1,10 @@
|
|||
#version 300 es
|
||||
|
||||
uniform mediump vec4 u_color;
|
||||
|
||||
out mediump vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
frag_color = u_color;
|
||||
}
|
10
src/shaders/mobile/bsp_wireframe_vs.glsl
Normal file
10
src/shaders/mobile/bsp_wireframe_vs.glsl
Normal file
|
@ -0,0 +1,10 @@
|
|||
#version 300 es
|
||||
|
||||
layout(location = 0) in mediump vec3 a_pos;
|
||||
|
||||
uniform mediump mat4 u_proj;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_proj * vec4(a_pos, 1.0);
|
||||
}
|
30
src/shaders/mobile/post_fs.glsl
Normal file
30
src/shaders/mobile/post_fs.glsl
Normal file
|
@ -0,0 +1,30 @@
|
|||
#version 300 es
|
||||
|
||||
flat in int barrel_enabled;
|
||||
in mediump vec3 uv;
|
||||
in mediump vec2 uv_dot;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
uniform sampler2D u_tex_vm;
|
||||
|
||||
out mediump vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 color1;
|
||||
mediump vec4 color2;
|
||||
|
||||
if (barrel_enabled == 1)
|
||||
{
|
||||
mediump vec3 tex_coord = dot(uv_dot, uv_dot) * vec3(-0.5, -0.5, -1.0) + uv;
|
||||
color1 = textureProj(u_tex, tex_coord);
|
||||
color2 = textureProj(u_tex, tex_coord);
|
||||
}
|
||||
else
|
||||
{
|
||||
color1 = texture(u_tex, uv.xy);
|
||||
color2 = texture(u_tex_vm, uv.xy);
|
||||
}
|
||||
|
||||
frag_color = mix(color1, color2, color2.a);
|
||||
}
|
38
src/shaders/mobile/post_vs.glsl
Normal file
38
src/shaders/mobile/post_vs.glsl
Normal file
|
@ -0,0 +1,38 @@
|
|||
#version 300 es
|
||||
|
||||
layout(location = 0) in mediump vec2 a_pos;
|
||||
|
||||
uniform int u_barrel_enabled;
|
||||
uniform mediump float u_barrel_strength;
|
||||
uniform mediump float u_barrel_height;
|
||||
uniform mediump float u_barrel_aspect;
|
||||
uniform mediump float u_barrel_cyl_ratio;
|
||||
|
||||
flat out int barrel_enabled;
|
||||
out mediump vec3 uv;
|
||||
out mediump vec2 uv_dot;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(a_pos, 0, 1.0);
|
||||
|
||||
barrel_enabled = u_barrel_enabled;
|
||||
if (u_barrel_enabled == 1)
|
||||
{
|
||||
mediump float scaled_height = u_barrel_strength * u_barrel_height;
|
||||
mediump float cyl_aspect = u_barrel_aspect * u_barrel_cyl_ratio;
|
||||
mediump float apsect_diag_sq = u_barrel_aspect * u_barrel_aspect + 1.0;
|
||||
mediump float diag_sq = scaled_height * scaled_height * apsect_diag_sq;
|
||||
|
||||
mediump float z = 0.5 * sqrt(diag_sq + 1.0) + 0.5;
|
||||
mediump float ny = (z - 1.0) / (cyl_aspect * cyl_aspect + 1.0);
|
||||
|
||||
uv_dot = sqrt(ny) * vec2(cyl_aspect, 1.0) * a_pos;
|
||||
uv = vec3(0.5, 0.5, 1.0) * z + vec3(-0.5, -0.5, 0.0);
|
||||
uv.xy += vec2(max(0.0, a_pos.x), max(0.0, a_pos.y));
|
||||
}
|
||||
else
|
||||
{
|
||||
uv.xy = vec2(max(0.0, a_pos.x), max(0.0, a_pos.y));
|
||||
}
|
||||
}
|
10
src/shaders/mobile/wireframe_fs.glsl
Normal file
10
src/shaders/mobile/wireframe_fs.glsl
Normal file
|
@ -0,0 +1,10 @@
|
|||
#version 300 es
|
||||
|
||||
uniform mediump vec4 u_color;
|
||||
|
||||
out mediump vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
frag_color = u_color;
|
||||
}
|
11
src/shaders/mobile/wireframe_vs.glsl
Normal file
11
src/shaders/mobile/wireframe_vs.glsl
Normal file
|
@ -0,0 +1,11 @@
|
|||
#version 300 es
|
||||
|
||||
layout(location = 0) in mediump vec3 a_pos;
|
||||
|
||||
uniform mediump mat4 u_proj;
|
||||
uniform mediump mat4 u_view;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_proj * u_view * vec4(a_pos, 1.0);
|
||||
}
|
46
src/shaders/standard/basic_fs.glsl
Normal file
46
src/shaders/standard/basic_fs.glsl
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*================================================================
|
||||
* shaders/standard/basic_fs.glsl
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Basic lit fragment shader with texture.
|
||||
1 directional light + ambient.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
in vec3 v_normal;
|
||||
in vec2 v_texcoord;
|
||||
|
||||
struct Light
|
||||
{
|
||||
vec3 ambient;
|
||||
vec3 directional;
|
||||
vec3 direction;
|
||||
};
|
||||
|
||||
uniform Light u_light;
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 albedo = texture(u_tex, v_texcoord);
|
||||
|
||||
// magic values for the look I want
|
||||
float directional_strength = 2.4;
|
||||
float ambient_strength = 1.0;
|
||||
float gamma = 1.1;
|
||||
|
||||
// Directional
|
||||
float light_dot = max(0, dot(v_normal, u_light.direction));
|
||||
vec3 lighting = u_light.directional * light_dot * directional_strength;
|
||||
|
||||
// Ambient
|
||||
lighting += u_light.ambient * ambient_strength;
|
||||
|
||||
frag_color = albedo * vec4(lighting, 1.0);
|
||||
frag_color.rgb = pow(frag_color.rgb, vec3(1.0 / gamma));
|
||||
}
|
21
src/shaders/standard/basic_unlit_fs.glsl
Normal file
21
src/shaders/standard/basic_unlit_fs.glsl
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*================================================================
|
||||
* shaders/standard/basic_unlit_fs.glsl
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Basic fragment shader with texture.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
in vec2 v_texcoord;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
frag_color = texture(u_tex, v_texcoord);
|
||||
}
|
24
src/shaders/standard/basic_unlit_vs.glsl
Normal file
24
src/shaders/standard/basic_unlit_vs.glsl
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*================================================================
|
||||
* shaders/standard/basic_unlit_vs.glsl
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Basic vertex shader.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
layout(location = 1) in vec2 a_texcoord;
|
||||
|
||||
uniform mat4 u_proj;
|
||||
uniform mat4 u_view;
|
||||
|
||||
out vec2 v_texcoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
v_texcoord = a_texcoord;
|
||||
gl_Position = u_proj * u_view * vec4(a_pos, 1.0);
|
||||
}
|
28
src/shaders/standard/basic_vs.glsl
Normal file
28
src/shaders/standard/basic_vs.glsl
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*================================================================
|
||||
* shaders/standard/basic_vs.glsl
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Basic vertex shader.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
layout(location = 1) in vec3 a_normal;
|
||||
layout(location = 2) in vec2 a_texcoord;
|
||||
|
||||
uniform mat4 u_proj;
|
||||
uniform mat4 u_view;
|
||||
|
||||
out vec3 v_normal;
|
||||
out vec2 v_texcoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
v_normal = -normalize(mat3(u_view) * a_normal);
|
||||
v_texcoord = a_texcoord;
|
||||
|
||||
gl_Position = u_proj * u_view * vec4(a_pos, 1.0);
|
||||
}
|
33
src/shaders/standard/bsp_fs.glsl
Normal file
33
src/shaders/standard/bsp_fs.glsl
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*================================================================
|
||||
* shaders/standard/bsp_fs.glsl
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
BSP fragment shader.
|
||||
Texture + lightmaps.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
in vec2 tex_coord;
|
||||
in vec2 lm_coord;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
uniform sampler2D u_lm;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex = texture(u_tex, tex_coord);
|
||||
vec4 lm = texture(u_lm, lm_coord);
|
||||
|
||||
// magic values for the look I want
|
||||
float lm_strength = 2.8;
|
||||
float gamma = 1.15;
|
||||
|
||||
frag_color = tex * lm * lm_strength;
|
||||
frag_color.rgb = pow(frag_color.rgb, vec3(1.0/gamma));
|
||||
frag_color.a = 1.0;
|
||||
}
|
28
src/shaders/standard/bsp_vs.glsl
Normal file
28
src/shaders/standard/bsp_vs.glsl
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*================================================================
|
||||
* shaders/standard/bsp_vs.glsl
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
BSP vertex shader.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
layout(location = 1) in vec2 a_tex_coord;
|
||||
layout(location = 2) in vec2 a_lm_coord;
|
||||
layout(location = 3) in vec3 a_normal;
|
||||
layout(location = 4) in vec4 a_color;
|
||||
|
||||
uniform mat4 u_proj;
|
||||
|
||||
out vec2 tex_coord;
|
||||
out vec2 lm_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_proj * vec4(a_pos, 1.0);
|
||||
tex_coord = a_tex_coord;
|
||||
lm_coord = a_lm_coord;
|
||||
}
|
19
src/shaders/standard/bsp_wireframe_fs.glsl
Normal file
19
src/shaders/standard/bsp_wireframe_fs.glsl
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*================================================================
|
||||
* shaders/standard/wireframe_fs.glsl
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
Basic wireframe shader.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
uniform vec4 u_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
frag_color = u_color;
|
||||
}
|
19
src/shaders/standard/bsp_wireframe_vs.glsl
Normal file
19
src/shaders/standard/bsp_wireframe_vs.glsl
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*================================================================
|
||||
* shaders/standard/wireframe_vs.glsl
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
Basic wireframe shader.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
|
||||
uniform mat4 u_proj;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_proj * vec4(a_pos, 1.0);
|
||||
}
|
42
src/shaders/standard/post_fs.glsl
Normal file
42
src/shaders/standard/post_fs.glsl
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*================================================================
|
||||
* shaders/standard/post_fs.glsl
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Post-processing effects.
|
||||
|
||||
Barrel distortion based on:
|
||||
https://www.decarpentier.nl/lens-distortion
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
flat in int barrel_enabled;
|
||||
in vec3 uv;
|
||||
in vec2 uv_dot;
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
uniform sampler2D u_tex_vm;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color1;
|
||||
vec4 color2;
|
||||
|
||||
if (barrel_enabled == 1)
|
||||
{
|
||||
vec3 tex_coord = dot(uv_dot, uv_dot) * vec3(-0.5, -0.5, -1.0) + uv;
|
||||
color1 = texture2DProj(u_tex, tex_coord);
|
||||
color2 = texture2DProj(u_tex_vm, tex_coord);
|
||||
}
|
||||
else
|
||||
{
|
||||
color1 = texture(u_tex, uv.xy);
|
||||
color2 = texture(u_tex_vm, uv.xy);
|
||||
}
|
||||
|
||||
frag_color = mix(color1, color2, color2.a);
|
||||
}
|
50
src/shaders/standard/post_vs.glsl
Normal file
50
src/shaders/standard/post_vs.glsl
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*================================================================
|
||||
* shaders/standard/post_vs.glsl
|
||||
*
|
||||
* Copyright (c) 2021 nullprop
|
||||
* ================================
|
||||
|
||||
Post-processing effects.
|
||||
|
||||
Barrel distortion based on:
|
||||
https://www.decarpentier.nl/lens-distortion
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec2 a_pos;
|
||||
|
||||
uniform int u_barrel_enabled;
|
||||
uniform float u_barrel_strength;
|
||||
uniform float u_barrel_height;
|
||||
uniform float u_barrel_aspect;
|
||||
uniform float u_barrel_cyl_ratio;
|
||||
|
||||
flat out int barrel_enabled;
|
||||
out vec3 uv;
|
||||
out vec2 uv_dot;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(a_pos, 0, 1.0);
|
||||
|
||||
barrel_enabled = u_barrel_enabled;
|
||||
if (u_barrel_enabled == 1)
|
||||
{
|
||||
float scaled_height = u_barrel_strength * u_barrel_height;
|
||||
float cyl_aspect = u_barrel_aspect * u_barrel_cyl_ratio;
|
||||
float apsect_diag_sq = u_barrel_aspect * u_barrel_aspect + 1.0;
|
||||
float diag_sq = scaled_height * scaled_height * apsect_diag_sq;
|
||||
|
||||
float z = 0.5 * sqrt(diag_sq + 1.0) + 0.5;
|
||||
float ny = (z - 1.0) / (cyl_aspect * cyl_aspect + 1.0);
|
||||
|
||||
uv_dot = sqrt(ny) * vec2(cyl_aspect, 1.0) * a_pos;
|
||||
uv = vec3(0.5, 0.5, 1.0) * z + vec3(-0.5, -0.5, 0.0);
|
||||
uv.xy += vec2(max(0, a_pos.x), max(0, a_pos.y));
|
||||
}
|
||||
else
|
||||
{
|
||||
uv.xy = vec2(max(0, a_pos.x), max(0, a_pos.y));
|
||||
}
|
||||
}
|
19
src/shaders/standard/wireframe_fs.glsl
Normal file
19
src/shaders/standard/wireframe_fs.glsl
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*================================================================
|
||||
* shaders/standard/wireframe_fs.glsl
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
Basic wireframe shader.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
uniform vec4 u_color;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
frag_color = u_color;
|
||||
}
|
20
src/shaders/standard/wireframe_vs.glsl
Normal file
20
src/shaders/standard/wireframe_vs.glsl
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*================================================================
|
||||
* shaders/standard/wireframe_vs.glsl
|
||||
*
|
||||
* Copyright (c) 2022 nullprop
|
||||
* ================================
|
||||
|
||||
Basic wireframe shader.
|
||||
=================================================================*/
|
||||
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec3 a_pos;
|
||||
|
||||
uniform mat4 u_proj;
|
||||
uniform mat4 u_view;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_proj * u_view * vec4(a_pos, 1.0);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue