initial
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#define GET_REF(attribute, name) \
|
||||
inline const typeof(attribute)& name() const { return attribute; }
|
||||
|
||||
#define GET_MUT_REF(attribute, name) \
|
||||
GET_REF(attribute, name) \
|
||||
inline typeof(attribute)& name() { return attribute; }
|
||||
|
||||
#define GET(attribute, name) \
|
||||
inline typeof(attribute) name() const { return attribute; }
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "Files.hpp"
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <print>
|
||||
|
||||
#if __linux__
|
||||
#include <linux/limits.h>
|
||||
#include <unistd.h>
|
||||
#include <err.h>
|
||||
#elif _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
namespace tw::io {
|
||||
|
||||
#ifdef __linux__
|
||||
const char DIRECTORY_SEPARATOR = '/';
|
||||
#elif _WIN32
|
||||
const char DIRECTORY_SEPARATOR = '\\';
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
#include "IoException.hpp"
|
||||
|
||||
Files::Files(char** argv, int32_t argc) {
|
||||
char *pBuffer = nullptr;
|
||||
#ifdef __linux__
|
||||
pBuffer = (char*)malloc(PATH_MAX);
|
||||
size_t bytes = readlink("/proc/self/exe", pBuffer, PATH_MAX);
|
||||
pBuffer[bytes] = '\0';
|
||||
#elif _WIN32
|
||||
pBuffer = (char*)malloc(_MAX_PATH);
|
||||
int bytes = GetModuleFileName(NULL, pBuffer, _MAX_PATH);
|
||||
if(bytes >= 0)
|
||||
pBuffer[bytes] = '\0';
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
unsigned int lastSlash = 0;
|
||||
for(size_t i = bytes; i > 0; i--) {
|
||||
if(pBuffer[i] == DIRECTORY_SEPARATOR) {
|
||||
lastSlash = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pBuffer[lastSlash] = '\0';
|
||||
|
||||
m_exe_path = std::filesystem::path(std::string(pBuffer));
|
||||
}
|
||||
|
||||
std::filesystem::path Files::get_path_of(const path& name) const {
|
||||
return m_exe_path / name;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Files::read_file_binary(const path& name) const {
|
||||
auto path = get_path_of(name);
|
||||
|
||||
std::ifstream stream(path, std::ios::binary | std::ios::ate);
|
||||
if(stream.fail()) {
|
||||
throw tw::io::IoException(path, std::strerror(errno));
|
||||
}
|
||||
|
||||
size_t length = stream.tellg();
|
||||
std::vector<uint32_t> content(length);
|
||||
|
||||
stream.seekg(0);
|
||||
stream.read((char*)content.data(), length);
|
||||
|
||||
stream.close();
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
namespace tw::io {
|
||||
|
||||
using namespace std::filesystem;
|
||||
|
||||
/**
|
||||
* Manages access to a application specific directory for storing assets.
|
||||
*/
|
||||
class Files {
|
||||
path m_exe_path;
|
||||
|
||||
public:
|
||||
Files(char** argv, int32_t argc);
|
||||
|
||||
path get_path_of(const path& path) const;
|
||||
|
||||
std::vector<uint32_t> read_file_binary(const path& path) const;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL_keycode.h>
|
||||
#include <SDL_mouse.h>
|
||||
#include <imgui_impl_sdl2.h>
|
||||
|
||||
#include <Window.hpp>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "debug/ComponentGui.hpp"
|
||||
#include "imgui.h"
|
||||
|
||||
namespace tw::io {
|
||||
|
||||
class InputManager {
|
||||
private:
|
||||
lft::win::Window* m_window;
|
||||
|
||||
bool m_is_quit;
|
||||
|
||||
float m_velocity_x;
|
||||
float m_velocity_y;
|
||||
|
||||
bool m_is_jumping;
|
||||
|
||||
bool m_is_pressed[4];
|
||||
|
||||
float m_motion_x;
|
||||
float m_motion_y;
|
||||
|
||||
public:
|
||||
GET(m_is_quit, is_quit);
|
||||
|
||||
float axis_y() const {
|
||||
float result = 0.0f;
|
||||
if(m_is_pressed[0]) {
|
||||
result += 1.0f;
|
||||
} if(m_is_pressed[1]) {
|
||||
result -= 1.0f;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
float axis_x() const {
|
||||
float result = 0.0f;
|
||||
if(m_is_pressed[2]) {
|
||||
result += 1.0f;
|
||||
} if(m_is_pressed[3]) {
|
||||
result -= 1.0f;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GET(m_motion_x, motion_x);
|
||||
GET(m_motion_y, motion_y);
|
||||
|
||||
InputManager(lft::win::Window* window) :
|
||||
m_window(window),
|
||||
m_is_quit(false)
|
||||
{
|
||||
m_is_pressed[0] = m_is_pressed[1] = m_is_pressed[2] = m_is_pressed[3] = false;
|
||||
}
|
||||
|
||||
void update() {
|
||||
SDL_Event event;
|
||||
m_motion_x = 0.0f;
|
||||
m_motion_y = 0.0f;
|
||||
|
||||
while(m_window->poll_event(&event)) {
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
|
||||
switch(event.type) {
|
||||
case SDL_QUIT:
|
||||
m_is_quit = true;
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP: {
|
||||
switch(event.key.keysym.sym) {
|
||||
case SDLK_w:
|
||||
m_is_pressed[0] = event.key.state;
|
||||
break;
|
||||
case SDLK_s:
|
||||
m_is_pressed[1] = event.key.state;
|
||||
break;
|
||||
case SDLK_a:
|
||||
m_is_pressed[2] = event.key.state;
|
||||
break;
|
||||
case SDLK_d:
|
||||
m_is_pressed[3] = event.key.state;
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case SDL_MOUSEMOTION:
|
||||
m_motion_x = event.motion.xrel;
|
||||
m_motion_y = event.motion.yrel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class InputState {
|
||||
public:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace tw::dbg {
|
||||
|
||||
template<>
|
||||
class ComponentGui<tw::io::InputManager> {
|
||||
public:
|
||||
void draw(tw::io::InputManager* instance) {
|
||||
ImGui::SeparatorText("Character Body Component");
|
||||
|
||||
float motion[2] = {instance->motion_x(), instance->motion_y()};
|
||||
ImGui::InputFloat2("Motion", motion);
|
||||
|
||||
float axis[2] = {instance->axis_x(), instance->axis_y()};
|
||||
ImGui::InputFloat2("Axis", axis);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <format>
|
||||
|
||||
namespace tw::io {
|
||||
|
||||
class IoException : public std::exception {
|
||||
std::string m_filename;
|
||||
std::string m_message;
|
||||
|
||||
public:
|
||||
IoException(std::string filename, std::string what) :
|
||||
m_message(std::format("{}: {}", filename, what)),
|
||||
m_filename(filename)
|
||||
{ }
|
||||
|
||||
const char* what() const noexcept override { return m_message.c_str(); }
|
||||
|
||||
const std::string& get_filename() const { return m_filename; }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace tw {
|
||||
|
||||
class InputManager {
|
||||
public:
|
||||
void set_axis();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <print>
|
||||
|
||||
namespace tw {
|
||||
|
||||
/**
|
||||
* Controls thread sleep to run exactly `n` times per second
|
||||
*/
|
||||
class LockStep {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Millis = std::chrono::microseconds;
|
||||
const uint64_t IN_SECOND = std::chrono::duration_cast<Millis>(std::chrono::seconds(1)).count();
|
||||
|
||||
uint64_t milliseconds;
|
||||
|
||||
std::chrono::time_point<Clock> m_last_point;
|
||||
|
||||
uint64_t m_last_interval_ms;
|
||||
|
||||
bool m_popped;
|
||||
|
||||
public:
|
||||
LockStep(uint32_t steps_per_second) {
|
||||
m_last_point = Clock::now();
|
||||
set_steps_per_second(steps_per_second);
|
||||
}
|
||||
|
||||
inline uint64_t milliseconds_per_frame() const {
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
inline void set_steps_per_second(uint32_t steps_per_second) {
|
||||
milliseconds = IN_SECOND / steps_per_second;
|
||||
}
|
||||
|
||||
inline uint64_t wait_time_in_ms() {
|
||||
return m_last_interval_ms;
|
||||
}
|
||||
|
||||
inline uint64_t fps() {
|
||||
if(m_last_interval_ms == 0) {
|
||||
return IN_SECOND;
|
||||
}
|
||||
return IN_SECOND / m_last_interval_ms;
|
||||
}
|
||||
|
||||
inline double delta_time() const {
|
||||
return m_last_interval_ms / (double)IN_SECOND;
|
||||
}
|
||||
|
||||
bool update() {
|
||||
auto now = Clock::now();
|
||||
|
||||
m_last_interval_ms = duration_cast<Millis>(now - m_last_point).count();
|
||||
|
||||
if(m_last_interval_ms >= milliseconds) {
|
||||
m_last_point = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits if time interval from previous call is less than time between two steps
|
||||
*/
|
||||
bool wait_for_next_step() {
|
||||
auto now = Clock::now();
|
||||
|
||||
m_last_interval_ms = duration_cast<Millis>(now - m_last_point).count();
|
||||
|
||||
if(m_last_interval_ms < milliseconds) {
|
||||
std::this_thread::sleep_for(Millis(milliseconds - m_last_interval_ms));
|
||||
return true;
|
||||
}
|
||||
|
||||
m_last_point = now;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "metrics/HistoryBuffer.hpp"
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <Jolt/Jolt.h>
|
||||
#include <Jolt/Physics/Character/CharacterVirtual.h>
|
||||
|
||||
namespace tw {
|
||||
|
||||
class CharacterBody {
|
||||
public:
|
||||
JPH::CharacterVirtual* m_character;
|
||||
|
||||
JPH::Vec3 m_desired_velocity;
|
||||
|
||||
CharacterBody(JPH::CharacterVirtual* character) :
|
||||
m_character(character)
|
||||
{ }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "metrics/HistoryBuffer.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
/**
|
||||
* Controls the character's body
|
||||
*/
|
||||
class CharacterController {
|
||||
float m_speed;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
HistoryBuffer<Clock::time_point, glm::vec3> m_history;
|
||||
HistoryBuffer<Clock::time_point, glm::vec3> m_position_history;
|
||||
|
||||
glm::vec3 m_input;
|
||||
|
||||
uint32_t m_frame_idx;
|
||||
|
||||
public:
|
||||
GET(m_speed, speed);
|
||||
GET_MUT_REF(m_history, input_history);
|
||||
GET_MUT_REF(m_position_history, position_history);
|
||||
|
||||
CharacterController(float speed) :
|
||||
m_speed(speed),
|
||||
m_history(Clock::now(), glm::vec3(), 10 * 20),
|
||||
m_position_history(Clock::now(), glm::vec3(), 10 * 20),
|
||||
m_frame_idx(0)
|
||||
{ }
|
||||
|
||||
void set_input(uint32_t frame_idx, glm::vec3 input) {
|
||||
// m_history.set(frame_idx, input);
|
||||
m_input = input;
|
||||
}
|
||||
|
||||
void set_frame_idx(uint32_t idx) {
|
||||
m_frame_idx = idx;
|
||||
}
|
||||
|
||||
glm::vec3 input() const {
|
||||
return m_input;
|
||||
// return m_history.values()[m_history.values().size() - 1];
|
||||
}
|
||||
|
||||
glm::vec3 input(uint32_t frame_idx) const {
|
||||
return m_input;
|
||||
// auto value = m_history.get(frame_idx);
|
||||
// if(value.has_value()) {
|
||||
// return *value.value();
|
||||
// }
|
||||
|
||||
// return glm::vec3();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "CharacterSystem.hpp"
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
namespace tw {
|
||||
|
||||
void CharacterSystem::add_character(glm::vec3 initial_position) {
|
||||
glm::mat4 transform = glm::translate(glm::mat4(), initial_position);
|
||||
m_positions.push_back(transform);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace tw {
|
||||
|
||||
class CharacterSystem {
|
||||
private:
|
||||
std::vector<glm::mat4> m_positions;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Character type agnostic function
|
||||
*/
|
||||
void add_character(glm::vec3 initial_position);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace tw {
|
||||
|
||||
#define CHUNK_WIDTH 16
|
||||
|
||||
struct ChunkData {
|
||||
using Type = uint8_t;
|
||||
|
||||
std::vector<Type> data;
|
||||
|
||||
ChunkData() : data(CHUNK_WIDTH * CHUNK_WIDTH * CHUNK_WIDTH)
|
||||
{ }
|
||||
|
||||
Type get(glm::ivec3 position) {
|
||||
assert(position.x >= 0 && position.x < CHUNK_WIDTH);
|
||||
assert(position.y >= 0 && position.y < CHUNK_WIDTH);
|
||||
assert(position.z >= 0 && position.z < CHUNK_WIDTH);
|
||||
|
||||
return data[position.x + position.y * CHUNK_WIDTH + position.z * CHUNK_WIDTH * CHUNK_WIDTH];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <glm/glm.hpp>
|
||||
#include <optional>
|
||||
|
||||
#include "ChunkData.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
class ChunkManager {
|
||||
public:
|
||||
ChunkManager(uint32_t render_distance);
|
||||
|
||||
std::optional<ChunkData&> get_chunk(glm::ivec2 position);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "ChunkData.hpp"
|
||||
#include "draw/MeshData.hpp"
|
||||
|
||||
|
||||
|
||||
namespace tw {
|
||||
|
||||
constexpr std::vector<uint8_t> CHUNK_VERTICES() {
|
||||
|
||||
}
|
||||
|
||||
class ChunkMeshData {
|
||||
std::vector<glm::vec3> m_vertices;
|
||||
std::vector<glm::vec3> m_normal;
|
||||
|
||||
std::vector<uint16_t> m_indices;
|
||||
|
||||
public:
|
||||
ChunkMeshData(const ChunkData& chunk) {
|
||||
auto mesh_data = drw::MeshData::plane(glm::vec2(8.0f, 8.0f));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
#include "JoltPhysicsWorld.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <print>
|
||||
#include <cstring>
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
#include "Jolt/Core/TempAllocator.h"
|
||||
#include "Jolt/Physics/Character/Character.h"
|
||||
#include "Jolt/Core/Factory.h"
|
||||
#include "Jolt/Core/JobSystemThreadPool.h"
|
||||
#include "Jolt/Core/Memory.h"
|
||||
#include "Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h"
|
||||
#include "Jolt/Physics/EActivation.h"
|
||||
#include "Jolt/Physics/StateRecorderImpl.h"
|
||||
#include "Jolt/RegisterTypes.h"
|
||||
|
||||
#include "World.hpp"
|
||||
#include "world/CharacterBody.hpp"
|
||||
#include "world/CharacterController.hpp"
|
||||
#include "world/RigidBody.hpp"
|
||||
#include "world/Transform.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
bool JoltPhysicsWorld::initialization() {
|
||||
JPH::RegisterDefaultAllocator();
|
||||
|
||||
JPH::Factory::sInstance = new JPH::Factory();
|
||||
|
||||
JPH::RegisterTypes();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
JoltPhysicsWorld::JoltPhysicsWorld(World* world) :
|
||||
m_is_initialized(initialization()),
|
||||
temp_allocator(std::make_unique<JPH::TempAllocatorImpl>(10 * 1024 * 1024)),
|
||||
job_system(JPH::cMaxPhysicsJobs, JPH::cMaxPhysicsBarriers, std::thread::hardware_concurrency() - 1),
|
||||
m_world(world),
|
||||
m_history(0, std::move(std::make_unique<JPH::StateRecorderImpl>()), 10),
|
||||
m_latest_frame(0),
|
||||
broad_phase_layer_interface(),
|
||||
object_vs_broadphase_layer_filter(),
|
||||
object_vs_object_layer_filter(),
|
||||
physics_system(),
|
||||
body_activation_listener(),
|
||||
contact_listener(),
|
||||
m_character_vs_character_collision()
|
||||
{
|
||||
// for(int i = 0; i < m_thread_pool.size(); i++) {
|
||||
// temp_allocator.emplace_back(std::make_unique<JPH::TempAllocatorImpl>(10 * 1024 * 1024));
|
||||
// }
|
||||
// for(auto& allocator : temp_allocator) {
|
||||
// // allocator = std::make_unique<JPH::TempAllocatorImpl>(10 * 1024 * 1024);
|
||||
// }
|
||||
|
||||
const uint32_t MAX_BODIES = 1024;
|
||||
const uint32_t BODY_MUTEXES = 0;
|
||||
const uint32_t MAX_BODY_PAIRS = 1024;
|
||||
const uint32_t MAX_CONTACT_CONSTRAINTS = 1024;
|
||||
|
||||
physics_system.Init(MAX_BODIES, BODY_MUTEXES, MAX_BODY_PAIRS, MAX_CONTACT_CONSTRAINTS,
|
||||
broad_phase_layer_interface,
|
||||
object_vs_broadphase_layer_filter,
|
||||
object_vs_object_layer_filter);
|
||||
|
||||
physics_system.SetBodyActivationListener(&body_activation_listener);
|
||||
|
||||
physics_system.SetContactListener(&contact_listener);
|
||||
|
||||
JPH::BoxShapeSettings floor_shape_settings(JPH::Vec3(100.0f, 1.0f, 100.0f));
|
||||
floor_shape_settings.SetEmbedded();
|
||||
|
||||
JPH::ShapeSettings::ShapeResult floor_shape_result = floor_shape_settings.Create();
|
||||
JPH::ShapeRefC floor_shape = floor_shape_result.Get();
|
||||
|
||||
JPH::BodyCreationSettings floor_settings(
|
||||
floor_shape, JPH::RVec3(0.0, 0., 0.0),
|
||||
JPH::Quat::sIdentity(),
|
||||
JPH::EMotionType::Static,
|
||||
Layers::NON_MOVING);
|
||||
|
||||
JPH::Body *floor = body_interface().CreateBody(floor_settings);
|
||||
body_interface().AddBody(floor->GetID(), JPH::EActivation::DontActivate);
|
||||
|
||||
|
||||
// Now you can interact with the dynamic body, in this case we're going to give it a velocity.
|
||||
// (note that if we had used CreateBody then we could have set the velocity straight on the body before adding it to the physics system)
|
||||
// body_interface().SetLinearVelocity(sphere_id, JPH::Vec3(0.0f, -5.0f, 0.0f));
|
||||
}
|
||||
|
||||
void JoltPhysicsWorld::update(uint32_t frame_idx, double delta_time) {
|
||||
auto view = m_world->registry().view<CharacterController, CharacterBody>();
|
||||
|
||||
view.each([&](entt::entity entity, const CharacterController& controller, CharacterBody& rb) {
|
||||
auto allocator = temp_allocator.get();
|
||||
|
||||
ZoneScoped;
|
||||
ZoneNameF("CharacterController update %d", (uint32_t)0);
|
||||
auto character = rb.m_character;
|
||||
|
||||
bool player_controls_horizontal_velocity = true || character->IsSupported();
|
||||
|
||||
bool sEnableCharacterInertia = true;
|
||||
bool mAllowSliding = true;
|
||||
|
||||
if (player_controls_horizontal_velocity) {
|
||||
glm::vec3 input = controller.input(frame_idx);
|
||||
// Smooth the player input
|
||||
JPH::Vec3 vel = JPH::Vec3(input.x, input.y, input.z) * controller.speed();
|
||||
float inertia_factor = 1.0f - std::exp(-delta_time * 10.0f); // 10.0 = responsiveness tuning knob
|
||||
rb.m_desired_velocity = sEnableCharacterInertia
|
||||
? rb.m_desired_velocity + inertia_factor * (vel - rb.m_desired_velocity)
|
||||
: vel;
|
||||
|
||||
// True if the player intended to move
|
||||
mAllowSliding = input.length() < 0.01f; // allow sliding when idle, prevent when moving
|
||||
}
|
||||
JPH::Vec3 current_vertical_velocity = character->GetLinearVelocity().Dot(character->GetUp()) * character->GetUp();
|
||||
JPH::Vec3 ground_velocity = character->GetGroundVelocity();
|
||||
JPH::Vec3 new_velocity;
|
||||
|
||||
bool moving_towards_ground = (current_vertical_velocity.GetY() - ground_velocity.GetY()) < 0.1f;
|
||||
if (character->GetGroundState() == JPH::CharacterVirtual::EGroundState::OnGround // If on ground
|
||||
&& (sEnableCharacterInertia ?
|
||||
moving_towards_ground // Inertia enabled: And not moving away from ground
|
||||
: !character->IsSlopeTooSteep(character->GetGroundNormal()))) // Inertia disabled: And not on a slope that is too steep
|
||||
{
|
||||
// Assume velocity of ground when on ground
|
||||
new_velocity = ground_velocity;
|
||||
|
||||
// Jump
|
||||
// if (inJump && moving_towards_ground)
|
||||
// new_velocity += sJumpSpeed * mCharacter->GetUp();
|
||||
}
|
||||
else {
|
||||
new_velocity = current_vertical_velocity;
|
||||
}
|
||||
|
||||
auto character_up_rotation = character->GetUp();
|
||||
// Gravity
|
||||
new_velocity += (physics_system.GetGravity()) * delta_time;
|
||||
new_velocity += rb.m_desired_velocity;
|
||||
|
||||
if (player_controls_horizontal_velocity)
|
||||
{
|
||||
// Player input
|
||||
}
|
||||
else
|
||||
{
|
||||
// Preserve horizontal velocity
|
||||
JPH::Vec3 current_horizontal_velocity = character->GetLinearVelocity() - current_vertical_velocity;
|
||||
new_velocity += current_horizontal_velocity;
|
||||
}
|
||||
|
||||
// Update character velocity
|
||||
character->SetLinearVelocity(new_velocity);
|
||||
|
||||
// auto velocity = character->GetLinearVelocity() + physics_system.GetGravity() * delta_time ;
|
||||
// character->SetLinearVelocity(velocity);
|
||||
JPH::CharacterVirtual::ExtendedUpdateSettings update_settings = {};
|
||||
update_settings.mStickToFloorStepDown = mAllowSliding
|
||||
? JPH::Vec3(0, -0.5f, 0) // snap to floor when idle
|
||||
: JPH::Vec3::sZero();
|
||||
update_settings.mWalkStairsStepUp = JPH::Vec3(0, 0.4f, 0); // re-enable stair stepping
|
||||
|
||||
{
|
||||
ZoneScopedN("Extended update");
|
||||
character->ExtendedUpdate(delta_time,
|
||||
-character->GetUp() * physics_system.GetGravity().Length(),
|
||||
update_settings,
|
||||
physics_system.GetDefaultBroadPhaseLayerFilter(Layers::MOVING),
|
||||
physics_system.GetDefaultLayerFilter(Layers::MOVING),
|
||||
{},
|
||||
{},
|
||||
*allocator);
|
||||
}
|
||||
});
|
||||
|
||||
physics_system.Update(delta_time, 1, temp_allocator.get(), &job_system);
|
||||
|
||||
auto stateRecorder = std::make_shared<JPH::StateRecorderImpl>();
|
||||
physics_system.SaveState(*stateRecorder.get());
|
||||
|
||||
m_history.set(frame_idx, stateRecorder);
|
||||
}
|
||||
|
||||
void JoltPhysicsWorld::step(uint32_t frame_idx, double delta_time) {
|
||||
update(frame_idx, delta_time);
|
||||
|
||||
m_world->registry().view<Transform, CharacterBody>()
|
||||
.each([&](Transform& ts, CharacterBody& rb) {
|
||||
auto character = rb.m_character;
|
||||
|
||||
auto transform = character->GetWorldTransform();
|
||||
memcpy(&ts.transform, &transform, sizeof(glm::mat4));
|
||||
});
|
||||
|
||||
// copy values
|
||||
// m_world->registry().view<Transform, RigidBody>()
|
||||
// .each([&](Transform& ts, RigidBody& rb) {
|
||||
// auto transform = body_interface().GetWorldTransform(rb.id);
|
||||
// memcpy(&ts.transform, &transform, sizeof(glm::mat4));
|
||||
// });
|
||||
//
|
||||
// m_world->registry().view<Transform, CharacterBody>()
|
||||
// .each([&](Transform& ts, CharacterBody& rb) {
|
||||
// auto character = rb.m_character;
|
||||
//
|
||||
// auto transform = character->GetWorldTransform();
|
||||
// memcpy(&ts.transform, &transform, sizeof(glm::mat4));
|
||||
// });
|
||||
|
||||
m_latest_frame = std::max(m_latest_frame, frame_idx);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
#pragma once
|
||||
|
||||
#include "world/RigidBody.hpp"
|
||||
#include <iostream>
|
||||
#include <print>
|
||||
#include "metrics/HistoryBuffer.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <Jolt/Jolt.h>
|
||||
|
||||
#include <Jolt/Physics/StateRecorderImpl.h>
|
||||
#include <Jolt/RegisterTypes.h>
|
||||
#include <Jolt/Core/Factory.h>
|
||||
#include <Jolt/Core/TempAllocator.h>
|
||||
#include <Jolt/Core/JobSystemThreadPool.h>
|
||||
#include <Jolt/Physics/PhysicsSettings.h>
|
||||
#include <Jolt/Physics/PhysicsSystem.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Jolt/Physics/Body/BodyActivationListener.h>
|
||||
#include <Jolt/Physics/Character/CharacterVirtual.h>
|
||||
|
||||
namespace tw {
|
||||
|
||||
namespace Layers
|
||||
{
|
||||
static constexpr JPH::ObjectLayer NON_MOVING = 0;
|
||||
static constexpr JPH::ObjectLayer MOVING = 1;
|
||||
static constexpr JPH::ObjectLayer NUM_LAYERS = 2;
|
||||
};
|
||||
|
||||
/// Class that determines if two object layers can collide
|
||||
class ObjectLayerPairFilterImpl : public JPH::ObjectLayerPairFilter
|
||||
{
|
||||
public:
|
||||
virtual bool ShouldCollide(JPH::ObjectLayer inObject1, JPH::ObjectLayer inObject2) const override
|
||||
{
|
||||
switch (inObject1)
|
||||
{
|
||||
case Layers::NON_MOVING:
|
||||
return true; // return inObject2 == Layers::MOVING; // Non moving only collides with moving
|
||||
case Layers::MOVING:
|
||||
return inObject2 == Layers::NON_MOVING; // Moving collides with everything
|
||||
default:
|
||||
JPH_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace BroadPhaseLayers
|
||||
{
|
||||
static constexpr JPH::BroadPhaseLayer NON_MOVING(0);
|
||||
static constexpr JPH::BroadPhaseLayer MOVING(1);
|
||||
static constexpr uint NUM_LAYERS(2);
|
||||
};
|
||||
|
||||
class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface
|
||||
{
|
||||
public:
|
||||
BPLayerInterfaceImpl()
|
||||
{
|
||||
// Create a mapping table from object to broad phase layer
|
||||
mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
|
||||
mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
|
||||
}
|
||||
|
||||
virtual uint GetNumBroadPhaseLayers() const override
|
||||
{
|
||||
return BroadPhaseLayers::NUM_LAYERS;
|
||||
}
|
||||
|
||||
virtual JPH::BroadPhaseLayer GetBroadPhaseLayer(JPH::ObjectLayer inLayer) const override
|
||||
{
|
||||
JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
|
||||
return mObjectToBroadPhase[inLayer];
|
||||
}
|
||||
|
||||
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
||||
virtual const char * GetBroadPhaseLayerName(JPH::BroadPhaseLayer inLayer) const override
|
||||
{
|
||||
switch ((JPH::BroadPhaseLayer::Type)inLayer)
|
||||
{
|
||||
case (JPH::BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING: return "NON_MOVING";
|
||||
case (JPH::BroadPhaseLayer::Type)BroadPhaseLayers::MOVING: return "MOVING";
|
||||
default: JPH_ASSERT(false); return "INVALID";
|
||||
}
|
||||
}
|
||||
#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
|
||||
|
||||
private:
|
||||
JPH::BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS];
|
||||
};
|
||||
|
||||
class ObjectVsBroadPhaseLayerFilterImpl : public JPH::ObjectVsBroadPhaseLayerFilter
|
||||
{
|
||||
public:
|
||||
virtual bool ShouldCollide(JPH::ObjectLayer inLayer1, JPH::BroadPhaseLayer inLayer2) const override
|
||||
{
|
||||
switch (inLayer1)
|
||||
{
|
||||
case Layers::NON_MOVING:
|
||||
return true;
|
||||
// return inLayer2 == BroadPhaseLayers::MOVING;
|
||||
case Layers::MOVING:
|
||||
return inLayer2 == BroadPhaseLayers::NON_MOVING;
|
||||
default:
|
||||
JPH_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MyContactListener : public JPH::ContactListener
|
||||
{
|
||||
public:
|
||||
// See: ContactListener
|
||||
virtual JPH::ValidateResult OnContactValidate(
|
||||
const JPH::Body &inBody1,
|
||||
const JPH::Body &inBody2,
|
||||
JPH::RVec3Arg inBaseOffset,
|
||||
const JPH::CollideShapeResult &inCollisionResult
|
||||
) override {
|
||||
std::cout << "Contact validate callback" << std::endl;
|
||||
|
||||
// Allows you to ignore a contact before it is created (using layers to not make objects collide is cheaper!)
|
||||
return JPH::ValidateResult::AcceptAllContactsForThisBodyPair;
|
||||
}
|
||||
|
||||
virtual void OnContactAdded(
|
||||
const JPH::Body &inBody1,
|
||||
const JPH::Body &inBody2,
|
||||
const JPH::ContactManifold &inManifold,
|
||||
JPH::ContactSettings &ioSettings
|
||||
) override
|
||||
{
|
||||
std::cout << "A contact was added" << std::endl;
|
||||
}
|
||||
|
||||
virtual void OnContactPersisted(
|
||||
const JPH::Body &inBody1,
|
||||
const JPH::Body &inBody2,
|
||||
const JPH::ContactManifold &inManifold,
|
||||
JPH::ContactSettings &ioSettings
|
||||
) override
|
||||
{
|
||||
std::cout << "A contact was persisted" << std::endl;
|
||||
}
|
||||
|
||||
virtual void OnContactRemoved(const JPH::SubShapeIDPair &inSubShapePair) override
|
||||
{
|
||||
std::cout << "A contact was removed" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
class MyBodyActivationListener : public JPH::BodyActivationListener
|
||||
{
|
||||
public:
|
||||
virtual void OnBodyActivated(const JPH::BodyID &inBodyID, uint64_t inBodyUserData) override
|
||||
{
|
||||
std::cout << "A body got activated" << std::endl;
|
||||
}
|
||||
|
||||
virtual void OnBodyDeactivated(const JPH::BodyID &inBodyID, uint64_t inBodyUserData) override
|
||||
{
|
||||
std::cout << "A body went to sleep" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
class World;
|
||||
|
||||
class JoltPhysicsWorld {
|
||||
private:
|
||||
static bool initialization();
|
||||
|
||||
bool m_is_initialized;
|
||||
|
||||
std::unique_ptr<JPH::TempAllocatorImpl> temp_allocator;
|
||||
JPH::JobSystemThreadPool job_system;
|
||||
|
||||
BPLayerInterfaceImpl broad_phase_layer_interface;
|
||||
ObjectVsBroadPhaseLayerFilterImpl object_vs_broadphase_layer_filter;
|
||||
ObjectLayerPairFilterImpl object_vs_object_layer_filter;
|
||||
|
||||
JPH::PhysicsSystem physics_system;
|
||||
MyBodyActivationListener body_activation_listener;
|
||||
MyContactListener contact_listener;
|
||||
|
||||
JPH::CharacterVsCharacterCollisionSimple m_character_vs_character_collision;
|
||||
|
||||
HistoryBuffer<int, std::shared_ptr<JPH::StateRecorderImpl>> m_history;
|
||||
|
||||
inline JPH::BodyInterface &body_interface() {
|
||||
return physics_system.GetBodyInterface();
|
||||
}
|
||||
|
||||
JPH::BodyID sphere_id;
|
||||
|
||||
World* m_world;
|
||||
|
||||
uint32_t m_latest_frame;
|
||||
|
||||
void update(uint32_t frame_idx, double delta_time);
|
||||
|
||||
public:
|
||||
JoltPhysicsWorld(World* world);
|
||||
|
||||
RigidBody create_dynamic_rigid_body(JPH::Shape* shape, glm::vec3 position) {
|
||||
JPH::BodyCreationSettings settings(
|
||||
shape,
|
||||
JPH::RVec3(position.x, position.y, position.z),
|
||||
JPH::Quat::sIdentity(),
|
||||
JPH::EMotionType::Dynamic,
|
||||
Layers::MOVING);
|
||||
|
||||
auto id = body_interface().CreateAndAddBody(settings, JPH::EActivation::Activate);
|
||||
|
||||
return {id};
|
||||
}
|
||||
|
||||
JPH::CharacterVirtual* create_character(JPH::Shape* shape, glm::vec3 position) {
|
||||
JPH::CharacterVirtualSettings settings;
|
||||
settings.mShape = shape;
|
||||
settings.mSupportingVolume = JPH::Plane(JPH::Vec3::sAxisY(), -0.5f);
|
||||
|
||||
auto* character = new JPH::CharacterVirtual(&settings,
|
||||
JPH::RVec3(position.x, position.y, position.z),
|
||||
JPH::Quat::sIdentity(),
|
||||
0, &physics_system);
|
||||
character->SetCharacterVsCharacterCollision(&m_character_vs_character_collision);
|
||||
// m_character_vs_character_collision.Add(character);
|
||||
|
||||
return character;
|
||||
}
|
||||
|
||||
void move_character(JPH::CharacterVirtual* character, glm::vec3 force) {
|
||||
character->SetLinearVelocity(JPH::Vec3(force.x, force.y, force.z));
|
||||
}
|
||||
|
||||
void add_force(RigidBody& rigidbody, glm::vec3 force) {
|
||||
body_interface().AddForce(rigidbody.id, JPH::RVec3(force.x, force.y, force.z));
|
||||
}
|
||||
|
||||
void step(uint32_t frame_idx, double delta_time);
|
||||
|
||||
void rollback(uint32_t frame) {
|
||||
if(frame == 0) return;
|
||||
auto snapshot = m_history.get(frame);
|
||||
|
||||
if(!snapshot.has_value()) {
|
||||
throw std::runtime_error("Failed to restore history");
|
||||
}
|
||||
|
||||
auto snapshot_value = snapshot.value()->get();
|
||||
physics_system.RestoreState(*snapshot_value);
|
||||
}
|
||||
|
||||
void apply_rollback(uint32_t frame) {
|
||||
for(uint32_t i = frame; i < m_latest_frame; i++) {
|
||||
update(i, 1000.0f / 20.0f);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <Jolt/Jolt.h>
|
||||
|
||||
#include <Jolt/Physics/Body/BodyID.h>
|
||||
|
||||
namespace tw {
|
||||
|
||||
class RigidBody {
|
||||
public:
|
||||
JPH::BodyID id;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
// #include "FastNoise/Generators/Fractal.h"
|
||||
// #include "FastNoise/SmartNode.h"
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <glm/glm.hpp>
|
||||
// #include <FastNoise/FastNoise.h>
|
||||
|
||||
namespace tw {
|
||||
|
||||
class Chunk {
|
||||
public:
|
||||
std::vector<uint8_t> m_voxels;
|
||||
};
|
||||
|
||||
class TerrainGenerator {
|
||||
uint32_t m_seed;
|
||||
|
||||
// FastNoise::SmartNode<FastNoise::Simplex> m_fn_simplex;
|
||||
// FastNoise::SmartNode<FastNoise::FractalFBm> m_fn_fractal;
|
||||
|
||||
public:
|
||||
TerrainGenerator(uint32_t seed) {
|
||||
m_seed = seed;
|
||||
|
||||
// m_fn_simplex = FastNoise::New<FastNoise::Simplex>();
|
||||
// m_fn_fractal = FastNoise::New<FastNoise::FractalFBm>();
|
||||
//
|
||||
// m_fn_fractal->SetSource(m_fn_simplex);
|
||||
// m_fn_fractal->SetOctaveCount(5);
|
||||
}
|
||||
|
||||
Chunk generate(glm::ivec3 offset) {
|
||||
// std::vector<float> noiseOutput(16 * 16);
|
||||
// m_fn_simplex->GenUniformGrid2D(noiseOutput.data(), 0, 0, 16, 16, 0.2f, m_seed);
|
||||
//
|
||||
// std::vector<uint8_t> chunk(16 * 16 * 16);
|
||||
//
|
||||
// for(uint32_t y = 0; y < 16; y++) {
|
||||
// for(uint32_t x = 0; x < 16; x++) {
|
||||
// for(uint32_t z = 0; z < noiseOutput[x + y * 16] * 8.0f; z++) {
|
||||
// chunk[x + y * 16 + z * 16 * 16] = 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return Chunk {chunk};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "glm/common.hpp"
|
||||
#include "glm/ext/matrix_transform.hpp"
|
||||
#include "glm/glm.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace tw {
|
||||
|
||||
struct Transform {
|
||||
glm::mat4 transform;
|
||||
|
||||
public:
|
||||
Transform(glm::vec3 translation) :
|
||||
transform(glm::translate(glm::mat4(1.0f), translation))
|
||||
{ }
|
||||
|
||||
Transform(glm::mat4 transform) :
|
||||
transform(transform)
|
||||
{ }
|
||||
|
||||
void set_position(glm::vec3 position) {
|
||||
transform = glm::translate(glm::mat4(1.0f), position);
|
||||
}
|
||||
|
||||
void translate(glm::vec3 translation) {
|
||||
transform = glm::translate(transform, translation);
|
||||
}
|
||||
|
||||
void look_at(glm::vec3 eye, glm::vec3 target) {
|
||||
transform = glm::lookAt(eye, target, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
}
|
||||
|
||||
inline constexpr glm::vec3 position() const {
|
||||
return glm::vec3(transform[3][0], transform[3][1], transform[3][2]) * transform[3][3];
|
||||
}
|
||||
|
||||
constexpr glm::vec3 right() const {
|
||||
return glm::vec3(transform[2][0], transform[2][1], transform[2][2]) * transform[2][3];
|
||||
}
|
||||
|
||||
constexpr bool is_closer_than(const Transform& other, float max_distance) const {
|
||||
auto from = position();
|
||||
auto to = other.position();
|
||||
|
||||
auto vector = to - from;
|
||||
|
||||
return vector.x * vector.x +
|
||||
vector.y * vector.y +
|
||||
vector.z * vector.z <
|
||||
max_distance * max_distance;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "World.hpp"
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
namespace tw {
|
||||
|
||||
World::World()
|
||||
{
|
||||
}
|
||||
|
||||
void World::step(double delta_time) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
#include "entt/entt.hpp"
|
||||
|
||||
namespace tw {
|
||||
|
||||
class World {
|
||||
protected:
|
||||
entt::registry m_registry;
|
||||
|
||||
public:
|
||||
GET_MUT_REF(m_registry, registry);
|
||||
|
||||
World();
|
||||
|
||||
World(const World&) = delete;
|
||||
World& operator=(const World&) = delete;
|
||||
|
||||
void step(double delta_time);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
namespace tw {
|
||||
|
||||
class WorldEntity {
|
||||
public:
|
||||
std::string name;
|
||||
uint32_t entity_id;
|
||||
|
||||
WorldEntity(std::string name, uint32_t entity_id) :
|
||||
name(name),
|
||||
entity_id(entity_id)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user