GPU culling work

This commit is contained in:
2025-03-17 14:46:50 -04:00
parent f482c37d17
commit 3a55398364
91 changed files with 1083 additions and 1749 deletions

BIN
src/assets/TestScene.bin LFS Normal file

Binary file not shown.

BIN
src/assets/TestScene.gltf LFS Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -5,15 +5,14 @@
#include "engine/gameobjects/components/ModelComponent.hpp"
#include "engine/assets/model/Model.hpp"
#include "engine/gameobjects/components/drawers.hpp"
#include "gui/safe_include.hpp"
namespace fgl::engine
namespace fgl::engine::components
{
void ModelComponent::drawImGui()
{
drawComponentTransform( m_transform );
// drawComponentTransform( m_transform );
// TODO: If the model is not set then we should be able to set it to one from the file selection
if ( this->m_model == nullptr )

View File

@@ -16,7 +16,6 @@
#include "engine/debug/DEBUG_NAMES.hpp"
#include "engine/descriptors/DescriptorPool.hpp"
#include "engine/rendering/Renderer.hpp"
#include "engine/tree/octtree/OctTreeNode.hpp"
#include "gui_window_names.hpp"
#include "safe_include.hpp"

View File

@@ -14,6 +14,7 @@ namespace fgl::engine::gui
ImGui::InputText( "Name", &name_input_temp );
if ( game_object.getName() != name_input_temp ) game_object.setName( name_input_temp );
/*
auto& transform { game_object.getTransform() };
// Transform - Position
@@ -29,6 +30,7 @@ namespace fgl::engine::gui
}
dragFloat3( "Scale", transform.scale );
*/
}
static GameObjectComponentPtr SELECTED_COMPONENT { nullptr };

View File

@@ -7,7 +7,6 @@
#include "engine/flags.hpp"
#include "engine/math/literals/size.hpp"
#include "engine/memory/buffers/Buffer.hpp"
#include "engine/tree/octtree/OctTreeNode.hpp"
#include "safe_include.hpp"
namespace fgl::engine::gui
@@ -128,8 +127,6 @@ namespace fgl::engine::gui
debug::timing::render();
}
imGuiOctTreeSettings( info );
if ( ImGui::Button( "Reload shaders" ) )
{
flags::triggerShaderReload();

View File

@@ -10,9 +10,7 @@
#include "engine/camera/Camera.hpp"
#include "engine/filesystem/scanner/FileScanner.hpp"
#include "engine/filesystem/types.hpp"
#include "engine/gameobjects/components/ModelComponent.hpp"
#include "engine/rendering/PresentSwapChain.hpp"
#include "engine/tree/octtree/OctTreeNode.hpp"
#include "safe_include.hpp"
namespace fgl::engine::gui
@@ -47,12 +45,16 @@ namespace fgl::engine::gui
GameObject obj { GameObject::createGameObject() };
std::shared_ptr< Model > model {
Model::createModel( data->m_path, info.model_vertex_buffer, info.model_index_buffer )
Model::
createModel( data->m_path, info.model_vertex_buffer, info.model_index_buffer )
};
obj.addFlag( IsEntity | IsVisible );
auto component { std::make_unique< ModelComponent >( std::move( model ) ) };
info.context.models()
.loadModel( data->m_path, info.model_vertex_buffer, info.model_index_buffer );
auto component { std::make_unique< components::ModelComponent >( std::move( model ) ) };
obj.addComponent( std::move( component ) );

View File

@@ -9,7 +9,6 @@
#include "engine/debug/timing/FlameGraph.hpp"
#include "engine/gameobjects/components/CameraComponent.hpp"
#include "gui/EditorGuiContext.hpp"
#include "gui/core.hpp"
int main()
{
@@ -63,12 +62,22 @@ int main()
editor_camera->setFOV( glm::radians( 90.0f ) );
// Create a default world to assign to the engine before we load or create a new one.
World world {};
constexpr bool playing { false };
//! Will be true until the window says it wants to close.
while ( engine_ctx.good() )
{
debug::timing::reset();
engine_ctx.tickDeltaTime();
engine_ctx.setWorld( world );
if ( playing ) world = engine_ctx.tickSimulation();
engine_ctx.handleTransfers();
// Process input

View File

@@ -11,7 +11,6 @@
#include <iostream>
#include "KeyboardMovementController.hpp"
#include "assets/material/Material.hpp"
#include "camera/Camera.hpp"
#include "camera/CameraManager.hpp"
#include "camera/GBufferRenderer.hpp"
@@ -48,52 +47,6 @@ namespace fgl::engine
m_matrix_info_pool.setDebugName( "Matrix info pool" );
m_draw_parameter_pool.setDebugName( "Draw parameter pool" );
m_vertex_buffer->setDebugName( "Vertex buffer" );
m_index_buffer->setDebugName( "Index buffer" );
constexpr float offset { 4.0f };
constexpr std::size_t grid_size { 6 };
constexpr float factor_offset { 1.0f / static_cast< float >( grid_size ) };
for ( std::size_t x = 0; x < grid_size; ++x )
for ( std::size_t y = 0; y < grid_size; ++y )
{
const std::filesystem::path path {
"/home/kj16609/Desktop/Projects/cxx/Mecha/src/assets/PBRSphere.gltf"
};
SceneBuilder builder { *m_vertex_buffer, *m_index_buffer };
builder.loadScene( path );
std::vector< GameObject > objs { builder.getGameObjects() };
for ( auto& obj : objs )
{
auto model_components { obj.getComponents< ModelComponent >() };
for ( const auto& model_component : model_components )
{
auto& prims = ( *model_component )->m_primitives;
for ( auto& prim : prims )
{
auto& pbr { prim.m_material->properties.pbr };
pbr.roughness_factor = static_cast< float >( x ) * factor_offset;
pbr.metallic_factor = static_cast< float >( y ) * factor_offset;
prim.m_material->update();
}
}
obj.getTransform().translation = WorldCoordinate(
-5.0f + ( static_cast< float >( x ) * offset ),
-5.0f + ( static_cast< float >( y ) * offset ),
0.0f );
m_game_objects_root.addGameObject( std::move( obj ) );
}
}
}
static Average< float, 60 * 15 > rolling_ms_average;
@@ -116,7 +69,7 @@ namespace fgl::engine
m_delta_time = time_diff.count();
}
void EngineContext::tickSimulation()
World EngineContext::tickSimulation()
{
ZoneScoped;
auto timer = debug::timing::push( "Tick Simulation" );
@@ -124,6 +77,8 @@ namespace fgl::engine
// The first step here should be culling things that aren't needed to be ticked.
// Perhaps implementing a tick system that doesn't care about the refresh rate might be good?
// That way we can still tick consistantly without actually needing to render anything.
return {};
}
void EngineContext::renderCameras( FrameInfo frame_info )
@@ -160,12 +115,10 @@ namespace fgl::engine
nullptr, // Camera
m_camera_manager.getCameras(),
// global_descriptor_sets[ frame_index ],
m_game_objects_root,
m_model_manager,
m_renderer.getCurrentTracyCTX(),
m_matrix_info_pool,
m_draw_parameter_pool,
*this->m_vertex_buffer,
*this->m_index_buffer,
// m_renderer.getSwapChain().getInputDescriptor( present_idx ),
this->m_renderer.getSwapChain() };

View File

@@ -11,7 +11,7 @@
#include "engine/assets/transfer/TransferManager.hpp"
#include "engine/math/literals/size.hpp"
#include "engine/rendering/Renderer.hpp"
#include "engine/tree/octtree/OctTreeNode.hpp"
#include "scene/World.hpp"
#include "systems/composition/GuiSystem.hpp"
namespace fgl::engine
@@ -43,17 +43,10 @@ namespace fgl::engine
Renderer m_renderer { m_window, m_device.phyDevice() };
std::unique_ptr< memory::Buffer > m_vertex_buffer { std::make_unique< memory::Buffer >(
1_GiB,
vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal ) };
std::unique_ptr< memory::Buffer > m_index_buffer { std::make_unique< memory::Buffer >(
512_MiB,
vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal ) };
ModelManager m_model_manager {};
//GameObject::Map game_objects {};
OctTreeNode m_game_objects_root { WorldCoordinate( constants::WORLD_CENTER ) };
// GameObject::Map game_objects {};
// OctTreeNode m_game_objects_root { WorldCoordinate( constants::WORLD_CENTER ) };
// SubPass 0
GuiSystem m_gui_system {};
@@ -88,8 +81,12 @@ namespace fgl::engine
std::chrono::time_point< Clock > m_last_tick { Clock::now() };
DeltaTime m_delta_time;
World m_world;
public:
ModelManager& models() { return m_model_manager; }
FGL_FORCE_INLINE_FLATTEN void hookInitImGui( const std::function< void( Window&, Renderer& ) >& func )
{
func( m_window, m_renderer );
@@ -107,6 +104,8 @@ namespace fgl::engine
void hookDestruction( const std::function< void() >& func ) { m_destruction_hooks.emplace_back( func ); }
void setWorld( const World& world );
public:
EngineContext();
@@ -126,7 +125,7 @@ namespace fgl::engine
void processInput();
void tickDeltaTime();
void tickSimulation();
World tickSimulation();
void renderCameras( FrameInfo frame_info );
void renderFrame();

View File

@@ -83,16 +83,13 @@ namespace fgl::engine
std::vector< std::weak_ptr< Camera > >& m_camera_list;
// descriptors::DescriptorSet& global_descriptor_set;
OctTreeNode& game_objects;
// OctTreeNode& game_objects;
TracyVkCtx tracy_ctx;
//Buffers
memory::Buffer& model_matrix_info_buffer;
memory::Buffer& draw_parameter_buffer;
memory::Buffer& model_vertex_buffer;
memory::Buffer& model_index_buffer;
// descriptors::DescriptorSet& gui_input_descriptor;
descriptors::DescriptorSet& getGBufferDescriptor() const;

View File

@@ -10,6 +10,7 @@
#include "engine/primitives/vectors/Vector.hpp"
#include "gameobjects/GameObject.hpp"
#include "gameobjects/components/TransformComponent.hpp"
namespace fgl::engine
{
@@ -31,6 +32,18 @@ namespace fgl::engine
void KeyboardMovementController::moveInPlaneXZ( GLFWwindow* window, float dt, GameObject& target )
{
assert( window );
auto components { target.getComponents< components::TransformComponent >() };
if ( components.empty() )
{
log::warn( "KeyboardMovementController: Attempted to move object with no transform" );
return;
}
components::TransformComponent& component { *components[ 0 ] };
WorldTransform& transform { *component };
constexpr float rotation_rate { 1.0f };
constexpr float pitch_modifier { 1.0f };
constexpr float yaw_modifier { 1.0f };
@@ -86,11 +99,11 @@ namespace fgl::engine
if ( pitch_change > std::numeric_limits< float >::epsilon()
|| pitch_change < -std::numeric_limits< float >::epsilon() )
target.getTransform().rotation.addX( dt * pitch_change );
transform.rotation.addX( dt * pitch_change );
if ( yaw_change > std::numeric_limits< float >::epsilon()
|| yaw_change < -std::numeric_limits< float >::epsilon() )
target.getTransform().rotation.addY( dt * yaw_change );
transform.rotation.addY( dt * yaw_change );
}
else // No cursor
{
@@ -98,7 +111,7 @@ namespace fgl::engine
const float xpos { pos.x };
const float ypos { pos.y };
UniversalRotation& target_rotation { target.getTransform().rotation };
UniversalRotation& target_rotation { transform.rotation };
target_rotation.addZ( ( xpos * 0.006f ) * look_speed );
target_rotation.addX( ( ypos * 0.006f ) * look_speed );
@@ -106,9 +119,9 @@ namespace fgl::engine
setCursorPos( window, { 0, 0 } );
}
const Vector forward_dir { target.getTransform().rotation.forward() };
const Vector up_dir { target.getTransform().rotation.up() };
const Vector right_dir { target.getTransform().rotation.right() };
const Vector forward_dir { transform.rotation.forward() };
const Vector up_dir { transform.rotation.up() };
const Vector right_dir { transform.rotation.right() };
Vector move_dir { 0.0f };
if ( glfwGetKey( window, key_mappings.move_forward ) == GLFW_PRESS ) move_dir += forward_dir;
@@ -121,7 +134,7 @@ namespace fgl::engine
const NormalVector n_move_dir { move_dir };
if ( glm::dot( move_dir.vec(), move_dir.vec() ) > std::numeric_limits< float >::epsilon() )
target.getTransform().translation += n_move_dir * ( move_speed * dt );
transform.translation += n_move_dir * ( move_speed * dt );
}
} // namespace fgl::engine

View File

@@ -53,8 +53,8 @@ namespace fgl::engine
//! Key type given by T
using KeyT = typename T::UIDKeyT;
std::unordered_map< KeyT, std::weak_ptr< T > > active_map {};
std::mutex map_mtx {};
std::unordered_map< KeyT, std::weak_ptr< T > > m_active_map {};
std::mutex m_map_mtx {};
public:
@@ -71,9 +71,9 @@ namespace fgl::engine
ZoneScoped;
const auto key { T::extractKey( std::forward< T_Args >( args )... ) };
std::lock_guard guard { map_mtx };
std::lock_guard guard { m_map_mtx };
if ( auto itter = active_map.find( key ); itter != active_map.end() )
if ( auto itter = m_active_map.find( key ); itter != m_active_map.end() )
{
// We've found the item in the map. We can now check if it's still active
@@ -83,13 +83,13 @@ namespace fgl::engine
}
//Item was expired. Remove it from the map and continue
active_map.erase( itter );
m_active_map.erase( itter );
}
std::shared_ptr< T > s_ptr { new T( std::forward< T_Args >( args )... ) };
// Add the weak pointer to the map so we can find it later.
active_map.insert( std::make_pair( key, s_ptr ) );
m_active_map.insert( std::make_pair( key, s_ptr ) );
return s_ptr;
}

