initial
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
project(tw_client)
|
||||
|
||||
add_subdirectory(shaders)
|
||||
|
||||
file(GLOB FILES
|
||||
src/*.cpp
|
||||
src/network/*.cpp
|
||||
src/world/*.cpp
|
||||
src/io/*.cpp
|
||||
src/draw/*.cpp
|
||||
src/draw/RenderPasses/*.cpp
|
||||
src/debug/*.cpp
|
||||
src/debug/tools/*.cpp
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${FILES})
|
||||
add_dependencies(tw_shaders imgui::plot imgui::plot)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
towards
|
||||
tw::network
|
||||
loft::common
|
||||
loft::base
|
||||
loft_window
|
||||
loft::render_graph
|
||||
tw::protocol
|
||||
tw::serialization
|
||||
tw::gui
|
||||
imgui::imgui
|
||||
imgui::plot
|
||||
glm::glm
|
||||
EnTT::EnTT
|
||||
Jolt
|
||||
Tracy::TracyClient
|
||||
TracyClient
|
||||
pqxx
|
||||
pq
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${PROJECT_SOURCE_DIR}/src/
|
||||
${JoltPhysics_SOURCE_DIR}/..
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
set(SHADER_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
file(GLOB SHADERS ${SHADER_DIR}/*.vert
|
||||
${SHADER_DIR}/*.frag
|
||||
${SHADER_DIR}/*.comp)
|
||||
|
||||
foreach(SHADER IN LISTS SHADERS)
|
||||
get_filename_component(FILENAME ${SHADER} NAME)
|
||||
set(OUTPUT_FILE ${FILENAME}.spirv)
|
||||
add_custom_command(OUTPUT ${OUTPUT_FILE}
|
||||
COMMAND ${Vulkan_GLSLANG_VALIDATOR_EXECUTABLE} -gVS ${SHADER} -V -o ${OUTPUT_FILE}
|
||||
DEPENDS ${SHADER}
|
||||
COMMENT "Compiling shader: ${SHADER}"
|
||||
)
|
||||
|
||||
list(APPEND SPV_SHADERS ${OUTPUT_FILE})
|
||||
endForeach()
|
||||
|
||||
add_custom_target(tw_shaders ALL DEPENDS ${SPV_SHADERS})
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 pos;
|
||||
layout(location = 1) in vec3 norm;
|
||||
|
||||
layout(set = 0, binding = 0) uniform Camera {
|
||||
mat4 proj;
|
||||
mat4 view;
|
||||
} cam;
|
||||
|
||||
layout( push_constant ) uniform constants
|
||||
{
|
||||
mat4 transform;
|
||||
} PushConstants;
|
||||
|
||||
|
||||
void main() {
|
||||
// vec3 bitangent = cross(norm, tangent.xyz) * tangent.w;
|
||||
// outTBN = mat3(tangent.xyz, bitangent, norm);
|
||||
//
|
||||
// outPos = pos.xyz * 0.05;
|
||||
// outNormal = norm.xyz;
|
||||
// outUV = uv;
|
||||
gl_Position = cam.proj * cam.view * PushConstants.transform * vec4(pos, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = vec4(0.0, 1.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 pos;
|
||||
layout(location = 1) in vec3 norm;
|
||||
|
||||
layout(set = 0, binding = 0) uniform Camera {
|
||||
mat4 proj;
|
||||
mat4 view;
|
||||
} cam;
|
||||
|
||||
layout( push_constant ) uniform constants
|
||||
{
|
||||
mat4 transform;
|
||||
} PushConstants;
|
||||
|
||||
|
||||
void main() {
|
||||
// vec3 bitangent = cross(norm, tangent.xyz) * tangent.w;
|
||||
// outTBN = mat3(tangent.xyz, bitangent, norm);
|
||||
//
|
||||
// outPos = pos.xyz * 0.05;
|
||||
// outNormal = norm.xyz;
|
||||
// outUV = uv;
|
||||
gl_Position = cam.proj * cam.view * PushConstants.transform * vec4(pos, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// 1115.4.0
|
||||
0x07230203,0x00010000,0x0008000b,0x0000005f,0x00000000,0x00020011,0x00000001,0x0006000b,
|
||||
0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
|
||||
0x000e000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x0000001b,
|
||||
0x00000030,0x00000031,0x00000035,0x00000039,0x0000003b,0x00000041,0x00030003,0x00000002,
|
||||
0x000001c2,0x00040005,0x00000004,0x6e69616d,0x00000000,0x00050005,0x00000009,0x61746962,
|
||||
0x6e65676e,0x00000074,0x00040005,0x0000000b,0x6d726f6e,0x00000000,0x00040005,0x0000000f,
|
||||
0x676e6174,0x00746e65,0x00040005,0x0000001b,0x5474756f,0x00004e42,0x00040005,0x00000030,
|
||||
0x5074756f,0x0000736f,0x00030005,0x00000031,0x00736f70,0x00050005,0x00000035,0x4e74756f,
|
||||
0x616d726f,0x0000006c,0x00040005,0x00000039,0x5574756f,0x00000056,0x00030005,0x0000003b,
|
||||
0x00007675,0x00060005,0x0000003f,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,
|
||||
0x0000003f,0x00000000,0x505f6c67,0x7469736f,0x006e6f69,0x00070006,0x0000003f,0x00000001,
|
||||
0x505f6c67,0x746e696f,0x657a6953,0x00000000,0x00070006,0x0000003f,0x00000002,0x435f6c67,
|
||||
0x4470696c,0x61747369,0x0065636e,0x00070006,0x0000003f,0x00000003,0x435f6c67,0x446c6c75,
|
||||
0x61747369,0x0065636e,0x00030005,0x00000041,0x00000000,0x00040005,0x00000045,0x656d6143,
|
||||
0x00006172,0x00050006,0x00000045,0x00000000,0x6a6f7270,0x00000000,0x00050006,0x00000045,
|
||||
0x00000001,0x77656976,0x00000000,0x00030005,0x00000047,0x006d6163,0x00050005,0x00000057,
|
||||
0x6e656353,0x646f4e65,0x00000065,0x00070006,0x00000057,0x00000000,0x6e617274,0x726f6673,
|
||||
0x7864496d,0x00000000,0x00050006,0x00000057,0x00000001,0x6873656d,0x00786449,0x00050005,
|
||||
0x00000059,0x656a624f,0x6e497463,0x00006f66,0x00050006,0x00000059,0x00000000,0x65646f6e,
|
||||
0x00000073,0x00050005,0x0000005b,0x656a626f,0x6e497463,0x00006f66,0x00050005,0x0000005c,
|
||||
0x736e6f63,0x746e6174,0x00000073,0x00070006,0x0000005c,0x00000000,0x6e617274,0x726f6673,
|
||||
0x7864496d,0x00000000,0x00060006,0x0000005c,0x00000001,0x6574616d,0x6c616972,0x00786449,
|
||||
0x00060005,0x0000005e,0x68737550,0x736e6f43,0x746e6174,0x00000073,0x00040047,0x0000000b,
|
||||
0x0000001e,0x00000001,0x00040047,0x0000000f,0x0000001e,0x00000003,0x00040047,0x0000001b,
|
||||
0x0000001e,0x00000003,0x00040047,0x00000030,0x0000001e,0x00000000,0x00040047,0x00000031,
|
||||
0x0000001e,0x00000000,0x00040047,0x00000035,0x0000001e,0x00000001,0x00040047,0x00000039,
|
||||
0x0000001e,0x00000002,0x00040047,0x0000003b,0x0000001e,0x00000002,0x00030047,0x0000003f,
|
||||
0x00000002,0x00050048,0x0000003f,0x00000000,0x0000000b,0x00000000,0x00050048,0x0000003f,
|
||||
0x00000001,0x0000000b,0x00000001,0x00050048,0x0000003f,0x00000002,0x0000000b,0x00000003,
|
||||
0x00050048,0x0000003f,0x00000003,0x0000000b,0x00000004,0x00030047,0x00000045,0x00000002,
|
||||
0x00040048,0x00000045,0x00000000,0x00000005,0x00050048,0x00000045,0x00000000,0x00000007,
|
||||
0x00000010,0x00050048,0x00000045,0x00000000,0x00000023,0x00000000,0x00040048,0x00000045,
|
||||
0x00000001,0x00000005,0x00050048,0x00000045,0x00000001,0x00000007,0x00000010,0x00050048,
|
||||
0x00000045,0x00000001,0x00000023,0x00000040,0x00040047,0x00000047,0x00000021,0x00000000,
|
||||
0x00040047,0x00000047,0x00000022,0x00000000,0x00050048,0x00000057,0x00000000,0x00000023,
|
||||
0x00000000,0x00050048,0x00000057,0x00000001,0x00000023,0x00000004,0x00040047,0x00000058,
|
||||
0x00000006,0x00000010,0x00030047,0x00000059,0x00000002,0x00050048,0x00000059,0x00000000,
|
||||
0x00000023,0x00000000,0x00040047,0x0000005b,0x00000021,0x00000001,0x00040047,0x0000005b,
|
||||
0x00000022,0x00000002,0x00030047,0x0000005c,0x00000002,0x00050048,0x0000005c,0x00000000,
|
||||
0x00000023,0x00000000,0x00050048,0x0000005c,0x00000001,0x00000023,0x00000004,0x00020013,
|
||||
0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,
|
||||
0x00000007,0x00000006,0x00000003,0x00040020,0x00000008,0x00000007,0x00000007,0x00040020,
|
||||
0x0000000a,0x00000001,0x00000007,0x0004003b,0x0000000a,0x0000000b,0x00000001,0x00040017,
|
||||
0x0000000d,0x00000006,0x00000004,0x00040020,0x0000000e,0x00000001,0x0000000d,0x0004003b,
|
||||
0x0000000e,0x0000000f,0x00000001,0x00040015,0x00000013,0x00000020,0x00000000,0x0004002b,
|
||||
0x00000013,0x00000014,0x00000003,0x00040020,0x00000015,0x00000001,0x00000006,0x00040018,
|
||||
0x00000019,0x00000007,0x00000003,0x00040020,0x0000001a,0x00000003,0x00000019,0x0004003b,
|
||||
0x0000001a,0x0000001b,0x00000003,0x0004002b,0x00000006,0x00000020,0x3f800000,0x0004002b,
|
||||
0x00000006,0x00000021,0x00000000,0x00040020,0x0000002f,0x00000003,0x00000007,0x0004003b,
|
||||
0x0000002f,0x00000030,0x00000003,0x0004003b,0x0000000a,0x00000031,0x00000001,0x0004002b,
|
||||
0x00000006,0x00000033,0x3d4ccccd,0x0004003b,0x0000002f,0x00000035,0x00000003,0x00040017,
|
||||
0x00000037,0x00000006,0x00000002,0x00040020,0x00000038,0x00000003,0x00000037,0x0004003b,
|
||||
0x00000038,0x00000039,0x00000003,0x00040020,0x0000003a,0x00000001,0x00000037,0x0004003b,
|
||||
0x0000003a,0x0000003b,0x00000001,0x0004002b,0x00000013,0x0000003d,0x00000001,0x0004001c,
|
||||
0x0000003e,0x00000006,0x0000003d,0x0006001e,0x0000003f,0x0000000d,0x00000006,0x0000003e,
|
||||
0x0000003e,0x00040020,0x00000040,0x00000003,0x0000003f,0x0004003b,0x00000040,0x00000041,
|
||||
0x00000003,0x00040015,0x00000042,0x00000020,0x00000001,0x0004002b,0x00000042,0x00000043,
|
||||
0x00000000,0x00040018,0x00000044,0x0000000d,0x00000004,0x0004001e,0x00000045,0x00000044,
|
||||
0x00000044,0x00040020,0x00000046,0x00000002,0x00000045,0x0004003b,0x00000046,0x00000047,
|
||||
0x00000002,0x00040020,0x00000048,0x00000002,0x00000044,0x0004002b,0x00000042,0x0000004b,
|
||||
0x00000001,0x00040020,0x00000055,0x00000003,0x0000000d,0x0004001e,0x00000057,0x00000013,
|
||||
0x00000013,0x0004001c,0x00000058,0x00000057,0x0000003d,0x0003001e,0x00000059,0x00000058,
|
||||
0x00040020,0x0000005a,0x00000002,0x00000059,0x0004003b,0x0000005a,0x0000005b,0x00000002,
|
||||
0x0004001e,0x0000005c,0x00000013,0x00000013,0x00040020,0x0000005d,0x00000009,0x0000005c,
|
||||
0x0004003b,0x0000005d,0x0000005e,0x00000009,0x00050036,0x00000002,0x00000004,0x00000000,
|
||||
0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,0x0004003d,
|
||||
0x00000007,0x0000000c,0x0000000b,0x0004003d,0x0000000d,0x00000010,0x0000000f,0x0008004f,
|
||||
0x00000007,0x00000011,0x00000010,0x00000010,0x00000000,0x00000001,0x00000002,0x0007000c,
|
||||
0x00000007,0x00000012,0x00000001,0x00000044,0x0000000c,0x00000011,0x00050041,0x00000015,
|
||||
0x00000016,0x0000000f,0x00000014,0x0004003d,0x00000006,0x00000017,0x00000016,0x0005008e,
|
||||
0x00000007,0x00000018,0x00000012,0x00000017,0x0003003e,0x00000009,0x00000018,0x0004003d,
|
||||
0x0000000d,0x0000001c,0x0000000f,0x0008004f,0x00000007,0x0000001d,0x0000001c,0x0000001c,
|
||||
0x00000000,0x00000001,0x00000002,0x0004003d,0x00000007,0x0000001e,0x00000009,0x0004003d,
|
||||
0x00000007,0x0000001f,0x0000000b,0x00050051,0x00000006,0x00000022,0x0000001d,0x00000000,
|
||||
0x00050051,0x00000006,0x00000023,0x0000001d,0x00000001,0x00050051,0x00000006,0x00000024,
|
||||
0x0000001d,0x00000002,0x00050051,0x00000006,0x00000025,0x0000001e,0x00000000,0x00050051,
|
||||
0x00000006,0x00000026,0x0000001e,0x00000001,0x00050051,0x00000006,0x00000027,0x0000001e,
|
||||
0x00000002,0x00050051,0x00000006,0x00000028,0x0000001f,0x00000000,0x00050051,0x00000006,
|
||||
0x00000029,0x0000001f,0x00000001,0x00050051,0x00000006,0x0000002a,0x0000001f,0x00000002,
|
||||
0x00060050,0x00000007,0x0000002b,0x00000022,0x00000023,0x00000024,0x00060050,0x00000007,
|
||||
0x0000002c,0x00000025,0x00000026,0x00000027,0x00060050,0x00000007,0x0000002d,0x00000028,
|
||||
0x00000029,0x0000002a,0x00060050,0x00000019,0x0000002e,0x0000002b,0x0000002c,0x0000002d,
|
||||
0x0003003e,0x0000001b,0x0000002e,0x0004003d,0x00000007,0x00000032,0x00000031,0x0005008e,
|
||||
0x00000007,0x00000034,0x00000032,0x00000033,0x0003003e,0x00000030,0x00000034,0x0004003d,
|
||||
0x00000007,0x00000036,0x0000000b,0x0003003e,0x00000035,0x00000036,0x0004003d,0x00000037,
|
||||
0x0000003c,0x0000003b,0x0003003e,0x00000039,0x0000003c,0x00050041,0x00000048,0x00000049,
|
||||
0x00000047,0x00000043,0x0004003d,0x00000044,0x0000004a,0x00000049,0x00050041,0x00000048,
|
||||
0x0000004c,0x00000047,0x0000004b,0x0004003d,0x00000044,0x0000004d,0x0000004c,0x00050092,
|
||||
0x00000044,0x0000004e,0x0000004a,0x0000004d,0x0004003d,0x00000007,0x0000004f,0x00000030,
|
||||
0x00050051,0x00000006,0x00000050,0x0000004f,0x00000000,0x00050051,0x00000006,0x00000051,
|
||||
0x0000004f,0x00000001,0x00050051,0x00000006,0x00000052,0x0000004f,0x00000002,0x00070050,
|
||||
0x0000000d,0x00000053,0x00000050,0x00000051,0x00000052,0x00000020,0x00050091,0x0000000d,
|
||||
0x00000054,0x0000004e,0x00000053,0x00050041,0x00000055,0x00000056,0x00000041,0x00000043,
|
||||
0x0003003e,0x00000056,0x00000054,0x000100fd,0x00010038
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "runtime.hpp"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
tw::Runtime runtime(argc, argv);
|
||||
runtime.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "debug/ComponentGui.hpp"
|
||||
#include "world/CharacterBody.hpp"
|
||||
|
||||
template<>
|
||||
class tw::dbg::ComponentGui<tw::CharacterBody> {
|
||||
public:
|
||||
void draw(CharacterBody* instance) {
|
||||
ImGui::SeparatorText("Character Body Component");
|
||||
|
||||
auto position = instance->m_character->GetPosition();
|
||||
ImGui::InputFloat3("Position", (float*)&position);
|
||||
|
||||
auto new_velocity = instance->m_character->GetLinearVelocity();
|
||||
ImGui::InputFloat3("New Velocity", (float*)&new_velocity);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "debug/ComponentGui.hpp"
|
||||
#include "world/CharacterController.hpp"
|
||||
|
||||
template<>
|
||||
class tw::dbg::ComponentGui<tw::CharacterController> {
|
||||
public:
|
||||
void draw(tw::CharacterController* instance) {
|
||||
ImGui::SeparatorText("Character Controller Component");
|
||||
|
||||
if(ImGui::BeginTable("history", 3)) {
|
||||
for(auto key : instance->input_history().buffer()) {
|
||||
// ImGui::TableNextRow();
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Text("%u", key.first);
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// if(instance->input_history().get(key.first).has_value()) {
|
||||
// glm::vec3 input_vec = *instance->input_history().get(key.first).value();
|
||||
// std::string input = std::format("{} {} {}", input_vec.x, input_vec.y, input_vec.z);
|
||||
// ImGui::Text("%s", input.c_str());
|
||||
// } else {
|
||||
// ImGui::Text("None");
|
||||
// }
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// if(instance->position_history().get(key).has_value()) {
|
||||
// glm::vec3 position_vec = *instance->position_history().get(key).value();
|
||||
// std::string position = std::format("{} {} {}", position_vec.x, position_vec.y, position_vec.z);
|
||||
// ImGui::Text("%s", position.c_str());
|
||||
// } else {
|
||||
// ImGui::Text("None");
|
||||
// }
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
namespace tw::dbg {
|
||||
|
||||
template<typename T>
|
||||
class ComponentGui {
|
||||
public:
|
||||
void draw(T* instance);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tw::dbg {
|
||||
|
||||
class DebugComponent {
|
||||
private:
|
||||
std::string m_name;
|
||||
|
||||
public:
|
||||
inline const std::string& name() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
DebugComponent(const std::string& name) :
|
||||
m_name{name} {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
#include <chrono>
|
||||
#include <format>
|
||||
#include <ratio>
|
||||
|
||||
#include "debug/ComponentGui.hpp"
|
||||
#include <implot.h>
|
||||
#include <implot_internal.h>
|
||||
#include "network/EntityInterpolation.hpp"
|
||||
|
||||
template<>
|
||||
class tw::dbg::ComponentGui<tw::net::EntityPositionInterpolation> {
|
||||
public:
|
||||
void draw(net::EntityPositionInterpolation* instance) {
|
||||
ImGui::SeparatorText("Entity Interpolation");
|
||||
|
||||
bool m_is_scrolling = true;
|
||||
static float m_metric_history = 10.0f;
|
||||
ImGui::Checkbox("Is Scrolling", &m_is_scrolling);
|
||||
if(m_is_scrolling) {
|
||||
ImGui::SliderFloat("History", &m_metric_history,1,30,"%.1f s");
|
||||
}
|
||||
|
||||
static int32_t time_buffer_len = 1000;
|
||||
ImGui::SliderInt("Buffer length (ms)", &time_buffer_len, 0, 1000);
|
||||
|
||||
auto now = std::chrono::high_resolution_clock::now() - std::chrono::milliseconds(time_buffer_len);
|
||||
auto [from, to, mix] = instance->get_values_around(now);
|
||||
auto mixed_value = glm::mix(from, to, mix);
|
||||
|
||||
ImGui::Text("Mixed value: (%f, %f)", mixed_value.x, mixed_value.y);
|
||||
|
||||
if(ImPlot::BeginPlot("Interpolation")) {
|
||||
auto plot_x_from = instance->times().begin()->time_since_epoch().count();
|
||||
std::vector<float> times(instance->times().max_size());
|
||||
for(auto it = instance->times().begin(); it != instance->times().end(); ++it) {
|
||||
times.push_back(it->time_since_epoch().count() - plot_x_from);
|
||||
}
|
||||
|
||||
float interpolated_time = now.time_since_epoch().count() - plot_x_from;
|
||||
std::vector<float> values(instance->values().max_size());
|
||||
for(auto it = instance->values().begin(); it != instance->values().end(); ++it) {
|
||||
values.push_back(it->x);
|
||||
}
|
||||
values.push_back(mixed_value.x);
|
||||
|
||||
ImPlot::PlotScatter("Server", times.data(), values.data(), times.size() - 1);
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f);
|
||||
ImPlot::SetNextMarkerStyle(ImPlotMarker_Square, 6, ImPlot::GetColormapColor(1), IMPLOT_AUTO, ImPlot::GetColormapColor(1));
|
||||
ImPlot::PlotScatter("Client", &interpolated_time, &mixed_value.x, 1);
|
||||
ImPlot::PopStyleVar();
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
|
||||
if(ImGui::BeginTable("entityInterpolation", 2)) {
|
||||
for(int i = 0; i < instance->values().size(); i++) {
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
auto value = instance->values()[i];
|
||||
auto now = std::chrono::high_resolution_clock::now() - std::chrono::milliseconds(time_buffer_len);
|
||||
if(instance->times()[i] > now && (i == instance->values().size() - 1 || instance->times()[i + 1] <= now)) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, ImGui::GetColorU32(ImVec4(0.7f, 0.3f, 0.3f, 0.65f)));
|
||||
ImGui::Text("%f %f %f", mixed_value.x, mixed_value.y, mixed_value.z);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.0f)));
|
||||
}
|
||||
|
||||
ImGui::Text("%f %f %f", value.x, value.y, value.z);
|
||||
|
||||
auto point = instance->times()[i].time_since_epoch();
|
||||
auto hours = std::chrono::duration_cast<std::chrono::hours>(point);
|
||||
point -= hours;
|
||||
hours %= 24;
|
||||
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(point);
|
||||
point -= minutes;
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(point);
|
||||
point -= seconds;
|
||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(point);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
auto fmt = std::format("{}:{}:{}.{}", hours, minutes, seconds, milliseconds);
|
||||
ImGui::Text(fmt.c_str());
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "EntityManagerGui.hpp"
|
||||
|
||||
#include "debug/tools/MatrixWidget.hpp"
|
||||
#include "debug/EntityInterpolationDebug.hpp"
|
||||
#include "debug/CharacterBodyDebug.hpp"
|
||||
#include "debug/CharacterControllerDebug.hpp"
|
||||
|
||||
#include "network/EntityInterpolation.hpp"
|
||||
#include "world/CharacterBody.hpp"
|
||||
#include "world/CharacterController.hpp"
|
||||
#include "world/Transform.hpp"
|
||||
#include "world/WorldEntity.hpp"
|
||||
#include "draw/Mesh.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <entt/entity/entity.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
EntityManagerGui::EntityManagerGui(World* world) :
|
||||
m_world(world) {
|
||||
|
||||
}
|
||||
|
||||
void draw_transform_controls(Transform* transform) {
|
||||
|
||||
}
|
||||
|
||||
void EntityManagerGui::draw_entity_components() {
|
||||
ImGui::Text("Entity ID: %d", (uint32_t)m_selected);
|
||||
|
||||
ImGui::BeginChild("Transform Component", ImVec2(0, 0), ImGuiChildFlags_Border);
|
||||
|
||||
auto transform = m_world->registry().try_get<Transform>(m_selected);
|
||||
|
||||
if(transform != nullptr) {
|
||||
ImGui::SeparatorText("Transform Component");
|
||||
draw_matrix_widget(transform->transform, true);
|
||||
|
||||
draw_transform_controls(transform);
|
||||
}
|
||||
|
||||
|
||||
auto mesh = m_world->registry().try_get<tw::drw::Mesh>(m_selected);
|
||||
if(mesh != nullptr) {
|
||||
ImGui::SeparatorText("Mesh Component");
|
||||
ImGui::Text("Num vertices: %i", mesh->num_vertices());
|
||||
ImGui::Text("Vertex offset: %i", mesh->vertex_offset());
|
||||
}
|
||||
|
||||
draw_debug_components<tw::CharacterBody, tw::CharacterController, tw::net::EntityPositionInterpolation>();
|
||||
|
||||
// auto character = m_world->registry().try_get<tw::CharacterBody>(m_selected);
|
||||
// if(character != nullptr) {
|
||||
// tw::dbg::ComponentGui<tw::CharacterBody>().draw(character);
|
||||
// }
|
||||
//
|
||||
// auto entity_interpolation = m_world->registry().try_get<EntityInterpolation>(m_selected);
|
||||
// if(entity_interpolation != nullptr) {
|
||||
// tw::dbg::ComponentGui<EntityInterpolation>().draw(entity_interpolation);
|
||||
// }
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void EntityManagerGui::draw() {
|
||||
ImGui::Begin("Transforms");
|
||||
|
||||
ImGui::BeginChild("Entities", ImVec2(0, 260), ImGuiChildFlags_Border);
|
||||
|
||||
ImGui::SeparatorText("Entities");
|
||||
|
||||
auto view = m_world->registry().view<const WorldEntity>();
|
||||
|
||||
view.each([&](const auto entity, const WorldEntity& info) {
|
||||
ImGui::PushID((uint32_t)info.entity_id);
|
||||
if(ImGui::Selectable(info.name.c_str(), m_selected_entity == info.entity_id)) {
|
||||
m_selected_entity = info.entity_id;
|
||||
m_selected = entity;
|
||||
}
|
||||
ImGui::PopID();
|
||||
});
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
if(m_selected_entity.has_value()) {
|
||||
draw_entity_components();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "debug/ComponentGui.hpp"
|
||||
#include "entt/entity/fwd.hpp"
|
||||
#include "world/World.hpp"
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
class EntityManagerGui {
|
||||
private:
|
||||
World* m_world;
|
||||
|
||||
entt::entity m_selected;
|
||||
std::optional<uint32_t> m_selected_entity;
|
||||
|
||||
void draw_entity_components();
|
||||
|
||||
template<class... Ts>
|
||||
void draw_debug_components() {
|
||||
(
|
||||
[&]() {
|
||||
auto character = m_world->registry().try_get<Ts>(m_selected);
|
||||
if(character != nullptr) {
|
||||
tw::dbg::ComponentGui<Ts>().draw(character);
|
||||
}
|
||||
}(),
|
||||
...);
|
||||
}
|
||||
|
||||
public:
|
||||
entt::entity& selected() {
|
||||
return m_selected;
|
||||
}
|
||||
|
||||
EntityManagerGui(World* world);
|
||||
|
||||
void draw();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "MatrixWidget.hpp"
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
void draw_matrix_widget(const glm::mat4 &mat, bool display_headers) {
|
||||
if(ImGui::BeginTable("transform_component", 4, ImGuiTableFlags_Borders)) {
|
||||
if(display_headers) {
|
||||
ImGui::TableSetupColumn("Right");
|
||||
ImGui::TableSetupColumn("Front");
|
||||
ImGui::TableSetupColumn("Up");
|
||||
ImGui::TableSetupColumn("Translation");
|
||||
ImGui::TableHeadersRow();
|
||||
}
|
||||
|
||||
for(int row = 0; row < 4; row++) {
|
||||
ImGui::TableNextRow();
|
||||
|
||||
for(int col = 0; col < 4; col++) {
|
||||
ImGui::TableSetColumnIndex(col);
|
||||
ImGui::Text("%f", mat[col][row]);
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
void draw_matrix_widget(const glm::mat4& mat, bool display_headers);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
#include <implot.h>
|
||||
#include <implot_internal.h>
|
||||
|
||||
#include "metrics/BucketMetric.hpp"
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
template<typename T, typename Interval, typename Op = net::SumOp<T>>
|
||||
class MetricWidget {
|
||||
std::string m_name;
|
||||
|
||||
net::BucketMetric<T, Interval, Op>& m_metric;
|
||||
|
||||
using Self = MetricWidget<T, Interval, Op>;
|
||||
|
||||
struct {
|
||||
T constraint_from;
|
||||
T constraint_to;
|
||||
|
||||
T from;
|
||||
T to;
|
||||
} y_axis;
|
||||
|
||||
bool m_is_scrolling = true;
|
||||
|
||||
public:
|
||||
MetricWidget(
|
||||
const std::string& name,
|
||||
net::BucketMetric<T, Interval, Op>& metric
|
||||
) :
|
||||
m_name(name),
|
||||
m_metric(metric)
|
||||
{
|
||||
y_axis = {
|
||||
.constraint_from = 0,
|
||||
.constraint_to = 500,
|
||||
.from = 0,
|
||||
.to = 250
|
||||
};
|
||||
}
|
||||
|
||||
Self& set_x_axis_limits_contraints(T from, T to) {
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, from, to);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Self& set_y_axis_limits(double from, double to) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
Self& enable_scrolling() {
|
||||
m_is_scrolling = true;
|
||||
}
|
||||
|
||||
Self& disable_scrolling() {
|
||||
m_is_scrolling = true;
|
||||
}
|
||||
|
||||
|
||||
void draw() {
|
||||
ImGui::PushID(m_name.c_str());
|
||||
|
||||
auto head = m_metric.get_head();
|
||||
auto head_timeline = m_metric.get_head_timeline();
|
||||
|
||||
auto tail = m_metric.get_tail();
|
||||
auto tail_timeline = m_metric.get_tail_timeline();
|
||||
|
||||
static float m_metric_history = 10.0f;
|
||||
ImGui::Checkbox("Is Scrolling", &m_is_scrolling);
|
||||
if(m_is_scrolling) {
|
||||
ImGui::SliderFloat("History", &m_metric_history,1,30,"%.1f s");
|
||||
}
|
||||
|
||||
ImGui::Text("Min: %i", m_metric.min());
|
||||
ImGui::Text("Max: %i", m_metric.max());
|
||||
|
||||
if(ImPlot::BeginPlot(m_name.c_str())) {
|
||||
auto from = tail_timeline.empty() ? *(head_timeline.end() - 1) : *(tail_timeline.end() - 1);
|
||||
|
||||
ImPlot::SetupAxes("Time", m_name.c_str(), ImPlotAxisFlags_None, ImPlotAxisFlags_None);
|
||||
if(m_is_scrolling) {
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, from - m_metric_history, from, ImGuiCond_Always);
|
||||
}
|
||||
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, m_metric.max() * 2, ImGuiCond_Always);
|
||||
// ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, 0, 10000);
|
||||
|
||||
ImPlot::PlotLine(m_name.c_str(), head_timeline.data(), head.data(), head.size());
|
||||
ImPlot::PlotLine(m_name.c_str(), tail_timeline.data(), tail.data(), tail.size());
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "metrics/BucketMetric.hpp"
|
||||
#include "metrics/NetworkStatsLogger.hpp"
|
||||
#include "debug/tools/MetricWidget.hpp"
|
||||
|
||||
#include <implot.h>
|
||||
#include <implot_internal.h>
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
class NetworkStatsGui {
|
||||
private:
|
||||
// MetricWidget<uint32_t, std::chrono::seconds, net::AverageOp<uint32_t>> m_ping_widget;
|
||||
// MetricWidget<uint32_t, std::chrono::seconds> m_outgoing_widget;
|
||||
// MetricWidget<uint32_t, std::chrono::seconds> m_incoming_widget;
|
||||
|
||||
public:
|
||||
// NetworkStatsGui() :
|
||||
// m_ping_widget("Ping", net::NetworkStatsLogger::instance()->ping()),
|
||||
// m_outgoing_widget("Outgoing", net::NetworkStatsLogger::instance()->outgoing()),
|
||||
// m_incoming_widget("Incoming", net::NetworkStatsLogger::instance()->incoming())
|
||||
// {
|
||||
// }
|
||||
|
||||
void draw() {
|
||||
// auto* instance = net::NetworkStatsLogger::instance();
|
||||
// auto& ping = instance->ping();
|
||||
|
||||
ImGui::Begin("Network Stats");
|
||||
|
||||
/* auto head = ping.get_head();
|
||||
auto head_timeline = ping.get_head_timeline();
|
||||
|
||||
auto tail = ping.get_tail();
|
||||
auto tail_timeline = ping.get_tail_timeline();
|
||||
|
||||
static float ping_history = 10.0f;
|
||||
ImGui::SliderFloat("Ping History", &ping_history,1,30,"%.1f s");
|
||||
|
||||
if(ImPlot::BeginPlot("Ping")) {
|
||||
auto from = tail_timeline.empty() ? *(head_timeline.end() - 1) : *(tail_timeline.end() - 1);
|
||||
|
||||
ImPlot::SetupAxes("FrameIdx","FPS", ImPlotAxisFlags_None, ImPlotAxisFlags_None);
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, from - ping_history, from, ImGuiCond_Always);
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 120);
|
||||
ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, 0, 10000);
|
||||
|
||||
ImPlot::PlotLine("Ping", head_timeline.data(), head.data(), head.size());
|
||||
ImPlot::PlotLine("Ping", tail_timeline.data(), tail.data(), tail.size());
|
||||
ImPlot::EndPlot();
|
||||
} */
|
||||
|
||||
// m_ping_widget.draw();
|
||||
// m_outgoing_widget.draw();
|
||||
// m_incoming_widget.draw();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <vector>
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include "metrics/NetworkStatsLogger.hpp"
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
class PacketBacklogGui {
|
||||
private:
|
||||
std::vector<uint32_t> m_buckets;
|
||||
uint32_t m_last_backlog_idx;
|
||||
|
||||
public:
|
||||
void draw() {
|
||||
// auto* instance = net::NetworkStatsLogger::instance();
|
||||
// size_t size = instance->get_size();
|
||||
|
||||
// if(ImGui::BeginTable("Network Packets", 5)) {
|
||||
// ImGui::TableSetupColumn("Message Type");
|
||||
// ImGui::TableSetupColumn("Time");
|
||||
// ImGui::TableSetupColumn("Is From Us");
|
||||
// ImGui::TableSetupColumn("Target");
|
||||
// ImGui::TableSetupColumn("Size");
|
||||
|
||||
// for(int32_t i = size-1; i >= 0; i--) {
|
||||
// auto& item = instance->get_item(i);
|
||||
// ImGui::PushID(item.timepoint.time_since_epoch().count());
|
||||
|
||||
// ImGui::TableNextRow();
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Text("%i", item.message_type);
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Text(std::format("{}", item.timepoint.time_since_epoch()).c_str());
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Checkbox("is_sent_from_us", &item.is_sent_by_us);
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Text(item.target.to_string().c_str());
|
||||
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Text("%ld", item.buffer.size());
|
||||
|
||||
// ImGui::PopID();
|
||||
// }
|
||||
// ImGui::EndTable();
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "Address.hpp"
|
||||
#include "ByteBuffer.hpp"
|
||||
#include "packets/Packet.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
class SavedPacket {
|
||||
public:
|
||||
PacketType type;
|
||||
bool is_from_client;
|
||||
|
||||
net::Address origin;
|
||||
net::Address target;
|
||||
|
||||
net::ByteBuffer content;
|
||||
|
||||
public:
|
||||
SavedPacket() :
|
||||
origin({}, 0),
|
||||
target({}, 0),
|
||||
content(1024)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class PacketLogger {
|
||||
private:
|
||||
std::vector<SavedPacket> m_packets;
|
||||
uint32_t m_left, m_right;
|
||||
|
||||
public:
|
||||
inline const std::vector<SavedPacket>& packets() const {
|
||||
return m_packets;
|
||||
}
|
||||
|
||||
PacketLogger(uint32_t max_packets) :
|
||||
m_packets(max_packets) {
|
||||
}
|
||||
|
||||
void push(PacketType type, bool is_from_client,
|
||||
net::Address origin, net::Address target,
|
||||
net::ByteBuffer& content) {
|
||||
SavedPacket& packet = m_packets[m_right];
|
||||
packet.type = type;
|
||||
packet.is_from_client = is_from_client;
|
||||
packet.origin = origin;
|
||||
packet.target = target;
|
||||
memcpy(packet.content.data().data(), content.data().data(), content.writing_head());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "PerformanceStatsGui.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <implot.h>
|
||||
#include <implot_internal.h>
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
PerformanceStatsGui::PerformanceStatsGui(LockStep& lock_step) :
|
||||
m_lockstep(lock_step),
|
||||
fps_history(1000),
|
||||
frame_idxs(1000)
|
||||
{
|
||||
}
|
||||
|
||||
void PerformanceStatsGui::draw() {
|
||||
ImGui::Begin("Stats");
|
||||
|
||||
ImGui::Text("FPS: %ld", m_lockstep.fps());
|
||||
fps_history[fps_history_idx] = m_lockstep.fps();
|
||||
frame_idxs[fps_history_idx] = frame_idx++;
|
||||
|
||||
if(fps_history_idx == fps_history.size() - 1) {
|
||||
is_plot_filled = true;
|
||||
}
|
||||
|
||||
fps_history_idx = (fps_history_idx + 1) % fps_history.size();
|
||||
|
||||
if(ImPlot::BeginPlot("FPS Plot")) {
|
||||
ImPlot::SetupAxes("FrameIdx","FPS", ImPlotAxisFlags_None, ImPlotAxisFlags_None);
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, std::max((double)frame_idx - 1000.0, 0.0), frame_idx, ImGuiCond_Always);
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1,0,120);
|
||||
ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, 0, 120);
|
||||
ImPlot::PlotLine("FPS", frame_idxs.data(), fps_history.data(), is_plot_filled ? (int)fps_history.size() : (int)fps_history_idx - 1);
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "runtime/LockStep.hpp"
|
||||
|
||||
namespace tw::dbg::tools {
|
||||
|
||||
class PerformanceStatsGui {
|
||||
private:
|
||||
LockStep& m_lockstep;
|
||||
|
||||
std::vector<uint32_t> fps_history;
|
||||
std::vector<uint32_t> frame_idxs;
|
||||
|
||||
uint32_t fps_history_idx = 0;
|
||||
uint32_t frame_idx = 0;
|
||||
bool is_plot_filled = false;
|
||||
|
||||
public:
|
||||
PerformanceStatsGui(LockStep& lock_step);
|
||||
|
||||
void draw();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
|
||||
class FrameRateCounter {
|
||||
using TimePoint = std::chrono::high_resolution_clock::time_point;
|
||||
|
||||
TimePoint m_last_frame;
|
||||
|
||||
double m_frame_count;
|
||||
|
||||
public:
|
||||
uint64_t frame_count() {
|
||||
return (uint64_t)m_frame_count;
|
||||
}
|
||||
|
||||
void next_frame() {
|
||||
TimePoint current = std::chrono::high_resolution_clock::now();
|
||||
|
||||
m_frame_count = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(current - m_last_frame).count();
|
||||
|
||||
m_last_frame = current;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace tw::drw {
|
||||
|
||||
/**
|
||||
* Stores location data about Mesh on the GPU.
|
||||
*/
|
||||
class Mesh {
|
||||
uint32_t m_vertex_offset;
|
||||
uint32_t m_num_vertices;
|
||||
|
||||
public:
|
||||
inline uint32_t num_vertices() const {
|
||||
return m_num_vertices;
|
||||
}
|
||||
|
||||
inline uint32_t vertex_offset() const {
|
||||
return m_vertex_offset;
|
||||
}
|
||||
|
||||
Mesh(uint32_t vertex_offset, uint32_t num_vertices) :
|
||||
m_vertex_offset{vertex_offset},
|
||||
m_num_vertices{num_vertices} {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "MeshBuffer.hpp"
|
||||
|
||||
namespace tw::drw {
|
||||
|
||||
void MeshBuffer::resize_buffer(size_t num_vertices) {
|
||||
BufferCreateInfo buffer_info = {
|
||||
.size = sizeof(glm::vec3) * 2 * num_vertices,
|
||||
.usage = VK_BUFFER_USAGE_2_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_2_TRANSFER_DST_BIT,
|
||||
.isExclusive = true,
|
||||
};
|
||||
|
||||
MemoryAllocationInfo memory_info = {
|
||||
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE
|
||||
};
|
||||
|
||||
m_gpu->memory()->create_buffer(&buffer_info, &memory_info, &m_vertex_buffer);
|
||||
m_max_vertex_count = num_vertices;
|
||||
}
|
||||
|
||||
MeshBuffer::MeshBuffer(const Gpu* gpu) :
|
||||
m_gpu(gpu),
|
||||
m_stream(gpu, 1000 * sizeof(glm::vec3) * 1),
|
||||
m_num_vertices(0) {
|
||||
resize_buffer(1000);
|
||||
}
|
||||
|
||||
Mesh MeshBuffer::push_mesh(const MeshData& mesh_data) {
|
||||
m_stream.write(&m_vertex_buffer, (void*)mesh_data.positions().data(), m_num_vertices * sizeof(glm::vec3), mesh_data.positions().size() * sizeof(glm::vec3));
|
||||
|
||||
m_stream.write(&m_vertex_buffer, (void*)mesh_data.normals().data(), (m_num_vertices + 1000) * sizeof(glm::vec3), mesh_data.normals().size() * sizeof(glm::vec3));
|
||||
|
||||
m_stream.flush();
|
||||
|
||||
Mesh result(m_num_vertices, mesh_data.positions().size());
|
||||
|
||||
m_num_vertices += mesh_data.positions().size();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "Gpu.hpp"
|
||||
#include "resources/BufferBusWriter.h"
|
||||
|
||||
#include "Mesh.hpp"
|
||||
#include "MeshData.hpp"
|
||||
|
||||
namespace tw::drw {
|
||||
|
||||
class MeshBuffer {
|
||||
private:
|
||||
const Gpu* m_gpu;
|
||||
|
||||
Buffer m_vertex_buffer;
|
||||
uint32_t m_max_vertex_count;
|
||||
uint32_t m_num_vertices;
|
||||
|
||||
// streams data from CPU to the GPU
|
||||
BufferBusWriter m_stream;
|
||||
|
||||
void resize_buffer(size_t num_vertices);
|
||||
|
||||
public:
|
||||
inline const Buffer& buffer() const { return m_vertex_buffer; }
|
||||
|
||||
MeshBuffer(const Gpu* gpu);
|
||||
|
||||
MeshBuffer(const MeshBuffer&) = delete;
|
||||
MeshBuffer& operator=(const MeshBuffer&) = delete;
|
||||
|
||||
Mesh push_mesh(const MeshData& mesh_data);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
|
||||
namespace tw::drw {
|
||||
class MeshData {
|
||||
private:
|
||||
std::vector<glm::vec3> m_positions;
|
||||
std::vector<glm::vec3> m_normals;
|
||||
|
||||
public:
|
||||
inline const std::vector<glm::vec3>& positions() const {
|
||||
return m_positions;
|
||||
}
|
||||
|
||||
inline const std::vector<glm::vec3>& normals() const {
|
||||
return m_normals;
|
||||
}
|
||||
|
||||
MeshData(std::vector<glm::vec3> positions, std::vector<glm::vec3> normals) :
|
||||
m_positions{positions},
|
||||
m_normals{normals}
|
||||
{
|
||||
}
|
||||
|
||||
static MeshData plane(glm::vec2 scale) {
|
||||
return MeshData({
|
||||
{-0.5f * scale.x, 0.0f, -0.5f * scale.y},
|
||||
{ 0.5f * scale.x, 0.0f, -0.5f * scale.y},
|
||||
{ 0.5f * scale.x, 0.0f, 0.5f * scale.y},
|
||||
|
||||
{ 0.5f * scale.x, 0.0f, 0.5f * scale.y},
|
||||
{-0.5f * scale.x, 0.0f, 0.5f * scale.y},
|
||||
{-0.5f * scale.x, 0.0f, -0.5f * scale.y},
|
||||
}, {
|
||||
{0.0f, 0.0f, 1.0f},
|
||||
{0.0f, 0.0f, 1.0f},
|
||||
{0.0f, 0.0f, 1.0f},
|
||||
|
||||
{0.0f, 0.0f, 1.0f},
|
||||
{0.0f, 0.0f, 1.0f},
|
||||
{0.0f, 0.0f, 1.0f},
|
||||
} );
|
||||
}
|
||||
|
||||
static MeshData cube(glm::vec3 scale) {
|
||||
return MeshData({
|
||||
{-0.5f, -0.5f, 0.5f},
|
||||
{ 0.5f, -0.5f, 0.5f},
|
||||
{ 0.5f, 0.5f, 0.5f},
|
||||
|
||||
{ 0.5f, 0.5f, 0.5f},
|
||||
{-0.5f, 0.5f, 0.5f},
|
||||
{-0.5f, -0.5f, 0.5f},
|
||||
|
||||
// Back face (−Z)
|
||||
{ 0.5f, -0.5f, -0.5f},
|
||||
{-0.5f, -0.5f, -0.5f},
|
||||
{-0.5f, 0.5f, -0.5f},
|
||||
|
||||
{-0.5f, 0.5f, -0.5f},
|
||||
{ 0.5f, 0.5f, -0.5f},
|
||||
{ 0.5f, -0.5f, -0.5f},
|
||||
|
||||
// Left face (−X)
|
||||
{-0.5f, -0.5f, -0.5f},
|
||||
{-0.5f, -0.5f, 0.5f},
|
||||
{-0.5f, 0.5f, 0.5f},
|
||||
|
||||
{-0.5f, 0.5f, 0.5f},
|
||||
{-0.5f, 0.5f, -0.5f},
|
||||
{-0.5f, -0.5f, -0.5f},
|
||||
|
||||
// Right face (+X)
|
||||
{ 0.5f, -0.5f, 0.5f},
|
||||
{ 0.5f, -0.5f, -0.5f},
|
||||
{ 0.5f, 0.5f, -0.5f},
|
||||
|
||||
{ 0.5f, 0.5f, -0.5f},
|
||||
{ 0.5f, 0.5f, 0.5f},
|
||||
{ 0.5f, -0.5f, 0.5f},
|
||||
|
||||
// Top face (+Y)
|
||||
{-0.5f, 0.5f, 0.5f},
|
||||
{ 0.5f, 0.5f, 0.5f},
|
||||
{ 0.5f, 0.5f, -0.5f},
|
||||
|
||||
{ 0.5f, 0.5f, -0.5f},
|
||||
{-0.5f, 0.5f, -0.5f},
|
||||
{-0.5f, 0.5f, 0.5f},
|
||||
|
||||
// Bottom face (−Y)
|
||||
{-0.5f, -0.5f, -0.5f},
|
||||
{ 0.5f, -0.5f, -0.5f},
|
||||
{ 0.5f, -0.5f, 0.5f},
|
||||
|
||||
{ 0.5f, -0.5f, 0.5f},
|
||||
{-0.5f, -0.5f, 0.5f},
|
||||
{-0.5f, -0.5f, -0.5f},
|
||||
}, {
|
||||
{ 0.0f, 0.0f, 1.0f},
|
||||
{ 0.0f, 0.0f, 1.0f},
|
||||
{ 0.0f, 0.0f, 1.0f},
|
||||
|
||||
{ 0.0f, 0.0f, 1.0f},
|
||||
{ 0.0f, 0.0f, 1.0f},
|
||||
{ 0.0f, 0.0f, 1.0f},
|
||||
|
||||
|
||||
{ 0.0f, 0.0f, -1.0f},
|
||||
{ 0.0f, 0.0f, -1.0f},
|
||||
{ 0.0f, 0.0f, -1.0f},
|
||||
|
||||
{ 0.0f, 0.0f, -1.0f},
|
||||
{ 0.0f, 0.0f, -1.0f},
|
||||
{ 0.0f, 0.0f, -1.0f},
|
||||
|
||||
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
{-1.0f, 0.0f, 0.0f},
|
||||
|
||||
|
||||
{ 1.0f, 0.0f, 0.0f},
|
||||
{ 1.0f, 0.0f, 0.0f},
|
||||
{ 1.0f, 0.0f, 0.0f},
|
||||
|
||||
{ 1.0f, 0.0f, 0.0f},
|
||||
{ 1.0f, 0.0f, 0.0f},
|
||||
{ 1.0f, 0.0f, 0.0f},
|
||||
|
||||
|
||||
{ 0.0f, 1.0f, 0.0f},
|
||||
{ 0.0f, 1.0f, 0.0f},
|
||||
{ 0.0f, 1.0f, 0.0f},
|
||||
|
||||
{ 0.0f, 1.0f, 0.0f},
|
||||
{ 0.0f, 1.0f, 0.0f},
|
||||
{ 0.0f, 1.0f, 0.0f},
|
||||
|
||||
|
||||
{ 0.0f, -1.0f, 0.0f},
|
||||
{ 0.0f, -1.0f, 0.0f},
|
||||
{ 0.0f, -1.0f, 0.0f},
|
||||
|
||||
{ 0.0f, -1.0f, 0.0f},
|
||||
{ 0.0f, -1.0f, 0.0f},
|
||||
{ 0.0f, -1.0f, 0.0f},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include "draw/WorldRenderer.hpp"
|
||||
#include "io/ShaderManager.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "draw/MeshBuffer.hpp"
|
||||
#include "shaders/PipelineBuilder.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace tw::drw {
|
||||
|
||||
class CharacterRenderPass {
|
||||
public:
|
||||
const WorldRenderer *m_world_renderer;
|
||||
io::ShaderManager *m_shader_manager;
|
||||
|
||||
VkPipelineLayout m_pipeline_layout;
|
||||
Pipeline m_pipeline;
|
||||
|
||||
lft::rg::RenderTaskBuilder m_task;
|
||||
|
||||
lft::rg::RenderTaskBuilder create_render_task() {
|
||||
return lft::rg::render_task<CharacterRenderPass>(
|
||||
"character", this,
|
||||
[](const lft::rg::TaskBuildInfo& info,
|
||||
CharacterRenderPass* context) {
|
||||
auto vertex_shader = context->m_shader_manager->load_shader("Opaque.vert.spirv");
|
||||
auto fragment_shader = context->m_shader_manager->load_shader("Opaque.frag.spirv");
|
||||
|
||||
context->m_pipeline_layout = PipelineLayoutBuilder()
|
||||
.input_set(0, context->m_world_renderer->global_descriptor_set_layout())
|
||||
.push_constant_range(0, sizeof(glm::mat4), VK_SHADER_STAGE_VERTEX_BIT)
|
||||
.build(info.gpu());
|
||||
|
||||
context->m_pipeline = PipelineBuilder(info.gpu(), info.viewport(),
|
||||
context->m_pipeline_layout, info.renderpass(),
|
||||
1,
|
||||
&vertex_shader, &fragment_shader)
|
||||
.set_vertex_input_info({
|
||||
{
|
||||
.binding = 0,
|
||||
.stride = sizeof(glm::vec3),
|
||||
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX
|
||||
},
|
||||
}, {
|
||||
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0 },
|
||||
{ 1, 0, VK_FORMAT_R32G32B32_SFLOAT, sizeof(glm::vec3) * 1000 },
|
||||
})
|
||||
.build();
|
||||
},
|
||||
[](const lft::rg::TaskRecordInfo& info,
|
||||
CharacterRenderPass* context) {
|
||||
info.recording()
|
||||
.bind_vertex_buffers({context->m_world_renderer->mesh_buffer()->buffer()}, {0});
|
||||
|
||||
auto binded_pipeline = info.recording()
|
||||
.bind_graphics_pipeline(context->m_pipeline)
|
||||
.bind_descriptor_set(0, context->m_world_renderer->global_descriptor_set());
|
||||
|
||||
auto viewport = info.viewport();
|
||||
VkRect2D rect = {
|
||||
.offset = {
|
||||
.x = 0,
|
||||
.y = 0
|
||||
}, .extent = {
|
||||
.width = (uint32_t)viewport.width,
|
||||
.height = (uint32_t)std::abs(viewport.height)
|
||||
}
|
||||
};
|
||||
vkCmdSetViewport(info.recording().cmdbuf(), 0, 1, &viewport);
|
||||
vkCmdSetScissor(info.recording().cmdbuf(), 0, 1, &rect);
|
||||
|
||||
context->m_world_renderer->world()->registry()
|
||||
.view<Mesh, Transform>()
|
||||
.each([&](const Mesh& mesh, const Transform& transform) {
|
||||
binded_pipeline.push_constants(VK_SHADER_STAGE_VERTEX_BIT, 0,
|
||||
sizeof(glm::mat4), &transform.transform);
|
||||
info.recording().draw(mesh.num_vertices(), 1, mesh.vertex_offset(), 0);
|
||||
});
|
||||
}
|
||||
)
|
||||
.add_dependency("terrain")
|
||||
.set_depth_output("gbuf_depth", VK_FORMAT_D32_SFLOAT_S8_UINT)
|
||||
.set_output_to_final();
|
||||
}
|
||||
|
||||
public:
|
||||
CharacterRenderPass(
|
||||
const WorldRenderer* world_renderer,
|
||||
io::ShaderManager* shader_manager
|
||||
) :
|
||||
m_world_renderer(world_renderer),
|
||||
m_shader_manager(shader_manager),
|
||||
m_task(create_render_task()),
|
||||
m_pipeline(VK_NULL_HANDLE, VK_NULL_HANDLE)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
lft::rg::RenderTaskBuilder& get_render_task() {
|
||||
return m_task;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include "Instance.hpp"
|
||||
#include "draw/WorldRenderer.hpp"
|
||||
#include "io/ShaderManager.hpp"
|
||||
#include "shaders/PipelineBuilder.h"
|
||||
#include "world/ChunkData.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace tw::drw {
|
||||
|
||||
class TerrainRenderPass {
|
||||
private:
|
||||
using Self = TerrainRenderPass;
|
||||
|
||||
const WorldRenderer *m_world_renderer;
|
||||
io::ShaderManager *m_shader_manager;
|
||||
|
||||
VkPipelineLayout m_pipeline_layout;
|
||||
Pipeline m_pipeline;
|
||||
|
||||
lft::rg::RenderTaskBuilder m_task;
|
||||
|
||||
Mesh m_chunk_mesh;
|
||||
|
||||
Mesh& get_chunk_mesh(glm::ivec2 position) {
|
||||
return m_chunk_mesh;
|
||||
}
|
||||
|
||||
lft::rg::RenderTaskBuilder create_render_task() {
|
||||
return lft::rg::render_task<Self>(
|
||||
"terrain", this,
|
||||
[](const lft::rg::TaskBuildInfo& info,
|
||||
Self* context) {
|
||||
auto vertex_shader = context->m_shader_manager->load_shader("Opaque2.vert.spirv");
|
||||
auto fragment_shader = context->m_shader_manager->load_shader("Opaque2.frag.spirv");
|
||||
|
||||
context->m_pipeline_layout = PipelineLayoutBuilder()
|
||||
.input_set(0, context->m_world_renderer->global_descriptor_set_layout())
|
||||
.push_constant_range(0, sizeof(glm::mat4), VK_SHADER_STAGE_VERTEX_BIT)
|
||||
.build(info.gpu());
|
||||
|
||||
std::println("Viewport: {} {}", info.viewport().width, info.viewport().height);
|
||||
|
||||
context->m_pipeline = PipelineBuilder(info.gpu(), info.viewport(),
|
||||
context->m_pipeline_layout, info.renderpass(),
|
||||
1,
|
||||
&vertex_shader, &fragment_shader)
|
||||
.set_vertex_input_info({
|
||||
{
|
||||
.binding = 0,
|
||||
.stride = sizeof(glm::vec3),
|
||||
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX
|
||||
},
|
||||
}, {
|
||||
{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0 },
|
||||
{ 1, 0, VK_FORMAT_R32G32B32_SFLOAT, sizeof(glm::vec3) * 1000 },
|
||||
})
|
||||
.build();
|
||||
},
|
||||
[](const lft::rg::TaskRecordInfo& info,
|
||||
Self* context) {
|
||||
info.recording()
|
||||
.bind_vertex_buffers({context->m_world_renderer->mesh_buffer()->buffer()}, {0});
|
||||
|
||||
auto binded_pipeline = info.recording()
|
||||
.bind_graphics_pipeline(context->m_pipeline)
|
||||
.bind_descriptor_set(0, context->m_world_renderer->global_descriptor_set());
|
||||
|
||||
auto viewport = info.viewport();
|
||||
VkRect2D rect = {
|
||||
.offset = {
|
||||
.x = 0,
|
||||
.y = 0
|
||||
}, .extent = {
|
||||
.width = (uint32_t)viewport.width,
|
||||
.height = (uint32_t)std::abs(viewport.height)
|
||||
}
|
||||
};
|
||||
vkCmdSetViewport(info.recording().cmdbuf(), 0, 1, &viewport);
|
||||
vkCmdSetScissor(info.recording().cmdbuf(), 0, 1, &rect);
|
||||
|
||||
glm::vec3 center = context->m_world_renderer->camera().position();
|
||||
|
||||
uint32_t render_distance = 4;
|
||||
|
||||
glm::vec3 from = glm::floor(center - glm::vec3(render_distance));
|
||||
glm::vec3 to = glm::ceil(center + glm::vec3(render_distance));
|
||||
|
||||
from = glm::vec3(-4, 0, -4);
|
||||
to = glm::vec3(4, 0, 4);
|
||||
|
||||
for(int32_t x = from.x; x < to.x; x++) {
|
||||
for(int32_t z = from.z; z < to.z; z++) {
|
||||
auto& mesh = context->get_chunk_mesh(glm::ivec2(x, z));
|
||||
|
||||
glm::mat4 ts = glm::translate(glm::mat4(1.0f), glm::vec3(x * CHUNK_WIDTH, 0, z * CHUNK_WIDTH));
|
||||
binded_pipeline.push_constants(VK_SHADER_STAGE_VERTEX_BIT, 0,
|
||||
sizeof(glm::mat4), &ts);
|
||||
|
||||
info.recording().draw(mesh.num_vertices(), 1, mesh.vertex_offset(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.set_depth_output("gbuf_depth", VK_FORMAT_D32_SFLOAT_S8_UINT)
|
||||
.set_output_to_final();
|
||||
}
|
||||
|
||||
public:
|
||||
TerrainRenderPass(
|
||||
WorldRenderer* world_renderer,
|
||||
io::ShaderManager* shader_manager
|
||||
) :
|
||||
m_world_renderer(world_renderer),
|
||||
m_shader_manager(shader_manager),
|
||||
m_task(create_render_task()),
|
||||
m_pipeline(VK_NULL_HANDLE, VK_NULL_HANDLE),
|
||||
m_chunk_mesh(world_renderer->mesh_buffer()->push_mesh(MeshData::plane(glm::vec2(16.0f))))
|
||||
{
|
||||
}
|
||||
|
||||
lft::rg::RenderTaskBuilder& get_render_task() {
|
||||
return m_task;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
#include "WorldRenderer.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <print>
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include "shaders/ShaderInputSet.h"
|
||||
#include "shaders/ShaderInputSetLayoutBuilder.hpp"
|
||||
#include "SDLWindow.h"
|
||||
#include "Swapchain.hpp"
|
||||
#include "RenderGraphBuilder.hpp"
|
||||
|
||||
#include "draw/RenderPasses/CharacterRenderPass.hpp"
|
||||
#include "ImGuiRenderPass.hpp"
|
||||
#include "draw/RenderPasses/TerrainRenderPass.hpp"
|
||||
#include "world/Camera.hpp"
|
||||
#include "world/World.hpp"
|
||||
|
||||
namespace tw::drw {
|
||||
|
||||
void lft_dbg_callback(lft::dbg::LogMessageSeverity severity,
|
||||
lft::dbg::LogMessageType type,
|
||||
const char *__restrict format,
|
||||
va_list args) {
|
||||
const char* titles[3] = {
|
||||
"\033[0;34m[info]:",
|
||||
"\033[0;33m[warn]:",
|
||||
"\033[0;31m[fail]:"
|
||||
};
|
||||
fwrite(titles[severity], 14, 1, stdout);
|
||||
|
||||
if(args != nullptr) {
|
||||
vfprintf(stdout, format, args);
|
||||
} else {
|
||||
fwrite(format, 1, strlen(format), stdout);
|
||||
}
|
||||
|
||||
fwrite("\033[0m", 4, 1, stdout);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
Instance create_instance(const std::string& name, const lft::win::Window* window) {
|
||||
std::vector<std::string> required_extensions = window->get_required_extensions();
|
||||
// required_extensions.push_back(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
|
||||
std::vector<std::string> required_layers = {
|
||||
"VK_LAYER_KHRONOS_validation"
|
||||
};
|
||||
|
||||
/**
|
||||
* Instance initializes a connection with Vulkan driver
|
||||
*/
|
||||
return Instance(
|
||||
name, name,
|
||||
required_extensions,
|
||||
required_layers,
|
||||
lft_dbg_callback);
|
||||
}
|
||||
|
||||
Surface create_surface(lft::win::SDLWindow* window, const Instance* instance) {
|
||||
return window->create_surface(instance);
|
||||
}
|
||||
|
||||
lft::rg::RenderGraph WorldRenderer::init_render_graph() {
|
||||
auto m_imgui_rp = new gui::ImGuiRenderPass(&m_gpu, (const lft::win::SDLWindow*)m_window);
|
||||
auto m_character_rp = new CharacterRenderPass(this, &m_shader_manager);
|
||||
auto m_terrain_rp = new TerrainRenderPass(this, &m_shader_manager);
|
||||
|
||||
m_rendergraph_builder.add_task(m_imgui_rp->get_render_task().build());
|
||||
m_rendergraph_builder.add_task(m_character_rp->get_render_task().build());
|
||||
m_rendergraph_builder.add_task(m_terrain_rp->get_render_task().build());
|
||||
|
||||
return m_rendergraph_builder.build();
|
||||
}
|
||||
|
||||
VkDescriptorSetLayout create_global_descriptor_set_layout(const Gpu* gpu) {
|
||||
return ShaderInputSetLayoutBuilder()
|
||||
.uniform_buffer(0)
|
||||
.build(gpu);
|
||||
}
|
||||
|
||||
VkDescriptorSet create_global_descriptor_set(
|
||||
const Gpu* gpu,
|
||||
VkDescriptorSetLayout descriptor_set_layout,
|
||||
Buffer camera_buffer
|
||||
) {
|
||||
return ShaderInputSetBuilder()
|
||||
.buffer(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, camera_buffer, 0, sizeof(glm::mat4) * 2 + sizeof(glm::vec4))
|
||||
.build(gpu, descriptor_set_layout);
|
||||
}
|
||||
|
||||
Buffer create_camera_buffer(const Gpu *gpu) {
|
||||
MemoryAllocationInfo allocInfo = {
|
||||
.usage = MEMORY_USAGE_AUTO_PREFER_DEVICE,
|
||||
.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
|
||||
};
|
||||
|
||||
BufferCreateInfo bufferInfo = {
|
||||
.size = sizeof(CameraData),
|
||||
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
.isExclusive = true
|
||||
};
|
||||
|
||||
Buffer buffer;
|
||||
gpu->memory()->create_buffer(&bufferInfo, &allocInfo, &buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
WorldRenderer::WorldRenderer(
|
||||
const std::string& name,
|
||||
const lft::win::Window* window,
|
||||
const World* world,
|
||||
const io::Files* files
|
||||
) :
|
||||
m_window(window),
|
||||
m_surface(create_surface((lft::win::SDLWindow*)(m_window), &m_instance)),
|
||||
m_instance(create_instance(name, m_window)),
|
||||
m_gpu(&m_instance, &m_surface),
|
||||
m_swapchain(&m_gpu, m_window->get_size(), &m_surface),
|
||||
|
||||
m_files(files),
|
||||
m_shader_manager(&m_gpu, m_files),
|
||||
|
||||
m_wait_on_image_fence(m_gpu.create_fence(false)),
|
||||
m_camera_buffer(create_camera_buffer(&m_gpu)),
|
||||
m_global_descriptor_set_layout(create_global_descriptor_set_layout(&m_gpu)),
|
||||
m_global_descriptor_set(create_global_descriptor_set(&m_gpu, m_global_descriptor_set_layout, m_camera_buffer)),
|
||||
|
||||
m_world(world),
|
||||
m_mesh_buffer(&m_gpu),
|
||||
|
||||
m_rendergraph_builder(&m_gpu, ImageChain::from_swapchain(m_swapchain), "swapchain"),
|
||||
m_rendergraph(init_render_graph()),
|
||||
m_camera(720.0f / 480.0f)
|
||||
{
|
||||
}
|
||||
|
||||
void WorldRenderer::update_camera_buffer() {
|
||||
void *pData = nullptr;
|
||||
m_gpu.memory()->map(m_camera_buffer.allocation, &pData);
|
||||
|
||||
memcpy(pData, m_camera.data(), sizeof(CameraData));
|
||||
|
||||
m_gpu.memory()->unmap(m_camera_buffer.allocation);
|
||||
}
|
||||
|
||||
void WorldRenderer::render() {
|
||||
if(!m_window->has_size(m_last_window_size)) {
|
||||
vkDeviceWaitIdle(m_gpu.dev());
|
||||
m_swapchain.resize(m_window->get_size());
|
||||
m_rendergraph_builder.set_image_chain(ImageChain::from_swapchain(m_swapchain));
|
||||
m_rendergraph = m_rendergraph_builder.build();
|
||||
m_last_window_size = m_window->get_size();
|
||||
}
|
||||
|
||||
uint32_t imageIdx = 0;
|
||||
auto result = m_swapchain.get_next_image_idx(
|
||||
VK_NULL_HANDLE, m_wait_on_image_fence, &imageIdx);
|
||||
|
||||
|
||||
if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
|
||||
std::println("Rebuilding");
|
||||
m_rendergraph_builder.build();
|
||||
}
|
||||
|
||||
update_camera_buffer();
|
||||
|
||||
m_rendergraph.run(imageIdx, VK_NULL_HANDLE, m_wait_on_image_fence);
|
||||
|
||||
m_swapchain.present({ m_rendergraph.buffer(0).final_signal(imageIdx) }, imageIdx);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Gpu.hpp"
|
||||
#include "Window.hpp"
|
||||
#include "Swapchain.hpp"
|
||||
#include "draw/MeshBuffer.hpp"
|
||||
#include "io/Files.hpp"
|
||||
#include "io/ShaderManager.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "world/Camera.hpp"
|
||||
|
||||
#include "RenderGraphBuilder.hpp"
|
||||
|
||||
namespace tw::drw {
|
||||
|
||||
class WorldRenderer {
|
||||
private:
|
||||
const lft::win::Window* m_window;
|
||||
VkExtent2D m_last_window_size;
|
||||
Instance m_instance;
|
||||
Surface m_surface;
|
||||
Gpu m_gpu;
|
||||
Swapchain m_swapchain;
|
||||
|
||||
const io::Files* m_files;
|
||||
io::ShaderManager m_shader_manager;
|
||||
|
||||
VkFence m_wait_on_image_fence;
|
||||
Buffer m_camera_buffer;
|
||||
VkDescriptorSetLayout m_global_descriptor_set_layout;
|
||||
VkDescriptorSet m_global_descriptor_set;
|
||||
|
||||
const World *m_world;
|
||||
|
||||
MeshBuffer m_mesh_buffer;
|
||||
|
||||
lft::rg::Builder m_rendergraph_builder;
|
||||
lft::rg::RenderGraph m_rendergraph;
|
||||
|
||||
Camera m_camera;
|
||||
|
||||
lft::rg::RenderGraph init_render_graph();
|
||||
|
||||
void update_camera_buffer();
|
||||
|
||||
public:
|
||||
GET_MUT_REF(m_camera, camera);
|
||||
|
||||
MeshBuffer* mesh_buffer() { return &m_mesh_buffer; }
|
||||
const MeshBuffer* mesh_buffer() const { return &m_mesh_buffer; }
|
||||
|
||||
const World* world() const { return m_world; }
|
||||
|
||||
const VkDescriptorSetLayout global_descriptor_set_layout() const {
|
||||
return m_global_descriptor_set_layout;
|
||||
}
|
||||
|
||||
const VkDescriptorSet global_descriptor_set() const {
|
||||
return m_global_descriptor_set;
|
||||
}
|
||||
|
||||
WorldRenderer(const std::string& name,
|
||||
const lft::win::Window* window,
|
||||
const World* world,
|
||||
const io::Files* files);
|
||||
|
||||
WorldRenderer(WorldRenderer&) = delete;
|
||||
|
||||
WorldRenderer& operator=(WorldRenderer&) = delete;
|
||||
|
||||
Mesh add_mesh(const MeshData& mesh_data) {
|
||||
return m_mesh_buffer.push_mesh(mesh_data);
|
||||
}
|
||||
|
||||
void render();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace tw::drw {
|
||||
|
||||
class WorldRendererGraph {
|
||||
public:
|
||||
WorldRendererGraph(const WorldRendererGraph* graph);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "ShaderManager.hpp"
|
||||
|
||||
namespace tw::io {
|
||||
|
||||
ShaderManager::ShaderManager(const Gpu* gpu, const Files* files) :
|
||||
m_files(files),
|
||||
m_shader_builder(gpu)
|
||||
{
|
||||
}
|
||||
|
||||
Shader ShaderManager::load_shader(const std::string& name) {
|
||||
return m_shader_builder.from_binary(m_files->read_file_binary(path("shaders") / name));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <shaders/Shader.hpp>
|
||||
#include <shaders/SpirvShaderBuilder.hpp>
|
||||
|
||||
#include "io/Files.hpp"
|
||||
|
||||
namespace tw::io {
|
||||
|
||||
/**
|
||||
* Unifies and simplifies shader loading
|
||||
*/
|
||||
class ShaderManager {
|
||||
const Files *m_files;
|
||||
const SpirvShaderBuilder m_shader_builder;
|
||||
|
||||
public:
|
||||
ShaderManager(const Gpu* gpu, const Files *files);
|
||||
|
||||
Shader load_shader(const std::string& name);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
/**
|
||||
* Writes entity derivation message
|
||||
*/
|
||||
template<typename TMesg>
|
||||
class EntityDerivationWriter {
|
||||
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "InterpolatedProperty.hpp"
|
||||
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
typedef InterpolatedProperty<glm::vec3, 60> EntityPositionInterpolation;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "EntityPositionInterpolator.hpp"
|
||||
|
||||
#include "world/Transform.hpp"
|
||||
#include "InterpolatedProperty.hpp"
|
||||
#include "EntityInterpolation.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
EntityPositionInterpolator::EntityPositionInterpolator(
|
||||
entt::registry* registry, entt::entity player_entity, size_t bufferingIntervalInMillis
|
||||
) : m_registry(registry),
|
||||
m_player_entity(player_entity),
|
||||
m_bufferingIntervalInMillis(bufferingIntervalInMillis)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void EntityPositionInterpolator::register_entity(entt::entity entity) {
|
||||
m_registry->emplace<EntityPositionInterpolation>(entity, glm::vec3());
|
||||
}
|
||||
|
||||
void EntityPositionInterpolator::add_position_for_entity(entt::entity entity, glm::vec3 position) {
|
||||
auto* interpolation = m_registry->try_get<EntityPositionInterpolation>(entity);
|
||||
if(interpolation == nullptr) {
|
||||
spdlog::warn("Attempt to add position for non-registered entity {}", (uint32_t)entity);
|
||||
return;
|
||||
}
|
||||
|
||||
interpolation->push(Clock::now(), position);
|
||||
}
|
||||
|
||||
glm::vec3 EntityPositionInterpolator::get_position_for_entity(entt::entity entity) {
|
||||
auto* interpolation = m_registry->try_get<EntityPositionInterpolation>(entity);
|
||||
if(interpolation == nullptr) {
|
||||
spdlog::warn("Attempt to get position for non-registered entity {}", (uint32_t)entity);
|
||||
return glm::vec3();
|
||||
}
|
||||
|
||||
auto now = Clock::now() - std::chrono::milliseconds(m_bufferingIntervalInMillis);
|
||||
|
||||
auto [from, to, value] = interpolation->get_values_around(now);
|
||||
return glm::mix(from, to, value);
|
||||
}
|
||||
|
||||
void EntityPositionInterpolator::update() {
|
||||
m_registry->view<EntityPositionInterpolation, Transform>()
|
||||
.each([&](const auto entity, const EntityPositionInterpolation& interpolation, Transform& ts) {
|
||||
auto now = Clock::now() - std::chrono::milliseconds(m_bufferingIntervalInMillis);
|
||||
|
||||
auto [from, to, value] = interpolation.get_values_around(now);
|
||||
ts.set_position(glm::mix(from, to, value));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <entt/entt.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
|
||||
class EntityPositionInterpolator {
|
||||
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
entt::registry* m_registry;
|
||||
|
||||
entt::entity m_player_entity;
|
||||
|
||||
size_t m_bufferingIntervalInMillis;
|
||||
|
||||
public:
|
||||
EntityPositionInterpolator(
|
||||
entt::registry* registry,
|
||||
entt::entity player_entity,
|
||||
size_t bufferingIntervalInMillis);
|
||||
|
||||
void register_entity(entt::entity entity);
|
||||
|
||||
void add_position_for_entity(entt::entity entity, glm::vec3 position);
|
||||
|
||||
glm::vec3 get_position_for_entity(entt::entity entity);
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
template<typename T, int length = 3>
|
||||
class InterpolatedProperty {
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
std::array<T, length> m_buffer;
|
||||
std::array<Clock::time_point, length> m_time_buffer;
|
||||
|
||||
public:
|
||||
InterpolatedProperty(T initial_value) {
|
||||
for(int i = 0; i < length; i++) {
|
||||
m_buffer[i] = initial_value;
|
||||
m_time_buffer[i] = Clock::now();
|
||||
}
|
||||
}
|
||||
|
||||
GET_REF(m_buffer, values);
|
||||
GET_REF(m_time_buffer, times);
|
||||
|
||||
void push(Clock::time_point point, T value) {
|
||||
if(point < m_time_buffer.at(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
for(i = 1; i < length; i++) {
|
||||
if(m_time_buffer.at(i) > point) {
|
||||
m_time_buffer[i - 1] = point;
|
||||
m_buffer[i - 1] = value;
|
||||
return;
|
||||
} else {
|
||||
m_time_buffer[i - 1] = m_time_buffer[i];
|
||||
m_buffer[i - 1] = m_buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
m_time_buffer[i - 1] = point;
|
||||
m_buffer[i - 1] = value;
|
||||
return;
|
||||
|
||||
// for(int i = 0; i < length; i++) {
|
||||
// if(m_time_buffer.at(i) > point) {
|
||||
// for(int o = 1; o < i; o++) {
|
||||
// m_time_buffer[o - 1] = m_time_buffer[o];
|
||||
// m_buffer[o - 1] = m_buffer[o];
|
||||
// }
|
||||
//
|
||||
// m_time_buffer[i] = point;
|
||||
// m_buffer[i] = value;
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// m_time_buffer[length] = point;
|
||||
// m_buffer[length] = value;
|
||||
|
||||
}
|
||||
|
||||
inline std::tuple<T, T, float> get_values_around(Clock::time_point point) const {
|
||||
T prev = m_buffer.at(0);
|
||||
Clock::time_point prev_point = m_time_buffer.at(0);
|
||||
|
||||
for(int i = 0; i < length; i++) {
|
||||
if(m_time_buffer.at(i) > point) {
|
||||
float value;
|
||||
if(i == 0) {
|
||||
value = 1.0f;
|
||||
} else {
|
||||
value = (float)(point - prev_point).count() / (m_time_buffer[i] - prev_point).count();
|
||||
}
|
||||
|
||||
return std::make_tuple(prev, m_buffer.at(i), value);
|
||||
}
|
||||
|
||||
prev = m_buffer[i];
|
||||
prev_point = m_time_buffer[i];
|
||||
}
|
||||
|
||||
return std::make_tuple(m_buffer[m_buffer.size() - 1], m_buffer[m_buffer.size() - 1], 1.0f);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "runtime.hpp"
|
||||
|
||||
#include "Address.hpp"
|
||||
#include "SDLWindow.h"
|
||||
#include "debug/tools/NetworkStatsGui.hpp"
|
||||
#include "debug/tools/PacketBacklogGui.hpp"
|
||||
#include "entt/entity/fwd.hpp"
|
||||
#include "io/InputState.hpp"
|
||||
#include "debug/tools/EntityManagerGui.hpp"
|
||||
#include "debug/tools/PerformanceStatsGui.hpp"
|
||||
#include "draw/MeshData.hpp"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
#include "imgui_impl_sdl2.h"
|
||||
|
||||
#include "implot.h"
|
||||
#include "implot_internal.h"
|
||||
#include "world/Transform.hpp"
|
||||
|
||||
#include <SDL_events.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace tw {
|
||||
|
||||
std::unique_ptr<lft::win::Window> create_window(const std::string& name, VkExtent2D extent) {
|
||||
return std::make_unique<lft::win::SDLWindow>(name, (VkRect2D){
|
||||
0, 0,
|
||||
extent.width, extent.height
|
||||
});
|
||||
}
|
||||
|
||||
int get_port_from_args(int argc, char** argv) {
|
||||
try {
|
||||
if(argc > 1) {
|
||||
return atoi(argv[1]);
|
||||
} else {
|
||||
return 8080;
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
std::println("Could not parse port from arguments, using default.");
|
||||
return 8080;
|
||||
}
|
||||
}
|
||||
|
||||
Runtime::Runtime(int argc, char** argv) :
|
||||
m_files(argv, argc),
|
||||
m_window(create_window("towards", {1920, 1200})),
|
||||
m_world{},
|
||||
m_physics_world(&m_world),
|
||||
m_world_renderer("towards", m_window.get(), &m_world, &m_files),
|
||||
m_input_manager(m_window.get()),
|
||||
m_world_controller(&m_input_manager, &m_world, &m_physics_world, &m_world_renderer, { "127.0.0.1", get_port_from_args(argc, argv) }),
|
||||
m_lockstep(60)
|
||||
{
|
||||
}
|
||||
|
||||
// bool Runtime::world_state_packet_handler(uint32_t* p_frame_idx, WorldSnapshotMessage* mesg) {
|
||||
// uint32_t frame_idx = *p_frame_idx;
|
||||
|
||||
// if(mesg->frame_idx < frame_idx) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
// for(int i = 0; i < mesg->player_states.size(); i++) {
|
||||
// if(!m_players.contains(mesg->player_states[i].id)) {
|
||||
// auto mesh = m_world_renderer.add_mesh(drw::MeshData::cube(glm::vec3(1.0f)));
|
||||
// const auto entity = m_world.registry().create();
|
||||
// m_players.insert({mesg->player_states[i].id, entity});
|
||||
|
||||
// m_world.registry().emplace<Transform>(entity, Transform(mesg->player_states[i].position));
|
||||
// m_world.registry().emplace<PlayerInfoComponent>(entity,
|
||||
// PlayerInfoComponent(
|
||||
// mesg->player_states[i].id,
|
||||
// mesg->player_states[i].name));
|
||||
// m_world.registry().emplace<drw::Mesh>(entity, mesh);
|
||||
|
||||
// } else {
|
||||
// auto entity = (entt::entity)mesg->player_states[i].id;
|
||||
// auto entity_ts = m_world.registry()
|
||||
// .try_get<Transform>(entity);
|
||||
|
||||
// if(entity_ts) {
|
||||
// entity_ts->transform = glm::translate(glm::mat4(1.0f), mesg->player_states[i].position);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
void Runtime::run() {
|
||||
dbg::tools::EntityManagerGui entity_manager(&m_world);
|
||||
dbg::tools::PerformanceStatsGui perf_stats(m_lockstep);
|
||||
dbg::tools::PacketBacklogGui packet_backlog;
|
||||
dbg::tools::NetworkStatsGui network_stats;
|
||||
|
||||
ImPlot::CreateContext();
|
||||
m_is_running = true;
|
||||
|
||||
while(is_running()) {
|
||||
if(m_lockstep.wait_for_next_step()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// ImGui::DockSpaceOverViewport();
|
||||
|
||||
entity_manager.draw();
|
||||
|
||||
ImGui::Begin("History");
|
||||
|
||||
if(ImGui::BeginTable("historyTable", 2)) {
|
||||
// for(auto key : m_world_controller.position_history().keys()) {
|
||||
// ImGui::TableNextRow();
|
||||
//
|
||||
// // auto v = *m_world_controller.player_history().get(key).value();
|
||||
// // auto input = std::format("{} {} {}", v.x, v.y, v.z);
|
||||
// auto p = *m_world_controller.position_history().get(key).value();
|
||||
// auto position = std::format("{} {} {}", p.x, p.y, p.z);
|
||||
//
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Text("%u", key);
|
||||
// // ImGui::TableNextColumn();
|
||||
// // ImGui::Text(input.c_str());
|
||||
// ImGui::TableNextColumn();
|
||||
// ImGui::Text(position.c_str());
|
||||
// }
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
bool change_imgui = false;
|
||||
|
||||
m_input_manager.update();
|
||||
if(m_input_manager.is_quit()) {
|
||||
m_is_running = false;
|
||||
}
|
||||
m_world_controller.update(m_lockstep.delta_time());
|
||||
|
||||
perf_stats.draw();
|
||||
packet_backlog.draw();
|
||||
network_stats.draw();
|
||||
tw::dbg::ComponentGui<tw::io::InputManager>().draw(&m_input_manager);
|
||||
|
||||
m_world.step(m_lockstep.delta_time());
|
||||
|
||||
m_world_renderer.render();
|
||||
FrameMark;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "draw/WorldRenderer.hpp"
|
||||
#include "io/InputState.hpp"
|
||||
#include "runtime/LockStep.hpp"
|
||||
#include "world/ClientWorldController.hpp"
|
||||
#include "world/JoltPhysicsWorld.hpp"
|
||||
#include "world/World.hpp"
|
||||
|
||||
#include "messages/PlayerMoveMessage.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
/**
|
||||
* Handles runtime of the client application.
|
||||
*/
|
||||
class Runtime {
|
||||
private:
|
||||
io::Files m_files;
|
||||
|
||||
std::unique_ptr<lft::win::Window> m_window;
|
||||
|
||||
tw::World m_world;
|
||||
|
||||
JoltPhysicsWorld m_physics_world;
|
||||
|
||||
tw::drw::WorldRenderer m_world_renderer;
|
||||
|
||||
tw::io::InputManager m_input_manager;
|
||||
|
||||
tw::ClientWorldController m_world_controller;
|
||||
|
||||
tw::LockStep m_lockstep;
|
||||
|
||||
bool m_is_running;
|
||||
|
||||
std::unordered_map<uint32_t, entt::entity> m_players;
|
||||
|
||||
void send_player_positions();
|
||||
|
||||
public:
|
||||
const bool is_running() const {
|
||||
return m_is_running;
|
||||
}
|
||||
|
||||
Runtime(int argc, char** argv);
|
||||
|
||||
void run();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "Camera.hpp"
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
|
||||
namespace tw {
|
||||
|
||||
Camera::Camera(float aspect_ratio) :
|
||||
m_data( glm::perspective(40.0f, aspect_ratio, 0.1f, 1000.0f),
|
||||
Transform(glm::vec3(10.0f, 10.0f, 10.0f))) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.hpp"
|
||||
#include "glm/ext/matrix_transform.hpp"
|
||||
#include <glm/glm.hpp>
|
||||
#include "world/Transform.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
struct CameraData {
|
||||
glm::mat4 projection;
|
||||
Transform view;
|
||||
|
||||
CameraData(glm::mat4 projection, Transform view) :
|
||||
projection(projection),
|
||||
view(view)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class Camera {
|
||||
CameraData m_data;
|
||||
|
||||
public:
|
||||
GET_MUT_REF(m_data.view, view);
|
||||
|
||||
glm::vec3 position() const {
|
||||
return m_data.view.position();
|
||||
}
|
||||
|
||||
void look_at(glm::vec3 from, glm::vec3 to) {
|
||||
m_data.view = glm::lookAt(from, to, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
}
|
||||
|
||||
/**
|
||||
* For uploading data to the GPU buffer
|
||||
*/
|
||||
inline const CameraData* data() const { return &m_data; }
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
Camera(float aspect_ratio);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
#include "ClientWorldController.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <chrono>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "Address.hpp"
|
||||
|
||||
#include "Entity.pb.h"
|
||||
#include "Login.pb.h"
|
||||
#include "WorldState.pb.h"
|
||||
#include "PlayerMove.pb.h"
|
||||
|
||||
#include "entt/entity/entity.hpp"
|
||||
#include "messenger/MessageHandler.hpp"
|
||||
#include "messenger/Messenger.hpp"
|
||||
#include "TcpStream.hpp"
|
||||
#include "messages/PlayerMoveMessage.hpp"
|
||||
#include "metrics/HistoryBuffer.hpp"
|
||||
#include "world/CharacterBody.hpp"
|
||||
#include "world/CharacterController.hpp"
|
||||
#include "world/JoltPhysicsWorld.hpp"
|
||||
#include "world/WorldEntity.hpp"
|
||||
#include "tw/serial/WorldStateWriter.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
typedef HistoryBuffer<long, glm::vec3> EntityPositionHistory;
|
||||
|
||||
entt::entity create_player_entity(World* world, JoltPhysicsWorld* physics_world, drw::WorldRenderer* renderer) {
|
||||
// entt::entity entity = world->registry().create();
|
||||
|
||||
// drw::Mesh mesh = renderer->add_mesh(drw::MeshData::cube(glm::vec3(1.0f)));
|
||||
|
||||
// world->registry()
|
||||
// .emplace<Transform>(entity,
|
||||
// Transform(glm::vec3(0.0f, 10.0f, 0.0f)));
|
||||
|
||||
// world->registry()
|
||||
// .emplace<EntityPositionHistory>(entity, 0, glm::vec3(0.0f, 10.0f, 0.0f), 1000);
|
||||
|
||||
// world->registry()
|
||||
// .emplace<tw::WorldEntity>(entity,
|
||||
// tw::WorldEntity(std::string("player"), (uint32_t)entity));
|
||||
|
||||
// world->registry()
|
||||
// .emplace<tw::drw::Mesh>(entity, mesh);
|
||||
|
||||
// world->registry()
|
||||
// .emplace<tw::CharacterController>(entity, 20.0f);
|
||||
// auto* body = &world->registry()
|
||||
// .emplace<CharacterBody>(entity, physics_world->create_character(
|
||||
// new JPH::BoxShape(JPH::Vec3Arg(0.5f, 0.5f, 0.5f)),
|
||||
// glm::vec3(0.0f, 10.0f, 0.0f)));
|
||||
|
||||
|
||||
// return entity;
|
||||
return entt::entity(0);
|
||||
}
|
||||
|
||||
|
||||
entt::entity
|
||||
ClientWorldController::create_entity(const std::string& name, glm::vec3 position) {
|
||||
const auto entity = m_world->registry().create();
|
||||
|
||||
if(!m_mesh.has_value()) {
|
||||
m_mesh = m_world_renderer->add_mesh(drw::MeshData::cube(glm::vec3(1.0f)));
|
||||
}
|
||||
spdlog::info("Creating entity {}", name);
|
||||
|
||||
m_world->registry()
|
||||
.emplace<Transform>(entity,
|
||||
Transform(position));
|
||||
|
||||
m_world->registry()
|
||||
.emplace<tw::WorldEntity>(entity,
|
||||
tw::WorldEntity(name, (uint32_t)entity));
|
||||
|
||||
m_world->registry()
|
||||
.emplace<tw::drw::Mesh>(entity,
|
||||
m_mesh.value());
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
net::TcpStream create_stream(tw::net::Address address) {
|
||||
auto stream = net::TcpStream::connect(address);
|
||||
if(!stream.has_value()) {
|
||||
spdlog::error("Failed to connect to server");
|
||||
throw std::runtime_error("Failed to connect to server");
|
||||
}
|
||||
|
||||
stream.value().set_non_blocking();
|
||||
|
||||
return std::move(stream.value());
|
||||
}
|
||||
|
||||
std::optional<entt::entity> ClientWorldController::map_from_server_entity(int id) {
|
||||
if(m_entity_mapping.contains(id)) {
|
||||
return m_entity_mapping[id];
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ClientWorldController::map_server_entity(int server_id, entt::entity local_id) {
|
||||
m_entity_mapping[server_id] = local_id;
|
||||
}
|
||||
|
||||
void ClientWorldController::apply_entity_positions(const mmo::EntityPosition* const* positions, size_t count) {
|
||||
for(int i = 0; i < count; i++) {
|
||||
const mmo::EntityPosition* const position = positions[i];
|
||||
std::optional<entt::entity> entity = map_from_server_entity(position->id());
|
||||
|
||||
if(!entity.has_value()) {
|
||||
spdlog::warn("Received position update for unknown entity {}", position->id());
|
||||
continue;
|
||||
}
|
||||
|
||||
glm::vec3 p = {position->x(), position->y(), position->z()};
|
||||
m_entity_interpolator.add_position_for_entity(entity.value(), p);
|
||||
|
||||
EntityPositionHistory* history = m_world->registry().try_get<EntityPositionHistory>(entity.value());
|
||||
|
||||
if(history != nullptr) {
|
||||
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now().time_since_epoch()).count();
|
||||
history->set(millis, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClientWorldController::ClientWorldController(
|
||||
const io::InputManager* inputs,
|
||||
World* world,
|
||||
JoltPhysicsWorld* physics_world,
|
||||
drw::WorldRenderer* world_renderer,
|
||||
tw::net::Address address
|
||||
) :
|
||||
m_input_manager(inputs),
|
||||
m_world(world),
|
||||
m_physics_world(physics_world),
|
||||
m_world_renderer(world_renderer),
|
||||
m_player_entity(create_player_entity(world, physics_world, world_renderer)),
|
||||
m_player_controller(&world_renderer->camera(), glm::vec3()),
|
||||
m_messenger{address},
|
||||
m_tick_step(20),
|
||||
m_is_connected(false),
|
||||
m_position_history_exporter("/home/martin/output.csv"),
|
||||
m_entity_interpolator(&m_world->registry(), m_player_entity, 300)
|
||||
{
|
||||
m_messenger->set_handler<mmo::LoginResponse>(
|
||||
[&](mmo::LoginResponse* mesg) {
|
||||
if(!m_is_connected) {
|
||||
spdlog::info("Joined the game!");
|
||||
}
|
||||
});
|
||||
|
||||
m_messenger->set_raw_handler(Message<mmo::WorldStateMessage>::value,
|
||||
[&](std::span<std::byte> data) -> tl::expected<void, net::NetworkError> {
|
||||
serial::WorldStateReader reader(data);
|
||||
|
||||
auto header = reader.read_header();
|
||||
|
||||
while(reader.has_spawn()) {
|
||||
auto spawn = reader.read_spawn();
|
||||
auto entity = create_entity("test", glm::vec3());
|
||||
spdlog::info("Spawning entity {}", spawn);
|
||||
|
||||
map_server_entity(spawn, entity);
|
||||
|
||||
m_entity_interpolator.register_entity(entity);
|
||||
}
|
||||
|
||||
while(reader.has_entity()) {
|
||||
auto entity_r = reader.read_entity();
|
||||
|
||||
std::optional<entt::entity> entity = map_from_server_entity(entity_r.id);
|
||||
|
||||
if(!entity.has_value()) {
|
||||
spdlog::warn("Received position update for unknown entity {}", entity_r.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
glm::vec3 p = {entity_r.position.x, entity_r.position.y, entity_r.position.z};
|
||||
m_entity_interpolator.add_position_for_entity(entity.value(), p);
|
||||
|
||||
EntityPositionHistory* history = m_world->registry().try_get<EntityPositionHistory>(entity.value());
|
||||
|
||||
if(history != nullptr) {
|
||||
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now().time_since_epoch()).count();
|
||||
history->set(millis, p);
|
||||
}
|
||||
}
|
||||
|
||||
// apply_entity_positions();
|
||||
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
m_messenger->set_handler<mmo::EntitySpawnMessage>(
|
||||
[&](mmo::EntitySpawnMessage* mesg) {
|
||||
auto entity = create_entity(mesg->name(), glm::vec3());
|
||||
|
||||
map_server_entity(mesg->entity_id(), entity);
|
||||
|
||||
m_entity_interpolator.register_entity(entity);
|
||||
});
|
||||
}
|
||||
|
||||
ClientWorldController::~ClientWorldController() {
|
||||
}
|
||||
|
||||
void ClientWorldController::export_entity_history() {
|
||||
}
|
||||
|
||||
void ClientWorldController::update(double delta_time) {
|
||||
m_player_controller.update(m_input_manager, delta_time);
|
||||
|
||||
ImGui::Begin("Player Controller");
|
||||
|
||||
ImGui::Text("Player entity ID: %d", (uint32_t)m_player_entity);
|
||||
ImGui::Text("Player count: %ld", m_entity_mapping.size());
|
||||
for(auto mapping : m_entity_mapping) {
|
||||
ImGui::Text("%d -> %d", (uint32_t)mapping.first, mapping.second);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if(m_tick_step.update()) {
|
||||
m_messenger->update();
|
||||
|
||||
if(!m_is_connected && false) {
|
||||
return;
|
||||
} else {
|
||||
|
||||
// CharacterController& character = m_world->registry().get<CharacterController>(m_player_entity);
|
||||
// character.set_input(m_frame_idx, m_player_controller.input());
|
||||
|
||||
mmo::PlayerMoveMessage player_move_message = {};
|
||||
|
||||
player_move_message.set_frame_idx(m_frame_idx);
|
||||
mmo::PlayerInput* player_input = new mmo::PlayerInput();
|
||||
player_input->set_x(m_player_controller.input().x);
|
||||
player_input->set_y(m_player_controller.input().y);
|
||||
player_input->set_z(m_player_controller.input().z);
|
||||
|
||||
player_move_message.set_allocated_input(player_input);
|
||||
auto r = m_messenger->send(player_move_message);
|
||||
|
||||
// CharacterBody& ts = m_world->registry().get<CharacterBody>(m_player_entity);
|
||||
// auto position = ts.m_character->GetPosition();
|
||||
// character.position_history().set(m_frame_idx, glm::vec3(position[0], position[1], position[2]));
|
||||
// EntityInterpolation& interpolation = m_world->registry().get<EntityInterpolation>(m_player_entity);
|
||||
// interpolation.push(std::chrono::steady_clock::now(), glm::vec3(position[0], position[1], position[2]));
|
||||
|
||||
// m_player_controller.set_target(glm::vec3(position[0], position[1], position[2]));
|
||||
//
|
||||
export_entity_history();
|
||||
|
||||
m_frame_idx++;
|
||||
|
||||
m_world->registry().view<Transform>()
|
||||
.each([&](const auto e, Transform& t) {
|
||||
m_position_history_exporter.write((uint32_t)e,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now().time_since_epoch()).count(), t.position());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
m_physics_world->step(m_frame_idx, delta_time);
|
||||
|
||||
m_entity_interpolator.update();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
#include <glm/gtx/io.hpp>
|
||||
|
||||
#include "Address.hpp"
|
||||
#include "TcpStream.hpp"
|
||||
#include "messenger/MessageHandler.hpp"
|
||||
#include "entt/entity/fwd.hpp"
|
||||
#include "io/InputState.hpp"
|
||||
#include "metrics/HistoryBufferExporter.hpp"
|
||||
#include "runtime/LockStep.hpp"
|
||||
#include "world/JoltPhysicsWorld.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "draw/WorldRenderer.hpp"
|
||||
#include "world/ThirdPersonPlayerController.hpp"
|
||||
#include "network/EntityPositionInterpolator.hpp"
|
||||
|
||||
|
||||
namespace tw {
|
||||
|
||||
/**
|
||||
* Multiplayer player controller. Requires address of a server to work.
|
||||
*/
|
||||
class ClientWorldController {
|
||||
/**
|
||||
* Inputs
|
||||
*/
|
||||
const io::InputManager* m_input_manager;
|
||||
|
||||
/**
|
||||
* Pointers to the world
|
||||
*/
|
||||
World* m_world;
|
||||
drw::WorldRenderer* m_world_renderer;
|
||||
JoltPhysicsWorld* m_physics_world;
|
||||
|
||||
entt::entity m_player_entity;
|
||||
ThirdPersonPlayerController m_player_controller;
|
||||
|
||||
std::optional<tw::net::MessageHandler> m_messenger;
|
||||
|
||||
LockStep m_tick_step;
|
||||
|
||||
uint32_t m_frame_idx = 1;
|
||||
entt::entity m_entity_id;
|
||||
|
||||
glm::vec3 m_input;
|
||||
|
||||
bool m_is_connected;
|
||||
|
||||
std::optional<drw::Mesh> m_mesh;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
HistoryBufferExporter<long, glm::vec3> m_position_history_exporter;
|
||||
|
||||
net::EntityPositionInterpolator m_entity_interpolator;
|
||||
|
||||
/**
|
||||
* Mapping from the server entity_id to local entity_id
|
||||
* Server might have the same entity under different name
|
||||
* TODO: Figure out if entt supports custUntitledom IDs to sync them
|
||||
*/
|
||||
std::unordered_map<uint32_t, entt::entity> m_entity_mapping;
|
||||
|
||||
entt::entity create_entity(const std::string& name, glm::vec3 position);
|
||||
|
||||
net::MessageHandler create_messenger();
|
||||
|
||||
std::optional<entt::entity> map_from_server_entity(int id);
|
||||
|
||||
void map_server_entity(int server_id, entt::entity local_id);
|
||||
|
||||
void apply_entity_positions(const mmo::EntityPosition* const* positions, size_t count);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Exports entity history to CSV file for analysis
|
||||
*/
|
||||
void export_entity_history();
|
||||
|
||||
public:
|
||||
ClientWorldController(
|
||||
const io::InputManager* inputs,
|
||||
World* world,
|
||||
JoltPhysicsWorld* physics_world,
|
||||
drw::WorldRenderer* world_renderer,
|
||||
tw::net::Address address
|
||||
);
|
||||
|
||||
~ClientWorldController();
|
||||
|
||||
void update(double delta_time);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace tw {
|
||||
|
||||
class EntityFactory {
|
||||
public:
|
||||
entt::entity create_player_entity(glm::vec3 position);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "LocalWorldController.hpp"
|
||||
#include "world/JoltPhysicsWorld.hpp"
|
||||
#include "world/WorldEntity.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
CharacterBody* create_player_body(World* world, JoltPhysicsWorld* physics_world, drw::WorldRenderer* renderer) {
|
||||
const auto entity = world->registry().create();
|
||||
|
||||
drw::Mesh mesh = renderer->add_mesh(drw::MeshData::cube(glm::vec3(1.0f)));
|
||||
|
||||
world->registry()
|
||||
.emplace<Transform>(entity,
|
||||
Transform(glm::vec3(0.0f, 10.0f, 0.0f)));
|
||||
|
||||
world->registry()
|
||||
.emplace<tw::WorldEntity>(entity,
|
||||
tw::WorldEntity(std::string("player"), (uint32_t)entity));
|
||||
|
||||
world->registry()
|
||||
.emplace<tw::drw::Mesh>(entity,
|
||||
mesh);
|
||||
|
||||
auto* body = &world->registry()
|
||||
.emplace<CharacterBody>(entity, physics_world->create_character(
|
||||
new JPH::BoxShape(JPH::Vec3Arg(0.5f, 0.5f, 0.5f)),
|
||||
glm::vec3(0.0f, 10.0f, 0.0f)));
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
LocalWorldController::LocalWorldController(
|
||||
io::InputManager* inputs,
|
||||
World* world,
|
||||
JoltPhysicsWorld* physics_world,
|
||||
drw::WorldRenderer* world_renderer) :
|
||||
m_input_manager(inputs),
|
||||
m_world(world),
|
||||
m_physics_world(physics_world),
|
||||
m_world_renderer(world_renderer),
|
||||
m_player_body(create_player_body(world, physics_world, world_renderer)),
|
||||
m_player_controller(&world_renderer->camera(), glm::vec3())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LocalWorldController::update(double delta_time) {
|
||||
m_player_controller.update(m_input_manager, delta_time);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "draw/WorldRenderer.hpp"
|
||||
#include "io/InputState.hpp"
|
||||
#include "world/CharacterBody.hpp"
|
||||
#include "world/JoltPhysicsWorld.hpp"
|
||||
#include "world/ThirdPersonPlayerController.hpp"
|
||||
#include "world/World.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
class LocalWorldController {
|
||||
private:
|
||||
io::InputManager* m_input_manager;
|
||||
|
||||
World* m_world;
|
||||
JoltPhysicsWorld* m_physics_world;
|
||||
drw::WorldRenderer* m_world_renderer;
|
||||
|
||||
CharacterBody* m_player_body;
|
||||
ThirdPersonPlayerController m_player_controller;
|
||||
|
||||
public:
|
||||
LocalWorldController(
|
||||
io::InputManager* inputs,
|
||||
World* world,
|
||||
JoltPhysicsWorld* physics_world,
|
||||
drw::WorldRenderer* world_renderer);
|
||||
|
||||
void update(double delta_time);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "ThirdPersonPlayerController.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
namespace tw {
|
||||
|
||||
|
||||
ThirdPersonPlayerController::ThirdPersonPlayerController(Camera* camera, glm::vec3 target) :
|
||||
m_camera(camera),
|
||||
m_camera_rotation(1.0f, 0.0f, 0.0f, 0.0f),
|
||||
m_camera_zoom(10.0f),
|
||||
m_sensitivity(0.01f),
|
||||
m_target(target),
|
||||
m_input(0.0f, 0.0f, 0.0f)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
glm::vec3 ThirdPersonPlayerController::get_axis() {
|
||||
glm::vec3 forward = m_camera_rotation * glm::vec3(0, 0, -1);
|
||||
forward.y = 0.0f;
|
||||
return glm::normalize(forward);
|
||||
}
|
||||
void ThirdPersonPlayerController::update(const tw::io::InputManager* input, double delta_time) {
|
||||
// glm::vec3 velocity = {
|
||||
// input->axis_x(),
|
||||
// 0.0f,
|
||||
// input->axis_y()
|
||||
// };
|
||||
|
||||
// velocity *= m_player_body->m_speed * delta_time;
|
||||
glm::vec3 forward = m_camera_rotation * glm::vec3(0, 0, -1);
|
||||
forward.y = 0.0f;
|
||||
forward = glm::normalize(forward);
|
||||
|
||||
glm::vec3 right = glm::cross(forward, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
|
||||
glm::vec3 velocity = forward * input->axis_y() + right * -input->axis_x();
|
||||
|
||||
if(velocity.x * velocity.x + velocity.y * velocity.y + velocity.z * velocity.z > 0.0f) {
|
||||
m_input = glm::normalize(velocity);
|
||||
} else {
|
||||
m_input = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
glm::quat yaw = glm::angleAxis(-input->motion_x() * m_sensitivity, glm::vec3(0, 1, 0));
|
||||
glm::quat pitch = glm::angleAxis(-input->motion_y() * m_sensitivity, glm::vec3(1, 0, 0));
|
||||
|
||||
m_camera_rotation = yaw * m_camera_rotation * pitch;
|
||||
|
||||
glm::vec3 offset = m_camera_rotation * glm::vec3(0, 0, m_camera_zoom);
|
||||
m_camera->view().look_at(get_target_position() + offset, get_target_position());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include "world/Camera.hpp"
|
||||
#include "io/InputState.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
/**
|
||||
* Player controller from third person
|
||||
*/
|
||||
class ThirdPersonPlayerController {
|
||||
Camera* m_camera;
|
||||
glm::vec3 m_input;
|
||||
|
||||
glm::quat m_camera_rotation;
|
||||
float m_camera_zoom;
|
||||
float m_sensitivity;
|
||||
|
||||
glm::vec3 m_target;
|
||||
|
||||
glm::vec3 get_target_position() {
|
||||
return m_target;
|
||||
}
|
||||
|
||||
public:
|
||||
GET_REF(m_input, input);
|
||||
|
||||
void set_target(glm::vec3 target) {
|
||||
m_target = target;
|
||||
}
|
||||
|
||||
ThirdPersonPlayerController(Camera* camera, glm::vec3 target);
|
||||
|
||||
glm::vec3 get_axis();
|
||||
|
||||
void update(const tw::io::InputManager* input, double delta_time);
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user