Init
This commit is contained in:
parent
9d581bae56
commit
a80e405b3e
9
.clang-format
Normal file
9
.clang-format
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
BasedOnStyle: Microsoft
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
PointerAlignment: Left
|
||||||
|
IndentWidth: 4 # spaces per indent level
|
||||||
|
TabWidth: 4 # width of a tab character
|
||||||
|
UseTab: Never # options: Never, ForIndentation, Alwayss
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
BreakTemplateDeclarations: Yes
|
||||||
|
AllowShortFunctionsOnASingleLine: Inline
|
||||||
151
CMakeLists.txt
Normal file
151
CMakeLists.txt
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
project(FekalniGtacko)
|
||||||
|
|
||||||
|
# Enable C++20
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
"src/client/main.cpp"
|
||||||
|
"src/client/app.hpp"
|
||||||
|
"src/client/app.cpp"
|
||||||
|
"src/client/gl.hpp"
|
||||||
|
"src/client/utils.hpp"
|
||||||
|
"src/assets/animation.hpp"
|
||||||
|
"src/assets/animation.cpp"
|
||||||
|
"src/assets/cache.hpp"
|
||||||
|
"src/assets/cache.cpp"
|
||||||
|
"src/assets/cmdfile.hpp"
|
||||||
|
"src/assets/cmdfile.cpp"
|
||||||
|
"src/assets/model.hpp"
|
||||||
|
"src/assets/model.cpp"
|
||||||
|
"src/assets/mesh_builder.hpp"
|
||||||
|
"src/assets/mesh_builder.cpp"
|
||||||
|
"src/assets/map.hpp"
|
||||||
|
"src/assets/map.cpp"
|
||||||
|
"src/assets/skeleton.hpp"
|
||||||
|
"src/assets/skeleton.cpp"
|
||||||
|
"src/collision/trianglemesh.cpp"
|
||||||
|
# "src/game/entity.hpp"
|
||||||
|
# "src/game/entity.cpp"
|
||||||
|
# "src/game/meshinstance.hpp"
|
||||||
|
# "src/game/meshinstance.cpp"
|
||||||
|
"src/game/player_input.hpp"
|
||||||
|
# "src/game/player.hpp"
|
||||||
|
# "src/game/player.cpp"
|
||||||
|
"src/game/transform_node.hpp"
|
||||||
|
# "src/game/world.hpp"
|
||||||
|
# "src/game/world.cpp"
|
||||||
|
"src/gameview/client_session.hpp"
|
||||||
|
"src/gameview/entityview.hpp"
|
||||||
|
"src/gameview/worldview.hpp"
|
||||||
|
"src/gfx/buffer_object.cpp"
|
||||||
|
"src/gfx/buffer_object.hpp"
|
||||||
|
"src/gfx/draw_list.hpp"
|
||||||
|
"src/gfx/renderer.hpp"
|
||||||
|
"src/gfx/renderer.cpp"
|
||||||
|
"src/gfx/shader_defs.hpp"
|
||||||
|
"src/gfx/shader_sources.hpp"
|
||||||
|
"src/gfx/shader_sources.cpp"
|
||||||
|
"src/gfx/shader.hpp"
|
||||||
|
"src/gfx/shader.cpp"
|
||||||
|
"src/gfx/surface.hpp"
|
||||||
|
"src/gfx/texture.cpp"
|
||||||
|
"src/gfx/texture.hpp"
|
||||||
|
"src/gfx/uniform_buffer.hpp"
|
||||||
|
"src/gfx/vertex_array.cpp"
|
||||||
|
"src/gfx/vertex_array.hpp"
|
||||||
|
"src/net/defs.hpp"
|
||||||
|
"src/net/fixed_str.hpp"
|
||||||
|
"src/net/inmessage.hpp"
|
||||||
|
"src/net/outmessage.hpp"
|
||||||
|
"src/net/quantized.hpp"
|
||||||
|
"src/utils/files.hpp"
|
||||||
|
"src/utils/files.cpp"
|
||||||
|
"src/utils/transform.hpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
# Android-specific setup
|
||||||
|
set(MAIN_NAME "main")
|
||||||
|
add_library(${MAIN_NAME} SHARED ${SOURCES})
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE GLESv3 log android)
|
||||||
|
else()
|
||||||
|
# Desktop build
|
||||||
|
set(MAIN_NAME "FekalniGtacko")
|
||||||
|
add_executable(${MAIN_NAME} ${SOURCES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
target_include_directories(${MAIN_NAME} PRIVATE
|
||||||
|
"src"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Platform-specific SDL2 handling
|
||||||
|
if (CMAKE_SYSTEM_NAME STREQUAL Emscripten)
|
||||||
|
|
||||||
|
# Emscripten provides SDL2 via its system libraries
|
||||||
|
message(STATUS "Target platform: WebAssembly (Emscripten)")
|
||||||
|
set(CMAKE_EXECUTABLE_SUFFIX ".html") # Optional: build HTML page
|
||||||
|
|
||||||
|
target_compile_options(${MAIN_NAME} PRIVATE
|
||||||
|
"-sUSE_SDL=2"
|
||||||
|
"-sNO_DISABLE_EXCEPTION_CATCHING=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_options(${MAIN_NAME} PRIVATE
|
||||||
|
"-sUSE_SDL=2"
|
||||||
|
"-sASYNCIFY"
|
||||||
|
"-sUSE_WEBGL2=1"
|
||||||
|
"-sNO_DISABLE_EXCEPTION_CATCHING=1"
|
||||||
|
"-sALLOW_MEMORY_GROWTH=1"
|
||||||
|
"--shell-file" "${CMAKE_SOURCE_DIR}/shell.html"
|
||||||
|
"--preload-file" "${CMAKE_SOURCE_DIR}/assets/@/"
|
||||||
|
)
|
||||||
|
|
||||||
|
else()
|
||||||
|
message(STATUS "Target platform: Native")
|
||||||
|
# Native platform
|
||||||
|
# find_package(SDL2 REQUIRED)
|
||||||
|
# SDL2 build options to avoid unwanted components
|
||||||
|
set(SDL_TEST OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
set(SDL_SHARED ON CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_STATIC OFF CACHE BOOL "" FORCE)
|
||||||
|
else()
|
||||||
|
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(external/SDL)
|
||||||
|
target_include_directories(${MAIN_NAME} PRIVATE "external/SDL/include")
|
||||||
|
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE SDL2main)
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE SDL2)
|
||||||
|
else()
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE SDL2-static)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(external/glad)
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE glad)
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(external/glm)
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE glm)
|
||||||
|
target_include_directories(${MAIN_NAME} PRIVATE "external/stb")
|
||||||
|
|
||||||
|
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_BULLET2_DEMOS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_PYBULLET OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_OPENGL3_DEMOS OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
add_subdirectory(external/bullet3)
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE BulletCollision LinearMath Bullet3Common)
|
||||||
|
target_include_directories(${MAIN_NAME} PRIVATE "external/bullet3/src")
|
||||||
62
CMakePresets.json
Normal file
62
CMakePresets.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"cmakeMinimumRequired": {
|
||||||
|
"major": 3,
|
||||||
|
"minor": 21,
|
||||||
|
"patch": 0
|
||||||
|
},
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "vs",
|
||||||
|
"displayName": "Visual Studio 18 2026",
|
||||||
|
"description": "Build with VS compiler using Multi-Config generator",
|
||||||
|
"generator": "Visual Studio 18 2026",
|
||||||
|
"toolset": "host=x64",
|
||||||
|
"architecture": "x64",
|
||||||
|
"binaryDir": "${sourceDir}/build/vs",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
||||||
|
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}",
|
||||||
|
"CMAKE_C_COMPILER": "cl.exe",
|
||||||
|
"CMAKE_CXX_COMPILER": "cl.exe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "emscripten",
|
||||||
|
"displayName": "Emscripten Multi-Config Build",
|
||||||
|
"description": "Build with Emscripten toolchain using Multi-Config generator",
|
||||||
|
"generator": "Ninja Multi-Config",
|
||||||
|
"binaryDir": "${sourceDir}/build/wasm",
|
||||||
|
"toolchainFile": "c:/dev/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "vs-release",
|
||||||
|
"displayName": "VS Release",
|
||||||
|
"configurePreset": "vs",
|
||||||
|
"configuration": "Release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vs-debug",
|
||||||
|
"displayName": "VS Debug",
|
||||||
|
"configurePreset": "vs",
|
||||||
|
"configuration": "Debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "emscripten-release",
|
||||||
|
"displayName": "Emscripten Release",
|
||||||
|
"configurePreset": "emscripten",
|
||||||
|
"configuration": "Release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "emscripten-debug",
|
||||||
|
"displayName": "Emscripten Debug",
|
||||||
|
"configurePreset": "emscripten",
|
||||||
|
"configuration": "Debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
59
shell.html
Normal file
59
shell.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>PortalGame</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var Module = {
|
||||||
|
preRun: [],
|
||||||
|
postRun: [],
|
||||||
|
canvas: (function() {
|
||||||
|
return document.getElementById('canvas');
|
||||||
|
})()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resize canvas to fit the window and inform SDL (via Emscripten)
|
||||||
|
function resizeCanvas() {
|
||||||
|
const canvas = document.getElementById('canvas');
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
|
||||||
|
// // Notify Emscripten SDL2
|
||||||
|
// if (typeof Module !== 'undefined' && Module.canvas) {
|
||||||
|
// const evt = new CustomEvent('resize');
|
||||||
|
// window.dispatchEvent(evt);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', resizeCanvas);
|
||||||
|
window.addEventListener('load', resizeCanvas);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{{ SCRIPT }}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
142
src/assets/animation.cpp
Normal file
142
src/assets/animation.cpp
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#include "animation.hpp"
|
||||||
|
#include "skeleton.hpp"
|
||||||
|
|
||||||
|
#include "utils/files.hpp"
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
std::shared_ptr<const assets::Animation> assets::Animation::LoadFromFile(const std::string& filename, const Skeleton* skeleton)
|
||||||
|
{
|
||||||
|
std::istringstream ifs = fs::ReadFileAsStream(filename);
|
||||||
|
|
||||||
|
std::shared_ptr<Animation> anim = std::make_shared<Animation>();
|
||||||
|
|
||||||
|
int last_frame = 0;
|
||||||
|
std::vector<size_t> frame_indices;
|
||||||
|
|
||||||
|
auto FillFrameRefs = [&](int end_frame)
|
||||||
|
{
|
||||||
|
int channel_start = ((int)anim->channels_.size() - 1) * (int)anim->num_frames_;
|
||||||
|
int target_size = channel_start + end_frame;
|
||||||
|
size_t num_frame_refs = frame_indices.size();
|
||||||
|
|
||||||
|
if (num_frame_refs >= target_size)
|
||||||
|
{
|
||||||
|
return; // Already filled
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_frame_refs % anim->num_frames_ == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Cannot fill frames of channel that has 0 frames: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t last_frame_idx = frame_indices.back();
|
||||||
|
frame_indices.resize(target_size, last_frame_idx);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(ifs, line))
|
||||||
|
{
|
||||||
|
if (line.empty() || line[0] == '#') // Skip empty lines and comments
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::istringstream iss(line);
|
||||||
|
|
||||||
|
std::string command;
|
||||||
|
iss >> command;
|
||||||
|
|
||||||
|
if (command == "f")
|
||||||
|
{
|
||||||
|
if (anim->num_frames_ == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Frame data specified before number of frames in animation file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anim->channels_.empty())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Frame data specified before any channels in animation file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
int frame_index;
|
||||||
|
iss >> frame_index;
|
||||||
|
|
||||||
|
if (frame_index < 0 || frame_index >= (int)anim->num_frames_)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Frame index out of bounds in animation file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_index < last_frame)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Frame indices must be in ascending order in animation file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_frame = frame_index;
|
||||||
|
|
||||||
|
Transform t;
|
||||||
|
iss >> t.position.x >> t.position.y >> t.position.z;
|
||||||
|
glm::vec3 angles_deg;
|
||||||
|
iss >> angles_deg.x >> angles_deg.y >> angles_deg.z;
|
||||||
|
t.SetAngles(angles_deg);
|
||||||
|
iss >> t.scale;
|
||||||
|
|
||||||
|
size_t idx = anim->frames_.size();
|
||||||
|
anim->frames_.push_back(t);
|
||||||
|
|
||||||
|
FillFrameRefs(frame_index); // Fill to current frame
|
||||||
|
frame_indices.push_back(idx);
|
||||||
|
}
|
||||||
|
else if (command == "ch")
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
iss >> name;
|
||||||
|
|
||||||
|
int bone_index = skeleton->GetBoneIndex(name);
|
||||||
|
|
||||||
|
if (bone_index < 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Bone referenced in animation not found in provided skeleton: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
FillFrameRefs(anim->num_frames_); // Fill to end for last channel
|
||||||
|
|
||||||
|
AnimationChannel& channel = anim->channels_.emplace_back();
|
||||||
|
channel.bone_index = bone_index;
|
||||||
|
channel.frames = nullptr; // Will be set up later
|
||||||
|
|
||||||
|
last_frame = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (command == "frames")
|
||||||
|
{
|
||||||
|
iss >> anim->num_frames_;
|
||||||
|
}
|
||||||
|
else if (command == "fps")
|
||||||
|
{
|
||||||
|
iss >> anim->tps_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anim->channels_.empty())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("No channels found in animation file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
FillFrameRefs(anim->num_frames_); // Fill to end for last channel
|
||||||
|
|
||||||
|
// Set up frame pointers
|
||||||
|
anim->frame_refs_.resize(frame_indices.size());
|
||||||
|
for (size_t i = 0; i < frame_indices.size(); ++i)
|
||||||
|
{
|
||||||
|
anim->frame_refs_[i] = &anim->frames_[frame_indices[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up channel frame pointers
|
||||||
|
for (size_t i = 0; i < anim->channels_.size(); ++i)
|
||||||
|
{
|
||||||
|
AnimationChannel& channel = anim->channels_[i];
|
||||||
|
channel.frames = &anim->frame_refs_[i * anim->num_frames_];
|
||||||
|
}
|
||||||
|
|
||||||
|
return anim;
|
||||||
|
}
|
||||||
43
src/assets/animation.hpp
Normal file
43
src/assets/animation.hpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "utils/transform.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
class Skeleton;
|
||||||
|
|
||||||
|
struct AnimationChannel
|
||||||
|
{
|
||||||
|
int bone_index;
|
||||||
|
const Transform* const* frames;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Animation
|
||||||
|
{
|
||||||
|
size_t num_frames_ = 0;
|
||||||
|
float tps_ = 24.0f;
|
||||||
|
|
||||||
|
std::vector<AnimationChannel> channels_;
|
||||||
|
std::vector<const Transform*> frame_refs_;
|
||||||
|
std::vector<Transform> frames_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Animation() = default;
|
||||||
|
|
||||||
|
size_t GetNumFrames() const { return num_frames_; }
|
||||||
|
float GetTPS() const { return tps_; }
|
||||||
|
float GetDuration() const { return static_cast<float>(num_frames_) / tps_; }
|
||||||
|
|
||||||
|
size_t GetNumChannels() const { return channels_.size(); }
|
||||||
|
const AnimationChannel& GetChannel(int index) const { return channels_[index]; }
|
||||||
|
|
||||||
|
static std::shared_ptr<const Animation> LoadFromFile(const std::string& filename, const Skeleton* skeleton);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
5
src/assets/cache.cpp
Normal file
5
src/assets/cache.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#include "cache.hpp"
|
||||||
|
|
||||||
|
assets::TextureCache assets::CacheManager::texture_cache_;
|
||||||
|
assets::SkeletonCache assets::CacheManager::skeleton_cache_;
|
||||||
|
assets::ModelCache assets::CacheManager::model_cache_;
|
||||||
79
src/assets/cache.hpp
Normal file
79
src/assets/cache.hpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "model.hpp"
|
||||||
|
#include "skeleton.hpp"
|
||||||
|
#include "gfx/texture.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Cache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using PtrType = std::shared_ptr<const T>;
|
||||||
|
|
||||||
|
PtrType Get(const std::string& key)
|
||||||
|
{
|
||||||
|
auto it = cache_.find(key);
|
||||||
|
if (it != cache_.end())
|
||||||
|
{
|
||||||
|
if (auto ptr = it->second.lock())
|
||||||
|
{
|
||||||
|
return ptr; // Return cached object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PtrType obj = Load(key);
|
||||||
|
cache_[key] = obj; // Cache the loaded object
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual PtrType Load(const std::string& key) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::weak_ptr<const T>> cache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextureCache final : public Cache<gfx::Texture>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PtrType Load(const std::string& key) override { return gfx::Texture::LoadFromFile(key); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class SkeletonCache final : public Cache<Skeleton>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PtrType Load(const std::string& key) override { return Skeleton::LoadFromFile(key); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModelCache final : public Cache<Model>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PtrType Load(const std::string& key) override { return Model::LoadFromFile(key); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class CacheManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::shared_ptr<const gfx::Texture> GetTexture(const std::string& filename) {
|
||||||
|
return texture_cache_.Get(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::shared_ptr<const Skeleton> GetSkeleton(const std::string& filename) {
|
||||||
|
return skeleton_cache_.Get(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::shared_ptr<const Model> GetModel(const std::string& filename) {
|
||||||
|
return model_cache_.Get(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static TextureCache texture_cache_;
|
||||||
|
static SkeletonCache skeleton_cache_;
|
||||||
|
static ModelCache model_cache_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace assets
|
||||||
25
src/assets/cmdfile.cpp
Normal file
25
src/assets/cmdfile.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include "cmdfile.hpp"
|
||||||
|
|
||||||
|
void assets::LoadCMDFile(const std::string& filename,
|
||||||
|
const std::function<void(const std::string& command, std::istringstream& iss)>& handler)
|
||||||
|
{
|
||||||
|
std::istringstream file = fs::ReadFileAsStream(filename);
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line))
|
||||||
|
{
|
||||||
|
if (line.empty() || line[0] == '#') // Skip empty lines and comments
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// skip whitespace
|
||||||
|
line.erase(0, line.find_first_not_of(" \t"));
|
||||||
|
|
||||||
|
std::istringstream iss(line);
|
||||||
|
|
||||||
|
std::string command;
|
||||||
|
iss >> command;
|
||||||
|
|
||||||
|
handler(command, iss);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
15
src/assets/cmdfile.hpp
Normal file
15
src/assets/cmdfile.hpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils/files.hpp"
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
|
||||||
|
void LoadCMDFile(const std::string& filename,
|
||||||
|
const std::function<void(const std::string& command, std::istringstream& iss)>& handler);
|
||||||
|
|
||||||
|
} // namespace assets
|
||||||
49
src/assets/map.cpp
Normal file
49
src/assets/map.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include "map.hpp"
|
||||||
|
#include "cache.hpp"
|
||||||
|
#include "utils/files.hpp"
|
||||||
|
#include "cmdfile.hpp"
|
||||||
|
|
||||||
|
std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
auto map = std::make_shared<Map>();
|
||||||
|
|
||||||
|
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
|
||||||
|
if (command == "basemodel")
|
||||||
|
{
|
||||||
|
std::string model_name;
|
||||||
|
iss >> model_name;
|
||||||
|
|
||||||
|
map->basemodel_ = CacheManager::GetModel("data/" + model_name + ".mdl");
|
||||||
|
}
|
||||||
|
else if (command == "static")
|
||||||
|
{
|
||||||
|
MapStaticObject obj;
|
||||||
|
|
||||||
|
std::string model_name;
|
||||||
|
iss >> model_name;
|
||||||
|
|
||||||
|
obj.model = assets::CacheManager::GetModel("data/" + model_name + ".mdl");
|
||||||
|
|
||||||
|
glm::vec3 angles;
|
||||||
|
|
||||||
|
iss >> obj.transform.position.x >> obj.transform.position.y >> obj.transform.position.z;
|
||||||
|
iss >> angles.x >> angles.y >> angles.z;
|
||||||
|
obj.transform.SetAngles(angles);
|
||||||
|
iss >> obj.transform.scale;
|
||||||
|
|
||||||
|
std::string flag;
|
||||||
|
while (iss >> flag)
|
||||||
|
{
|
||||||
|
if (flag == "+color")
|
||||||
|
{
|
||||||
|
iss >> obj.color.r >> obj.color.g >> obj.color.b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
map->static_objects_.push_back(std::move(obj));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
30
src/assets/map.hpp
Normal file
30
src/assets/map.hpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "model.hpp"
|
||||||
|
#include "utils/transform.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
|
||||||
|
struct MapStaticObject
|
||||||
|
{
|
||||||
|
Transform transform;
|
||||||
|
std::shared_ptr<const Model> model;
|
||||||
|
glm::vec3 color = glm::vec3(1.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
class Map
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Map() = default;
|
||||||
|
static std::shared_ptr<const Map> LoadFromFile(const std::string& filename);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<const Model> basemodel_;
|
||||||
|
std::vector<MapStaticObject> static_objects_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace assets
|
||||||
116
src/assets/mesh_builder.cpp
Normal file
116
src/assets/mesh_builder.cpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include "mesh_builder.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
assets::MeshBuilder::MeshBuilder(gfx::MeshFlags mflags) : mflags_(mflags) {}
|
||||||
|
|
||||||
|
void assets::MeshBuilder::BeginSurface(gfx::SurfaceFlags sflags, const std::string& name, std::shared_ptr<const gfx::Texture> texture)
|
||||||
|
{
|
||||||
|
if (!mesh_)
|
||||||
|
mesh_ = std::make_shared<Mesh>();
|
||||||
|
|
||||||
|
FinalizeSurface();
|
||||||
|
|
||||||
|
gfx::Surface surface;
|
||||||
|
surface.sflags = sflags;
|
||||||
|
surface.texture = texture;
|
||||||
|
surface.first = tris_.size();
|
||||||
|
mesh_->surfaces.push_back(surface);
|
||||||
|
|
||||||
|
mesh_->surface_names[name] = mesh_->surfaces.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void assets::MeshBuilder::AddVertex(const MeshVertex& v)
|
||||||
|
{
|
||||||
|
verts_.push_back(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assets::MeshBuilder::AddTriangle(const MeshTriangle& t)
|
||||||
|
{
|
||||||
|
if (!mesh_ || mesh_->surfaces.size() == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Cannot add triangle without a surface. Call BeginSurface() first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
tris_.push_back(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
static void BufferPut(std::vector<char>& buffer, const T& val)
|
||||||
|
{
|
||||||
|
const char* data = reinterpret_cast<const char*>(&val);
|
||||||
|
buffer.insert(buffer.end(), data, data + sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int GetVertexAttrFlags(gfx::MeshFlags mflags)
|
||||||
|
{
|
||||||
|
int attrs = gfx::VA_POSITION | gfx::VA_NORMAL | gfx::VA_UV;
|
||||||
|
|
||||||
|
if (mflags & gfx::MF_LIGHTMAP_UV)
|
||||||
|
{
|
||||||
|
attrs |= gfx::VA_LIGHTMAP_UV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mflags & gfx::MF_SKELETAL)
|
||||||
|
{
|
||||||
|
attrs |= gfx::VA_BONE_INDICES | gfx::VA_BONE_WEIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void assets::MeshBuilder::Build()
|
||||||
|
{
|
||||||
|
// Finalize last surface
|
||||||
|
FinalizeSurface();
|
||||||
|
|
||||||
|
// Generate VBO data
|
||||||
|
std::vector<char> buffer;
|
||||||
|
for (const MeshVertex& vert : verts_)
|
||||||
|
{
|
||||||
|
BufferPut(buffer, vert.pos);
|
||||||
|
BufferPut(buffer, vert.normal);
|
||||||
|
BufferPut(buffer, vert.uv);
|
||||||
|
|
||||||
|
if (mflags_ & gfx::MF_LIGHTMAP_UV)
|
||||||
|
{
|
||||||
|
BufferPut(buffer, vert.lightmap_uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mflags_ & gfx::MF_SKELETAL)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
BufferPut(buffer, static_cast<int32_t>(vert.bones[i].bone_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
BufferPut(buffer, vert.bones[i].weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate VA
|
||||||
|
std::shared_ptr<gfx::VertexArray> va =
|
||||||
|
std::make_shared<gfx::VertexArray>(GetVertexAttrFlags(mflags_), gfx::VF_CREATE_EBO);
|
||||||
|
va->SetVBOData(buffer.data(), buffer.size());
|
||||||
|
va->SetIndices(reinterpret_cast<const GLuint*>(tris_.data()), tris_.size() * 3U);
|
||||||
|
|
||||||
|
// Assign VA to surfaces & set mesh flags
|
||||||
|
for (gfx::Surface& surface : mesh_->surfaces)
|
||||||
|
{
|
||||||
|
surface.va = va;
|
||||||
|
surface.mflags = mflags_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void assets::MeshBuilder::FinalizeSurface()
|
||||||
|
{
|
||||||
|
if (mesh_->surfaces.size() > 0)
|
||||||
|
{
|
||||||
|
// finalize previous surface
|
||||||
|
gfx::Surface& prev_surface = mesh_->surfaces.back();
|
||||||
|
prev_surface.count = tris_.size() - prev_surface.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/assets/mesh_builder.hpp
Normal file
64
src/assets/mesh_builder.hpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "gfx/surface.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
|
||||||
|
struct MeshVertexBoneInfluence
|
||||||
|
{
|
||||||
|
int bone_index;
|
||||||
|
float weight;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MeshVertex
|
||||||
|
{
|
||||||
|
glm::vec3 pos;
|
||||||
|
glm::vec3 normal;
|
||||||
|
glm::vec2 uv;
|
||||||
|
glm::vec2 lightmap_uv;
|
||||||
|
MeshVertexBoneInfluence bones[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MeshTriangle
|
||||||
|
{
|
||||||
|
uint32_t vert[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Mesh
|
||||||
|
{
|
||||||
|
gfx::MeshFlags mflags = gfx::MF_NONE;
|
||||||
|
std::vector<gfx::Surface> surfaces;
|
||||||
|
std::map<std::string, size_t> surface_names;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MeshBuilder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MeshBuilder(gfx::MeshFlags mflags);
|
||||||
|
|
||||||
|
void BeginSurface(gfx::SurfaceFlags sflags, const std::string& name, std::shared_ptr<const gfx::Texture> texture);
|
||||||
|
void AddVertex(const MeshVertex& v);
|
||||||
|
void AddTriangle(const MeshTriangle& t);
|
||||||
|
|
||||||
|
void Build();
|
||||||
|
|
||||||
|
void SetMeshFlag(gfx::MeshFlags flag) { mflags_ |= flag; }
|
||||||
|
|
||||||
|
std::shared_ptr<const Mesh> GetMesh() const { return mesh_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void FinalizeSurface();
|
||||||
|
|
||||||
|
private:
|
||||||
|
gfx::MeshFlags mflags_ = 0;
|
||||||
|
std::vector<MeshVertex> verts_;
|
||||||
|
std::vector<MeshTriangle> tris_;
|
||||||
|
std::shared_ptr<Mesh> mesh_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace assets
|
||||||
71
src/assets/model.cpp
Normal file
71
src/assets/model.cpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#include "model.hpp"
|
||||||
|
|
||||||
|
#include "cmdfile.hpp"
|
||||||
|
#include "cache.hpp"
|
||||||
|
|
||||||
|
std::shared_ptr<const assets::Model> assets::Model::LoadFromFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
auto model = std::make_shared<Model>();
|
||||||
|
|
||||||
|
MeshBuilder mb(gfx::MF_NONE);
|
||||||
|
|
||||||
|
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
|
||||||
|
if (command == "v")
|
||||||
|
{
|
||||||
|
MeshVertex v;
|
||||||
|
iss >> v.pos.x >> v.pos.y >> v.pos.z;
|
||||||
|
iss >> v.normal.x >> v.normal.y >> v.normal.z;
|
||||||
|
iss >> v.uv.x >> v.uv.y;
|
||||||
|
|
||||||
|
// TODO: LUV & bone data
|
||||||
|
|
||||||
|
mb.AddVertex(v);
|
||||||
|
}
|
||||||
|
else if (command == "f")
|
||||||
|
{
|
||||||
|
MeshTriangle t;
|
||||||
|
iss >> t.vert[0] >> t.vert[1] >> t.vert[2];
|
||||||
|
|
||||||
|
mb.AddTriangle(t);
|
||||||
|
}
|
||||||
|
else if (command == "surface")
|
||||||
|
{
|
||||||
|
std::string surface_name, texture_name;
|
||||||
|
gfx::SurfaceFlags sflags = gfx::SF_NONE;
|
||||||
|
|
||||||
|
iss >> surface_name;
|
||||||
|
|
||||||
|
// Optional flags
|
||||||
|
std::string flag;
|
||||||
|
while (iss >> flag)
|
||||||
|
{
|
||||||
|
if (flag == "+texture")
|
||||||
|
iss >> texture_name;
|
||||||
|
else if (flag == "+doublesided")
|
||||||
|
sflags |= gfx::SF_DOUBLE_SIDED;
|
||||||
|
else if (flag == "+transparent")
|
||||||
|
sflags |= gfx::SF_TRANSPARENT;
|
||||||
|
else if (flag == "+ocolor")
|
||||||
|
sflags |= gfx::SF_OBJECT_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const gfx::Texture> texture;
|
||||||
|
if (!texture_name.empty())
|
||||||
|
{
|
||||||
|
texture = CacheManager::GetTexture("data/" + surface_name + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
|
mb.BeginSurface(sflags, surface_name, texture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unknown command in model file: " + command);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: skeleton
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
21
src/assets/model.hpp
Normal file
21
src/assets/model.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "mesh_builder.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
|
||||||
|
class Model
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Model() = default;
|
||||||
|
static std::shared_ptr<const Model> LoadFromFile(const std::string& filename);
|
||||||
|
|
||||||
|
const std::shared_ptr<const Mesh>& GetMesh() const { return mesh_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<const Mesh> mesh_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
95
src/assets/skeleton.cpp
Normal file
95
src/assets/skeleton.cpp
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#include "skeleton.hpp"
|
||||||
|
|
||||||
|
#include "utils/files.hpp"
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
void assets::Skeleton::AddBone(const std::string& name, const std::string& parent_name, const Transform& transform)
|
||||||
|
{
|
||||||
|
int index = static_cast<int>(bones_.size());
|
||||||
|
|
||||||
|
Bone& bone = bones_.emplace_back();
|
||||||
|
bone.name = name;
|
||||||
|
bone.parent_idx = GetBoneIndex(parent_name);
|
||||||
|
bone.bind_transform = transform;
|
||||||
|
bone.inv_bind_matrix = glm::inverse(transform.ToMatrix());
|
||||||
|
|
||||||
|
bone_map_[bone.name] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
int assets::Skeleton::GetBoneIndex(const std::string& name) const
|
||||||
|
{
|
||||||
|
auto it = bone_map_.find(name);
|
||||||
|
if (it != bone_map_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assets::Animation* assets::Skeleton::GetAnimation(const std::string& name) const
|
||||||
|
{
|
||||||
|
auto it = anims_.find(name);
|
||||||
|
if (it != anims_.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const assets::Skeleton> assets::Skeleton::LoadFromFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
std::istringstream ifs = fs::ReadFileAsStream(filename);
|
||||||
|
|
||||||
|
std::shared_ptr<Skeleton> skeleton = std::make_shared<Skeleton>();
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(ifs, line))
|
||||||
|
{
|
||||||
|
if (line.empty() || line[0] == '#') // Skip empty lines and comments
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::istringstream iss(line);
|
||||||
|
|
||||||
|
std::string command;
|
||||||
|
iss >> command;
|
||||||
|
|
||||||
|
if (command == "b")
|
||||||
|
{
|
||||||
|
Transform t;
|
||||||
|
glm::vec3 angles;
|
||||||
|
std::string bone_name, parent_name;
|
||||||
|
|
||||||
|
iss >> bone_name >> parent_name;
|
||||||
|
iss >> t.position.x >> t.position.y >> t.position.z;
|
||||||
|
iss >> angles.x >> angles.y >> angles.z;
|
||||||
|
iss >> t.scale;
|
||||||
|
|
||||||
|
if (iss.fail())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to parse bone definition in file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
t.SetAngles(angles);
|
||||||
|
|
||||||
|
skeleton->AddBone(bone_name, parent_name, t);
|
||||||
|
}
|
||||||
|
else if (command == "anim")
|
||||||
|
{
|
||||||
|
std::string anim_name, anim_filename;
|
||||||
|
iss >> anim_name >> anim_filename;
|
||||||
|
|
||||||
|
if (iss.fail())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to parse animation definition in file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const Animation> anim = Animation::LoadFromFile("data/" + anim_filename + ".anim", skeleton.get());
|
||||||
|
skeleton->AddAnimation(anim_name, anim);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return skeleton;
|
||||||
|
}
|
||||||
47
src/assets/skeleton.hpp
Normal file
47
src/assets/skeleton.hpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "utils/transform.hpp"
|
||||||
|
#include "animation.hpp"
|
||||||
|
|
||||||
|
namespace assets
|
||||||
|
{
|
||||||
|
struct Bone
|
||||||
|
{
|
||||||
|
int parent_idx;
|
||||||
|
std::string name;
|
||||||
|
Transform bind_transform;
|
||||||
|
glm::mat4 inv_bind_matrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Skeleton
|
||||||
|
{
|
||||||
|
std::vector<Bone> bones_;
|
||||||
|
std::map<std::string, int> bone_map_;
|
||||||
|
|
||||||
|
std::map<std::string, std::shared_ptr<const Animation>> anims_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Skeleton() = default;
|
||||||
|
|
||||||
|
void AddBone(const std::string& name, const std::string& parent_name, const Transform& transform);
|
||||||
|
|
||||||
|
int GetBoneIndex(const std::string& name) const;
|
||||||
|
|
||||||
|
size_t GetNumBones() const { return bones_.size(); }
|
||||||
|
const Bone& GetBone(size_t idx) const { return bones_[idx]; }
|
||||||
|
|
||||||
|
const Animation* GetAnimation(const std::string& name) const;
|
||||||
|
|
||||||
|
static std::shared_ptr<const Skeleton> LoadFromFile(const std::string& filename);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim) { anims_[name] = anim; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
50
src/client/app.cpp
Normal file
50
src/client/app.cpp
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#include "app.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
App::App()
|
||||||
|
{
|
||||||
|
std::cout << "Initializing App..." << std::endl;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::Frame()
|
||||||
|
{
|
||||||
|
float delta_time = time_ - prev_time_;
|
||||||
|
prev_time_ = time_;
|
||||||
|
|
||||||
|
if (delta_time < 0.0f)
|
||||||
|
{
|
||||||
|
delta_time = 0.0f; // Prevent negative delta time
|
||||||
|
}
|
||||||
|
else if (delta_time > 0.1f)
|
||||||
|
{
|
||||||
|
delta_time = 0.1f; // Cap delta time to avoid large jumps
|
||||||
|
}
|
||||||
|
|
||||||
|
// detect inputs originating in this frame
|
||||||
|
game::PlayerInputFlags new_input = input_ & ~prev_input_;
|
||||||
|
prev_input_ = input_;
|
||||||
|
|
||||||
|
float aspect = static_cast<float>(viewport_size_.x) / static_cast<float>(viewport_size_.y);
|
||||||
|
|
||||||
|
renderer_.Begin(viewport_size_.x, viewport_size_.y);
|
||||||
|
|
||||||
|
// TODO: draw world
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::MouseMove(const glm::vec2& delta)
|
||||||
|
{
|
||||||
|
float sensitivity = 0.002f; // Sensitivity factor for mouse movement
|
||||||
|
|
||||||
|
float delta_yaw = delta.x * sensitivity;
|
||||||
|
float delta_pitch = -delta.y * sensitivity;
|
||||||
|
|
||||||
|
// TODO: rotate
|
||||||
|
}
|
||||||
|
|
||||||
|
App::~App()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
29
src/client/app.hpp
Normal file
29
src/client/app.hpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "game/player_input.hpp"
|
||||||
|
#include "gfx/renderer.hpp"
|
||||||
|
|
||||||
|
class App
|
||||||
|
{
|
||||||
|
float time_ = 0.0f;
|
||||||
|
glm::ivec2 viewport_size_ = { 800, 600 };
|
||||||
|
game::PlayerInputFlags input_ = 0;
|
||||||
|
game::PlayerInputFlags prev_input_ = 0;
|
||||||
|
|
||||||
|
float prev_time_ = 0.0f;
|
||||||
|
|
||||||
|
gfx::Renderer renderer_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
App();
|
||||||
|
|
||||||
|
void Frame();
|
||||||
|
|
||||||
|
void SetTime(float time) { time_ = time; }
|
||||||
|
void SetViewportSize(int width, int height) { viewport_size_ = { width, height }; }
|
||||||
|
void SetInput(game::PlayerInputFlags input) { input_ = input; }
|
||||||
|
void MouseMove(const glm::vec2& delta);
|
||||||
|
|
||||||
|
~App();
|
||||||
|
};
|
||||||
|
|
||||||
8
src/client/gl.hpp
Normal file
8
src/client/gl.hpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#if defined(EMSCRIPTEN) || defined(ANDROID)
|
||||||
|
#define PG_GLES
|
||||||
|
#include <GLES3/gl3.h>
|
||||||
|
// #include <GLES3/gl2ext.h>
|
||||||
|
#else
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
243
src/client/main.cpp
Normal file
243
src/client/main.cpp
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
#include <SDL.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include "app.hpp"
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/html5_webgl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "gl.hpp"
|
||||||
|
|
||||||
|
static SDL_Window *s_window = nullptr;
|
||||||
|
static SDL_GLContext s_context = nullptr;
|
||||||
|
static bool s_quit = false;
|
||||||
|
static std::unique_ptr<App> s_app;
|
||||||
|
|
||||||
|
static void ThrowSDLError(const std::string& message)
|
||||||
|
{
|
||||||
|
std::string error = SDL_GetError();
|
||||||
|
throw std::runtime_error(message + ": " + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitSDL()
|
||||||
|
{
|
||||||
|
std::cout << "Initializing SDL..." << std::endl;
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) != 0)
|
||||||
|
{
|
||||||
|
ThrowSDLError("SDL_Init");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PG_GLES
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||||
|
#else
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||||
|
|
||||||
|
std::cout << "Creating SDL window..." << std::endl;
|
||||||
|
s_window = SDL_CreateWindow("PortalGame", 100, 100, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||||
|
if (!s_window)
|
||||||
|
{
|
||||||
|
ThrowSDLError("SDL_CreateWindow");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef PG_GLES
|
||||||
|
static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) {
|
||||||
|
//if (severity == 0x826b)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
////std::cout << message << std::endl;
|
||||||
|
fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
|
||||||
|
(type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""),
|
||||||
|
type, severity, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PG_GLES
|
||||||
|
|
||||||
|
|
||||||
|
static void InitGL()
|
||||||
|
{
|
||||||
|
std::cout << "Creating OpenGL context..." << std::endl;
|
||||||
|
s_context = SDL_GL_CreateContext(s_window);
|
||||||
|
if (!s_context)
|
||||||
|
{
|
||||||
|
ThrowSDLError("SDL_GL_CreateContext");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make context current
|
||||||
|
if (SDL_GL_MakeCurrent(s_window, s_context) != 0)
|
||||||
|
{
|
||||||
|
SDL_GL_DeleteContext(s_context);
|
||||||
|
ThrowSDLError("SDL_GL_MakeCurrent");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef PG_GLES
|
||||||
|
// Initialize GLAD
|
||||||
|
std::cout << "Initializing GLAD..." << std::endl;
|
||||||
|
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
|
||||||
|
{
|
||||||
|
SDL_GL_DeleteContext(s_context);
|
||||||
|
throw std::runtime_error("Failed to initialize GLAD");
|
||||||
|
}
|
||||||
|
glEnable(GL_DEBUG_OUTPUT);
|
||||||
|
glDebugMessageCallback(GLDebugCallback, 0);
|
||||||
|
|
||||||
|
SDL_GL_SetSwapInterval(0);
|
||||||
|
#endif // PG_GLES
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ShutdownGL()
|
||||||
|
{
|
||||||
|
if (s_context)
|
||||||
|
{
|
||||||
|
SDL_GL_DeleteContext(s_context);
|
||||||
|
s_context = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ShutdownSDL()
|
||||||
|
{
|
||||||
|
if (s_window)
|
||||||
|
{
|
||||||
|
SDL_DestroyWindow(s_window);
|
||||||
|
s_window = nullptr;
|
||||||
|
}
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PollEvents()
|
||||||
|
{
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event))
|
||||||
|
{
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case SDL_QUIT:
|
||||||
|
s_quit = true;
|
||||||
|
return;
|
||||||
|
|
||||||
|
case SDL_MOUSEMOTION:
|
||||||
|
int xrel = event.motion.xrel;
|
||||||
|
int yrel = event.motion.yrel;
|
||||||
|
if (xrel != 0 || yrel != 0)
|
||||||
|
{
|
||||||
|
s_app->MouseMove(glm::vec2(static_cast<float>(xrel), static_cast<float>(yrel)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Frame()
|
||||||
|
{
|
||||||
|
Uint32 current_time = SDL_GetTicks();
|
||||||
|
s_app->SetTime(current_time / 1000.0f); // Set time in seconds
|
||||||
|
|
||||||
|
PollEvents();
|
||||||
|
|
||||||
|
int width, height;
|
||||||
|
SDL_GetWindowSize(s_window, &width, &height);
|
||||||
|
s_app->SetViewportSize(width, height);
|
||||||
|
|
||||||
|
game::PlayerInputFlags input = 0;
|
||||||
|
const uint8_t* kbd_state = SDL_GetKeyboardState(nullptr);
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_w)])
|
||||||
|
input |= game::PI_FORWARD;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_s)])
|
||||||
|
input |= game::PI_BACKWARD;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_a)])
|
||||||
|
input |= game::PI_LEFT;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_d)])
|
||||||
|
input |= game::PI_RIGHT;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_SPACE)])
|
||||||
|
input |= game::PI_JUMP;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_LCTRL)])
|
||||||
|
input |= game::PI_CROUCH;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_e)])
|
||||||
|
input |= game::PI_USE;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F3)])
|
||||||
|
input |= game::PI_DEBUG1;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F4)])
|
||||||
|
input |= game::PI_DEBUG2;
|
||||||
|
|
||||||
|
if (kbd_state[SDL_GetScancodeFromKey(SDLK_F5)])
|
||||||
|
input |= game::PI_DEBUG3;
|
||||||
|
|
||||||
|
int mouse_state = SDL_GetMouseState(nullptr, nullptr);
|
||||||
|
|
||||||
|
if (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT))
|
||||||
|
input |= game::PI_ATTACK;
|
||||||
|
|
||||||
|
|
||||||
|
s_app->SetInput(input);
|
||||||
|
|
||||||
|
s_app->Frame();
|
||||||
|
SDL_GL_SwapWindow(s_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Main() {
|
||||||
|
InitSDL();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InitGL();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
ShutdownSDL();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||||
|
|
||||||
|
s_app = std::make_unique<App>();
|
||||||
|
|
||||||
|
#ifdef EMSCRIPTEN
|
||||||
|
emscripten_set_main_loop(Frame, 0, true);
|
||||||
|
#else
|
||||||
|
while (!s_quit)
|
||||||
|
{
|
||||||
|
Frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
s_app.reset();
|
||||||
|
|
||||||
|
ShutdownGL();
|
||||||
|
ShutdownSDL();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Main();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
std::cerr << "[ERROR] " << e.what() << std::endl;
|
||||||
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", e.what(), nullptr);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
13
src/client/utils.hpp
Normal file
13
src/client/utils.hpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Trida zajistujici, ze je objekt nekopirovatelny a nepresouvatelny
|
||||||
|
class NonCopyableNonMovable {
|
||||||
|
public:
|
||||||
|
NonCopyableNonMovable() = default;
|
||||||
|
|
||||||
|
NonCopyableNonMovable(const NonCopyableNonMovable&) = delete;
|
||||||
|
NonCopyableNonMovable& operator=(const NonCopyableNonMovable&) = delete;
|
||||||
|
|
||||||
|
NonCopyableNonMovable(NonCopyableNonMovable&&) = delete;
|
||||||
|
NonCopyableNonMovable& operator=(NonCopyableNonMovable&&) = delete;
|
||||||
|
};
|
||||||
54
src/collision/aabb.hpp
Normal file
54
src/collision/aabb.hpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
template <size_t dim>
|
||||||
|
struct AABB
|
||||||
|
{
|
||||||
|
static constexpr float C_INFINITY = std::numeric_limits<float>::infinity();
|
||||||
|
|
||||||
|
using Vec = glm::vec<dim, float, glm::defaultp>;
|
||||||
|
|
||||||
|
Vec min = Vec(C_INFINITY);
|
||||||
|
Vec max = Vec(-C_INFINITY);
|
||||||
|
|
||||||
|
AABB() = default;
|
||||||
|
AABB(const Vec& min, const Vec& max) : min(min), max(max) {}
|
||||||
|
|
||||||
|
void AddPoint(const Vec& point)
|
||||||
|
{
|
||||||
|
min = glm::min(min, point);
|
||||||
|
max = glm::max(max, point);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollidesWith(const AABB<dim>& other) const;
|
||||||
|
|
||||||
|
AABB<dim> Intersection(const AABB<dim>& other) const
|
||||||
|
{
|
||||||
|
Vec new_min = glm::max(min, other.min);
|
||||||
|
Vec new_max = glm::min(max, other.max);
|
||||||
|
return AABB<dim>(new_min, new_max);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AABB<2>::CollidesWith(const AABB<2>& other) const
|
||||||
|
{
|
||||||
|
return (min.x <= other.max.x && max.x >= other.min.x) &&
|
||||||
|
(min.y <= other.max.y && max.y >= other.min.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AABB<3>::CollidesWith(const AABB<3>& other) const
|
||||||
|
{
|
||||||
|
return (min.x <= other.max.x && max.x >= other.min.x) &&
|
||||||
|
(min.y <= other.max.y && max.y >= other.min.y) &&
|
||||||
|
(min.z <= other.max.z && max.z >= other.min.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
using AABB2 = AABB<2>;
|
||||||
|
using AABB3 = AABB<3>;
|
||||||
|
}
|
||||||
20
src/collision/trianglemesh.cpp
Normal file
20
src/collision/trianglemesh.cpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#include "trianglemesh.hpp"
|
||||||
|
|
||||||
|
collision::TriangleMesh::TriangleMesh()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void collision::TriangleMesh::AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2)
|
||||||
|
{
|
||||||
|
btVector3 bt_v0(v0.x, v0.y, v0.z);
|
||||||
|
btVector3 bt_v1(v1.x, v1.y, v1.z);
|
||||||
|
btVector3 bt_v2(v2.x, v2.y, v2.z);
|
||||||
|
bt_mesh_.addTriangle(bt_v0, bt_v1, bt_v2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void collision::TriangleMesh::Build()
|
||||||
|
{
|
||||||
|
bt_shape_ = std::make_unique<btBvhTriangleMeshShape>(&bt_mesh_, true, true);
|
||||||
|
|
||||||
|
}
|
||||||
27
src/collision/trianglemesh.hpp
Normal file
27
src/collision/trianglemesh.hpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <span>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include "aabb.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <btBulletCollisionCommon.h>
|
||||||
|
|
||||||
|
namespace collision
|
||||||
|
{
|
||||||
|
class TriangleMesh
|
||||||
|
{
|
||||||
|
btTriangleMesh bt_mesh_;
|
||||||
|
std::unique_ptr<btBvhTriangleMeshShape> bt_shape_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TriangleMesh();
|
||||||
|
|
||||||
|
void AddTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2);
|
||||||
|
void Build();
|
||||||
|
|
||||||
|
btBvhTriangleMeshShape* GetShape() { return bt_shape_.get(); }
|
||||||
|
};
|
||||||
|
}
|
||||||
23
src/game/player_input.hpp
Normal file
23
src/game/player_input.hpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
using PlayerInputFlags = uint16_t;
|
||||||
|
|
||||||
|
enum PlayerInputFlag : PlayerInputFlags
|
||||||
|
{
|
||||||
|
PI_FORWARD = 1 << 0,
|
||||||
|
PI_BACKWARD = 1 << 1,
|
||||||
|
PI_LEFT = 1 << 2,
|
||||||
|
PI_RIGHT = 1 << 3,
|
||||||
|
PI_JUMP = 1 << 4,
|
||||||
|
PI_CROUCH = 1 << 5,
|
||||||
|
PI_USE = 1 << 6,
|
||||||
|
PI_ATTACK = 1 << 7,
|
||||||
|
PI_DEBUG1 = 1 << 8,
|
||||||
|
PI_DEBUG2 = 1 << 9,
|
||||||
|
PI_DEBUG3 = 1 << 10,
|
||||||
|
};
|
||||||
|
}
|
||||||
29
src/game/transform_node.hpp
Normal file
29
src/game/transform_node.hpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils/transform.hpp"
|
||||||
|
|
||||||
|
namespace game
|
||||||
|
{
|
||||||
|
struct TransformNode
|
||||||
|
{
|
||||||
|
const TransformNode* parent = nullptr;
|
||||||
|
Transform local_transform;
|
||||||
|
glm::mat4 matrix = glm::mat4(1.0f); // Global
|
||||||
|
|
||||||
|
TransformNode()
|
||||||
|
{
|
||||||
|
UpdateMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateMatrix()
|
||||||
|
{
|
||||||
|
matrix = local_transform.ToMatrix();
|
||||||
|
|
||||||
|
if (parent)
|
||||||
|
{
|
||||||
|
matrix = parent->matrix * matrix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
12
src/gameview/client_session.hpp
Normal file
12
src/gameview/client_session.hpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace game::view
|
||||||
|
{
|
||||||
|
|
||||||
|
class ClientSession
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace game::view
|
||||||
26
src/gameview/entityview.hpp
Normal file
26
src/gameview/entityview.hpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "game/transform_node.hpp"
|
||||||
|
#include "gfx/draw_list.hpp"
|
||||||
|
|
||||||
|
class World;
|
||||||
|
|
||||||
|
namespace game::view
|
||||||
|
{
|
||||||
|
|
||||||
|
class EntityView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EntityView(World& world) : world_(world) {}
|
||||||
|
|
||||||
|
virtual void Draw(gfx::DrawList& dlist) {}
|
||||||
|
virtual void Update() {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
World& world_;
|
||||||
|
TransformNode root_;
|
||||||
|
bool visible_ = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace game::view
|
||||||
14
src/gameview/worldview.hpp
Normal file
14
src/gameview/worldview.hpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace game::view
|
||||||
|
{
|
||||||
|
|
||||||
|
class WorldView
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace game::view
|
||||||
29
src/gfx/buffer_object.cpp
Normal file
29
src/gfx/buffer_object.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#include <stdexcept>
|
||||||
|
#include "buffer_object.hpp"
|
||||||
|
|
||||||
|
gfx::BufferObject::BufferObject(GLenum target, GLenum usage) : m_target(target), m_usage(usage), m_size(0U) {
|
||||||
|
glGenBuffers(1, &m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::BufferObject::~BufferObject() {
|
||||||
|
glDeleteBuffers(1, &m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::BufferObject::Bind() const {
|
||||||
|
glBindBuffer(m_target, m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::BufferObject::Unbind() const {
|
||||||
|
glBindBuffer(m_target, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::BufferObject::SetData(const void* data, size_t size) {
|
||||||
|
Bind();
|
||||||
|
|
||||||
|
if (size > m_size)
|
||||||
|
glBufferData(m_target, size, data, m_usage);
|
||||||
|
else
|
||||||
|
glBufferSubData(m_target, 0, size, data);
|
||||||
|
|
||||||
|
m_size = size;
|
||||||
|
}
|
||||||
30
src/gfx/buffer_object.hpp
Normal file
30
src/gfx/buffer_object.hpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "client/gl.hpp"
|
||||||
|
#include "client/utils.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* \brief Wrapper pro OpenGL buffer object
|
||||||
|
*/
|
||||||
|
class BufferObject : public NonCopyableNonMovable
|
||||||
|
{
|
||||||
|
GLuint m_id;
|
||||||
|
GLenum m_target;
|
||||||
|
GLenum m_usage;
|
||||||
|
size_t m_size;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BufferObject(GLenum target, GLenum usage);
|
||||||
|
~BufferObject();
|
||||||
|
|
||||||
|
void Bind() const;
|
||||||
|
void Unbind() const;
|
||||||
|
|
||||||
|
void SetData(const void* data, size_t size);
|
||||||
|
|
||||||
|
GLuint GetId() const { return m_id; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
31
src/gfx/draw_list.hpp
Normal file
31
src/gfx/draw_list.hpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "assets/map.hpp"
|
||||||
|
#include "assets/skeleton.hpp"
|
||||||
|
#include "surface.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
|
||||||
|
struct DrawSurfaceCmd
|
||||||
|
{
|
||||||
|
const Surface* surface;
|
||||||
|
const glm::mat4* matrices; // model matrix, continues in array of matrices for skeletal meshes
|
||||||
|
const glm::vec4* color; // optional tint
|
||||||
|
uint32_t first; // first triangle index
|
||||||
|
uint32_t count; // num triangles
|
||||||
|
float dist; // distance to camera - for transparnt sorting
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DrawList
|
||||||
|
{
|
||||||
|
std::vector<DrawSurfaceCmd> surfaces;
|
||||||
|
|
||||||
|
void AddSurface(const DrawSurfaceCmd& cmd) { surfaces.emplace_back(cmd); }
|
||||||
|
|
||||||
|
void Clear() { surfaces.clear(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gfx
|
||||||
178
src/gfx/renderer.cpp
Normal file
178
src/gfx/renderer.cpp
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
#include "client/gl.hpp"
|
||||||
|
|
||||||
|
#include "gfx/shader_sources.hpp"
|
||||||
|
|
||||||
|
gfx::Renderer::Renderer()
|
||||||
|
{
|
||||||
|
ShaderSources::MakeShader(mesh_shader_.shader, SS_MESH_VERT, SS_MESH_FRAG);
|
||||||
|
ShaderSources::MakeShader(skel_mesh_shader_.shader, SS_SKEL_MESH_VERT, SS_SKEL_MESH_FRAG);
|
||||||
|
ShaderSources::MakeShader(solid_shader_, SS_SOLID_VERT, SS_SOLID_FRAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::Begin(size_t width, size_t height)
|
||||||
|
{
|
||||||
|
glViewport(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::ClearColor(const glm::vec3& color)
|
||||||
|
{
|
||||||
|
glClearColor(color.r, color.g, color.b, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::ClearDepth()
|
||||||
|
{
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::DrawList(gfx::DrawList& list, const DrawListParams& params)
|
||||||
|
{
|
||||||
|
DrawSurfaceList(list.surfaces, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::InvalidateShaders()
|
||||||
|
{
|
||||||
|
InvalidateMeshShader(mesh_shader_);
|
||||||
|
InvalidateMeshShader(skel_mesh_shader_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::InvalidateMeshShader(MeshShader& mshader)
|
||||||
|
{
|
||||||
|
mshader.global_setup = false;
|
||||||
|
mshader.color = glm::vec4(-1.0f); // invalidate color
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::SetupMeshShader(MeshShader& mshader, const DrawListParams& params)
|
||||||
|
{
|
||||||
|
const Shader& shader = *mshader.shader;
|
||||||
|
|
||||||
|
if (current_shader_ != &shader)
|
||||||
|
{
|
||||||
|
glUseProgram(shader.GetId());
|
||||||
|
current_shader_ = &shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mshader.global_setup)
|
||||||
|
{
|
||||||
|
return; // Global uniforms are already set up
|
||||||
|
}
|
||||||
|
|
||||||
|
glUniformMatrix4fv(shader.U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, ¶ms.view_proj[0][0]);
|
||||||
|
|
||||||
|
mshader.global_setup = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Renderer::DrawSurfaceList(std::span<DrawSurfaceCmd> list, const DrawListParams& params)
|
||||||
|
{
|
||||||
|
// sort the list to minimize state changes
|
||||||
|
std::ranges::sort(list, [](const DrawSurfaceCmd& a, const DrawSurfaceCmd& b) {
|
||||||
|
const Surface* sa = a.surface;
|
||||||
|
const Surface* sb = b.surface;
|
||||||
|
|
||||||
|
const bool trans_a = sa->sflags & SF_TRANSPARENT;
|
||||||
|
const bool trans_b = sb->sflags & SF_TRANSPARENT;
|
||||||
|
|
||||||
|
if (trans_a != trans_b)
|
||||||
|
return trans_b; // opaque first
|
||||||
|
|
||||||
|
if (trans_a) // both transparent
|
||||||
|
{
|
||||||
|
return a.dist > b.dist; // do not optimize transparent, sort by distance instead
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto cmp = sa <=> sb; cmp != 0)
|
||||||
|
return cmp < 0;
|
||||||
|
|
||||||
|
if (auto cmp = sa->texture <=> sb->texture; cmp != 0)
|
||||||
|
return cmp < 0;
|
||||||
|
|
||||||
|
if (auto cmp = sa->va.get() <=> sb->va.get(); cmp != 0)
|
||||||
|
return cmp < 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDepthFunc(GL_LESS);
|
||||||
|
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glCullFace(GL_BACK);
|
||||||
|
|
||||||
|
InvalidateShaders();
|
||||||
|
|
||||||
|
// cache to eliminate fake state changes
|
||||||
|
const gfx::Texture* last_texture = nullptr;
|
||||||
|
const gfx::VertexArray* last_vao = nullptr;
|
||||||
|
bool last_double_sided = false;
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0); // for all future bindings
|
||||||
|
|
||||||
|
for (const DrawSurfaceCmd& cmd : list)
|
||||||
|
{
|
||||||
|
const Surface* surface = cmd.surface;
|
||||||
|
|
||||||
|
// mesh flags
|
||||||
|
const bool skeletal_flag = surface->mflags & MF_SKELETAL;
|
||||||
|
// surface flags
|
||||||
|
const bool double_sided_flag = surface->sflags & SF_DOUBLE_SIDED;
|
||||||
|
const bool transparent_flag = surface->sflags & SF_TRANSPARENT;
|
||||||
|
const bool object_color_flag = surface->sflags & SF_OBJECT_COLOR;
|
||||||
|
|
||||||
|
// adjust state
|
||||||
|
if (last_double_sided != double_sided_flag)
|
||||||
|
{
|
||||||
|
if (double_sided_flag)
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
else
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
last_double_sided = double_sided_flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
// select shader
|
||||||
|
MeshShader& mshader = skeletal_flag ? skel_mesh_shader_ : mesh_shader_;
|
||||||
|
SetupMeshShader(mshader, params);
|
||||||
|
|
||||||
|
// set model matrix
|
||||||
|
glUniformMatrix4fv(mshader.shader->U(gfx::SU_MODEL), 1, GL_FALSE, &cmd.matrices[0][0][0]);
|
||||||
|
|
||||||
|
// set color
|
||||||
|
glm::vec4 color = (object_color_flag && cmd.color) ? glm::vec4(*cmd.color) : glm::vec4(1.0f);
|
||||||
|
if (mshader.color != color)
|
||||||
|
{
|
||||||
|
glUniform4fv(mshader.shader->U(gfx::SU_COLOR), 1, &color[0]);
|
||||||
|
mshader.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind texture
|
||||||
|
if (last_texture != surface->texture.get())
|
||||||
|
{
|
||||||
|
GLuint tex_id = surface->texture ? surface->texture->GetId() : 0;
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex_id);
|
||||||
|
last_texture = surface->texture.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind VAO
|
||||||
|
if (last_vao != surface->va.get())
|
||||||
|
{
|
||||||
|
glBindVertexArray(surface->va->GetVAOId());
|
||||||
|
last_vao = surface->va.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t first_tri = surface->first + cmd.first;
|
||||||
|
size_t num_tris = cmd.count ? cmd.count : surface->count;
|
||||||
|
|
||||||
|
// draw
|
||||||
|
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(num_tris * 3U), GL_UNSIGNED_INT,
|
||||||
|
(void*)(first_tri * 3U * sizeof(GLuint)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
52
src/gfx/renderer.hpp
Normal file
52
src/gfx/renderer.hpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "shader.hpp"
|
||||||
|
#include "draw_list.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
struct DrawListParams
|
||||||
|
{
|
||||||
|
glm::mat4 view_proj;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MeshShader
|
||||||
|
{
|
||||||
|
std::unique_ptr<Shader> shader;
|
||||||
|
|
||||||
|
// cached state to avoid redundant uniform updates which are expensive especially on WebGL
|
||||||
|
bool global_setup = false;
|
||||||
|
glm::vec4 color = glm::vec4(-1.0f); // invalid to force initial setup
|
||||||
|
};
|
||||||
|
|
||||||
|
class Renderer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Renderer();
|
||||||
|
|
||||||
|
void Begin(size_t width, size_t height);
|
||||||
|
|
||||||
|
void ClearColor(const glm::vec3& color);
|
||||||
|
void ClearDepth();
|
||||||
|
|
||||||
|
void DrawList(gfx::DrawList& list, const DrawListParams& params);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MeshShader mesh_shader_;
|
||||||
|
MeshShader skel_mesh_shader_;
|
||||||
|
std::unique_ptr<Shader> solid_shader_;
|
||||||
|
|
||||||
|
const Shader* current_shader_ = nullptr;
|
||||||
|
|
||||||
|
void InvalidateShaders();
|
||||||
|
void InvalidateMeshShader(MeshShader& mshader);
|
||||||
|
void SetupMeshShader(MeshShader& mshader, const DrawListParams& params);
|
||||||
|
|
||||||
|
void DrawSurfaceList(std::span<DrawSurfaceCmd> queue, const DrawListParams& params);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
103
src/gfx/shader.cpp
Normal file
103
src/gfx/shader.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include "shader.hpp"
|
||||||
|
#define LOG_LENGTH 1024
|
||||||
|
|
||||||
|
// Nazvy uniformnich promennych v shaderech
|
||||||
|
static const char* const s_uni_names[] = {
|
||||||
|
"u_model", // SU_MODEL
|
||||||
|
"u_view_proj", // SU_VIEW_PROJ
|
||||||
|
"u_tex", // SU_TEX
|
||||||
|
"u_color" // SU_COLOR
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vytvori shader z daneho zdroje
|
||||||
|
static GLuint CreateShader(const char* src, GLenum type) {
|
||||||
|
GLuint id = glCreateShader(type);
|
||||||
|
|
||||||
|
glShaderSource(id, 1, &src, NULL);
|
||||||
|
glCompileShader(id);
|
||||||
|
|
||||||
|
GLint is_compiled = 0;
|
||||||
|
glGetShaderiv(id, GL_COMPILE_STATUS, &is_compiled);
|
||||||
|
if (is_compiled == GL_FALSE) {
|
||||||
|
GLchar log[LOG_LENGTH];
|
||||||
|
glGetShaderInfoLog(id, LOG_LENGTH, NULL, log);
|
||||||
|
|
||||||
|
glDeleteShader(id);
|
||||||
|
|
||||||
|
throw std::runtime_error(std::string("Nelze zkompilovat shader: ") + log);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::Shader::Shader(const char* vert_src, const char* frag_src) {
|
||||||
|
m_id = glCreateProgram();
|
||||||
|
|
||||||
|
if (!m_id)
|
||||||
|
throw std::runtime_error("Nelze vytvorit program!");
|
||||||
|
|
||||||
|
GLuint vert, frag;
|
||||||
|
|
||||||
|
// Vytvoreni vertex shaderu
|
||||||
|
vert = CreateShader(vert_src, GL_VERTEX_SHADER);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vyvoreni fragment shaderu
|
||||||
|
frag = CreateShader(frag_src, GL_FRAGMENT_SHADER);
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
glDeleteShader(vert);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pripojeni shaderu k programu
|
||||||
|
glAttachShader(m_id, vert);
|
||||||
|
glAttachShader(m_id, frag);
|
||||||
|
|
||||||
|
// Linknuti programu
|
||||||
|
glLinkProgram(m_id);
|
||||||
|
|
||||||
|
// Smazani shaderu
|
||||||
|
glDeleteShader(vert);
|
||||||
|
glDeleteShader(frag);
|
||||||
|
|
||||||
|
GLint is_linked = 0;
|
||||||
|
glGetProgramiv(m_id, GL_LINK_STATUS, &is_linked);
|
||||||
|
if (is_linked == GL_FALSE) {
|
||||||
|
GLchar log[LOG_LENGTH];
|
||||||
|
glGetProgramInfoLog(m_id, LOG_LENGTH, NULL, log);
|
||||||
|
|
||||||
|
glDeleteProgram(m_id);
|
||||||
|
|
||||||
|
throw std::runtime_error(std::string("Nelze linknout shader: ") + log);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ziskani lokaci uniformnich promennych
|
||||||
|
for (size_t i = 0; i < SU_COUNT; i++)
|
||||||
|
m_uni[i] = glGetUniformLocation(m_id, s_uni_names[i]);
|
||||||
|
|
||||||
|
SetupBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::Shader::~Shader() {
|
||||||
|
// Smazani programu
|
||||||
|
glDeleteProgram(m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gfx::Shader::SetupBindings()
|
||||||
|
{
|
||||||
|
glUseProgram(m_id);
|
||||||
|
|
||||||
|
glUniform1i(m_uni[SU_TEX], 0);
|
||||||
|
|
||||||
|
// Bones UBO
|
||||||
|
int ubo_index = glGetUniformBlockIndex(m_id, "Bones");
|
||||||
|
if (ubo_index != GL_INVALID_INDEX)
|
||||||
|
{
|
||||||
|
glUniformBlockBinding(m_id, ubo_index, 0); // bind to binding point 0
|
||||||
|
}
|
||||||
|
|
||||||
|
glUseProgram(0);
|
||||||
|
}
|
||||||
52
src/gfx/shader.hpp
Normal file
52
src/gfx/shader.hpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "client/gl.hpp"
|
||||||
|
#include "client/utils.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* \brief Uniformy shaderu
|
||||||
|
*/
|
||||||
|
enum ShaderUniform
|
||||||
|
{
|
||||||
|
SU_MODEL,
|
||||||
|
SU_VIEW_PROJ,
|
||||||
|
SU_TEX,
|
||||||
|
SU_COLOR,
|
||||||
|
|
||||||
|
SU_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Wrapper pro OpenGL shader program
|
||||||
|
*/
|
||||||
|
class Shader : public NonCopyableNonMovable
|
||||||
|
{
|
||||||
|
GLuint m_id;
|
||||||
|
GLint m_uni[SU_COUNT];
|
||||||
|
|
||||||
|
public:
|
||||||
|
Shader(const char* vert_src, const char* frag_src);
|
||||||
|
~Shader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Vr<EFBFBD>t<EFBFBD> lokaci uniformy
|
||||||
|
*
|
||||||
|
* \param u Uniforma
|
||||||
|
* \return Lokace
|
||||||
|
*/
|
||||||
|
GLint U(ShaderUniform u) const { return m_uni[u]; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Vr<EFBFBD>t<EFBFBD> OpenGL n<EFBFBD>zev shaderu
|
||||||
|
*
|
||||||
|
* \return N<EFBFBD>zev shaderu
|
||||||
|
*/
|
||||||
|
GLuint GetId() const { return m_id; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetupBindings();
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
4
src/gfx/shader_defs.hpp
Normal file
4
src/gfx/shader_defs.hpp
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define SD_MAX_LIGHTS 4
|
||||||
|
#define SD_MAX_BONES 256
|
||||||
206
src/gfx/shader_sources.cpp
Normal file
206
src/gfx/shader_sources.cpp
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
#include "shader_sources.hpp"
|
||||||
|
#include "shader_defs.hpp"
|
||||||
|
#include "client/gl.hpp"
|
||||||
|
|
||||||
|
#ifndef PG_GLES
|
||||||
|
#define GLSL_VERSION \
|
||||||
|
"#version 330 core\n" \
|
||||||
|
"\n"
|
||||||
|
#else
|
||||||
|
#define GLSL_VERSION \
|
||||||
|
"#version 300 es\n" \
|
||||||
|
"precision mediump float;\n" \
|
||||||
|
"\n"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define STRINGIFY_HELPER(x) #x
|
||||||
|
#define STRINGIFY(x) STRINGIFY_HELPER(x)
|
||||||
|
|
||||||
|
#define SHADER_DEFS \
|
||||||
|
"#define MAX_LIGHTS " STRINGIFY(SD_MAX_LIGHTS) "\n" \
|
||||||
|
"#define MAX_BONES " STRINGIFY(SD_MAX_BONES) "\n" \
|
||||||
|
"\n"
|
||||||
|
|
||||||
|
#define SHADER_HEADER \
|
||||||
|
GLSL_VERSION \
|
||||||
|
SHADER_DEFS
|
||||||
|
|
||||||
|
#define MESH_MATRICES_GLSL R"GLSL(
|
||||||
|
uniform mat4 u_view_proj;
|
||||||
|
uniform mat4 u_model; // Transform matrix
|
||||||
|
)GLSL"
|
||||||
|
|
||||||
|
#define LIGHT_MATRICES_GLSL R"GLSL(
|
||||||
|
uniform vec3 u_ambient_light;
|
||||||
|
uniform int u_num_lights;
|
||||||
|
uniform vec3 u_light_positions[MAX_LIGHTS];
|
||||||
|
uniform vec4 u_light_colors_rs[MAX_LIGHTS]; // rgb = color, a = radius
|
||||||
|
)GLSL"
|
||||||
|
|
||||||
|
#define COMPUTE_LIGHTS_GLSL R"GLSL(
|
||||||
|
vec3 ComputeLights(in vec3 sector_pos, in vec3 sector_normal)
|
||||||
|
{
|
||||||
|
vec3 color = u_ambient_light;
|
||||||
|
for (int i = 0; i < u_num_lights; ++i) {
|
||||||
|
vec3 light_pos = u_light_positions[i];
|
||||||
|
vec3 light_color = u_light_colors_rs[i].rgb;
|
||||||
|
float light_radius = u_light_colors_rs[i].a;
|
||||||
|
|
||||||
|
vec3 to_light = light_pos - sector_pos.xyz;
|
||||||
|
float dist2 = dot(to_light, to_light);
|
||||||
|
if (dist2 < light_radius * light_radius) {
|
||||||
|
float dist = sqrt(dist2);
|
||||||
|
float attenuation = 1.0 - (dist / light_radius);
|
||||||
|
float dot = max(dot(sector_normal, normalize(to_light)), 0.0);
|
||||||
|
color += light_color * dot * attenuation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
)GLSL"
|
||||||
|
|
||||||
|
// Zdrojove kody shaderu
|
||||||
|
static const char* const s_srcs[] = {
|
||||||
|
|
||||||
|
// SS_MESH_VERT
|
||||||
|
SHADER_HEADER
|
||||||
|
R"GLSL(
|
||||||
|
layout (location = 0) in vec3 a_pos;
|
||||||
|
layout (location = 1) in vec3 a_normal;
|
||||||
|
layout (location = 2) in vec3 a_color;
|
||||||
|
layout (location = 3) in vec2 a_uv;
|
||||||
|
|
||||||
|
)GLSL"
|
||||||
|
MESH_MATRICES_GLSL
|
||||||
|
LIGHT_MATRICES_GLSL
|
||||||
|
COMPUTE_LIGHTS_GLSL
|
||||||
|
R"GLSL(
|
||||||
|
|
||||||
|
out vec2 v_uv;
|
||||||
|
out vec3 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 world_pos = u_model * vec4(a_pos, 1.0);
|
||||||
|
vec3 world_normal = normalize(mat3(u_model) * a_normal);
|
||||||
|
gl_Position = u_view_proj * world_pos;
|
||||||
|
|
||||||
|
v_uv = vec2(a_uv.x, 1.0 - a_uv.y);
|
||||||
|
v_color = ComputeLights(world_pos.xyz, world_normal) * a_color;
|
||||||
|
}
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
// SS_MESH_FRAG
|
||||||
|
SHADER_HEADER
|
||||||
|
R"GLSL(
|
||||||
|
in vec2 v_uv;
|
||||||
|
in vec3 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_tex;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 o_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
o_color = vec4(texture(u_tex, v_uv));
|
||||||
|
o_color.rgb *= v_color; // Apply vertex color
|
||||||
|
//o_color = vec4(1.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
// SS_SKEL_MESH_VERT
|
||||||
|
SHADER_HEADER
|
||||||
|
R"GLSL(
|
||||||
|
layout (location = 0) in vec3 a_pos;
|
||||||
|
layout (location = 1) in vec3 a_normal;
|
||||||
|
layout (location = 3) in vec2 a_uv;
|
||||||
|
layout (location = 5) in ivec4 a_bone_ids;
|
||||||
|
layout (location = 6) in vec4 a_bone_weights;
|
||||||
|
|
||||||
|
layout (std140) uniform Bones {
|
||||||
|
mat4 u_bone_matrices[MAX_BONES];
|
||||||
|
};
|
||||||
|
|
||||||
|
)GLSL"
|
||||||
|
MESH_MATRICES_GLSL
|
||||||
|
LIGHT_MATRICES_GLSL
|
||||||
|
COMPUTE_LIGHTS_GLSL
|
||||||
|
R"GLSL(
|
||||||
|
|
||||||
|
out vec2 v_uv;
|
||||||
|
|
||||||
|
out vec3 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
mat4 bone_transform = mat4(0.0);
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
int bone_id = a_bone_ids[i];
|
||||||
|
if (bone_id >= 0) {
|
||||||
|
bone_transform += u_bone_matrices[bone_id] * a_bone_weights[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 world_pos = u_model * bone_transform * vec4(a_pos, 1.0);
|
||||||
|
vec3 world_normal = normalize(mat3(u_model) * mat3(bone_transform) * a_normal);
|
||||||
|
gl_Position = u_view_proj * world_pos;
|
||||||
|
|
||||||
|
v_uv = vec2(a_uv.x, 1.0 - a_uv.y);
|
||||||
|
|
||||||
|
v_color = ComputeLights(world_pos.xyz, world_normal);
|
||||||
|
}
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
// SS_SKEL_MESH_FRAG
|
||||||
|
SHADER_HEADER
|
||||||
|
R"GLSL(
|
||||||
|
in vec2 v_uv;
|
||||||
|
|
||||||
|
in vec3 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_tex;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 o_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
o_color = vec4(texture(u_tex, v_uv));
|
||||||
|
o_color.rgb *= v_color; // Apply vertex color
|
||||||
|
}
|
||||||
|
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
// SS_SOLID_VERT
|
||||||
|
SHADER_HEADER
|
||||||
|
R"GLSL(
|
||||||
|
layout (location = 0) in vec3 a_pos;
|
||||||
|
layout (location = 2) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_view_proj;
|
||||||
|
uniform vec4 u_color;
|
||||||
|
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_view_proj * vec4(a_pos, 1.0);
|
||||||
|
v_color = a_color * u_color;
|
||||||
|
}
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
// SS_SOLID_FRAG
|
||||||
|
SHADER_HEADER
|
||||||
|
R"GLSL(
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 o_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
o_color = v_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
)GLSL",
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vrati zdrojovy kod shaderu
|
||||||
|
const char* gfx::ShaderSources::Get(ShaderSource ss) {
|
||||||
|
return s_srcs[ss];
|
||||||
|
}
|
||||||
36
src/gfx/shader_sources.hpp
Normal file
36
src/gfx/shader_sources.hpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "shader.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
enum ShaderSource
|
||||||
|
{
|
||||||
|
SS_MESH_VERT,
|
||||||
|
SS_MESH_FRAG,
|
||||||
|
|
||||||
|
SS_SKEL_MESH_VERT,
|
||||||
|
SS_SKEL_MESH_FRAG,
|
||||||
|
|
||||||
|
SS_SOLID_VERT,
|
||||||
|
SS_SOLID_FRAG,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ShaderSources
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Vrati zdrojovy kod shaderu
|
||||||
|
static const char* Get(ShaderSource ss);
|
||||||
|
|
||||||
|
static void MakeShader(std::unique_ptr<Shader>& shader, ShaderSource ss_vert, ShaderSource ss_frag) {
|
||||||
|
shader = std::make_unique<Shader>(
|
||||||
|
Get(ss_vert),
|
||||||
|
Get(ss_frag)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
38
src/gfx/surface.hpp
Normal file
38
src/gfx/surface.hpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "texture.hpp"
|
||||||
|
#include "vertex_array.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
|
||||||
|
using MeshFlags = uint8_t;
|
||||||
|
|
||||||
|
enum MeshFlag : MeshFlags
|
||||||
|
{
|
||||||
|
MF_NONE = 0x00,
|
||||||
|
MF_LIGHTMAP_UV = 0x01, // unused
|
||||||
|
MF_SKELETAL = 0x02,
|
||||||
|
};
|
||||||
|
|
||||||
|
using SurfaceFlags = uint8_t;
|
||||||
|
|
||||||
|
enum SurfaceFlag : SurfaceFlags
|
||||||
|
{
|
||||||
|
SF_NONE = 0x00,
|
||||||
|
SF_DOUBLE_SIDED = 0x01,
|
||||||
|
SF_TRANSPARENT = 0x02,
|
||||||
|
SF_OBJECT_COLOR = 0x08, // use object level tint
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Surface
|
||||||
|
{
|
||||||
|
std::shared_ptr<const gfx::Texture> texture;
|
||||||
|
std::shared_ptr<const gfx::VertexArray> va;
|
||||||
|
size_t first = 0; // first triangle VA EBO
|
||||||
|
size_t count = 0; // number of triangles
|
||||||
|
MeshFlags mflags = MF_NONE;
|
||||||
|
SurfaceFlags sflags = SF_NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gfx
|
||||||
87
src/gfx/texture.cpp
Normal file
87
src/gfx/texture.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#include "texture.hpp"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "utils/files.hpp"
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
static void GetGLFilterModes(bool linear, bool mipmaps, GLenum& filter_min, GLenum& filter_mag) {
|
||||||
|
if (linear)
|
||||||
|
{
|
||||||
|
filter_min = GL_LINEAR;
|
||||||
|
filter_mag = GL_LINEAR;
|
||||||
|
|
||||||
|
if (mipmaps)
|
||||||
|
{
|
||||||
|
filter_min = GL_LINEAR_MIPMAP_LINEAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filter_min = GL_NEAREST;
|
||||||
|
filter_mag = GL_NEAREST;
|
||||||
|
|
||||||
|
if (mipmaps)
|
||||||
|
{
|
||||||
|
// Mipmaps always linear
|
||||||
|
filter_min = GL_NEAREST_MIPMAP_LINEAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::Texture::Texture(GLuint width, GLuint height, const void* data, GLint internalformat, GLenum format, GLenum type, bool linear, bool mipmaps) {
|
||||||
|
glGenTextures(1, &m_id);
|
||||||
|
|
||||||
|
if (!m_id)
|
||||||
|
throw std::runtime_error("Nelze vytvorit texturu!");
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, m_id);
|
||||||
|
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, type, data);
|
||||||
|
|
||||||
|
GLenum filter_min, filter_mag;
|
||||||
|
GetGLFilterModes(linear, mipmaps, filter_min, filter_mag);
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_min);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_mag);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||||
|
|
||||||
|
if (mipmaps)
|
||||||
|
{
|
||||||
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::Texture::~Texture() {
|
||||||
|
glDeleteTextures(1, &m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<gfx::Texture> gfx::Texture::LoadFromFile(const std::string& filename)
|
||||||
|
{
|
||||||
|
printf("Loading texture from file: %s\n", filename.c_str());
|
||||||
|
|
||||||
|
std::string content = fs::ReadFileAsString(filename);
|
||||||
|
|
||||||
|
int width, height, channels;
|
||||||
|
unsigned char* data = stbi_load_from_memory(reinterpret_cast<const unsigned char*>(content.data()), content.size(), &width, &height, &channels, 4);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw std::runtime_error("Failed to load texture from file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Texture> texture;
|
||||||
|
try {
|
||||||
|
texture = std::make_shared<Texture>(width, height, data, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, false, true);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
stbi_image_free(data);
|
||||||
|
throw; // Rethrow the exception after freeing the data
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_image_free(data);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
26
src/gfx/texture.hpp
Normal file
26
src/gfx/texture.hpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include "client/utils.hpp"
|
||||||
|
#include "client/gl.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* \brief Wrapper pro OpenGL texturu
|
||||||
|
*/
|
||||||
|
class Texture : public NonCopyableNonMovable
|
||||||
|
{
|
||||||
|
GLuint m_id;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Texture(GLuint width, GLuint height, const void* data, GLint internalformat, GLenum format, GLenum type, bool linear, bool mipmaps);
|
||||||
|
~Texture();
|
||||||
|
|
||||||
|
GLuint GetId() const { return m_id; }
|
||||||
|
|
||||||
|
static std::shared_ptr<Texture> LoadFromFile(const std::string& filename);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
21
src/gfx/uniform_buffer.hpp
Normal file
21
src/gfx/uniform_buffer.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "buffer_object.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
template<class T>
|
||||||
|
class UniformBuffer : public BufferObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UniformBuffer() : BufferObject(GL_UNIFORM_BUFFER, GL_DYNAMIC_DRAW)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetData(const T* data, size_t count = 1)
|
||||||
|
{
|
||||||
|
BufferObject::SetData(data, sizeof(T) * count);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
98
src/gfx/vertex_array.cpp
Normal file
98
src/gfx/vertex_array.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#include "vertex_array.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// Struktura pro informace o jednotlivych vertex atributu
|
||||||
|
struct VertexAttribInfo {
|
||||||
|
GLuint index;
|
||||||
|
GLint size;
|
||||||
|
GLenum type;
|
||||||
|
GLboolean normalized;
|
||||||
|
size_t byte_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Informace o jednotlivych vertex atributech
|
||||||
|
static const VertexAttribInfo s_ATTR_INFO[] = {
|
||||||
|
{ 0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3 }, // VA_POSITION
|
||||||
|
{ 1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3 }, // VA_NORMAL
|
||||||
|
{ 2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 }, // VA_COLOR
|
||||||
|
{ 3, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2 }, // VA_UV
|
||||||
|
{ 4, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2 }, // VA_LIGHTMAP_UV
|
||||||
|
{ 5, 4, GL_INT, GL_FALSE, sizeof(int32_t) * 4 }, // VA_BONE_INDICES
|
||||||
|
{ 6, 4, GL_FLOAT, GL_FALSE, sizeof(float) * 4 }, // VA_BONE_WEIGHTS
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pocet typu vertex atributu
|
||||||
|
static const size_t s_ATTR_COUNT = 7;
|
||||||
|
|
||||||
|
gfx::VertexArray::VertexArray(int attrs, int flags) : m_usage(GL_STATIC_DRAW), m_num_indices(0) {
|
||||||
|
glGenVertexArrays(1, &m_vao);
|
||||||
|
glBindVertexArray(m_vao);
|
||||||
|
|
||||||
|
if (flags & VF_DYNAMIC)
|
||||||
|
m_usage = GL_DYNAMIC_DRAW;
|
||||||
|
|
||||||
|
// vytvori buffer pro vrcholy
|
||||||
|
m_vbo = std::make_unique<BufferObject>(GL_ARRAY_BUFFER, m_usage);
|
||||||
|
m_vbo->Bind();
|
||||||
|
|
||||||
|
// vypocet velikosti jednoho vrcholu
|
||||||
|
size_t stride = 0U;
|
||||||
|
for (size_t i = 0; i < s_ATTR_COUNT; i++) {
|
||||||
|
if (attrs & (1 << i))
|
||||||
|
stride += s_ATTR_INFO[i].byte_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// povoli a nastavi jednotlive vertex atributy
|
||||||
|
size_t offset = 0U;
|
||||||
|
for (size_t i = 0; i < s_ATTR_COUNT; i++) {
|
||||||
|
const auto& info = s_ATTR_INFO[i];
|
||||||
|
if (attrs & (1 << i)) {
|
||||||
|
glEnableVertexAttribArray(info.index);
|
||||||
|
|
||||||
|
if (info.type != GL_INT)
|
||||||
|
{
|
||||||
|
glVertexAttribPointer(info.index, info.size, info.type, info.normalized, stride, (const void*)offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
glVertexAttribIPointer(info.index, info.size, info.type, stride, (const void*)offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += info.byte_size;
|
||||||
|
} else if (i == 2)
|
||||||
|
{
|
||||||
|
// pokud neni barva, nastavime defaultni hodnotu 1.0f,1.0f,1.0f,1.0f
|
||||||
|
glDisableVertexAttribArray(info.index);
|
||||||
|
glVertexAttrib4f(info.index, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// vytvori buffer pro indexy
|
||||||
|
if (flags & VF_CREATE_EBO) {
|
||||||
|
m_ebo = std::make_unique<BufferObject>(GL_ELEMENT_ARRAY_BUFFER, m_usage);
|
||||||
|
m_ebo->Bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::VertexArray::~VertexArray() {
|
||||||
|
// uklid
|
||||||
|
glDeleteVertexArrays(1, &m_vao);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nastavi data do VBO
|
||||||
|
void gfx::VertexArray::SetVBOData(const void* data, size_t size) {
|
||||||
|
glBindVertexArray(m_vao);
|
||||||
|
m_vbo->SetData(data, size);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nastavi indexy do EBO
|
||||||
|
void gfx::VertexArray::SetIndices(const GLuint* data, size_t size) {
|
||||||
|
glBindVertexArray(m_vao);
|
||||||
|
m_ebo->SetData(data, size * sizeof(*data));
|
||||||
|
m_num_indices = size;
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
62
src/gfx/vertex_array.hpp
Normal file
62
src/gfx/vertex_array.hpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cassert>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "client/gl.hpp"
|
||||||
|
#include "client/utils.hpp"
|
||||||
|
#include "buffer_object.hpp"
|
||||||
|
|
||||||
|
namespace gfx
|
||||||
|
{
|
||||||
|
// Vycet jednotlivych atributu vrcholu
|
||||||
|
enum VertexAttr
|
||||||
|
{
|
||||||
|
VA_POSITION = 1 << 0,
|
||||||
|
VA_NORMAL = 1 << 1,
|
||||||
|
VA_COLOR = 1 << 2,
|
||||||
|
VA_UV = 1 << 3,
|
||||||
|
VA_LIGHTMAP_UV = 1 << 4,
|
||||||
|
VA_BONE_INDICES = 1 << 5,
|
||||||
|
VA_BONE_WEIGHTS = 1 << 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Informace pro vytvoreni VertexArraye
|
||||||
|
enum VertexArrayFlags
|
||||||
|
{
|
||||||
|
VF_CREATE_EBO = 1 << 0,
|
||||||
|
VF_DYNAMIC = 1 << 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// VertexArray - trida pro praci s modely
|
||||||
|
class VertexArray : public NonCopyableNonMovable
|
||||||
|
{
|
||||||
|
GLuint m_vao;// , m_vbo, m_ebo;
|
||||||
|
std::unique_ptr<BufferObject> m_vbo;
|
||||||
|
std::unique_ptr<BufferObject> m_ebo;
|
||||||
|
size_t m_num_indices;
|
||||||
|
GLenum m_usage;
|
||||||
|
|
||||||
|
public:
|
||||||
|
VertexArray(int attrs, int flags);
|
||||||
|
~VertexArray();
|
||||||
|
|
||||||
|
// Nastavi data do VBO
|
||||||
|
void SetVBOData(const void* data, size_t size);
|
||||||
|
|
||||||
|
// Nastavi indexy do EBO
|
||||||
|
void SetIndices(const GLuint* data, size_t size);
|
||||||
|
|
||||||
|
GLuint GetVAOId() const { return m_vao; }
|
||||||
|
GLuint GetVBOId() const { return m_vbo->GetId(); }
|
||||||
|
GLuint GetEBOId() const {
|
||||||
|
assert(m_ebo && "GetEBOId() ale !ebo");
|
||||||
|
return m_ebo->GetId();
|
||||||
|
}
|
||||||
|
|
||||||
|
//size_t GetVBOSize() const { return m_vbo->; }
|
||||||
|
size_t GetNumIndices() const { return m_num_indices; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
53
src/net/defs.hpp
Normal file
53
src/net/defs.hpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "fixed_str.hpp"
|
||||||
|
#include "quantized.hpp"
|
||||||
|
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
|
||||||
|
enum MessageType : uint8_t
|
||||||
|
{
|
||||||
|
MSG_NONE,
|
||||||
|
|
||||||
|
/*~~~~~~~~ Client->Server ~~~~~~~~*/
|
||||||
|
// IN <PlayerInputFlags> <ViewYaw> <ViewPitch>
|
||||||
|
MSG_IN,
|
||||||
|
|
||||||
|
/*~~~~~~~~ World ~~~~~~~~*/
|
||||||
|
// CHWORLD <MapName>
|
||||||
|
MSG_CHWORLD,
|
||||||
|
|
||||||
|
/*~~~~~~~~ Entity ~~~~~~~~*/
|
||||||
|
// ENTSPAWN <EntNum> <EntType> data...
|
||||||
|
MSG_ENTSPAWN,
|
||||||
|
// ENTMSG <EntNum> data...
|
||||||
|
MSG_ENTMSG,
|
||||||
|
// ENTRM <EntNum>
|
||||||
|
MSG_ENTRM,
|
||||||
|
|
||||||
|
/*~~~~~~~~~~~~~~~~*/
|
||||||
|
MSG_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
using MapName = FixedStr<32>;
|
||||||
|
|
||||||
|
using ViewYaw = Quantized<uint16_t, 0, 360>;
|
||||||
|
using ViewPitch = Quantized<uint16_t, -180, 180>;
|
||||||
|
|
||||||
|
using EntNum = uint16_t;
|
||||||
|
|
||||||
|
enum EntType : uint8_t
|
||||||
|
{
|
||||||
|
ET_NONE,
|
||||||
|
|
||||||
|
ET_PAWN,
|
||||||
|
ET_CAR,
|
||||||
|
|
||||||
|
ET_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace net
|
||||||
34
src/net/fixed_str.hpp
Normal file
34
src/net/fixed_str.hpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
struct FixedStr
|
||||||
|
{
|
||||||
|
size_t len = 0;
|
||||||
|
char str[N];
|
||||||
|
|
||||||
|
size_t MaxLen() const { return N; }
|
||||||
|
|
||||||
|
FixedStr& operator=(const std::string& stdstr)
|
||||||
|
{
|
||||||
|
size_t putsize = std::min(N, stdstr.size());
|
||||||
|
len = putsize;
|
||||||
|
memcpy(str, stdstr.data(), putsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
operator std::string() { return std::string(str, len); }
|
||||||
|
operator std::string_view() { return std::string_view(str, len); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
using FixedStrLen = std::conditional_t<(N > 255), uint16_t, uint8_t>;
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace net
|
||||||
121
src/net/inmessage.hpp
Normal file
121
src/net/inmessage.hpp
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <concepts>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "fixed_str.hpp"
|
||||||
|
#include "quantized.hpp"
|
||||||
|
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
|
||||||
|
class InMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InMessage() : ptr_(nullptr), end_(nullptr) {}
|
||||||
|
InMessage(const char* ptr, size_t len) : ptr_(ptr), end_(ptr_ + len) {}
|
||||||
|
|
||||||
|
const char* Ptr() const { return ptr_; }
|
||||||
|
const char* End() const { return end_; }
|
||||||
|
|
||||||
|
bool CheckAvail(size_t n)
|
||||||
|
{
|
||||||
|
return ptr_ + n <= end_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Read(char* dest, size_t n)
|
||||||
|
{
|
||||||
|
if (!CheckAvail(n))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memcpy(dest, ptr_, n);
|
||||||
|
ptr_ += n;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::integral T>
|
||||||
|
bool Read(T& value)
|
||||||
|
{
|
||||||
|
if (!CheckAvail(sizeof(T)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
value = *(reinterpret_cast<const T*>(ptr_));
|
||||||
|
ptr_ += sizeof(T);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
bool Read(FixedStr<N>& str)
|
||||||
|
{
|
||||||
|
FixedStrLen<N> len;
|
||||||
|
|
||||||
|
if (!Read(len) || len > N || !Read(str.str, len))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
str.len = len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, long long MinL, long long MaxL>
|
||||||
|
bool Read(Quantized<T, MinL, MaxL>& quant)
|
||||||
|
{
|
||||||
|
T value;
|
||||||
|
if (!Read(value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
quant.value = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadVarInt(int64_t& value)
|
||||||
|
{
|
||||||
|
uint64_t uvalue = 0;
|
||||||
|
|
||||||
|
uint8_t p;
|
||||||
|
if (!Read(p))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool next = p & 0b10000000;
|
||||||
|
bool negative = p & 0b01000000;
|
||||||
|
uvalue |= p & 0b00111111;
|
||||||
|
|
||||||
|
size_t shift = 6;
|
||||||
|
|
||||||
|
while (next)
|
||||||
|
{
|
||||||
|
if (!Read(p))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
next = p & 0b10000000;
|
||||||
|
uvalue |= static_cast<uint64_t>(p & 0b01111111) << shift;
|
||||||
|
shift += 7;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
value = static_cast<int64_t>(uvalue);
|
||||||
|
if (negative)
|
||||||
|
value = -value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SubMessage(InMessage& sub, size_t n)
|
||||||
|
{
|
||||||
|
if (!CheckAvail(n))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
sub.ptr_ = ptr_;
|
||||||
|
ptr_ += n;
|
||||||
|
sub.end_ = ptr_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* ptr_;
|
||||||
|
const char* end_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace net
|
||||||
83
src/net/outmessage.hpp
Normal file
83
src/net/outmessage.hpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fixed_str.hpp"
|
||||||
|
#include "quantized.hpp"
|
||||||
|
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
|
||||||
|
class OutMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OutMessage(std::vector<char>& buffer) : buffer_(buffer) { buffer.clear(); }
|
||||||
|
|
||||||
|
template <std::integral T>
|
||||||
|
size_t Reserve()
|
||||||
|
{
|
||||||
|
size_t pos = buffer_.size();
|
||||||
|
buffer_.resize(pos + sizeof(T));
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::integral T>
|
||||||
|
void WriteAt(size_t pos, T value)
|
||||||
|
{
|
||||||
|
*(reinterpret_cast<T*>(&buffer_[pos])) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::integral T>
|
||||||
|
void Write(T value)
|
||||||
|
{
|
||||||
|
WriteAt(Reserve<T>(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Write(const char* str, size_t n)
|
||||||
|
{
|
||||||
|
size_t pos = buffer_.size();
|
||||||
|
buffer_.resize(pos + n);
|
||||||
|
memcpy(&buffer_[pos], str, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
void Write(const FixedStr<N>& str)
|
||||||
|
{
|
||||||
|
Write(static_cast<FixedStrLen<N>>(str.len));
|
||||||
|
Write(str.str, str.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, long long MinL, long long MaxL>
|
||||||
|
void Write(Quantized<T, MinL, MaxL> quant)
|
||||||
|
{
|
||||||
|
Write(quant.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteVarInt(int64_t value)
|
||||||
|
{
|
||||||
|
const bool negative = value < 0;
|
||||||
|
uint64_t uvalue = static_cast<uint64_t>(negative ? -value : value);
|
||||||
|
|
||||||
|
char p[10];
|
||||||
|
p[0] = negative ? 0b11000000 : 0b10000000;
|
||||||
|
p[0] |= uvalue & 0b00111111;
|
||||||
|
uvalue >>= 6;
|
||||||
|
|
||||||
|
size_t i;
|
||||||
|
for (i = 1; uvalue; ++i)
|
||||||
|
{
|
||||||
|
p[i] = 0b10000000 | (uvalue & 0b01111111);
|
||||||
|
uvalue >>= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
p[i - 1] &= 0b01111111; // end mark
|
||||||
|
|
||||||
|
Write(p, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<char>& buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace net
|
||||||
40
src/net/quantized.hpp
Normal file
40
src/net/quantized.hpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <concepts>
|
||||||
|
|
||||||
|
namespace net
|
||||||
|
{
|
||||||
|
|
||||||
|
template <std::unsigned_integral T, long long MinL = -1, long long MaxL = 1, long long DivisorL = 1>
|
||||||
|
struct Quantized
|
||||||
|
{
|
||||||
|
static constexpr float Min = static_cast<float>(MinL) / static_cast<float>(DivisorL);
|
||||||
|
static constexpr float Max = static_cast<float>(MaxL) / static_cast<float>(DivisorL);
|
||||||
|
|
||||||
|
static constexpr T max_int = std::numeric_limits<T>::max();
|
||||||
|
static constexpr float range = Max - Min;
|
||||||
|
static constexpr float scale = static_cast<float>(max_int) / range;
|
||||||
|
static constexpr float inv_scale = range / static_cast<float>(max_int);
|
||||||
|
|
||||||
|
public:
|
||||||
|
T value;
|
||||||
|
|
||||||
|
Quantized(T value) : value(value) {}
|
||||||
|
Quantized(float fvalue) { Encode(value); }
|
||||||
|
|
||||||
|
void Encode(float fvalue) noexcept
|
||||||
|
{
|
||||||
|
fvalue = std::clamp(fvalue, Min, Max);
|
||||||
|
|
||||||
|
float normalized = (fvalue - Min) * scale;
|
||||||
|
value = static_cast<T>(normalized + 0.5f); // round
|
||||||
|
}
|
||||||
|
|
||||||
|
float Decode() const noexcept { return Min + static_cast<float>(value) * inv_scale; }
|
||||||
|
|
||||||
|
static constexpr float MaxError() noexcept { return inv_scale * 0.5f; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace net
|
||||||
33
src/utils/files.cpp
Normal file
33
src/utils/files.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "files.hpp"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_rwops.h>
|
||||||
|
|
||||||
|
std::string fs::ReadFileAsString(const std::string& path)
|
||||||
|
{
|
||||||
|
SDL_RWops *rw = SDL_RWFromFile(path.c_str(), "rb");
|
||||||
|
|
||||||
|
if (!rw)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open file: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sint64 res_size = SDL_RWsize(rw);
|
||||||
|
if (res_size < 0)
|
||||||
|
{
|
||||||
|
SDL_RWclose(rw);
|
||||||
|
throw std::runtime_error("Failed to get file size: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string content;
|
||||||
|
content.resize(static_cast<size_t>(res_size));
|
||||||
|
SDL_RWread(rw, content.data(), 1, res_size);
|
||||||
|
SDL_RWclose(rw);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istringstream fs::ReadFileAsStream(const std::string& path)
|
||||||
|
{
|
||||||
|
std::string content = ReadFileAsString(path);
|
||||||
|
return std::istringstream(content);
|
||||||
|
}
|
||||||
9
src/utils/files.hpp
Normal file
9
src/utils/files.hpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace fs
|
||||||
|
{
|
||||||
|
std::string ReadFileAsString(const std::string& path);
|
||||||
|
std::istringstream ReadFileAsStream(const std::string& path);
|
||||||
|
}
|
||||||
58
src/utils/transform.hpp
Normal file
58
src/utils/transform.hpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
|
struct Transform
|
||||||
|
{
|
||||||
|
glm::vec3 position = glm::vec3(0.0f);
|
||||||
|
glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // Identity quaternion
|
||||||
|
float scale = 1.0f;
|
||||||
|
|
||||||
|
glm::mat4 ToMatrix() const
|
||||||
|
{
|
||||||
|
float x = rotation.x;
|
||||||
|
float y = rotation.y;
|
||||||
|
float z = rotation.z;
|
||||||
|
float w = rotation.w;
|
||||||
|
|
||||||
|
float x2 = x + x;
|
||||||
|
float y2 = y + y;
|
||||||
|
float z2 = z + z;
|
||||||
|
float xx = x * x2;
|
||||||
|
float xy = x * y2;
|
||||||
|
float xz = x * z2;
|
||||||
|
float yy = y * y2;
|
||||||
|
float yz = y * z2;
|
||||||
|
float zz = z * z2;
|
||||||
|
float wx = w * x2;
|
||||||
|
float wy = w * y2;
|
||||||
|
float wz = w * z2;
|
||||||
|
|
||||||
|
float sx = scale;
|
||||||
|
float sy = scale;
|
||||||
|
float sz = scale;
|
||||||
|
|
||||||
|
return glm::mat4(
|
||||||
|
(1.0f - (yy + zz)) * sx, (xy + wz) * sx, (xz - wy) * sx, 0.0f,
|
||||||
|
(xy - wz) * sy, (1.0f - (xx + zz)) * sy, (yz + wx) * sy, 0.0f,
|
||||||
|
(xz + wy) * sz, (yz - wx) * sz, (1.0f - (xx + yy)) * sz, 0.0f,
|
||||||
|
position.x, position.y, position.z, 1.0f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAngles(const glm::vec3& angles_deg)
|
||||||
|
{
|
||||||
|
glm::vec3 angles_rad = glm::radians(angles_deg);
|
||||||
|
rotation = glm::quat(angles_rad);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform Lerp(const Transform& a, const Transform& b, float t)
|
||||||
|
{
|
||||||
|
Transform result;
|
||||||
|
result.position = glm::mix(a.position, b.position, t);
|
||||||
|
result.rotation = glm::slerp(a.rotation, b.rotation, t);
|
||||||
|
result.scale = glm::mix(a.scale, b.scale, t);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user