View File

@@ -0,0 +1,25 @@
//
// Created by kj16609 on 3/17/25.
//
#include "InstanceManager.hpp"
namespace fgl::engine
{
using namespace fgl::literals::size_literals;
inline InstanceArray createInstances( memory::Buffer& buffer )
{
InstanceArray instances {};
std::ranges::generate( instances, [ & ]() -> InstanceVector { return InstanceVector( buffer, 0 ); } );
return instances;
}
inline InstanceManager::InstanceManager() :
m_buffer( 128_MiB, vk::BufferUsageFlagBits::eStorageBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal ),
m_instances( createInstances( m_buffer ) )
{}
} // namespace fgl::engine

View File

@@ -0,0 +1,31 @@
//
// Created by kj16609 on 3/17/25.
//
#pragma once
#include "memory/buffers/Buffer.hpp"
#include "memory/buffers/vector/DeviceVector.hpp"
#include "rendering/PresentSwapChain.hpp"
namespace fgl::engine
{
struct ModelGPUInstance;
using InstanceVector = DeviceVector< ModelGPUInstance >;
using InstanceArray = PerFrameArray< InstanceVector >;
class InstanceManager
{
//! Buffer for each instance to use
memory::Buffer m_buffer;
InstanceArray m_instances;
public:
//! Constructor to initialize m_buffer and m_instances
[[nodiscard]] InstanceManager();
~InstanceManager() = default;
};
} // namespace fgl::engine

View File

