Files
FGL-Engine/src/engine/model/builders/SceneBuilder.cpp
2024-08-04 12:56:53 -04:00

544 lines
16 KiB
C++

//
// Created by kj16609 on 5/18/24.
//
#include "SceneBuilder.hpp"
#include "engine/model/Model.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Weffc++"
#include "objectloaders/tiny_gltf.h"
#pragma GCC diagnostic pop
#include <engine/logging/logging.hpp>
#include "engine/assets/stores.hpp"
#include "engine/descriptors/DescriptorSet.hpp"
#include "engine/gameobjects/GameObject.hpp"
#include "engine/image/ImageView.hpp"
namespace fgl::engine
{
SceneBuilder::SceneBuilder( memory::Buffer& vertex_buffer, memory::Buffer& index_buffer ) :
m_vertex_buffer( vertex_buffer ),
m_index_buffer( index_buffer )
{}
int SceneBuilder::getTexcoordCount( const tinygltf::Primitive& prim ) const
{
ZoneScoped;
int counter { 0 };
for ( const auto& [ key, value ] : prim.attributes )
{
if ( key.starts_with( "TEXCOORD" ) ) ++counter;
}
return counter;
}
template < typename T >
std::vector< T > extractData( const tinygltf::Model& model, const tinygltf::Accessor& accessor )
{
ZoneScoped;
if ( accessor.sparse.isSparse )
{
//Sparse loading required
throw std::runtime_error( "Sparse loading not implemeneted" );
}
const auto& buffer_view { model.bufferViews.at( accessor.bufferView ) };
const auto& buffer { model.buffers.at( buffer_view.buffer ) };
std::vector< T > data {};
data.reserve( accessor.count );
std::uint16_t copy_size { 0 };
switch ( accessor.componentType )
{
default:
throw std::runtime_error( "Unhandled access size" );
case TINYGLTF_COMPONENT_TYPE_FLOAT:
copy_size = 32 / 8;
break;
case TINYGLTF_COMPONENT_TYPE_BYTE:
copy_size = 8 / 8;
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
copy_size = 32 / 8;
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
copy_size = 16 / 8;
break;
}
switch ( accessor.type )
{
default:
throw std::runtime_error( "Unhandled access type" );
case TINYGLTF_TYPE_VEC3:
copy_size *= 3;
break;
case TINYGLTF_TYPE_VEC2:
copy_size *= 2;
break;
case TINYGLTF_TYPE_SCALAR:
copy_size *= 1;
break;
}
constexpr auto T_SIZE { sizeof( T ) };
if ( T_SIZE != copy_size )
throw std::runtime_error(
std::string( "Accessor copy values not matching sizeof(T): sizeof(T) == " ) + std::to_string( T_SIZE )
+ " vs copy_size = " + std::to_string( copy_size ) );
const auto real_size { copy_size * accessor.count };
data.resize( accessor.count );
std::memcpy( data.data(), buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset, real_size );
return data;
}
std::vector< std::uint32_t > SceneBuilder::
extractIndicies( const tinygltf::Primitive& prim, const tinygltf::Model& model )
{
ZoneScoped;
const auto& indicies_accessor { model.accessors.at( prim.indices ) };
if ( indicies_accessor.componentType == TINYGLTF_COMPONENT_TYPE_INT )
{
return extractData< std::uint32_t >( model, indicies_accessor );
}
else
{
//TODO: Figure out any time we can use a smaller indicies value instead of a 32 bit number all the time
std::vector< std::uint32_t > indicies {};
const auto tmp { extractData< std::uint16_t >( model, indicies_accessor ) };
indicies.reserve( tmp.size() );
for ( const auto val : tmp ) indicies.emplace_back( val );
return indicies;
}
}
const tinygltf::Accessor& SceneBuilder::getAccessorForAttribute(
const tinygltf::Primitive& prim, const tinygltf::Model& root, const std::string attrib ) const
{
ZoneScoped;
return root.accessors.at( prim.attributes.at( attrib ) );
}
std::shared_ptr< Texture > SceneBuilder::
getTextureForParameter( const tinygltf::Parameter& parameter, const tinygltf::Model& root )
{
const auto texture_idx { parameter.TextureIndex() };
const tinygltf::Texture& tex_info { root.textures[ texture_idx ] };
const auto source_idx { tex_info.source };
const tinygltf::Image& source { root.images[ source_idx ] };
if ( source.uri.empty() ) throw std::runtime_error( "Unsupported loading method for image (Must be a file)" );
const std::filesystem::path filepath { source.uri };
const auto full_path { m_root / filepath };
const auto sampler_idx { tex_info.sampler };
const tinygltf::Sampler& sampler_info { root.samplers[ sampler_idx ] };
Sampler sampler { sampler_info.minFilter, sampler_info.magFilter, sampler_info.wrapS, sampler_info.wrapT };
std::shared_ptr< Texture > texture { getTextureStore().load( full_path ) };
texture->getImageView().getSampler() = std::move( sampler );
//Prepare the texture into the global system
Texture::getTextureDescriptorSet().bindTexture( 0, texture );
Texture::getTextureDescriptorSet().update();
return texture;
}
PrimitiveTextures SceneBuilder::loadTextures( const tinygltf::Primitive& prim, const tinygltf::Model& root )
{
ZoneScoped;
const auto mat_idx { prim.material };
if ( mat_idx == -1 )
{
//There is no matrial for this
throw std::runtime_error( "No material for primitive. One was expected" );
}
const tinygltf::Material& materials { root.materials[ mat_idx ] };
for ( const auto& [ key, value ] : materials.values )
{
log::debug( "Found key: {}", key );
}
auto findParameter = [ &materials ]( const std::string name ) -> std::optional< tinygltf::Parameter >
{
const auto& itter { materials.values.find( name ) };
if ( itter == materials.values.end() )
return std::nullopt;
else
return { itter->second };
};
const auto albedo { findParameter( "baseColorTexture" ) };
const auto normal { findParameter( "normalTexture" ) };
const auto occlusion_texture { findParameter( "occlusionTexture" ) };
PrimitiveTextures textures {};
if ( albedo.has_value() )
{
textures.albedo = getTextureForParameter( *albedo, root );
}
if ( normal.has_value() )
{
textures.normal = getTextureForParameter( *normal, root );
}
return textures;
}
std::vector< GameObject > SceneBuilder::getGameObjects()
{
std::vector< GameObject > objects { std::move( this->game_objects ) };
return objects;
}
std::vector< glm::vec3 > SceneBuilder::
extractPositionInfo( const tinygltf::Primitive& prim, const tinygltf::Model& root )
{
ZoneScoped;
const tinygltf::Accessor& accessor { getAccessorForAttribute( prim, root, "POSITION" ) };
return extractData< glm::vec3 >( root, accessor );
}
std::vector< glm::vec3 > SceneBuilder::
extractNormalInfo( const tinygltf::Primitive& prim, const tinygltf::Model& root )
{
ZoneScoped;
if ( !hasAttribute( prim, "NORMAL" ) ) return {};
const tinygltf::Accessor& accessor { getAccessorForAttribute( prim, root, "NORMAL" ) };
return extractData< glm::vec3 >( root, accessor );
}
std::vector< glm::vec2 > SceneBuilder::extractUVInfo( const tinygltf::Primitive& prim, const tinygltf::Model& root )
{
ZoneScoped;
log::debug( "Extracting UV info" );
//TODO: Figure out how I can use multiple textures for various things.
if ( !hasAttribute( prim, "TEXCOORD_0" ) ) return {};
const tinygltf::Accessor& accessor { getAccessorForAttribute( prim, root, "TEXCOORD_0" ) };
return extractData< glm::vec2 >( root, accessor );
}
bool SceneBuilder::hasAttribute( const tinygltf::Primitive& prim, const std::string_view str )
{
ZoneScoped;
return prim.attributes.contains( std::string( str ) );
}
std::vector< Vertex > SceneBuilder::
extractVertexInfo( const tinygltf::Primitive& prim, const tinygltf::Model& root )
{
ZoneScoped;
log::debug( "Extracting vert info" );
const auto pos { extractPositionInfo( prim, root ) };
std::vector< Vertex > verts {};
verts.reserve( pos.size() );
const std::vector< glm::vec3 > normals { extractNormalInfo( prim, root ) };
//TODO: If we don't have normals we likely will need to compute them ourselves.
// I have no idea if this is actually going to be needed for us. But I might wanna implement it
// anyways, Just in case.
const bool has_normals { !normals.empty() };
const std::vector< glm::vec2 > uvs { extractUVInfo( prim, root ) };
const bool has_uv { !uvs.empty() };
for ( std::size_t i = 0; i < pos.size(); ++i )
{
Vertex vert {};
vert.m_position = pos[ i ];
vert.m_normal = has_normals ? normals[ i ] : glm::vec3();
vert.m_uv = has_uv ? uvs[ i ] : glm::vec2();
vert.m_color = glm::vec3( 0.1f );
verts.emplace_back( vert );
}
log::debug(
"Found {} verts.\n\t- Has UV info: {}\n\t- Has normals: {}",
verts.size(),
has_uv ? "Yes" : "No",
has_normals ? "Yes" : "No" );
return verts;
}
Primitive SceneBuilder::loadPrimitive( const tinygltf::Primitive& prim, const tinygltf::Model& root )
{
ZoneScoped;
std::string att_str;
for ( const auto& attrib : prim.attributes )
{
att_str += "Attribute: " + attrib.first + "\n";
}
log::debug( "Attributes for primitive:\n{}", att_str );
//TODO: Get normal colors from texture
[[maybe_unused]] const bool has_normal { hasAttribute( prim, "NORMAL" ) };
const bool has_position { hasAttribute( prim, "POSITION" ) };
const bool has_texcoord { hasAttribute( prim, "TEXCOORD_0" ) };
[[maybe_unused]] const int texcoord_count { has_texcoord ? getTexcoordCount( prim ) : 0 };
if ( !has_position ) throw std::runtime_error( "Failed to load model. Missing expected POSITION attribute" );
switch ( static_cast< PrimitiveMode >( prim.mode ) )
{
case POINTS:
[[fallthrough]];
case LINE:
[[fallthrough]];
case LINE_LOOP:
[[fallthrough]];
case LINE_STRIP:
[[fallthrough]];
case TRIS:
[[fallthrough]];
case TRI_STRIP:
[[fallthrough]];
case TRI_FAN:
{
std::vector< Vertex > verts { extractVertexInfo( prim, root ) };
std::vector< std::uint32_t > indicies { extractIndicies( prim, root ) };
Primitive primitive_mesh { Primitive::fromVerts(
std::move( verts ),
static_cast< PrimitiveMode >( prim.mode ),
std::move( indicies ),
m_vertex_buffer,
m_index_buffer ) };
// If we have a texcoord then we have a UV map. Meaning we likely have textures to use
if ( !has_texcoord ) return primitive_mesh;
primitive_mesh.m_textures = loadTextures( prim, root );
return primitive_mesh;
}
default:
{
log::error( "Unsupported mode for primtiive loading: {}", prim.mode );
throw std::runtime_error( "Unsupported mode for primitive loading" );
}
}
FGL_UNREACHABLE();
}
OrientedBoundingBox< CoordinateSpace::Model > createModelBoundingBox( const std::vector< Primitive >& primitives )
{
ZoneScoped;
if ( primitives.size() <= 0 ) return {};
OrientedBoundingBox< CoordinateSpace::Model > box { primitives.at( 0 ).m_bounding_box };
for ( std::uint64_t i = 1; i < primitives.size(); i++ ) box = box.combine( primitives[ i ].m_bounding_box );
return box;
}
glm::vec3 convertToVec3( const std::vector< double >& data )
{
return { static_cast< float >( data[ 0 ] ),
static_cast< float >( data[ 1 ] ),
static_cast< float >( data[ 2 ] ) };
}
TransformComponent SceneBuilder::loadTransform( int node_idx, const tinygltf::Model& root )
{
const auto node { root.nodes[ node_idx ] };
const glm::vec3 translation { convertToVec3( node.translation ) };
const glm::quat rotation { static_cast< float >( node.rotation[ 0 ] ),
static_cast< float >( node.rotation[ 1 ] ),
static_cast< float >( node.rotation[ 2 ] ),
static_cast< float >( node.rotation[ 3 ] ) };
const glm::vec3 scale { convertToVec3( node.scale ) };
TransformComponent transform_component {};
transform_component.rotation = rotation;
transform_component.scale = scale;
transform_component.translation = WorldCoordinate( translation );
return transform_component;
}
std::shared_ptr< Model > SceneBuilder::loadModel( const int mesh_idx, const tinygltf::Model& root )
{
ZoneScoped;
const auto mesh { root.meshes[ mesh_idx ] };
const auto& primitives { mesh.primitives };
log::debug( "Mesh idx {} has {} primitives", mesh_idx, primitives.size() );
std::vector< Primitive > finished_primitives {};
for ( const auto& prim : primitives )
{
Primitive primitive { loadPrimitive( prim, root ) };
finished_primitives.emplace_back( std::move( primitive ) );
}
log::debug( "Finished loading model with {} primitives", finished_primitives.size() );
const auto bounding_box { createModelBoundingBox( finished_primitives ) };
return std::make_shared< Model >( std::move( finished_primitives ), bounding_box, mesh.name );
}
void SceneBuilder::handleNode( const int node_idx, const tinygltf::Model& root )
{
ZoneScoped;
const tinygltf::Node& node { root.nodes[ node_idx ] };
log::debug( "Handling node: Index:{} Name:\"{}\"", node_idx, node.name );
const int mesh_idx { node.mesh };
const int skin_idx { node.skin };
log::debug( "Mesh IDX: {}", mesh_idx );
log::debug( "Skin IDX: {}", skin_idx );
GameObject obj { GameObject::createGameObject() };
std::shared_ptr< Model > model { loadModel( mesh_idx, root ) };
assert( model );
std::unique_ptr< ModelComponent > component { std::make_unique< ModelComponent >( std::move( model ) ) };
obj.addComponent( std::move( component ) );
obj.addFlag( IS_VISIBLE | IS_ENTITY );
//TODO: Set transform from node
const std::vector< double > translation { node.translation };
const std::vector< double > rotation { node.rotation };
const std::vector< double > scale { node.scale };
if ( rotation.size() == 4 )
obj.getTransform().rotation = glm::quat(
static_cast< float >( rotation[ 0 ] ),
static_cast< float >( rotation[ 1 ] ),
static_cast< float >( rotation[ 2 ] ),
static_cast< float >( rotation[ 3 ] ) );
if ( scale.size() == 3 )
obj.getTransform().scale = glm::vec3(
static_cast< float >( scale[ 0 ] ),
static_cast< float >( scale[ 1 ] ),
static_cast< float >( scale[ 2 ] ) );
if ( translation.size() == 3 )
obj.getTransform().translation = WorldCoordinate(
static_cast< float >( translation[ 0 ] ),
static_cast< float >( translation[ 1 ] ),
static_cast< float >( translation[ 2 ] ) );
if ( node.name.empty() )
obj.setName( "Unnamed Game Object" );
else
obj.setName( node.name );
this->game_objects.emplace_back( std::move( obj ) );
}
void SceneBuilder::handleScene( const tinygltf::Scene& scene, const tinygltf::Model& root )
{
ZoneScoped;
log::debug( "Handling scene: ", scene.name );
log::debug( "Scene has {} nodes", scene.nodes.size() );
for ( const auto node_idx : scene.nodes )
{
handleNode( node_idx, root );
}
}
void SceneBuilder::loadScene( const std::filesystem::path path )
{
ZoneScoped;
if ( !std::filesystem::exists( path ) ) throw std::runtime_error( "Failed to find scene at filepath" );
m_root = path.parent_path();
tinygltf::TinyGLTF loader {};
tinygltf::Model gltf_model {};
std::string err {};
std::string warn {};
if ( path.extension() == ".gltf" )
{
//Must use the ASCII loader
if ( !loader.LoadASCIIFromFile( &gltf_model, &err, &warn, path ) )
{
log::info( "Failed to scene at {}", path );
if ( !warn.empty() ) log::warn( warn );
if ( !err.empty() ) log::error( err );
throw std::runtime_error( "Failed to load binary model" );
}
}
else
{
if ( !loader.LoadBinaryFromFile( &gltf_model, &err, &warn, path ) )
{
log::info( "Failed to scene at {}", path );
if ( !warn.empty() ) log::warn( warn );
if ( !err.empty() ) log::error( err );
throw std::runtime_error( "Failed to load binary model" );
}
}
if ( !err.empty() )
{
log::error( "Error loading model {}: \"{}\"", path.string(), err );
throw std::runtime_error( err );
}
if ( !warn.empty() )
{
log::warn( "Warning loading model {}: \"{}\"", path.string(), warn );
}
const auto scenes { gltf_model.scenes };
for ( const auto& scene : scenes )
{
handleScene( scene, gltf_model );
}
}
} // namespace fgl::engine