#1 - quicr module
This commit is contained in:
@@ -14,8 +14,12 @@ public:
|
||||
|
||||
JPH::Vec3 m_desired_velocity;
|
||||
|
||||
// JPH::Vec3's default constructor leaves the value uninitialised, and the
|
||||
// desired velocity is accumulated across steps, so it has to start at zero
|
||||
// or the first step feeds garbage into the character's velocity.
|
||||
CharacterBody(JPH::CharacterVirtual* character) :
|
||||
m_character(character)
|
||||
m_character(character),
|
||||
m_desired_velocity(JPH::Vec3::sZero())
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
@@ -11,6 +12,7 @@ namespace tw {
|
||||
* Controls the character's body
|
||||
*/
|
||||
class CharacterController {
|
||||
private:
|
||||
float m_speed;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
@@ -18,7 +20,16 @@ class CharacterController {
|
||||
HistoryBuffer<Clock::time_point, glm::vec3> m_history;
|
||||
HistoryBuffer<Clock::time_point, glm::vec3> m_position_history;
|
||||
|
||||
glm::vec3 m_input;
|
||||
// Frame-indexed input ring, capacity 64
|
||||
struct InputSlot {
|
||||
uint32_t frame;
|
||||
glm::vec3 input;
|
||||
bool valid;
|
||||
};
|
||||
std::array<InputSlot, 64> m_input_ring;
|
||||
|
||||
uint32_t m_last_input_frame;
|
||||
glm::vec3 m_last_input;
|
||||
|
||||
uint32_t m_frame_idx;
|
||||
|
||||
@@ -31,12 +42,26 @@ public:
|
||||
m_speed(speed),
|
||||
m_history(Clock::now(), glm::vec3(), 10 * 20),
|
||||
m_position_history(Clock::now(), glm::vec3(), 10 * 20),
|
||||
m_last_input_frame(0),
|
||||
m_last_input(0.0f),
|
||||
m_frame_idx(0)
|
||||
{ }
|
||||
{
|
||||
// Initialize input ring
|
||||
for(auto& slot : m_input_ring) {
|
||||
slot.frame = 0;
|
||||
slot.input = glm::vec3(0.0f);
|
||||
slot.valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
void set_input(uint32_t frame_idx, glm::vec3 input) {
|
||||
// m_history.set(frame_idx, input);
|
||||
m_input = input;
|
||||
size_t idx = frame_idx % m_input_ring.size();
|
||||
m_input_ring[idx].frame = frame_idx;
|
||||
m_input_ring[idx].input = input;
|
||||
m_input_ring[idx].valid = true;
|
||||
|
||||
m_last_input_frame = frame_idx;
|
||||
m_last_input = input;
|
||||
}
|
||||
|
||||
void set_frame_idx(uint32_t idx) {
|
||||
@@ -44,18 +69,23 @@ public:
|
||||
}
|
||||
|
||||
glm::vec3 input() const {
|
||||
return m_input;
|
||||
// return m_history.values()[m_history.values().size() - 1];
|
||||
return m_last_input;
|
||||
}
|
||||
|
||||
// Returns the input for the specified frame, or falls back to the most recently set input
|
||||
// if the frame slot has been overwritten or never written
|
||||
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();
|
||||
// }
|
||||
size_t idx = frame_idx % m_input_ring.size();
|
||||
const auto& slot = m_input_ring[idx];
|
||||
|
||||
// return glm::vec3();
|
||||
// If slot contains the exact frame we're looking for, return it
|
||||
if(slot.valid && slot.frame == frame_idx) {
|
||||
return slot.input;
|
||||
}
|
||||
|
||||
// Otherwise, fall back to the most recent input
|
||||
// This handles dropped packets (slot never written) or wraparound (slot overwritten)
|
||||
return m_last_input;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ JoltPhysicsWorld::JoltPhysicsWorld(World* world) :
|
||||
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(),
|
||||
@@ -50,6 +49,11 @@ JoltPhysicsWorld::JoltPhysicsWorld(World* world) :
|
||||
contact_listener(),
|
||||
m_character_vs_character_collision()
|
||||
{
|
||||
// Initialize snapshot ring
|
||||
for(auto& snapshot : m_snapshot_ring) {
|
||||
snapshot.frame = 0;
|
||||
snapshot.valid = false;
|
||||
}
|
||||
// for(int i = 0; i < m_thread_pool.size(); i++) {
|
||||
// temp_allocator.emplace_back(std::make_unique<JPH::TempAllocatorImpl>(10 * 1024 * 1024));
|
||||
// }
|
||||
@@ -92,6 +96,27 @@ JoltPhysicsWorld::JoltPhysicsWorld(World* world) :
|
||||
// body_interface().SetLinearVelocity(sphere_id, JPH::Vec3(0.0f, -5.0f, 0.0f));
|
||||
}
|
||||
|
||||
void JoltPhysicsWorld::save_snapshot(uint32_t frame) {
|
||||
size_t idx = frame % m_snapshot_ring.size();
|
||||
auto& snapshot = m_snapshot_ring[idx];
|
||||
|
||||
// Clear and set up the slot
|
||||
snapshot.recorder.Clear();
|
||||
snapshot.frame = frame;
|
||||
snapshot.valid = true;
|
||||
snapshot.desired_velocities.clear();
|
||||
|
||||
// Save the physics world state
|
||||
physics_system.SaveState(snapshot.recorder);
|
||||
|
||||
// Save character states in deterministic order
|
||||
auto view = m_world->registry().view<CharacterController, CharacterBody>();
|
||||
view.each([&](const CharacterController& controller, CharacterBody& rb) {
|
||||
rb.m_character->SaveState(snapshot.recorder);
|
||||
snapshot.desired_velocities.push_back(rb.m_desired_velocity);
|
||||
});
|
||||
}
|
||||
|
||||
void JoltPhysicsWorld::update(uint32_t frame_idx, double delta_time) {
|
||||
auto view = m_world->registry().view<CharacterController, CharacterBody>();
|
||||
|
||||
@@ -117,7 +142,9 @@ void JoltPhysicsWorld::update(uint32_t frame_idx, double delta_time) {
|
||||
: vel;
|
||||
|
||||
// True if the player intended to move
|
||||
mAllowSliding = input.length() < 0.01f; // allow sliding when idle, prevent when moving
|
||||
// glm::vec3::length() is the static component count (3), not the
|
||||
// magnitude, so this has to go through glm::length.
|
||||
mAllowSliding = glm::length(input) < 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();
|
||||
@@ -181,16 +208,15 @@ void JoltPhysicsWorld::update(uint32_t frame_idx, double delta_time) {
|
||||
});
|
||||
|
||||
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) {
|
||||
void JoltPhysicsWorld::step(uint32_t frame_idx, double delta_time, bool record_snapshot) {
|
||||
update(frame_idx, delta_time);
|
||||
|
||||
if(record_snapshot) {
|
||||
save_snapshot(frame_idx);
|
||||
}
|
||||
|
||||
m_world->registry().view<Transform, CharacterBody>()
|
||||
.each([&](Transform& ts, CharacterBody& rb) {
|
||||
auto character = rb.m_character;
|
||||
@@ -199,22 +225,52 @@ void JoltPhysicsWorld::step(uint32_t frame_idx, double delta_time) {
|
||||
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);
|
||||
}
|
||||
|
||||
bool JoltPhysicsWorld::rollback(uint32_t frame) {
|
||||
if(frame == 0) return true;
|
||||
|
||||
size_t idx = frame % m_snapshot_ring.size();
|
||||
auto& snapshot = m_snapshot_ring[idx];
|
||||
|
||||
// Check if snapshot exists and matches the requested frame
|
||||
if(!snapshot.valid || snapshot.frame != frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore the physics world state
|
||||
snapshot.recorder.Rewind();
|
||||
physics_system.RestoreState(snapshot.recorder);
|
||||
|
||||
// Restore character states in the same deterministic order
|
||||
size_t char_idx = 0;
|
||||
auto view = m_world->registry().view<CharacterController, CharacterBody>();
|
||||
view.each([&](const CharacterController& controller, CharacterBody& rb) {
|
||||
rb.m_character->RestoreState(snapshot.recorder);
|
||||
if(char_idx < snapshot.desired_velocities.size()) {
|
||||
rb.m_desired_velocity = snapshot.desired_velocities[char_idx];
|
||||
}
|
||||
char_idx++;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JoltPhysicsWorld::apply_rollback(uint32_t frame) {
|
||||
// Replay frames from frame+1 through m_latest_frame inclusive
|
||||
for(uint32_t i = frame + 1; i <= m_latest_frame; i++) {
|
||||
update(i, FIXED_DELTA_TIME);
|
||||
save_snapshot(i);
|
||||
}
|
||||
|
||||
// Sync transforms from characters to match the current state
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <print>
|
||||
#include "metrics/HistoryBuffer.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <glm/glm.hpp>
|
||||
#include <Jolt/Jolt.h>
|
||||
|
||||
@@ -189,7 +190,14 @@ private:
|
||||
|
||||
JPH::CharacterVsCharacterCollisionSimple m_character_vs_character_collision;
|
||||
|
||||
HistoryBuffer<int, std::shared_ptr<JPH::StateRecorderImpl>> m_history;
|
||||
struct Snapshot {
|
||||
uint32_t frame;
|
||||
bool valid;
|
||||
JPH::StateRecorderImpl recorder;
|
||||
std::vector<JPH::Vec3> desired_velocities;
|
||||
};
|
||||
|
||||
std::array<Snapshot, 64> m_snapshot_ring;
|
||||
|
||||
inline JPH::BodyInterface &body_interface() {
|
||||
return physics_system.GetBodyInterface();
|
||||
@@ -203,7 +211,11 @@ private:
|
||||
|
||||
void update(uint32_t frame_idx, double delta_time);
|
||||
|
||||
void save_snapshot(uint32_t frame);
|
||||
|
||||
public:
|
||||
static constexpr double FIXED_DELTA_TIME = 1.0 / 20.0;
|
||||
|
||||
JoltPhysicsWorld(World* world);
|
||||
|
||||
RigidBody create_dynamic_rigid_body(JPH::Shape* shape, glm::vec3 position) {
|
||||
@@ -242,24 +254,17 @@ public:
|
||||
body_interface().AddForce(rigidbody.id, JPH::RVec3(force.x, force.y, force.z));
|
||||
}
|
||||
|
||||
void step(uint32_t frame_idx, double delta_time);
|
||||
void step(uint32_t frame_idx, double delta_time, bool record_snapshot = false);
|
||||
|
||||
void rollback(uint32_t frame) {
|
||||
if(frame == 0) return;
|
||||
auto snapshot = m_history.get(frame);
|
||||
bool rollback(uint32_t frame);
|
||||
|
||||
if(!snapshot.has_value()) {
|
||||
throw std::runtime_error("Failed to restore history");
|
||||
}
|
||||
void apply_rollback(uint32_t frame);
|
||||
|
||||
auto snapshot_value = snapshot.value()->get();
|
||||
physics_system.RestoreState(*snapshot_value);
|
||||
}
|
||||
uint32_t latest_frame() const { return m_latest_frame; }
|
||||
|
||||
void apply_rollback(uint32_t frame) {
|
||||
for(uint32_t i = frame; i < m_latest_frame; i++) {
|
||||
update(i, 1000.0f / 20.0f);
|
||||
}
|
||||
bool has_snapshot(uint32_t frame) const {
|
||||
size_t idx = frame % m_snapshot_ring.size();
|
||||
return m_snapshot_ring[idx].valid && m_snapshot_ring[idx].frame == frame;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user