@@ -54,6 +54,11 @@ namespace fgl::engine
return box;
}
std::shared_ptr< ModelRenderInfo > Model::getRenderHandle() const
{
return m_render_handle.lock();
}
bool Model::ready() const
{
//Return true if even a single primitive is ready

View File

@@ -15,11 +15,16 @@
#include <vector>
#include "Primitive.hpp"
#include "assets/AssetManager.hpp"
#include "engine/assets/material/Material.hpp"
#include "engine/primitives/boxes/OrientedBoundingBox.hpp"
#include "memory/buffers/vector/IndexedVector.hpp"
namespace fgl::engine
{
struct ModelRenderInfo;
class ModelRenderHandle;
namespace memory
{
class Buffer;
@@ -33,30 +38,31 @@ namespace fgl::engine
MaterialID material_id { constants::INVALID_TEXTURE_ID };
};
class Model
class Model final : public AssetInterface< Model >
{
static std::vector< vk::DrawIndexedIndirectCommand > buildParameters( const std::vector< Primitive >&
primitives );
static OrientedBoundingBox< CoordinateSpace::Model > buildBoundingBox( const std::vector< Primitive >&
primitives );
std::vector< vk::DrawIndexedIndirectCommand > m_draw_parameters;
static OrientedBoundingBox< CoordinateSpace::Model >
buildBoundingBox( const std::vector< Primitive >& primitives );
std::string m_name { "Unnamed model" };
//! Bounding box of the model
OrientedBoundingBox< CoordinateSpace::Model > m_bounding_box;
IndexedVector< ModelMatrixInfo >::Index m_model_info {};
public:
bool ready() const;
[[nodiscard]] bool ready() const;
//! Returns the bounding box in model space
const OrientedBoundingBox< CoordinateSpace::Model >& getBoundingBox() const { return m_bounding_box; }
[[nodiscard]] const OrientedBoundingBox< CoordinateSpace::Model >& getBoundingBox() const
{
return m_bounding_box;
}
std::vector< Primitive > m_primitives {};
std::vector< vk::DrawIndexedIndirectCommand > getDrawCommand( std::uint32_t index ) const;
[[nodiscard]] std::vector< vk::DrawIndexedIndirectCommand > getDrawCommand( std::uint32_t index ) const;
//TODO: Switch to using shared_ptr instead of unique_ptr
static std::shared_ptr< Model > createModel(

View File

@@ -0,0 +1,8 @@
//
// Created by kj16609 on 3/17/25.
//
#include "ModelInstance.hpp"
namespace fgl::engine
{
} // namespace fgl::engine

View File

@@ -0,0 +1,64 @@
//
// Created by kj16609 on 3/17/25.
//
#pragma once
#include <glm/mat4x4.hpp>
#include <memory>
#include "memory/buffers/vector/DeviceVector.hpp"
#include "primitives/Transform.hpp"
#include "rendering/PresentSwapChain.hpp"
namespace fgl::engine
{
class Model;
struct ModelGPUInstance
{
//! Index of the model
std::uint32_t m_model_index;
glm::mat4x4 m_transform;
};
using InstanceIndex = std::uint32_t;
using namespace fgl::literals::size_literals;
class ModelInstance
{
std::shared_ptr< Model > m_model;
InstanceIndex m_index;
//! CPU side data to be modified
ModelGPUInstance m_cpu_data;
//! True if the last frame changed this instance in any way
bool m_updated { false };
public:
//! Returns GPU instance data
ModelGPUInstance gpuInstanceData() const
{
ModelGPUInstance data {};
data.m_model_index = m_model->getGPUID();
return data;
}
//! Returns the current update state and sets it to false if it was true.
bool acquireNeedsUpdate()
{
if ( m_updated ) [[unlikely]]
{
m_updated = false;
return true;
}
return false;
}
};
} // namespace fgl::engine

View File

@@ -0,0 +1,10 @@
//
// Created by kj16609 on 3/12/25.
//
#include "ModelManager.hpp"
namespace fgl::engine
{
}

View File

@@ -0,0 +1,24 @@
//
// Created by kj16609 on 3/12/25.
//
#pragma once
#include "memory/buffers/vector/DeviceVector.hpp"
#include "rendering/PresentSwapChain.hpp"
namespace fgl::engine
{
class ModelManager
{
//! The managed draw command buffer.
/**
* Contains all draw commands for the rendering process. Updated by the CPU whenever a new model is created
*/
DeviceVector< vk::DrawIndexedIndirectCommand > m_draw_commands;
//! Draw buffers the GPU compute shader writes into
PerFrameArray< DeviceVector< vk::DrawIndexedIndirectCommand > > m_gpu_draw_commands {};
//! Contains extra information such as model instance indexes by the compute shader
PerFrameArray<> m_gpu_draw_info {};
};
} // namespace fgl::engine

View File

@@ -7,7 +7,7 @@
#include <filesystem>
#include <vector>
#include "engine/primitives/TransformComponent.hpp"
#include "engine/primitives/Transform.hpp"
namespace fgl::engine
{

View File

@@ -595,7 +595,10 @@ namespace fgl::engine
assert( model );
std::unique_ptr< ModelComponent > component { std::make_unique< ModelComponent >( std::move( model ) ) };
std::unique_ptr< components::ModelComponent > component {
std::make_unique< components::ModelComponent >( std::move( model ) )
};
obj.addComponent( std::move( component ) );
obj.addFlag( IsVisible | IsEntity );

View File

@@ -18,7 +18,7 @@
#include "engine/assets/material/Material.hpp"
#include "engine/gameobjects/GameObject.hpp"
#include "engine/primitives/TransformComponent.hpp"
#include "engine/primitives/Transform.hpp"
namespace fgl::engine
{

View File

@@ -197,12 +197,12 @@ namespace fgl::engine
return m_imgui_set;
}
Texture::Texture( Image& image, Sampler sampler ) :
Texture::Texture( const std::shared_ptr< Image >& image, Sampler sampler ) :
m_texture_id( texture_id_pool.getID() ),
m_image(),
m_image_view( image.getView() ),
m_image( image ),
m_image_view( image->getView() ),
//TODO: Figure out how to get extents from images.
m_extent(),
m_extent( image->getExtent() ),
m_name( "Default Texture Name" )
{
m_image_view->getSampler() = std::move( sampler );

View File

@@ -91,6 +91,7 @@ namespace fgl::engine
Texture() = delete;
~Texture();
Texture( const std::shared_ptr<Image>& image, Sampler sampler = Sampler() );
Image& getImageRef();
@@ -100,8 +101,6 @@ namespace fgl::engine
Texture( Texture&& other ) = delete;
Texture& operator=( Texture&& ) = delete;
Texture( Image& image, Sampler sampler = Sampler() );
bool ready() const;
[[nodiscard]] TextureID getID() const;

View File

@@ -19,7 +19,7 @@
#include "engine/memory/buffers/HostSingleT.hpp"
#include "engine/memory/buffers/UniqueFrameSuballocation.hpp"
#include "engine/primitives/Frustum.hpp"
#include "engine/primitives/TransformComponent.hpp"
#include "engine/primitives/Transform.hpp"
#include "engine/rendering/types.hpp"
namespace vk::raii
@@ -54,11 +54,13 @@ namespace fgl::engine
vk::Extent2D m_target_extent;
std::unique_ptr< CompositeSwapchain > m_old_composite_swapchain { nullptr };
std::unique_ptr< CompositeSwapchain > m_composite_swapchain;
std::unique_ptr< GBufferSwapchain > m_old_gbuffer_swapchain { nullptr };
std::unique_ptr< GBufferSwapchain > m_gbuffer_swapchain;
//TODO: Move to deffered deleter
std::unique_ptr< CompositeSwapchain > m_old_composite_swapchain { nullptr };
std::unique_ptr< GBufferSwapchain > m_old_gbuffer_swapchain { nullptr };
std::shared_ptr< GBufferRenderer > m_camera_renderer;
//! True if the camera is active and to be rendered

View File

@@ -100,7 +100,7 @@ namespace fgl::engine
for ( const auto& image : m_buffer.m_target.m_attachment_resources.m_images )
{
m_gbuffer_target.emplace_back( std::make_unique< Texture >( *image ) );
m_gbuffer_target.emplace_back( std::make_unique< Texture >( image ) );
}
}

View File

@@ -12,7 +12,7 @@ namespace fgl::engine
if ( m_id != INVALID_ID )
{
log::debug( "Destroyed game object {}", this->m_id );
for ( const auto& component : components ) delete component;
for ( const auto& component : m_components ) delete component;
}
}
@@ -20,8 +20,7 @@ namespace fgl::engine
{
m_id = other.m_id;
object_flags = other.object_flags;
m_transform = other.m_transform;
components = std::move( other.components );
m_components = std::move( other.m_components );
m_name = std::move( other.m_name );
other.m_id = INVALID_ID;
@@ -32,8 +31,7 @@ namespace fgl::engine
GameObject::GameObject( GameObject&& other ) noexcept :
m_id( other.m_id ),
object_flags( other.object_flags ),
m_transform( other.m_transform ),
components( std::move( other.components ) ),
m_components( std::move( other.m_components ) ),
m_name( other.m_name )
{
other.m_id = INVALID_ID;

View File

@@ -11,7 +11,7 @@
#include "components/ModelComponent.hpp"
#include "engine/gameobjects/components/interface/GameObjectComponent.hpp"
#include "engine/primitives/TransformComponent.hpp"
#include "engine/primitives/Transform.hpp"
namespace fgl::engine
{
@@ -45,9 +45,11 @@ namespace fgl::engine
GameObjectID m_id { INVALID_ID };
GameObjectFlagType object_flags { GameObjectFlagMask::MaskDefault };
GameObjectTransform m_transform {};
std::vector< GameObjectComponentPtr > components {};
std::vector< GameObjectComponentPtr > m_components {};
GameObject* m_parent { nullptr };
std::vector< GameObject > m_children {};
std::string m_name {};
@@ -67,17 +69,15 @@ namespace fgl::engine
requires is_component< T >
void addComponent( std::unique_ptr< T >&& ptr )
{
components.emplace_back( ptr.release() );
m_components.emplace_back( ptr.release() );
}
Scale& getScale() { return m_transform.scale; }
template < typename T >
requires is_component< T >
bool hasComponent() const
{
ZoneScoped;
for ( const GameObjectComponentPtr comp : components )
for ( const GameObjectComponentPtr comp : m_components )
{
if ( comp->id() == T::ID ) return true;
}
@@ -92,7 +92,7 @@ namespace fgl::engine
ZoneScopedN( "Get components" );
std::vector< const T* > temp {};
for ( const ComponentEngineInterface* comp : components )
for ( const ComponentEngineInterface* comp : m_components )
{
if ( comp->id() == T::ID ) temp.emplace_back( static_cast< const T* >( comp ) );
}
@@ -107,7 +107,7 @@ namespace fgl::engine
ZoneScopedN( "Get components" );
std::vector< T* > temp {};
for ( ComponentEngineInterface* comp : components )
for ( ComponentEngineInterface* comp : m_components )
{
if ( comp->id() == T::ID ) temp.emplace_back( static_cast< T* >( comp ) );
}
@@ -115,9 +115,9 @@ namespace fgl::engine
return temp;
}
std::vector< GameObjectComponentPtr >& getComponents() { return components; }
std::vector< GameObjectComponentPtr >& getComponents() { return m_components; }
const std::vector< GameObjectComponentPtr >& getComponents() const { return components; }
const std::vector< GameObjectComponentPtr >& getComponents() const { return m_components; }
//Flags
GameObjectFlagType flags() const { return object_flags; }
@@ -126,17 +126,6 @@ namespace fgl::engine
void removeFlag( GameObjectFlagType flag ) { object_flags &= ( ~flag ); }
//Transform
GameObjectTransform& getTransform() { return m_transform; }
const GameObjectTransform& getTransform() const { return m_transform; }
const WorldCoordinate& getPosition() const { return m_transform.translation; }
// const Rotation& getRotation() const { return m_transform.rotation; }
QuatRotation getRotation() const { return m_transform.rotation.forcedQuat(); }
//Misc
static GameObject createGameObject();

View File

@@ -11,6 +11,8 @@ namespace fgl::engine
{
using ComponentID = std::uint32_t;
ASSIGN_COMPONENT_ID( TransformComponentID, 0 );
ASSIGN_COMPONENT_ID( ModelComponentID, 1 );
ASSIGN_COMPONENT_ID( CameraComponentID, 2 );

View File

@@ -0,0 +1,17 @@
//
// Created by kj16609 on 3/1/25.
//
#include "ModelComponent.hpp"
#include "engine/assets/model/Model.hpp"
namespace fgl::engine::components
{
ModelComponent::ModelComponent( std::shared_ptr< Model >&& model ) :
m_model( std::forward< decltype( m_model ) >( model ) ),
m_render_handle( m_model->getRenderHandle() )
{}
} // namespace fgl::engine::components

View File

@@ -6,20 +6,28 @@
#include <memory>
#include "ComponentIDS.hpp"
#include "assets/model/ModelRenderHandle.hpp"
#include "engine/gameobjects/components/interface/GameObjectComponent.hpp"
namespace fgl::engine
{
class ModelRenderHandle;
}
namespace fgl::engine::components
{
struct GameModel;
class Model;
COMPONENT_CLASS( ModelComponent, ModelComponentID )
{
std::shared_ptr< Model > m_model;
ModelRenderHandle m_render_handle;
// In the future this should also contain a handle to the pipeline that is going to be used for rendering this model.
public:
ModelComponent( std::shared_ptr< Model > && model ) : m_model( std::forward< decltype( m_model ) >( model ) )
{}
explicit ModelComponent( std::shared_ptr< Model > && model );
void drawImGui() override;
@@ -36,4 +44,4 @@ namespace fgl::engine
static_assert( is_component< ModelComponent > );
} // namespace fgl::engine
} // namespace fgl::engine::components

View File

@@ -0,0 +1,32 @@
//
// Created by kj16609 on 3/3/25.
//
#pragma once
#include "ComponentIDS.hpp"
#include "interface/GameObjectComponent.hpp"
namespace fgl::engine::components
{
COMPONENT_CLASS( TransformComponent, TransformComponentID )
{
WorldTransform m_transform;
public:
explicit TransformComponent( WorldTransform & transform );
void drawImGui() override;
std::string_view humanName() const override;
std::string_view className() const override;
WorldTransform& operator*()
{
return m_transform;
}
virtual ~TransformComponent() override = default;
};
} // namespace fgl::engine::components

View File

@@ -7,14 +7,14 @@
#include <vector>
#include "ComponentEngineInterface.hpp"
#include "engine/primitives/TransformComponent.hpp"
#include "engine/primitives/Transform.hpp"
#define COMPONENT_CLASS( class_name, id_name ) class class_name final : public GameObjectComponent< id_name >
namespace fgl::engine
{
struct ComponentTransform final : public TransformComponent< CoordinateSpace::World >
struct ComponentTransform final : public Transform< CoordinateSpace::World >
{
enum Mode
{
@@ -29,7 +29,7 @@ namespace fgl::engine
ComponentTransform() = default;
ComponentTransform( const WorldCoordinate position_i, const Scale scale_i, const QuatRotation rotation_i ) :
TransformComponent( position_i, scale_i, rotation_i )
Transform( position_i, scale_i, rotation_i )
{}
};
@@ -50,7 +50,6 @@ namespace fgl::engine
struct GameObjectComponent : public GameObjectComponentBase
{
constexpr static ComponentID ID { T_ID };
ComponentTransform m_transform { WorldCoordinate( 0.0f ), Scale( 1.0 ), QuatRotation() };
virtual ComponentID id() const override final { return ID; }
};

View File

@@ -0,0 +1,12 @@
//
// Created by kj16609 on 2/17/25.
//
#include "GlobalIlluminator.hpp"
namespace fgl::engine::lights
{
GlobalIlluminator::GlobalIlluminator( const NormalVector direction ) : m_direction( direction )
{}
} // namespace fgl::engine::lights

View File

@@ -0,0 +1,26 @@
//
// Created by kj16609 on 2/17/25.
//
#pragma once
#include "primitives/vectors/NormalVector.hpp"
namespace fgl::engine::lights
{
//! Sun illumation.
class GlobalIlluminator
{
NormalVector m_direction;
public:
explicit GlobalIlluminator( NormalVector direction );
};
} // namespace fgl::engine::lights
namespace fgl::engine
{
using namespace lights;
}

View File

@@ -0,0 +1,62 @@
//
// Created by kj16609 on 2/17/25.
//
#include "ShadowMap.hpp"
#include "assets/image/Image.hpp"
#include "assets/texture/Texture.hpp"
namespace fgl::engine::shadows
{
std::shared_ptr< Image > getDepthImage( const vk::Extent2D extent )
{
constexpr auto format { vk::Format::eR16Unorm };
constexpr vk::ImageUsageFlags usage_flags { vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eDepthStencilAttachment };
constexpr auto inital_layout { vk::ImageLayout::eUndefined };
constexpr auto final_layout { vk::ImageLayout::eDepthReadOnlyOptimal };
return std::make_shared< Image >( extent, format, usage_flags, inital_layout, final_layout );
}
PerFrameArray< std::shared_ptr< Image > > createDepthImages( vk::Extent2D extent )
{
PerFrameArray< std::shared_ptr< Image > > array {};
for ( std::size_t i = 0; i < array.size(); ++i ) array[ i ] = getDepthImage( extent );
return array;
}
PerFrameArray< std::shared_ptr< Texture > > ShadowMap::createDepthTargets()
{
PerFrameArray< std::shared_ptr< Texture > > array {};
for ( std::size_t i = 0; i < array.size(); ++i ) array[ i ] = std::make_shared< Texture >( m_image[ i ] );
return array;
}
void ShadowMap::renderForCamera( const Camera& camera )
{
// model -> world -> camera (identity) -> screen (shadow)
// since the camera in this case is the shadow map we just need to convert the screen space to world space. So we can just use an identity matrix
const Matrix< MatrixType::WorldToCamera > camera_matrix { m_transform.mat() };
const Matrix< MatrixType::CameraToScreen > identity { 1.0f };
const Matrix< MatrixType::WorldToScreen > matrix { camera_matrix * identity };
}
ShadowMap::ShadowMap( const vk::Extent2D extent ) :
m_image( createDepthImages( extent ) ),
m_target( createDepthTargets() )
{}
} // namespace fgl::engine::shadows

View File

@@ -0,0 +1,47 @@
//
// Created by kj16609 on 2/17/25.
//
#pragma once
#include <vulkan/vulkan_raii.hpp>
#include <memory>
#include "primitives/Transform.hpp"
#include "primitives/matricies/Matrix.hpp"
#include "primitives/matricies/MatrixEvolvedTypes.hpp"
#include "rendering/PresentSwapChain.hpp"
namespace fgl::engine
{
class Image;
class Texture;
} // namespace fgl::engine
namespace fgl::engine::shadows
{
class ShadowMap
{
PerFrameArray< std::shared_ptr< Image > > m_image;
PerFrameArray< std::shared_ptr< Texture > > m_target;
Matrix< MatrixType::WorldToScreen > m_matrix { 1.0f };
Transform< CoordinateSpace::World > m_transform {};
PerFrameArray< std::shared_ptr< Texture > > createDepthTargets();
public:
void renderForCamera( const Camera& camera );
ShadowMap( vk::Extent2D extent );
~ShadowMap();
};
} // namespace fgl::engine::shadows
namespace fgl::engine
{
using namespace shadows;
}

View File

@@ -13,7 +13,8 @@ namespace fgl::engine::memory
[[nodiscard]] BufferVector::BufferVector( Buffer& buffer, std::uint32_t count, std::uint32_t stride ) :
BufferSuballocation( buffer.allocate( count * stride ) ),
m_count( count ),
m_stride( stride )
m_stride( stride ),
m_capacity( count )
{}
//! Returns the offset count from the start of the buffer to the first element
@@ -41,21 +42,38 @@ namespace fgl::engine::memory
return m_count;
}
[[nodiscard]] std::uint32_t BufferVector::capacity() const noexcept
{
assert( !std::isnan( m_count ) );
assert( m_count * m_stride <= this->bytesize() );
return m_capacity;
}
void BufferVector::resize( const std::uint32_t count )
{
assert( count > 0 );
assert( !std::isnan( m_stride ) );
assert( !std::isnan( m_count ) );
//If the capacity is higher then what we are requesting then we simply just ignore the request.
// TODO: Maybe this is bad? I'm unsure. But reducing the number of allocations is always good
if ( count < m_count ) return;
// we are reclaiming size
//TODO: Figure out a way to truely reclaim any size
if ( count < capacity() )
{
m_count = count;
return;
}
BufferVector other { this->getBuffer(), count, m_stride };
// the capacity is not enough for the new size, we must reallocate.
if ( count > capacity() )
{
BufferVector other { this->getBuffer(), count, m_stride };
TransferManager::getInstance().copyToVector( *this, other, 0 );
TransferManager::getInstance().copyToVector( *this, other, 0 );
*this = std::move( other );
*this = std::move( other );
}
this->m_count = count;
}
} // namespace fgl::engine::memory

View File

@@ -19,6 +19,7 @@ namespace fgl::engine::memory
//! Number of items in the vector
std::uint32_t m_count { std::numeric_limits< std::uint32_t >::quiet_NaN() };
std::uint32_t m_capacity { std::numeric_limits< std::uint32_t >::quiet_NaN() };
//! Bytes for each item
std::uint32_t m_stride { std::numeric_limits< std::uint32_t >::quiet_NaN() };
@@ -43,7 +44,9 @@ namespace fgl::engine::memory
std::uint32_t getOffsetCount() const;
std::uint32_t stride() const noexcept;
std::uint32_t size() const noexcept;
std::uint32_t capacity() const noexcept;
void resize( std::uint32_t count );
// void reserve( std::uint32_t count );
};
} // namespace fgl::engine::memory

View File

@@ -45,8 +45,7 @@ namespace fgl::engine
memory::TransferManager::getInstance().copyToVector< T, DeviceVector< T > >( data, *this );
}
//TODO: This
void resize( const std::size_t new_size );
void resize( const std::size_t new_size ) { BufferVector::resize( new_size ); }
void updateData( const std::size_t idx, const T& data )
{

View File

@@ -0,0 +1,69 @@
//
// Created by kj16609 on 3/17/25.
//
#pragma once
#include "DeviceVector.hpp"
namespace fgl::engine
{
template < typename T >
class IndexedVector : DeviceVector< T >
{
std::queue< std::uint32_t > m_free_indexes {};
public:
class Index
{
IndexedVector< T >& m_vector;
std::uint32_t m_idx;
public:
void update( const T& t )
{
memory::TransferManager::getInstance()
.copyToVector< T, DeviceVector< T > >( m_vector.m_data, m_idx, t );
}
private:
Index( IndexedVector< T >& vector, const std::uint32_t idx ) : m_vector( vector ), m_idx( idx ) {}
// Privated to force returning to the IndexedVector
~Index() = default;
};
IndexedVector() = delete;
IndexedVector( memory::Buffer& buffer, const std::uint32_t desired_count = 0 ) :
DeviceVector< T >( buffer, desired_count )
{
for ( std::uint32_t i = 0; i < desired_count; ++i ) m_free_indexes.push( i );
}
Index< T > acquire( const T& t )
{
if ( m_free_indexes.empty() )
{
resize( this->size() + 1 );
m_free_indexes.push( this->size() - 1 );
}
Index< T > index { *this, m_free_indexes.front() };
m_free_indexes.pop();
index.update( t );
return index;
}
void release( Index< T >&& index_i )
{
Index< T > index { std::forward< Index< T > >( index_i ) };
m_free_indexes.push( index.m_idx );
}
};
} // namespace fgl::engine

View File

@@ -2,13 +2,13 @@
// Created by kj16609 on 2/27/24.
//
#include "TransformComponent.hpp"
#include "Transform.hpp"
namespace fgl::engine
{
template < CoordinateSpace CType >
glm::mat4 TransformComponent< CType >::mat4() const
glm::mat4 Transform< CType >::mat4() const
{
const glm::mat3 rotation_mat { rotation.forcedQuat().mat() };
@@ -21,30 +21,30 @@ namespace fgl::engine
}
template < CoordinateSpace CType >
Matrix< MatrixTransformType< CType >() > TransformComponent< CType >::mat() const
Matrix< MatrixTransformType< CType >() > Transform< CType >::mat() const
{
return Matrix< MatrixTransformType< CType >() >( mat4() );
}
template < CoordinateSpace CType >
NormalVector TransformComponent< CType >::forward() const
NormalVector Transform< CType >::forward() const
{
return rotation.forward();
}
template < CoordinateSpace CType >
NormalVector TransformComponent< CType >::right() const
NormalVector Transform< CType >::right() const
{
return rotation.right();
}
template < CoordinateSpace CType >
NormalVector TransformComponent< CType >::up() const
NormalVector Transform< CType >::up() const
{
return rotation.up();
}
template struct TransformComponent< CoordinateSpace::World >;
template struct TransformComponent< CoordinateSpace::Model >;
template struct Transform< CoordinateSpace::World >;
template struct Transform< CoordinateSpace::Model >;
} // namespace fgl::engine

View File

@@ -35,15 +35,15 @@ namespace fgl::engine
FGL_UNREACHABLE();
};
//TransformComponent is always in world space
//Transform is always in world space
template < CoordinateSpace CType = CoordinateSpace::World >
struct TransformComponent
struct Transform
{
Coordinate< CType > translation { constants::WORLD_CENTER };
Scale scale { 1.0f, 1.0f, 1.0f };
UniversalRotation rotation { constants::DEFAULT_ROTATION };
//TODO: Figure this out and replace TransformComponent with a template of CType instead
//TODO: Figure this out and replace Transform with a template of CType instead
[[nodiscard]] glm::mat4 mat4() const;
[[nodiscard]] Matrix< MatrixTransformType< CType >() > mat() const;
@@ -61,8 +61,17 @@ namespace fgl::engine
[[nodiscard]] NormalVector down() const { return -up(); }
};
template < CoordinateSpace CType = CoordinateSpace::World >
struct UniversalTransform
{
std::variant< Transform< CType >, glm::mat4 > m_variant;
//! Converts the transform into a matrix
void decay() { m_variant = std::get< Transform< CType > >( m_variant ).mat4(); }
};
template < CoordinateSpace CType, MatrixType MType >
TransformComponent< CType > decompose( const glm::mat4 matrix )
Transform< CType > decompose( const glm::mat4 matrix )
{
glm::mat4 localMatrix = matrix;
@@ -133,8 +142,8 @@ namespace fgl::engine
}
// A game object will be going from world to camera space
using GameObjectTransform = TransformComponent< CoordinateSpace::World >;
// using ModelTransform = TransformComponent< CoordinateSpace::Model >;
using WorldTransform = TransformComponent< CoordinateSpace::World >;
using GameObjectTransform = Transform< CoordinateSpace::World >;
// using ModelTransform = Transform< CoordinateSpace::Model >;
using WorldTransform = Transform< CoordinateSpace::World >;
} // namespace fgl::engine

View File

@@ -10,7 +10,7 @@
#include "BoundingBox.hpp"
#include "engine/constants.hpp"
#include "engine/primitives/Scale.hpp"
#include "engine/primitives/TransformComponent.hpp"
#include "engine/primitives/Transform.hpp"
#include "engine/primitives/matricies/Matrix.hpp"
#include "engine/primitives/points/Coordinate.hpp"
@@ -43,7 +43,7 @@ namespace fgl::engine
union
{
TransformComponent< CType > m_transform;
Transform< CType > m_transform;
glm::mat4 m_matrix;
};
@@ -64,7 +64,7 @@ namespace fgl::engine
assert( inital_scale != constants::DEFAULT_VEC3 );
}
[[nodiscard]] OrientedBoundingBox( const TransformComponent< CType >& transform ) :
[[nodiscard]] OrientedBoundingBox( const Transform< CType >& transform ) :
transform_mode( TransformMode::Transform ),
m_transform( transform )
{
@@ -131,14 +131,14 @@ namespace fgl::engine
TransformMode getMode() const { return transform_mode; }
[[nodiscard]] TransformComponent< CType >& getTransform()
[[nodiscard]] Transform< CType >& getTransform()
{
FGL_ASSERT(
transform_mode == TransformMode::Transform, "FIXME: Transform not initalized. Mode switch needed" );
return m_transform;
}
[[nodiscard]] const TransformComponent< CType >& getTransform() const
[[nodiscard]] const Transform< CType >& getTransform() const
{
FGL_ASSERT(
transform_mode == TransformMode::Transform, "FIXME: Transform not initalized. Mode switch needed" );

View File

@@ -47,6 +47,12 @@ namespace fgl::engine
return point - ( this->getDirection() * distance );
}
template < CoordinateSpace CType >
SimplePlane< CType > PointPlane< CType >::toSimple() const
{
return SimplePlane< CType >( m_vector, -glm::dot( m_vector.vec(), m_coordinate.vec() ) );
}
template class PointPlane< CoordinateSpace::World >;
template class PointPlane< CoordinateSpace::Model >;
template class PointPlane< CoordinateSpace::Screen >;

View File

@@ -14,6 +14,43 @@ namespace fgl::engine
class Vector;
/**
* @brief
* @tparam CType
*/
template < CoordinateSpace CType >
class FGL_PACKED SimplePlane
{
NormalVector m_vector;
float m_distance;
public:
/**
* @param vector
* @param distance
* @return A new instance of a simple plane with the given dimensions.
*/
[[nodiscard]] SimplePlane( NormalVector vector, float distance ) : m_vector( vector ), m_distance( distance ) {}
[[nodiscard]] NormalVector getDirection() const { return m_vector; }
[[nodiscard]] float distance() const { return m_distance; }
[[nodiscard]] float distanceFrom( const Coordinate< CType >& coord ) const
{
return glm::dot( m_vector.vec(), coord.vec() ) + m_distance;
}
[[nodiscard]] bool isForward( const Coordinate< CType >& coord ) const { return distanceFrom( coord ) > 0.0f; }
[[nodiscard]] Coordinate< CType > mapToPlane( const Coordinate< CType >& point ) const
{
const float dist = distanceFrom( point );
return Coordinate< CType >( point.vec() - dist * m_vector.vec() );
}
};
template < CoordinateSpace CType >
class PointPlane
{
@@ -31,19 +68,24 @@ namespace fgl::engine
PointPlane( Coordinate< CType > pos, NormalVector vec );
FGL_FORCE_INLINE NormalVector getDirection() const { return m_vector; }
FGL_FORCE_INLINE [[nodiscard]] NormalVector getDirection() const { return m_vector; }
float distance() const;
[[nodiscard]] float distance() const;
FGL_FORCE_INLINE Coordinate< CType > getPosition() const { return m_coordinate; }
float distanceFrom( const Coordinate< CType > coord ) const;
float distanceFrom( Coordinate< CType > coord ) const;
bool isForward( const Coordinate< CType > coord ) const { return distanceFrom( coord ) > 0.0f; }
Coordinate< CType > mapToPlane( const Coordinate< CType > point ) const;
[[nodiscard]] Coordinate< CType > mapToPlane( Coordinate< CType > point ) const;
[[nodiscard]] SimplePlane< CType > toSimple() const;
};
static_assert(
sizeof( glm::vec4 ) == sizeof( SimplePlane< CoordinateSpace::World > ), "SimplePlane was not 4 floats" );
template < CoordinateSpace CType >
using Plane = PointPlane< CType >;

View File

@@ -59,6 +59,8 @@ namespace fgl::engine
Vector operator*( const float scalar ) const;
NormalVector operator-() const { return NormalVector( -static_cast< glm::vec3 >( *this ) ); }
explicit operator glm::vec3() const { return static_cast< glm::vec3 >( *this ); }
};
} // namespace fgl::engine

View File

@@ -132,7 +132,8 @@ namespace fgl::engine
rendering_info.setColorAttachmentFormats( state.formats.colors );
rendering_info.setDepthAttachmentFormat( state.formats.depth );
if ( state.formats.depth != vk::Format::eUndefined )
rendering_info.setDepthAttachmentFormat( state.formats.depth );
if ( state.m_dynamic_state.size() > 0 ) info.setPDynamicState( &dynamic_state_create_info );
@@ -209,7 +210,7 @@ namespace fgl::engine
m_state->push_constant.stageFlags = flags;
}
PipelineBuilder::BuilderState::Formats::Formats() : depth( pickDepthFormat() )
PipelineBuilder::BuilderState::Formats::Formats()
{}
[[nodiscard]] vk::PipelineColorBlendAttachmentState& PipelineBuilder::BuilderState::addColorAttachment()
@@ -292,6 +293,11 @@ namespace fgl::engine
m_state->rasterization_info.cullMode = vk::CullModeFlagBits::eNone;
}
void PipelineBuilder::addDepthAttachment()
{
m_state->formats.depth = pickDepthFormat();
}
AttachmentBuilder PipelineBuilder::addAttachment()
{
return { *this };

View File

@@ -72,7 +72,7 @@ namespace fgl::engine
struct Formats
{
std::vector< vk::Format > colors {};
vk::Format depth;
vk::Format depth { vk::Format::eUndefined };
Formats();
} formats {};
@@ -106,6 +106,7 @@ namespace fgl::engine
void disableCulling();
void addDepthAttachment();
[[nodiscard]] AttachmentBuilder addAttachment();
[[nodiscard]] AttachmentBuilder addColorAttachment();

View File

@@ -0,0 +1,10 @@
//
// Created by kj16609 on 2/28/25.
//
#include "Scene.hpp"
namespace fgl::engine
{
Scene::Scene( const std::string& name ) : m_name( name )
{}
} // namespace fgl::engine

View File

@@ -0,0 +1,33 @@
//
// Created by kj16609 on 2/28/25.
//
#pragma once
#include "camera/Camera.hpp"
namespace fgl::engine
{
class GameObject;
class World;
class Scene
{
std::string m_name {};
std::vector< std::shared_ptr< GameObject > > m_objects {};
public:
Scene() = delete;
FGL_DELETE_MOVE( Scene );
FGL_DELETE_COPY( Scene );
std::string& getName() { return m_name; };
const std::string& getName() const { return m_name; };
//!! Loads a scene from a gltf file
Scene( const std::string& name );
};
} // namespace fgl::engine

View File

@@ -0,0 +1,44 @@
//
// Created by kj16609 on 3/2/25.
//
#include "World.hpp"
namespace fgl::engine
{
bool World::sceneExists( const std::string& name )
{
return std::ranges::
find_if( m_scenes, [ &name ]( const auto& scene ) -> bool { return scene.getName() == name; } )
!= m_scenes.end();
}
std::shared_ptr< Scene > World::createScene()
{
constexpr std::string default_scene { "Scene" };
std::string name { default_scene };
// keep finding new name until we find one not used yet.
if ( sceneExists( name ) )
{
std::uint8_t i { 1 };
name = std::format( "Scene {}", i );
while ( sceneExists( name ) )
{
name = std::format( "Scene {}", ++i );
}
}
auto ptr { std::make_shared< Scene >( name ) };
m_scenes.push_back( ptr );
return ptr;
}
World::World( const std::filesystem::path& path )
{}
} // namespace fgl::engine

View File

@@ -0,0 +1,39 @@
//
// Created by kj16609 on 3/2/25.
//
#pragma once
#include <vector>
#include "Scene.hpp"
namespace fgl::engine
{
/** A world is the highest representation of scenes in an engine,
* a world is comprised up of multiple scenes. Only one world can be running at any given time
*
*/
class World
{
std::vector< std::shared_ptr< Scene > > m_scenes {};
std::shared_ptr< ModelManager > m_model_manager { std::make_shared< ModelManager >() };
public:
bool sceneExists( const std::string& name );
std::shared_ptr< Scene > createScene();
FGL_DEFAULT_ALL_RO5( World );
explicit World( const std::filesystem::path& path );
explicit World( std::vector< std::byte >& data );
//! Dumps the world save info to a vector
std::vector< std::byte > dump();
~World() = default;
};
} // namespace fgl::engine

View File

@@ -8,7 +8,6 @@
#include "engine/FrameInfo.hpp"
#include "engine/camera/Camera.hpp"
#include "engine/tree/octtree/OctTreeNode.hpp"
namespace fgl::engine
{

View File

@@ -1,157 +0,0 @@
//
// Created by kj16609 on 3/14/24.
//
#include "DrawPair.hpp"
#include <tracy/TracyC.h>
#include <unordered_map>
#include "engine/assets/model/Model.hpp"
#include "engine/debug/drawers.hpp"
#include "engine/debug/profiling/counters.hpp"
#include "engine/gameobjects/components/ModelComponent.hpp"
#include "engine/math/intersections.hpp"
#include "engine/tree/octtree/OctTreeNode.hpp"
namespace fgl::engine
{
std::pair< std::vector< vk::DrawIndexedIndirectCommand >, std::vector< ModelMatrixInfo > > getDrawCallsFromTree(
OctTreeNode& root,
const Frustum& frustum,
const GameObjectFlagType game_object_flags,
const TreeFilterFlags tree_flags,
std::function< bool( const GameObject& ) > filterFunc )
{
ZoneScoped;
std::unordered_map< DrawKey, DrawPair > draw_pairs {};
draw_pairs.reserve( 512 );
const auto nodes { root.getAllLeafsInFrustum( frustum ) };
for ( auto* node : nodes )
{
ZoneScopedN( "Process leaf" );
for ( const auto& obj : *node )
{
ZoneScopedN( "Process object" );
if ( ( obj.flags() & game_object_flags ) != game_object_flags ) continue;
if ( !filterFunc( obj ) ) continue;
//Check if we have a renderable component
if ( !obj.hasComponent< ModelComponent >() ) continue;
const auto model_components { obj.getComponents< ModelComponent >() };
const Matrix< MatrixType::ModelToWorld > obj_matrix { obj.getTransform().mat() };
for ( const auto* model_component_ptr : model_components )
{
const auto& model_transform { model_component_ptr->m_transform };
const Matrix< MatrixType::ModelToWorld > world_matrix { model_transform.mat() * obj_matrix };
const auto& comp { *model_component_ptr };
for ( const Primitive& primitive : comp->m_primitives )
{
if ( !primitive.ready() ) continue;
// Does this primitive pass the bounds check
const OrientedBoundingBox< CoordinateSpace::World > world_bounding_box {
world_matrix * primitive.getBoundingBox()
};
// No. Skip it
if ( !intersects( frustum, world_bounding_box ) ) continue;
//assert( primitive.m_texture );
const ModelMatrixInfo matrix_info { .model_matrix = world_matrix,
.material_id = primitive.m_material->getID() };
// If the textureless flag is on and we have a texture then skip the primitive.c
if ( tree_flags & IsTextureless )
{
if ( primitive.m_material != nullptr ) continue;
}
else
{
// Flag is not present
if ( primitive.m_material == nullptr ) continue;
}
const auto key {
std::make_pair( matrix_info.material_id, primitive.m_index_buffer.getOffset() )
};
assert( primitive.m_index_buffer.size() > 0 );
profiling::addVertexDrawn( primitive.m_index_buffer.size() );
if ( auto itter = draw_pairs.find( key ); itter != draw_pairs.end() )
{
ZoneScopedN( "Accumulate for draw pair" );
//Draw command for this mesh already exists. Simply add a count to it
auto& [ itter_key, pair ] = *itter;
auto& [ existing_cmd, model_matrix ] = pair;
existing_cmd.instanceCount += 1;
model_matrix.emplace_back( matrix_info );
assert( model_matrix.size() == existing_cmd.instanceCount );
}
else
{
ZoneScopedN( "Create new draw pair" );
vk::DrawIndexedIndirectCommand cmd {};
cmd.firstIndex = primitive.m_index_buffer.getOffsetCount();
cmd.indexCount = primitive.m_index_buffer.size();
cmd.vertexOffset = static_cast< int32_t >( primitive.m_vertex_buffer.getOffsetCount() );
cmd.instanceCount = 1;
std::vector< ModelMatrixInfo > matrix_infos {};
matrix_infos.reserve( 128 );
matrix_infos.emplace_back( matrix_info );
draw_pairs.emplace( key, std::make_pair( cmd, std::move( matrix_infos ) ) );
}
}
}
}
}
if ( draw_pairs.empty() )
{
return {};
}
std::vector< vk::DrawIndexedIndirectCommand > draw_commands {};
std::vector< ModelMatrixInfo > model_matrices {};
draw_commands.reserve( draw_pairs.size() );
model_matrices.reserve( draw_pairs.size() * 2 );
TracyCZoneN( filter_zone_TRACY, "Reorganize draw commands", true );
for ( auto& [ key, pair ] : draw_pairs )
{
auto cmd { pair.first };
assert( cmd != vk::DrawIndexedIndirectCommand() );
cmd.firstInstance = static_cast< std::uint32_t >( model_matrices.size() );
assert( cmd.instanceCount == pair.second.size() );
assert( pair.second.size() > 0 );
draw_commands.emplace_back( cmd );
model_matrices.insert( model_matrices.end(), pair.second.begin(), pair.second.end() );
}
TracyCZoneEnd( filter_zone_TRACY );
return { std::move( draw_commands ), std::move( model_matrices ) };
}
} // namespace fgl::engine

View File

@@ -1,67 +0,0 @@
//
// Created by kj16609 on 3/14/24.
//
#pragma once
#include <vulkan/vulkan.hpp>
#include "engine/gameobjects/GameObject.hpp"
#include "engine/utils.hpp"
namespace fgl::engine
{
struct Frustum;
class OctTreeNode;
struct ModelMatrixInfo;
// <TextureID, MemoryOffset>
using DrawKey = std::pair< TextureID, vk::DeviceSize >;
using DrawPair = std::pair< vk::DrawIndexedIndirectCommand, std::vector< ModelMatrixInfo > >;
inline bool operator<( const DrawPair& left, const DrawPair& right )
{
return left.first.firstIndex < right.first.firstIndex;
}
inline bool operator==( const DrawPair& left, const DrawPair& right )
{
return left.first.firstIndex == right.first.firstIndex && left.first.indexCount && right.first.indexCount;
}
inline bool defaultTrueFunc( [[maybe_unused]] const GameObject& )
{
return true;
}
enum TreeFilterFlags
{
IsTextureless = 1 << 0,
DefaultFlags = 0,
};
std::pair< std::vector< vk::DrawIndexedIndirectCommand >, std::vector< ModelMatrixInfo > > getDrawCallsFromTree(
OctTreeNode& root,
const Frustum& frustum,
GameObjectFlagType game_object_flags,
TreeFilterFlags tree_flags = DefaultFlags,
std::function< bool( const GameObject& ) > filterFunc = &defaultTrueFunc );
} // namespace fgl::engine
namespace std
{
template <>
struct hash< fgl::engine::DrawKey >
{
inline size_t operator()( const fgl::engine::DrawKey& key ) const noexcept
{
const auto id_hash { std::hash< fgl::engine::TextureID >()( key.first ) };
const auto offset_hash { std::hash< vk::DeviceSize >()( key.second ) };
size_t seed { 0 };
fgl::engine::hashCombine( seed, id_hash, offset_hash );
return seed;
}
};
} // namespace std

View File

@@ -7,14 +7,12 @@
#include <tracy/TracyC.h>
#include <vulkan/vulkan.hpp>
#include "DrawPair.hpp"
#include "engine/assets/material/Material.hpp"
#include "engine/camera/Camera.hpp"
#include "engine/debug/profiling/counters.hpp"
#include "engine/debug/timing/FlameGraph.hpp"
#include "engine/rendering/pipelines/v2/Pipeline.hpp"
#include "engine/rendering/pipelines/v2/PipelineBuilder.hpp"
#include "engine/tree/octtree/OctTreeNode.hpp"
namespace fgl::engine
{
@@ -22,50 +20,25 @@ namespace fgl::engine
{
ZoneScoped;
/*
{
// PipelineConfigInfo standard_info { render_pass };
// PipelineConfigInfo::addGBufferAttachmentsConfig( standard_info );
// PipelineConfigInfo textured_info { render_pass };
// PipelineConfigInfo::addGBufferAttachmentsConfig( textured_info );
PipelineBuilder builder { 0 };
PipelineBuilder builder { 0 };
builder.addDescriptorSet( Camera::getDescriptorLayout() );
addGBufferAttachments( builder );
addGBufferAttachments( builder );
builder.addDescriptorSet( Camera::getDescriptorLayout() );
builder.addDescriptorSet( Texture::getDescriptorLayout() );
builder.addDescriptorSet( Material::getDescriptorLayout() );
builder.setFragmentShader( Shader::loadFragment( "shaders/textureless-gbuffer.frag" ) );
builder.setVertexShader( Shader::loadVertex( "shaders/textureless-gbuffer.vert" ) );
builder.setFragmentShader( Shader::loadFragment( "shaders/textured.slang" ) );
builder.setVertexShader( Shader::loadVertex( "shaders/textured.slang" ) );
builder.setAttributeDescriptions( ModelVertex::getAttributeDescriptions() );
builder.setBindingDescriptions( ModelVertex::getBindingDescriptions() );
builder.setAttributeDescriptions( ModelVertex::getAttributeDescriptions() );
builder.setBindingDescriptions( ModelVertex::getBindingDescriptions() );
m_standard_pipeline = builder.create();
m_standard_pipeline->setDebugName( "Standard entity pipeline" );
}
*/
{
// PipelineConfigInfo textured_info { render_pass };
// PipelineConfigInfo::addGBufferAttachmentsConfig( textured_info );
PipelineBuilder builder { 0 };
addGBufferAttachments( builder );
builder.addDescriptorSet( Camera::getDescriptorLayout() );
builder.addDescriptorSet( Texture::getDescriptorLayout() );
builder.addDescriptorSet( Material::getDescriptorLayout() );
builder.setFragmentShader( Shader::loadFragment( "shaders/textured.slang" ) );
builder.setVertexShader( Shader::loadVertex( "shaders/textured.slang" ) );
builder.setAttributeDescriptions( ModelVertex::getAttributeDescriptions() );
builder.setBindingDescriptions( ModelVertex::getBindingDescriptions() );
m_textured_pipeline = builder.create();
m_textured_pipeline->setDebugName( "Textured entity pipeline" );
}
m_textured_pipeline = builder.create();
m_textured_pipeline->setDebugName( "Textured entity pipeline" );
}
EntityRendererSystem::~EntityRendererSystem()
@@ -97,60 +70,13 @@ namespace fgl::engine
texturedPass( info );
}
void EntityRendererSystem::texturelessPass( [[maybe_unused]] const FrameInfo& info )
{
/*
ZoneScopedN( "Textureless pass" );
auto& command_buffer { info.command_buffer };
TracyVkZone( info.tracy_ctx, *command_buffer, "Render textureless entities" );
//Bind pipeline
m_standard_pipeline->bind( command_buffer );
m_standard_pipeline->bindDescriptor( command_buffer, info.getCameraDescriptor() );
//Get all commands for drawing anything without a texture
auto [ draw_commands, model_matricies ] = getDrawCallsFromTree(
info.game_objects, info.camera->getFrustumBounds(), IS_VISIBLE | IS_ENTITY, IS_TEXTURELESS );
//TODO: Filter Textureless models (#6)
if ( draw_commands.size() == 0 ) return;
auto& model_matrix_info_buffer { m_simple_model_matrix_info_buffers[ info.frame_idx ] };
model_matrix_info_buffer =
std::make_unique< ModelMatrixInfoBufferSuballocation >( info.model_matrix_info_buffer, model_matricies );
auto& draw_parameter_buffer { m_draw_simple_parameter_buffers[ info.frame_idx ] };
draw_parameter_buffer =
std::make_unique< DrawParameterBufferSuballocation >( info.draw_parameter_buffer, draw_commands );
const std::vector< vk::Buffer > vert_buffers { info.model_vertex_buffer.getVkBuffer(),
model_matrix_info_buffer->getVkBuffer() };
command_buffer.bindVertexBuffers( 0, vert_buffers, { 0, model_matrix_info_buffer->getOffset() } );
command_buffer.bindIndexBuffer( info.model_index_buffer.getVkBuffer(), 0, vk::IndexType::eUint32 );
command_buffer.drawIndexedIndirect(
draw_parameter_buffer->getVkBuffer(),
draw_parameter_buffer->getOffset(),
draw_parameter_buffer->size(),
draw_parameter_buffer->stride() );
*/
}
void EntityRendererSystem::texturedPass( const FrameInfo& info )
{
ZoneScopedN( "Textured pass" );
auto& command_buffer { info.command_buffer.render_cb };
TracyVkZone( info.tracy_ctx, **command_buffer, "Render textured entities" );
auto [ draw_commands, model_matricies ] =
getDrawCallsFromTree( info.game_objects, info.camera->getFrustumBounds(), IsVisible | IsEntity );
if ( draw_commands.empty() ) return;
// compute shader
m_textured_pipeline->bind( command_buffer );
@@ -158,9 +84,6 @@ namespace fgl::engine
m_textured_pipeline->bindDescriptor( command_buffer, Texture::getDescriptorSet() );
m_textured_pipeline->bindDescriptor( command_buffer, Material::getDescriptorSet() );
profiling::addModelDrawn( model_matricies.size() );
profiling::addInstances( draw_commands.size() );
auto& model_matrix_info_buffer { m_textured_model_matrix_info_buffers[ info.frame_idx ] };
model_matrix_info_buffer =
std::make_unique< ModelMatrixInfoBufferSuballocation >( info.model_matrix_info_buffer, model_matricies );

View File

@@ -29,6 +29,7 @@ namespace fgl::engine
//! Pipeline for basic textured models (Single texture)
std::unique_ptr< Pipeline > m_textured_pipeline {};
// std::unique_ptr< ComputePipeline > m_cull_pipeline {};
using DrawParameterBufferSuballocation = HostVector< vk::DrawIndexedIndirectCommand >;

View File

@@ -0,0 +1,34 @@
//
// Created by kj16609 on 2/28/25.
//
#include "ShadowRenderer.hpp"
#include "FrameInfo.hpp"
namespace fgl::engine
{
CommandBuffer& ShadowRenderer::setupSystem( const FrameInfo& info )
{}
void ShadowRenderer::pass( FrameInfo& info )
{
// Render any shadowmaps attach to the camera
auto& command_buffer { setupSystem( info ) };
//TODO: Implement object culling for shadowmaps
if ( draw_commands.empty() ) return;
m_pipeline->bind( command_buffer );
}
ShadowRenderer::ShadowRenderer()
{
PipelineBuilder builder { 0 };
builder.setVertexShader( Shader::loadVertex( "shaders/shadowmap.slang" ) );
m_pipeline = builder.create();
m_pipeline->setDebugName( "Shadow map pipeline" );
}
} // namespace fgl::engine

View File

@@ -0,0 +1,30 @@
//
// Created by kj16609 on 2/28/25.
//
#pragma once
#include <memory>
#include "memory/buffers/vector/HostVector.hpp"
#include "rendering/pipelines/v2/Pipeline.hpp"
namespace fgl::engine
{
struct FrameInfo;
struct ModelMatrixInfo;
class ShadowRenderer
{
std::unique_ptr< Pipeline > m_pipeline {};
using DrawParameterBuffer = HostVector< vk::DrawIndexedIndirectCommand >;
using DrawIndexedIndirectCommand = HostVector< ModelMatrixInfo >;
public:
CommandBuffer& setupSystem( const FrameInfo& info );
void pass( FrameInfo& info );
ShadowRenderer();
};
} // namespace fgl::engine

View File

@@ -1,99 +0,0 @@
//
// Created by kj16609 on 11/3/24.
//
#include "Chunk.hpp"
#include "engine/math/intersections.hpp"
#include "engine/primitives/boxes/AxisAlignedBoundingCube.hpp"
#include "engine/utils.hpp"
/*
namespace fgl::engine::tree
{
std::shared_ptr< Chunk > ChunkManager::createChunk( const ChunkID id )
{
std::shared_ptr< Chunk > chunk { std::make_shared< Chunk >( id ) };
m_chunks.insert( std::make_pair( id, chunk ) );
return chunk;
}
void ChunkManager::cleanup()
{
std::lock_guard guard { m_delete_mtx };
while ( m_delete_list.size() > 0 )
{
const auto item { m_delete_list.front() };
m_delete_list.pop();
auto itter { m_chunks.find( item->getID() ) };
m_chunks.erase( itter );
}
}
std::shared_ptr< Chunk > ChunkManager::getChunk( const ChunkID id )
{}
void ChunkManager::markForDeletion( std::shared_ptr< Chunk >& chunk )
{
m_delete_list.push( chunk );
}
ChunkManager& ChunkManager::getInstance()
{
static ChunkManager manager {};
return manager;
}
ChunkID getID( const glm::vec3 point )
{
// the inital chunk starts at 0,0,0. meaning that the bounds of the inital chunk goes from
// +/- CHUNK_SIZE / 2.0f
// We need to start by dividing the point into the size of the chunks.
constexpr glm::vec3 CHUNK_VEC3 { Chunk::CHUNK_HALF };
const glm::vec3 chunk_pos { point / CHUNK_VEC3 };
// chunk_pos now needs to be clamped to interegers, we always round DOWN.
const glm::vec3 round_offset { glm::sign( chunk_pos ) * glm::vec3( 0.5f ) };
const glm::vec3 chunk_coords { glm::round( chunk_pos + round_offset ) };
const glm::vec< 3, int > chunk_coords_i { chunk_coords };
ChunkID hash { 0 };
engine::hashCombine( hash, chunk_coords_i[ 0 ], chunk_coords_i[ 1 ], chunk_coords_i[ 2 ] );
return hash;
}
std::shared_ptr< Chunk > Chunk::getShared()
{
return shared_from_this();
}
Chunk::Chunk( const ChunkID id ) : m_id( id ), m_center( getPosition( id ) )
{}
ChunkID Chunk::getID() const
{
return m_id;
}
bool Chunk::isVisible( const Frustum& frustum ) const
{
// Create a bounding box of this chunk
const AxisAlignedBoundingCube< CS::World > bounds { WorldCoordinate( m_center ), CHUNK_HALF };
return intersects( frustum, bounds );
}
void Chunk::deleteLater()
{
// ChunkManager::getInstance().markForDeletion( this->getShared() );
}
} // namespace fgl::engine::tree
*/

View File

@@ -1,85 +0,0 @@
//
// Created by kj16609 on 11/2/24.
//
#pragma once
#include <memory>
#include <queue>
#include <unordered_map>
#include "engine/gameobjects/GameObject.hpp"
/*
namespace fgl::engine
{
struct Frustum;
}
namespace fgl::engine::tree
{
class Chunk;
using ChunkID = std::size_t;
class ChunkManager
{
std::unordered_map< ChunkID, std::shared_ptr< Chunk > > m_chunks {};
std::mutex m_delete_mtx {};
std::queue< std::shared_ptr< Chunk > > m_delete_list {};
std::shared_ptr< Chunk > createChunk( ChunkID id );
//! Deletes any chunks pending deletion
void cleanup();
//! Returns a shared pointer to the chunk with the given ID.
std::shared_ptr< Chunk > getChunk( ChunkID id );
public:
void markForDeletion( std::shared_ptr< Chunk >& chunk );
static ChunkManager& getInstance();
};
ChunkID getID( const glm::vec3 point );
glm::vec3 getPosition( const ChunkID id );
class Chunk : public std::enable_shared_from_this< Chunk >
{
//! Determines if the chunk is active to the rendering system
bool m_rendering_active { true };
const ChunkID m_id;
const glm::vec3 m_center;
//! Contains a list of all objects within this chunk
std::unordered_map< GameObject::GameObjectID, std::shared_ptr< GameObject > > m_objects {};
std::shared_ptr< Chunk > getShared();
public:
Chunk() = delete;
Chunk( const ChunkID id );
ChunkID getID() const;
void addGameObject( std::shared_ptr< GameObject > object );
// Size of a chunk from center to
constexpr static float CHUNK_SIZE { 100.0f };
constexpr static float CHUNK_HALF { CHUNK_SIZE / 2.0f };
//! Returns the number of game objects within this chunk
std::size_t childCount();
//! Returns true if the bounds of this chunk are visible.
bool isVisible( const Frustum& frustum ) const;
//! Marks this node to be deleted later
void deleteLater();
};
} // namespace fgl::engine::tree
*/

View File

@@ -1,9 +0,0 @@
//
// Created by kj16609 on 1/24/25.
//
#include "BVHTree.hpp"
namespace fgl::engine
{
}

View File

@@ -1,45 +0,0 @@
//
// Created by kj16609 on 1/24/25.
//
#pragma once
#include <cstdint>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "glm/vec3.hpp"
#pragma GCC diagnostic pop
namespace fgl::engine
{
using BVHIndex = std::uint32_t;
class BVHTree
{
struct GameObjectInfo
{};
struct BVHNode
{
using FlagType = std::uint8_t;
enum Masks : FlagType
{
Flag_None = 0,
//! This node was visible during the last test
Flag_Previously_Visible = 1 << 0,
//! This node was not visible during the last test
Flag_Previously_Invisible = 1 << 1,
};
FlagType m_flags { Masks::Flag_None };
glm::vec3 m_centerpoint;
//! Index to try next if we hit this index.
BVHIndex m_hit;
//! Index t otry if we fail this index.
BVHIndex m_miss;
};
public:
};
} // namespace fgl::engine

View File

@@ -1,732 +0,0 @@
//
// Created by kj16609 on 3/1/24.
//
#include "OctTreeNode.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <engine/FrameInfo.hpp>
#include <glm/gtx/string_cast.hpp>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Weffc++"
#include <imgui.h>
#pragma GCC diagnostic pop
#include "engine/assets/model/Model.hpp"
#include "engine/clock.hpp"
#include "engine/debug/drawers.hpp"
#include "engine/math/intersections.hpp"
#include "engine/primitives/Frustum.hpp"
namespace fgl::engine
{
struct FrameInfo;
static bool draw_leaf_fit_bounds { false };
static bool draw_leaf_real_bounds { false };
static bool draw_inview_bounds { false };
static bool draw_branches { false };
static bool draw_model_bounding_boxes { false };
static std::size_t number_moved { 0 };
static std::optional< std::chrono::microseconds > time { std::nullopt };
void imGuiOctTreeSettings( const FrameInfo& info )
{
if ( ImGui::CollapsingHeader( "OctTree debug settings" ) )
{
ImGui::Checkbox( "Draw leaf fitted bounding boxes", &draw_leaf_fit_bounds );
ImGui::Checkbox( "Draw leaf real bounding boxes", &draw_leaf_real_bounds );
ImGui::Checkbox( "Draw ALL in view bounding boxes", &draw_inview_bounds );
ImGui::Checkbox( "Draw branches", &draw_branches );
ImGui::Checkbox( "Draw all model bounding boxes", &draw_model_bounding_boxes );
if ( ImGui::Button( "Reorganize Octtree" ) )
{
const auto start { fgl::Clock::now() };
number_moved = info.game_objects.reorganize();
const auto end { fgl::Clock::now() };
const auto time_diff { end - start };
time = std::chrono::duration_cast< std::chrono::microseconds >( time_diff );
}
if ( ImGui::Button( "Recalculate Bounds" ) )
{
const auto start { fgl::Clock::now() };
info.game_objects.recalculateChildBounds();
const auto end { fgl::Clock::now() };
const auto time_diff { end - start };
time = std::chrono::duration_cast< std::chrono::microseconds >( time_diff );
}
if ( ImGui::Button( "Optimize Octree Travel" ) )
{
info.game_objects.optimizePath();
}
if ( time.has_value() )
{
ImGui::Text( "Time spent reorganizing: %.2ldus", time.value().count() );
ImGui::Text( "Moved %ld objects", number_moved );
}
}
}
void OctTreeNode::getAllLeafsInFrustum( const Frustum& frustum, std::vector< OctTreeNodeLeaf* >& out_leafs )
{
ZoneScoped;
auto& leafs { out_leafs };
switch ( m_node_data.index() )
{
case 0: // NodeArray
{
//Check if we are inside the frustum.
if ( !isInFrustum( frustum ) ) return;
assert( std::holds_alternative< OctTreeNodeArray >( m_node_data ) );
const OctTreeNodeArray& node_array { std::get< OctTreeNodeArray >( m_node_data ) };
//Search deeper
if ( m_skip != nullptr )
{
m_skip->getAllLeafsInFrustum( frustum, out_leafs );
return;
}
node_array[ LEFT ][ FORWARD ][ TOP ]->getAllLeafsInFrustum( frustum, out_leafs );
node_array[ LEFT ][ FORWARD ][ BOTTOM ]->getAllLeafsInFrustum( frustum, out_leafs );
node_array[ LEFT ][ BACK ][ TOP ]->getAllLeafsInFrustum( frustum, out_leafs );
node_array[ LEFT ][ BACK ][ BOTTOM ]->getAllLeafsInFrustum( frustum, out_leafs );
node_array[ RIGHT ][ FORWARD ][ TOP ]->getAllLeafsInFrustum( frustum, out_leafs );
node_array[ RIGHT ][ FORWARD ][ BOTTOM ]->getAllLeafsInFrustum( frustum, out_leafs );
node_array[ RIGHT ][ BACK ][ TOP ]->getAllLeafsInFrustum( frustum, out_leafs );
node_array[ RIGHT ][ BACK ][ BOTTOM ]->getAllLeafsInFrustum( frustum, out_leafs );
return;
}
case 1: // NodeLeaf
{
OctTreeNodeLeaf& leaf { std::get< OctTreeNodeLeaf >( m_node_data ) };
if ( leaf.empty() ) return;
//Check if we are inside the frustum.
if ( !isInFrustum( frustum ) ) return;
assert( std::holds_alternative< OctTreeNodeLeaf >( m_node_data ) );
leafs.emplace_back( &leaf );
//debug::world::drawBoundingBox( m_bounds );
return;
}
default:
FGL_UNREACHABLE();
}
FGL_UNREACHABLE();
}
OctTreeNode& OctTreeNode::operator[]( const WorldCoordinate coord ) const
{
assert( std::holds_alternative< OctTreeNodeArray >( m_node_data ) );
// Bounding box center
const auto bounds_center { this->m_bounds.getPosition().vec() };
const auto coordinate_center { coord.vec() };
//const auto test_dim { glm::greaterThanEqual( coord.vec(), bounds_center ) };
//const auto& node_array { std::get< OctTreeNodeArray >( m_node_data ) };
//const auto& node { node_array[ test_dim.x ? 1 : 0 ][ test_dim.y ? 1 : 0 ][ test_dim.z ? 1 : 0 ] };
const std::size_t x_idx { coordinate_center.x > bounds_center.x ? 1ul : 0ul };
const std::size_t y_idx { coordinate_center.y > bounds_center.y ? 1ul : 0ul };
const std::size_t z_idx { coordinate_center.z > bounds_center.z ? 1ul : 0ul };
const auto& node { std::get< OctTreeNodeArray >( m_node_data )[ x_idx ][ y_idx ][ z_idx ] };
FGL_ASSERT( node, "Node was invalid!" );
FGL_ASSERT( node->canContain( coord ), "Node was not capable of containing the object!" );
return *node.get();
}
OctTreeNode::OctTreeNode( const WorldCoordinate center, const float span, OctTreeNode* parent ) :
m_fit_bounding_box( center, glm::vec3( glm::abs( span ) ) ),
m_bounds( center, glm::abs( span ) ),
m_node_data( OctTreeNodeLeaf() ),
m_parent( parent )
{
assert( std::holds_alternative< OctTreeNodeLeaf >( m_node_data ) );
std::get< OctTreeNodeLeaf >( m_node_data ).reserve( MAX_NODES_IN_LEAF );
}
OctTreeNode::~OctTreeNode()
{}
void OctTreeNode::split( const int depth )
{
ZoneScoped;
if ( std::holds_alternative< OctTreeNodeArray >( m_node_data ) ) return;
auto game_objects { std::get< OctTreeNodeLeaf >( std::move( m_node_data ) ) };
//Figure out the half span
const float half_span { m_bounds.span() / 2.0f };
const Coordinate< CoordinateSpace::World > center { m_bounds.getPosition() };
OctTreeNodeArray new_nodes {};
const float left_x { center.x - half_span };
const float right_x { center.x + half_span };
const float forward_y { center.y + half_span };
const float backward_y { center.y - half_span };
const float top_z { center.z + half_span };
const float bottom_z { center.z - half_span };
assert( !std::isinf( left_x ) && !std::isinf( right_x ) );
assert( !std::isinf( forward_y ) && !std::isinf( backward_y ) );
assert( !std::isinf( top_z ) && !std::isinf( bottom_z ) );
new_nodes[ LEFT ][ FORWARD ][ TOP ] =
std::make_unique< OctTreeNode >( WorldCoordinate( left_x, forward_y, top_z ), half_span, this );
new_nodes[ LEFT ][ FORWARD ][ BOTTOM ] =
std::make_unique< OctTreeNode >( WorldCoordinate( left_x, forward_y, bottom_z ), half_span, this );
new_nodes[ LEFT ][ BACK ][ TOP ] =
std::make_unique< OctTreeNode >( WorldCoordinate( left_x, backward_y, top_z ), half_span, this );
new_nodes[ LEFT ][ BACK ][ BOTTOM ] =
std::make_unique< OctTreeNode >( WorldCoordinate( left_x, backward_y, bottom_z ), half_span, this );
new_nodes[ RIGHT ][ FORWARD ][ TOP ] =
std::make_unique< OctTreeNode >( WorldCoordinate( right_x, forward_y, top_z ), half_span, this );
new_nodes[ RIGHT ][ FORWARD ][ BOTTOM ] =
std::make_unique< OctTreeNode >( WorldCoordinate( right_x, forward_y, bottom_z ), half_span, this );
new_nodes[ RIGHT ][ BACK ][ TOP ] =
std::make_unique< OctTreeNode >( WorldCoordinate( right_x, backward_y, top_z ), half_span, this );
new_nodes[ RIGHT ][ BACK ][ BOTTOM ] =
std::make_unique< OctTreeNode >( WorldCoordinate( right_x, backward_y, bottom_z ), half_span, this );
FGL_ASSUME( game_objects.size() <= MAX_NODES_IN_LEAF )
for ( GameObject& obj : game_objects )
{
const auto& obj_coordinate { obj.getTransform().translation };
const bool is_right { obj_coordinate.x > center.x };
const bool is_forward { obj_coordinate.y > center.y };
const bool is_up { obj_coordinate.z > center.z };
const std::unique_ptr< OctTreeNode >& node {
new_nodes[ is_right ? 1 : 0 ][ is_forward ? 1 : 0 ][ is_up ? 1 : 0 ]
};
assert( std::holds_alternative< OctTreeNodeLeaf >( node->m_node_data ) );
std::get< OctTreeNodeLeaf >( node->m_node_data ).emplace_back( std::move( obj ) );
}
this->m_node_data = std::move( new_nodes );
recalculateChildBounds();
if ( depth - 1 >= 1 )
{
split( depth );
}
}
OctTreeNode* OctTreeNode::addGameObject( GameObject&& obj )
{
assert( this->canContain( obj ) );
if ( std::holds_alternative< OctTreeNodeLeaf >( m_node_data ) ) // This node is a leaf
{
auto& objects { std::get< OctTreeNodeLeaf >( m_node_data ) };
assert( objects.capacity() == MAX_NODES_IN_LEAF );
// If the amount of nodes is about to exceed the number of leafs, Then split the nodes
if ( objects.size() + 1 > MAX_NODES_IN_LEAF )
{
split();
auto* node { this->addGameObject( std::move( obj ) ) };
return node;
}
const bool should_recalc_bounds { obj.hasComponent< ModelComponent >() };
objects.emplace_back( std::move( obj ) );
if ( should_recalc_bounds ) recalculateLeafBounds();
return this;
}
if ( std::holds_alternative< OctTreeNodeArray >( m_node_data ) )
{
auto* node { ( *this )[ obj.getPosition() ].addGameObject( std::forward< GameObject >( obj ) ) };
return node;
}
FGL_UNREACHABLE();
}
bool OctTreeNode::isLeaf() const
{
return std::holds_alternative< OctTreeNodeLeaf >( m_node_data );
}
bool OctTreeNode::isBranch() const
{
return std::holds_alternative< OctTreeNodeArray >( m_node_data );
}
std::size_t OctTreeNode::itemCount() const
{
//TODO: Store this value in the nodes itself
if ( !isLeaf() )
{
std::size_t sum { 0 };
FOR_EACH_OCTTREE_NODE
{
sum += std::get< OctTreeNodeArray >( m_node_data )[ x ][ y ][ z ]->itemCount();
}
return sum;
}
return std::get< OctTreeNodeLeaf >( m_node_data ).size();
}
const OctTreeNodeArray& OctTreeNode::getBranches() const
{
return std::get< OctTreeNodeArray >( m_node_data );
}
const OctTreeNodeLeaf& OctTreeNode::getLeaf() const
{
return std::get< OctTreeNodeLeaf >( m_node_data );
}
OctTreeNodeLeaf& OctTreeNode::getLeaf()
{
return std::get< OctTreeNodeLeaf >( m_node_data );
}
bool OctTreeNode::isInFrustum( const Frustum& frustum ) const
{
#if ENABLE_IMGUI
if ( !isEmpty() && intersects( frustum, m_fit_bounding_box ) )
{
if ( isLeaf() && itemCount() > 0 ) [[unlikely]]
{
if ( draw_leaf_fit_bounds ) [[unlikely]]
debug::drawBoundingBox( m_fit_bounding_box );
if ( draw_leaf_real_bounds ) [[unlikely]]
debug::drawBoundingBox( m_bounds );
if ( draw_model_bounding_boxes ) [[unlikely]]
{
for ( const auto& obj : getLeaf() )
{
const Matrix< MatrixType::ModelToWorld > obj_transform { obj.getTransform().mat() };
for ( const auto* model : obj.getComponents< ModelComponent >() )
{
const auto model_bounds { ( *model )->getBoundingBox() };
const Matrix< MatrixType::ModelToWorld > model_transform { model->m_transform.mat() };
const auto combined_transform { obj_transform * model_transform };
debug::drawBoundingBox( combined_transform * model_transform );
}
}
}
}
return true;
}
return false;
#else
return !isEmpty() && intersects( frustum, m_fit_bounding_box );
#endif
}
bool OctTreeNode::isEmpty() const
{
return std::holds_alternative< OctTreeNodeLeaf >( m_node_data )
&& std::get< OctTreeNodeLeaf >( m_node_data ).empty();
}
/**
*
* @return Returns true if the fit bounding box is larger than the virtual bounds
*/
void OctTreeNode::recalculateBounds()
{
if ( isBranch() ) [[likely]]
{
recalculateNodeBounds();
return;
}
else
{
FGL_ASSERT( isLeaf(), "Expected leaf, Got whatever the fuck this is instead" );
recalculateLeafBounds();
return;
}
FGL_UNREACHABLE();
}
std::vector< OctTreeNodeLeaf* > OctTreeNode::getAllLeafs()
{
ZoneScoped;
std::vector< OctTreeNodeLeaf* > leafs {};
leafs.reserve( LEAF_RESERVE_SIZE );
this->getAllLeafs( leafs );
return leafs;
}
std::vector< OctTreeNodeLeaf* > OctTreeNode::getAllLeafsInFrustum( const Frustum& frustum )
{
ZoneScoped;
std::vector< OctTreeNodeLeaf* > leafs {};
leafs.reserve( LEAF_RESERVE_SIZE );
this->getAllLeafsInFrustum( frustum, leafs );
return leafs;
}
void OctTreeNode::clear()
{
if ( std::holds_alternative< OctTreeNodeLeaf >( this->m_node_data ) )
{
std::get< OctTreeNodeLeaf >( this->m_node_data ).clear();
}
else if ( std::holds_alternative< OctTreeNodeArray >( this->m_node_data ) )
{
const auto& node_array { std::get< OctTreeNodeArray >( this->m_node_data ) };
FOR_EACH_OCTTREE_NODE
{
node_array[ x ][ y ][ z ]->clear();
}
}
}
WorldCoordinate OctTreeNode::getCenter() const
{
return m_bounds.getPosition();
}
WorldCoordinate OctTreeNode::getFitCenter() const
{
return m_fit_bounding_box.getPosition();
}
void OctTreeNode::drawDebug() const
{}
void OctTreeNode::recalculateChildBounds()
{
if ( isBranch() )
{
FOR_EACH_OCTTREE_NODE
{
auto& node { std::get< OctTreeNodeArray >( m_node_data )[ x ][ y ][ z ] };
node->recalculateChildBounds();
}
}
else
{
recalculateBounds();
}
}
OctTreeNode* OctTreeNode::findID( const GameObject::GameObjectID id )
{
ZoneScoped;
if ( std::holds_alternative< OctTreeNodeLeaf >( this->m_node_data ) )
{
//We are the last node. Check if we have the ID
const auto& game_objects { std::get< OctTreeNodeLeaf >( m_node_data ) };
if ( std::ranges::
find_if( game_objects, [ id ]( const GameObject& obj ) noexcept { return obj.getId() == id; } )
!= game_objects.end() )
{
return this;
}
return nullptr;
}
if ( std::holds_alternative< OctTreeNodeArray >( this->m_node_data ) )
{
if ( m_skip != nullptr ) return m_skip->findID( id );
const auto& node_array { std::get< OctTreeNodeArray >( this->m_node_data ) };
FOR_EACH_OCTTREE_NODE
{
const auto& node { node_array[ x ][ y ][ z ]->findID( id ) };
if ( node != nullptr ) return node;
}
return nullptr;
}
FGL_UNREACHABLE();
}
auto OctTreeNode::getGameObjectItter( const GameObject::GameObjectID id )
{
assert( std::holds_alternative< OctTreeNodeLeaf >( this->m_node_data ) );
auto& game_objects { std::get< OctTreeNodeLeaf >( this->m_node_data ) };
return std::ranges::
find_if( game_objects, [ id ]( const GameObject& obj ) noexcept { return id == obj.getId(); } );
}
bool OctTreeNode::canContain( const GameObject& obj ) const
{
return canContain( obj.getTransform().translation );
}
bool OctTreeNode::canContain( const WorldCoordinate& coord ) const
{
const auto center { this->getCenter() };
// top right forward
const auto high_center { center.vec() + glm::vec3( this->m_bounds.scale() ) };
// bottom left back
const auto low_center { center.vec() - glm::vec3( this->m_bounds.scale() ) };
const bool under_high_center { glm::all( glm::lessThanEqual( coord.vec(), high_center ) ) };
const bool above_low_center { glm::all( glm::greaterThan( coord.vec(), low_center ) ) };
return under_high_center && above_low_center;
}
GameObject OctTreeNode::extract( const GameObject::GameObjectID id )
{
const auto itter { getGameObjectItter( id ) };
auto game_object { std::move( *itter ) };
auto& game_objects { std::get< OctTreeNodeLeaf >( this->m_node_data ) };
game_objects.erase( itter );
return game_object;
}
OctTreeNode* OctTreeNode::getRoot()
{
if ( m_parent == nullptr ) return this;
return m_parent->getRoot();
}
void OctTreeNode::getAllLeafs( std::vector< OctTreeNodeLeaf* >& out_leafs )
{
ZoneScoped;
if ( std::holds_alternative< OctTreeNodeLeaf >( m_node_data ) )
{
auto& leaf { std::get< OctTreeNodeLeaf >( m_node_data ) };
//No point in us giving back an empty leaf
if ( !leaf.empty() ) out_leafs.emplace_back( &leaf );
}
else
{
if ( m_skip != nullptr )
{
m_skip->getAllLeafs( out_leafs );
return;
}
const auto& nodes { std::get< OctTreeNodeArray >( m_node_data ) };
// If we have a node to skip to, Skip to it.
FOR_EACH_OCTTREE_NODE
{
auto ret { nodes[ x ][ y ][ z ]->getAllLeafs() };
out_leafs.insert( out_leafs.end(), ret.begin(), ret.end() );
}
}
}
OctTreeNode* OctTreeNode::optimizePath()
{
ZoneScoped;
// The node returned here will be the optimal node to jump to. If we return nullptr then that means this node has multiple paths it can take.
if ( std::holds_alternative< NodeDataT >( m_node_data ) )
{
const auto& nodes { std::get< NodeDataT >( m_node_data ) };
OctTreeNode* optimal { nullptr };
FOR_EACH_OCTTREE_NODE
{
// Why did I skip the 0,0,0 node previously?
// if ( x == 0 && y == 0 && z == 0 ) continue;
const auto& node { nodes[ x ][ y ][ z ] };
if ( auto* optimal_ret = node->optimizePath(); optimal_ret != nullptr )
{
// If the node returns nullptr, Then it means that this node shouldn't jump to any node directly.
if ( optimal != nullptr )
{
m_skip = nullptr;
return this;
}
optimal = optimal_ret;
}
}
m_skip = optimal;
return optimal;
}
if ( std::holds_alternative< LeafDataT >( m_node_data ) )
{
const auto& leaf_data { std::get< LeafDataT >( m_node_data ) };
if ( leaf_data.empty() ) return nullptr;
return this;
}
FGL_UNREACHABLE();
}
std::size_t OctTreeNode::reorganize()
{
ZoneScoped;
std::size_t counter { 0 };
if ( std::holds_alternative< NodeDataT >( m_node_data ) )
{
const auto& nodes { std::get< NodeDataT >( m_node_data ) };
FOR_EACH_OCTTREE_NODE
{
// Why did I skip the 0,0,0 node previously?
// if ( x == 0 && y == 0 && z == 0 ) continue;
const auto& node { nodes[ x ][ y ][ z ] };
counter += node->reorganize();
}
return counter;
}
if ( std::holds_alternative< LeafDataT >( m_node_data ) )
{
//Check if any of the nodes in this group need to be moved.
for ( auto& game_objects = std::get< LeafDataT >( m_node_data ); const auto& game_object : game_objects )
{
if ( !this->canContain( game_object ) )
{
++counter;
//Need to move this game object.
auto moved_game_object { this->extract( game_object ) };
//Insert at root
//TODO: See if we can optimize this by traveling UP the tree.
getRoot()->addGameObject( std::move( moved_game_object ) );
}
}
return counter;
}
FGL_UNREACHABLE();
}
bool OctTreeNode::isBoundsExpanded() const
{
return m_fit_bounding_box == m_bounds;
/*
const auto fit_points { m_fit_bounding_box.points() };
for ( const auto& p : fit_points )
{
// Return true if a point is outside the bounds. This indicates that out bounding box is bigger than our bounds.
if ( !m_bounds.contains( p ) ) return true;
}
return false;
*/
}
void OctTreeNode::recalculateNodeBounds()
{
FGL_ASSERT( std::holds_alternative< NodeDataT >( m_node_data ), "Node data was not an array!" );
const auto& nodes { std::get< NodeDataT >( m_node_data ) };
// We start out by telling all of our children to recalculate their bounds
m_fit_bounding_box = static_cast< AxisAlignedBoundingBox< CoordinateSpace::World > >( m_bounds );
FOR_EACH_OCTTREE_NODE
{
// If true then the bounds were bigger then the inital bounding box. So we should try to combine it without current bounding box.
m_fit_bounding_box = m_fit_bounding_box.combine( nodes[ x ][ y ][ z ]->m_fit_bounding_box );
}
// if ( isBoundsExpanded() && m_parent )
if ( m_parent != nullptr ) m_parent->recalculateBounds();
}
void OctTreeNode::recalculateLeafBounds()
{
FGL_ASSERT( std::holds_alternative< LeafDataT >( m_node_data ), "Node data was not a leaf!" );
const auto& data { std::get< LeafDataT >( m_node_data ) };
m_fit_bounding_box = static_cast< AxisAlignedBoundingBox< CoordinateSpace::World > >( m_bounds );
if ( data.empty() ) return;
// If true, Then the fit has already been set, and we should combine with it
bool fit_set { false };
for ( const auto& game_object : data )
{
const Matrix< MatrixType::ModelToWorld > game_object_transform { game_object.getTransform().mat() };
for ( const ModelComponent* model : game_object.getComponents< ModelComponent >() )
{
const OrientedBoundingBox< CS::Model > model_bounding_box { ( *model )->getBoundingBox() };
const Matrix< MatrixType::ModelToWorld > model_transform { model->m_transform.mat() };
// Combine the game object and model transform
const Matrix< MatrixType::ModelToWorld > combined_transform { model_transform * game_object_transform };
const OrientedBoundingBox< CoordinateSpace::World > world_bounding_box { combined_transform
* model_bounding_box };
const auto aligned_bounding_box { world_bounding_box.alignToWorld() };
if ( fit_set ) [[likely]]
m_fit_bounding_box = m_fit_bounding_box.combine( aligned_bounding_box );
else
{
m_fit_bounding_box = aligned_bounding_box;
fit_set = true;
}
}
}
// Have our parent recalculate its bounds
// if ( isBoundsExpanded() && m_parent )
if ( m_parent != nullptr ) m_parent->recalculateBounds();
}
} // namespace fgl::engine

View File

@@ -1,147 +0,0 @@
//
// Created by kj16609 on 3/1/24.
//
#pragma once
#include "engine/primitives/boxes/AxisAlignedBoundingCube.hpp"
#include "gameobjects/GameObject.hpp"
namespace fgl::engine
{
constexpr std::size_t MAX_NODES_IN_LEAF { 32 };
constexpr std::size_t STARTING_DEPTH { 1 };
constexpr float ROOT_SPAN { std::numeric_limits< float >::max() };
struct Frustum;
constexpr std::uint8_t TOP { 1 };
constexpr std::uint8_t BOTTOM { 0 };
constexpr std::uint8_t RIGHT { 1 };
constexpr std::uint8_t LEFT { 0 };
constexpr std::uint8_t FORWARD { 1 };
constexpr std::uint8_t BACK { 0 };
class OctTreeNode;
class GameObject;
using OctTreeNodeArray = std::array< std::array< std::array< std::unique_ptr< OctTreeNode >, 2 >, 2 >, 2 >;
using OctTreeNodeLeaf = std::vector< GameObject >;
static_assert( sizeof( OctTreeNodeArray ) == sizeof( OctTreeNode* ) * 2 * 2 * 2 );
static_assert( sizeof( OctTreeNode* ) == sizeof( std::uint64_t ) );
struct FrameInfo;
void imGuiOctTreeSettings( const FrameInfo& info );
class OctTreeNode
{
//! Fit to each model
AxisAlignedBoundingBox< CoordinateSpace::World > m_fit_bounding_box;
//! Real bounds of the node
AxisAlignedBoundingCube< CoordinateSpace::World > m_bounds;
using NodeDataT = OctTreeNodeArray;
using LeafDataT = OctTreeNodeLeaf;
std::variant< NodeDataT, LeafDataT > m_node_data;
OctTreeNode* m_parent;
//! Node to skip too when attempting to parse this node.
//! This is done in order to prevent navigating through an empty set of nodes.
//! This is only set if there is only one leaf filled with data.
OctTreeNode* m_skip { nullptr };
public:
OctTreeNode() = delete;
OctTreeNode( WorldCoordinate center, float span = ROOT_SPAN, OctTreeNode* parent = nullptr );
OctTreeNode( const OctTreeNode& other ) = delete;
OctTreeNode( OctTreeNode&& other ) = delete;
~OctTreeNode();
OctTreeNode& operator=( const OctTreeNode& ) = delete;
OctTreeNode& operator=( OctTreeNode&& ) = delete;
void clear();
WorldCoordinate getCenter() const;
WorldCoordinate getFitCenter() const;
void drawDebug() const;
void recalculateChildBounds();
private:
//! Returns the node of a given ID (Searches down)
OctTreeNode* findID( GameObject::GameObjectID id );
//! Returns true if the node contains a given ID
bool contains( const GameObject::GameObjectID id ) { return findID( id ) != nullptr; }
//! Splits a node. Does nothing if node is not a leaf.
void split( int depth = 1 );
OctTreeNode* getRoot();
//! returns true if this node should contain the given object
bool canContain( const GameObject& obj ) const;
bool canContain( const WorldCoordinate& coord ) const;
GameObject extract( GameObject::GameObjectID id );
GameObject extract( const GameObject& obj ) { return this->extract( obj.getId() ); }
bool isInFrustum( const Frustum& frustum ) const;
bool isEmpty() const;
auto getGameObjectItter( GameObject::GameObjectID id );
void getAllLeafs( std::vector< OctTreeNodeLeaf* >& out_leafs );
void getAllLeafsInFrustum( const Frustum& frustum, std::vector< OctTreeNodeLeaf* >& out_leafs );
OctTreeNode& operator[]( const WorldCoordinate coord ) const;
public:
//! Rebuilds the tree checking if nodes have moved.
std::size_t reorganize();
OctTreeNode* optimizePath();
//! Returns true if the fixed bounding box is larger then the inital bounding box
bool isBoundsExpanded() const;
void recalculateNodeBounds();
void recalculateLeafBounds();
void recalculateBounds();
constexpr static std::size_t LEAF_RESERVE_SIZE { 1024 };
[[nodiscard]] std::vector< OctTreeNodeLeaf* > getAllLeafs();
[[nodiscard]] std::vector< OctTreeNodeLeaf* > getAllLeafsInFrustum( const Frustum& frustum );
//! Adds a game object, Will split the node if the auto split threshold is reached
OctTreeNode* addGameObject( GameObject&& obj );
bool isLeaf() const;
bool isBranch() const;
std::size_t itemCount() const;
const OctTreeNodeArray& getBranches() const;
const OctTreeNodeLeaf& getLeaf() const;
OctTreeNodeLeaf& getLeaf();
};
#define FOR_EACH_OCTTREE_NODE \
for ( std::size_t x = 0; x < 2; ++x ) \
for ( std::size_t y = 0; y < 2; ++y ) \
for ( std::size_t z = 0; z < 2; ++z )
} // namespace fgl::engine

View File

@@ -1,77 +0,0 @@
//
// Created by kj16609 on 3/11/24.
//
#include "QuadTree.hpp"
#include "engine/gameobjects/GameObject.hpp"
namespace fgl::engine
{
bool QuadTreeNode::contains( const WorldCoordinate coord ) const
{
const auto centered_coordinate { coord - m_center };
const bool top_in_range { glm::all( glm::lessThanEqual( centered_coordinate.vec(), m_node_bounds ) ) };
const bool bottom_in_range { glm::all( glm::greaterThanEqual( centered_coordinate.vec(), -m_node_bounds ) ) };
return top_in_range && bottom_in_range;
}
QuadTreeNode& QuadTreeNode::operator[]( const WorldCoordinate pos )
{
assert( std::holds_alternative< QuadTreeNodeArray >( m_node_data ) );
const auto test_dim { glm::greaterThanEqual( pos.vec(), this->m_center.vec() ) };
const auto& node_array { std::get< QuadTreeNodeArray >( m_node_data ) };
const auto& node { node_array[ test_dim.x ][ test_dim.y ] };
assert( node );
return *node.get();
}
void QuadTreeNode::split( const int depth )
{
if ( std::holds_alternative< QuadTreeNodeArray >( m_node_data ) ) return;
auto leaf_data { std::move( std::get< QuadTreeNodeLeaf >( m_node_data ) ) };
QuadTreeNodeArray new_nodes {
{ { { std::make_unique< QuadTreeNode >(), std::make_unique< QuadTreeNode >() } },
{ { std::make_unique< QuadTreeNode >(), std::make_unique< QuadTreeNode >() } } }
};
m_node_data = std::move( new_nodes );
FGL_ASSUME( leaf_data.size() <= MAX_QUAD_NODES_IN_LEAF );
for ( auto& item : leaf_data )
{
const auto item_pos { item.getPosition() };
auto& node { ( *this )[ item_pos ] };
assert( std::holds_alternative< QuadTreeNodeLeaf >( node.m_node_data ) );
node.addGameObject( std::move( item ) );
}
if ( depth > 1 ) split( depth - 1 );
}
void QuadTreeNode::addGameObject( GameObject&& obj )
{
assert( contains( obj.getPosition() ) );
if ( std::holds_alternative< QuadTreeNodeLeaf >( m_node_data ) )
{
std::get< QuadTreeNodeLeaf >( m_node_data ).emplace_back( std::forward< GameObject >( obj ) );
return;
}
else
{
assert( std::holds_alternative< QuadTreeNodeArray >( m_node_data ) );
( *this )[ obj.getPosition() ].addGameObject( std::forward< GameObject >( obj ) );
return;
}
}
} // namespace fgl::engine

View File

@@ -1,45 +0,0 @@
//
// Created by kj16609 on 3/11/24.
//
#pragma once
#include <memory>
#include <vector>
#include "engine/primitives/Scale.hpp"
#include "engine/primitives/points/Coordinate.hpp"
namespace fgl::engine
{
class GameObject;
enum class CoordinateSpace;
class QuadTreeNode;
template < CoordinateSpace CType >
struct Frustum;
using QuadTreeNodeArray = std::array< std::array< std::unique_ptr< QuadTreeNode >, 2 >, 2 >;
using QuadTreeNodeLeaf = std::vector< GameObject >;
constexpr std::size_t MAX_QUAD_NODES_IN_LEAF { 8 };
class QuadTreeNode
{
WorldCoordinate m_center { constants::WORLD_CENTER };
Scale m_node_bounds { std::numeric_limits< float >::infinity() };
std::variant< QuadTreeNodeArray, QuadTreeNodeLeaf > m_node_data { QuadTreeNodeLeaf() };
bool contains( WorldCoordinate coord ) const;
public:
QuadTreeNode& operator[]( WorldCoordinate pos );
void split( int depth = 1 );
void addGameObject( GameObject&& obj );
};
} // namespace fgl::engine

View File

@@ -0,0 +1,5 @@
struct AxisAlignedBoundingBox
{
};

19
src/shaders/bvh.slang Normal file
View File

@@ -0,0 +1,19 @@
#version 450
typedef uint32_t BVHIndex;
typedef uint8_t BVHFlags;
public struct BVHNode
{
BVHFlags m_flags;
float3 m_centerpoint;
float3 m_scale;
float4 m_rotation;
BVHIndex next_hit;
BVHIndex next_miss;
}

View File

@@ -1,6 +1,6 @@
#version 450
import camera;
import objects.camera;
struct Vertex {
vec4 position : SV_Position;

87
src/shaders/culling.slang Normal file
View File

@@ -0,0 +1,87 @@
#version 450
import vk.drawindexedindirect;
import bounds.axisalignedbb;
import objects.frustum;
import objects.gamemodel;
struct ModelInstance
{
uint32_t model_index;
mat4x4 matrix;
};
RWStructuredBuffer< vk::DrawIndexedIndirectCommand > commands : COMMANDS;
RWStructuredBuffer< ModelInstance > instances : COMMAND_INSTANCES;
ConstantBuffer< AxisAlignedBoundingBox > bounding_boxes[] : BOUNDS;
ConstantBuffer< Frustum > frustum : FRUSTUM;
ConstantBuffer< ModelInstance > model_instances[] : MODEL_INSTANCES;
ConstantBuffer< GameModel > models[] : MODELS;
//TODO: shader command constants
struct PushConstants
{
// number of total models and their instances
uint32_t draw_count;
uint32_t start_idx;
uint32_t end_idx;
};
ConstantBuffer<PushConstants> pc;
[[shader("compute")]]
[numthreads(64,1,1)]
void main( uint3 dispatch_id : SV_DispatchThreadID)
{
// this will be dispatched with 0..N instances, each thread will be 1 instance from `model_instances`
uint instance_index = pc.start_idx + dispatch_id.x; // global thread
if ( dispatch_id.x > pc.end_idx ) return;
if ( instance_index > pc.draw_count ) return;
ModelInstance model_instance = model_instances[ instance_index ];
var model_index = model_instance.model_index;
GameModel model = models[ model_index ];
//TODO: Cull (For now we will just pretend it's all in view)
const bool in_view = true;
if ( in_view )
{
uint32_t old_instance_count = 0;
InterlockedAdd( commands[ model_index ].instance_count, 1, old_instance_count );
var command = &commands[ model_index ];
// if the old instance count was zero, we are the first command and need to populate.
if ( old_instance_count == 0 )
{
// first index, populate the command's data
//TODO: Populate from GameModel once i've actually implemented that class properly
command->first_index = 0;
command->index_count = 0;
command->vertex_offset = 0;
atomicAdd( command->first_instance, instance_index );
}
barrier();
var idx = command->first_instance + old_instance_count;
instances[ instance_index ] = model_instances[ idx ];
}
}

View File

@@ -1,7 +1,7 @@
#version 450
import camera;
import gbuffer;
import objects.camera;
import objects.gbuffer;
struct LineVertex
{

View File

@@ -0,0 +1,8 @@
#version 450
struct Frustum
{
};

View File

@@ -0,0 +1,9 @@
#version 450
struct GameModel {
};

View File

@@ -0,0 +1,11 @@
#version 450
struct GameObject
{
}

View File

@@ -0,0 +1,15 @@
import model.vertex;
struct CoarseVertex {
float4 position : SV_Position;
}
[shader("vertex")]
CoarseVertex vertexMain( ModelVertex in_vertex )
{
}

View File

@@ -1,8 +1,8 @@
#version 450
import model.vertex;
import camera;
import gbuffer;
import objects.camera;
import objects.gbuffer;
import material;
struct CoarseVertex {
@@ -37,7 +37,7 @@ CoarseVertex vertexMain( ModelVertex in_vertex )
}
[[vk::binding(0,3)]]
ParameterBlock<Material> materials[] : MATERIALS;
ConstantBuffer<Material> materials[] : MATERIALS;
[[vk::binding(0,2)]]
Sampler2D[ ] tex : TEXTURES;

View File

@@ -0,0 +1,15 @@
#version 450
namespace vk
{
struct DrawIndexedIndirectCommand
{
uint32_t index_count;
uint32_t instance_count;
uint32_t first_index;
int32_t vertex_offset;
uint32_t first_instance;
};
}

View File

@@ -2,9 +2,9 @@
// Created by kj16609 on 2/18/25.
//
#include <catch2/catch_test_macros.hpp>
#include "primitives/Transform.hpp"
#include "primitives/TransformComponent.hpp"
#include <catch2/catch_test_macros.hpp>
TEST_CASE( "Transform", "[transform]" )
{