diff --git a/Source/FolderObserver.cpp b/Source/FolderObserver.cpp index 949c3fbd..7c89ffc3 100644 --- a/Source/FolderObserver.cpp +++ b/Source/FolderObserver.cpp @@ -77,6 +77,7 @@ RTGL1::FolderObserver::FolderObserver( const fs::path &ovrdFolder ) ovrdFolder / SHADERS_FOLDER, ovrdFolder / TEXTURES_FOLDER, ovrdFolder / TEXTURES_FOLDER_DEV, + ovrdFolder / REPLACEMENTS_FOLDER, }; } diff --git a/Source/GltfExporter.cpp b/Source/GltfExporter.cpp index e9e6f05c..d22a5577 100644 --- a/Source/GltfExporter.cpp +++ b/Source/GltfExporter.cpp @@ -255,7 +255,7 @@ struct DeepCopyOfPrimitive static_assert( std::is_trivially_copyable_v< decltype( pIndices )::value_type > ); // deep copy - pTextureName = Utils::SafeCstr( c.pTextureName ); + pTextureName = Utils::SafeCstr( c.pTextureName ); pVertices.assign( fromVertices.begin(), fromVertices.end() ); pIndices.assign( fromIndices.begin(), fromIndices.end() ); @@ -289,10 +289,10 @@ struct DeepCopyOfPrimitive { assert( other.info.vertexCount == other.pVertices.size() ); assert( other.info.indexCount == other.pIndices.size() ); - - this->pTextureName = std::move( other.pTextureName ); - this->pVertices = std::move( other.pVertices ); - this->pIndices = std::move( other.pIndices ); + + this->pTextureName = std::move( other.pTextureName ); + this->pVertices = std::move( other.pVertices ); + this->pIndices = std::move( other.pIndices ); // copy and fix this->info = other.info; @@ -318,7 +318,7 @@ struct DeepCopyOfPrimitive } std::span< const uint32_t > Indices() const { return { pIndices.begin(), pIndices.end() }; } - + std::string_view MaterialName() const { return pTextureName; } RgFloat4D Color() const { return Utils::UnpackColor4DPacked32( info.color ); } @@ -344,9 +344,9 @@ struct DeepCopyOfPrimitive private: static void FixupPointers( DeepCopyOfPrimitive& inout ) { - inout.info.pTextureName = inout.pTextureName.data(); - inout.info.pVertices = inout.pVertices.data(); - inout.info.pIndices = inout.pIndices.data(); + inout.info.pTextureName = inout.pTextureName.data(); + inout.info.pVertices = inout.pVertices.data(); + inout.info.pIndices = inout.pIndices.data(); assert( inout.info.vertexCount == inout.pVertices.size() ); assert( inout.info.indexCount == inout.pIndices.size() ); @@ -1400,79 +1400,111 @@ auto MakeLightsForPrimitive( const RgMeshInfo& mesh, } -bool PrepareFolder( const std::filesystem::path& gltfPath ) +bool CreateDir( const std::filesystem::path& folder, bool forceEmpty ) { - using namespace RTGL1; + std::error_code ec; + + create_directories( folder, ec ); + if( ec && forceEmpty ) + { + RTGL1::debug::Warning( + "std::filesystem::create_directories error: {} - {}", ec.message(), folder.string() ); + return false; + } - auto folder = GetGltfFolder( gltfPath ); + if( !is_directory( folder ) ) + { + RTGL1::debug::Warning( "Expected \'{}\' to be a directory", folder.string() ); + return false; + } - auto createEmptyFoldersFor = [ &folder, &gltfPath ]() { - // create empty folder for .gltf - std::error_code ec; + return true; +} - std::filesystem::create_directories( folder, ec ); - if( ec ) - { - debug::Warning( "{}: std::filesystem::create_directories error: {}", - folder.string(), - ec.message() ); - return false; - } - assert( std::filesystem::is_directory( folder ) ); - // create junction folder to store original textures - auto junction = GetOriginalTexturesFolder( gltfPath ); +bool CreateJunction( const std::filesystem::path& junction, + const std::filesystem::path& target, + bool forceEmpty ) +{ + std::error_code ec; - // but there's no privilege to create symlinks, - // so create a folder that contains texture copies from ovrd/mat or ovrd/matdev + // but there's no privilege to create symlinks, + // so create a folder that contains texture copies from ovrd/mat or ovrd/matdev #if 1 - std::filesystem::create_directories( junction, ec ); - if( ec ) - { - debug::Warning( "{}: std::filesystem::create_directories error: {}", - folder.string(), - ec.message() ); - return false; - } - assert( std::filesystem::is_directory( folder ) ); + create_directories( junction, ec ); + if( ec && forceEmpty ) + { + RTGL1::debug::Warning( + "std::filesystem::create_directories error: {} - {}", ec.message(), junction.string() ); + return false; + } + + if( !is_directory( junction ) ) + { + RTGL1::debug::Warning( "Expected \'{}\' to be a directory", junction.string() ); + return false; + } #else - auto ovrdTextures = ovrdFolder / TEXTURES_FOLDER_DEV; + assert( !target.empty() ); + std::filesystem::create_directory_symlink( target, junction, ec ); + if( ec && forceEmpty ) + { + debug::Warning( "std::filesystem::create_directory_symlink error: {} - {}", + ec.message(), + junction.string() ); + return false; + } - std::filesystem::create_directory_symlink( ovrdTextures, junction, ec ); - if( ec ) - { - debug::Warning( "{}: std::filesystem::create_directory_symlink error: {}", - junction.string(), - ec.message() ); - return false; - } - assert( std::filesystem::is_symlink( junction ) ); - assert( std::filesystem::equivalent( ovrdTextures, junction ) ); + if( !std::filesystem::is_symlink( junction ) ) + { + debug::Warning( "Expected \'{}\' to be a directory", junction.string() ); + return false; + } + assert( std::filesystem::equivalent( target, junction ) ); #endif - return true; - }; + return true; +} - if( !std::filesystem::exists( folder ) ) - { - return createEmptyFoldersFor(); - } -#ifdef RG_USE_SURFACE_WIN32 +bool PrepareFolder( const std::filesystem::path& gltfPath, bool forceEmpty ) +{ + auto folder = GetGltfFolder( gltfPath ); + auto texturesFolder = GetOriginalTexturesFolder( gltfPath ); + + if( forceEmpty && exists( folder ) ) { +#ifdef RG_USE_SURFACE_WIN32 auto msg = std::format( "Folder already exists:\n{}\n\n" "Are you sure you want to write ON TOP of its contents?", std::filesystem::absolute( folder ).string() ); - int msgboxID = MessageBox( - nullptr, msg.c_str(), "Overwrite folder", MB_ICONSTOP | MB_YESNO | MB_DEFBUTTON2 ); - - return msgboxID == IDYES; - } + if( MessageBox( nullptr, + msg.c_str(), + "Overwrite folder", + MB_ICONSTOP | MB_YESNO | MB_DEFBUTTON2 ) != IDYES ) + { + return false; + } #else - debug::Warning( "{}: Folder already exists, overwrite disabled", folder.string() ); - return false; + debug::Warning( "Folder already exists, overwrite disabled: {}", folder.string() ); + return false; #endif // RG_USE_SURFACE_WIN32 + } + + // prepare folder for .gltf file + if( !CreateDir( folder, forceEmpty ) ) + { + return false; + } + + // create junction folder to store original textures + if( !CreateJunction( texturesFolder, {}, forceEmpty ) ) + { + return false; + } + + return true; } } @@ -1551,7 +1583,8 @@ void RTGL1::GltfExporter::AddLight( const LightCopy& light ) void RTGL1::GltfExporter::ExportToFiles( const std::filesystem::path& gltfPath, const TextureManager& textureManager, - const std::filesystem::path& ovrdFolder ) + const std::filesystem::path& ovrdFolder, + bool isSceneGltf ) { if( scene.empty() ) { @@ -1565,7 +1598,7 @@ void RTGL1::GltfExporter::ExportToFiles( const std::filesystem::path& gltfPath, return; } - if( !PrepareFolder( gltfPath ) ) + if( !PrepareFolder( gltfPath, isSceneGltf ) ) { debug::Warning( "Denied to write to the folder {}", std::filesystem::absolute( GetGltfFolder( gltfPath ) ).string() ); @@ -1581,13 +1614,13 @@ void RTGL1::GltfExporter::ExportToFiles( const std::filesystem::path& gltfPath, // lock pointers - auto fbin = GltfBin{ gltfPath }; - auto storage = GltfStorage{ scene, sceneLights.size() }; + auto fbin = GltfBin{ gltfPath }; + auto storage = GltfStorage{ scene, sceneLights.size() }; auto textureStorage = GltfTextures{ sceneMaterials, GetOriginalTexturesFolder( gltfPath ), textureManager, ovrdFolder / TEXTURES_FOLDER_DEV }; - auto lightStorage = GltfLights{ sceneLights, storage.lightNodes }; + auto lightStorage = GltfLights{ sceneLights, storage.lightNodes }; auto materialcount = 0u; diff --git a/Source/GltfExporter.h b/Source/GltfExporter.h index 35f9f22c..e843f8d8 100644 --- a/Source/GltfExporter.h +++ b/Source/GltfExporter.h @@ -78,7 +78,8 @@ class GltfExporter void ExportToFiles( const std::filesystem::path& gltfPath, const TextureManager& textureManager, - const std::filesystem::path& ovrdFolder ); + const std::filesystem::path& ovrdFolder, + bool isSceneGltf ); // TODO: allocators, to not pass references static void MakeLightsForPrimitiveDynamic( const RgMeshInfo& mesh, diff --git a/Source/GltfImporter.cpp b/Source/GltfImporter.cpp index b6daa541..dd841c33 100644 --- a/Source/GltfImporter.cpp +++ b/Source/GltfImporter.cpp @@ -114,14 +114,22 @@ namespace return nullptr; } - const char* NodeName( const cgltf_node& node ) + auto nodeName( const cgltf_node& n ) { - return node.name ? node.name : ""; + if( !Utils::IsCstrEmpty( n.name ) ) + { + return std::string_view{ n.name }; + } + return std::string_view{}; } - const char* NodeName( const cgltf_node* node ) + auto nodeName( const cgltf_node* n ) { - return NodeName( *node ); + if( n ) + { + return nodeName( *n ); + } + return std::string_view{}; } const char* CgltfErrorName( cgltf_result r ) @@ -166,8 +174,8 @@ namespace std::string_view msg ) { debug::Warning( "{}: Ignoring primitive of ...->{}->{}: Attribute {}: {}", gltfPath, - NodeName( node.parent ), - NodeName( node ), + nodeName( node.parent ), + nodeName( node ), Utils::SafeCstr( attr.name ), msg ); }; @@ -276,8 +284,8 @@ namespace "TANGENT - {}. " "TEXCOORD_0 - {}", gltfPath, - NodeName( node.parent ), - NodeName( node ), + nodeName( node.parent ), + nodeName( node ), position, normal, tangent, @@ -289,7 +297,7 @@ namespace if( !vertexCount ) { debug::Warning( - "{}: Ignoring ...->{}->{}: ", gltfPath, NodeName( node.parent ), NodeName( node ) ); + "{}: Ignoring ...->{}->{}: ", gltfPath, nodeName( node.parent ), nodeName( node ) ); return {}; } @@ -377,8 +385,8 @@ namespace debug::Warning( "{}: Ignoring primitive of ...->{}->{}: Indices: Sparse accessors are " "not supported", gltfPath, - NodeName( node.parent ), - NodeName( node ) ); + nodeName( node.parent ), + nodeName( node ) ); return {}; } @@ -393,8 +401,8 @@ namespace debug::Warning( "{}: Ignoring primitive of ...->{}->{}: Indices: cgltf_accessor_read_uint fail", gltfPath, - NodeName( node.parent ), - NodeName( node ) ); + nodeName( node.parent ), + nodeName( node ) ); return {}; } @@ -638,9 +646,21 @@ namespace }; } - auto ParseNodeAsLight( const cgltf_node& node, uint64_t uniqueId, float oneGameUnitInMeters ) + auto ParseNodeAsLight( const cgltf_node* srcNode, uint64_t uniqueId, float oneGameUnitInMeters ) -> std::optional< LightCopy > { + if( !srcNode || !srcNode->light ) + { + return {}; + } + + if( srcNode->children_count > 0 ) + { + debug::Warning( "Ignoring child nodes on the light: \'{}\'", srcNode->name ); + } + + const cgltf_node& node = *srcNode; + constexpr auto candelaToLuminousFlux = []( float lumensPerSteradian ) { // to lumens return lumensPerSteradian * ( 4 * float( Utils::M_PI ) ); @@ -847,220 +867,222 @@ auto RTGL1::GltfImporter::ParseFile( VkCommandBuffer cmdForTextures, if( mainNode->mesh || mainNode->light ) { - debug::Warning( "{}: Main node ({}) should not have meshes / lights. Ignoring", - gltfPath, - mainNode->name ); + debug::Warning( + "Main node ({}) should not have meshes / lights. {}", gltfPath, nodeName( mainNode ) ); } auto hashCombine = []< typename T >( size_t seed, const T& v ) { return seed ^ ( std::hash< T >{}( v ) + 0x9e3779b9 + ( seed << 6 ) + ( seed >> 2 ) ); }; - auto fileNameHash = hashCombine( 0, gltfPath ); + const auto fileNameHash = hashCombine( 0, gltfPath ); auto result = WholeModelFile{}; - // meshes - for( cgltf_node* srcNode : std::span( mainNode->children, mainNode->children_count ) ) + for( cgltf_node* srcNode : std::span{ mainNode->children, mainNode->children_count } ) { - if( !srcNode || !srcNode->mesh ) + if( !srcNode ) { continue; } - if( Utils::IsCstrEmpty( srcNode->name ) ) + if( nodeName( srcNode ).empty() ) { - debug::Warning( "{}: Found srcMesh with null name (a child node of {}). Ignoring", + debug::Warning( "Ignoring a node with null name: a child of {}->{}", gltfPath, - mainNode->name ); + nodeName( mainNode ) ); continue; } if( srcNode->children_count > 0 ) { - debug::Warning( "{}: Found a child nodes of {}->{}. Ignoring them", - gltfPath, - mainNode->name, - srcNode->name ); + if( std::ranges::any_of( std::span{ srcNode->children, srcNode->children_count }, + []( const cgltf_node* c ) { return c->mesh; } ) ) + { + debug::Warning( "Ignoring child nodes that contain a mesh: {}->{}->{}", + gltfPath, + nodeName( mainNode ), + nodeName( srcNode ) ); + } } - if( result.models.contains( srcNode->name ) ) + + const auto srcNodeHash = hashCombine( fileNameHash, nodeName( srcNode ) ); + + + // global lights + if( auto l = ParseNodeAsLight( srcNode, srcNodeHash, oneGameUnitInMeters ) ) { - debug::Warning( "{}: Multiple nodes with the same name: {} (a child node of {}). " - "Ignoring duplicates", - gltfPath, - srcNode->name, - mainNode->name ); - continue; + result.lights.push_back( *l ); } - const auto primitiveExtra = json_parser::ReadStringAs< PrimitiveExtraInfo >( - Utils::SafeCstr( srcNode->extras.data ) ); + // make model + if( result.models.contains( nodeName( srcNode ) ) ) + { + debug::Warning( "Ignoring duplicates: multiple nodes with the same name: {}->{}->{}", + gltfPath, + nodeName( mainNode ), + nodeName( srcNode ) ); + continue; + } const auto [ iter, isNew ] = - result.models.emplace( srcNode->name, + result.models.emplace( nodeName( srcNode ), WholeModelFile::RawModelData{ - .uniqueObjectID = hashCombine( fileNameHash, srcNode->name ), + .uniqueObjectID = srcNodeHash, .meshTransform = MakeRgTransformFromGltfNode( *srcNode ), .primitives = {}, .localLights = {}, } ); - assert( isNew ); auto& result_dstModel = iter->second; - for( uint32_t i = 0; i < srcNode->mesh->primitives_count; i++ ) - { - const cgltf_primitive& srcPrim = srcNode->mesh->primitives[ i ]; - auto vertices = GatherVertices( srcPrim, *srcNode, gltfPath ); - if( vertices.empty() ) - { - continue; - } + // mesh + if( srcNode->mesh ) + { + const auto primitiveExtra = json_parser::ReadStringAs< PrimitiveExtraInfo >( + Utils::SafeCstr( srcNode->extras.data ) ); - auto indices = GatherIndices( srcPrim, *srcNode, gltfPath ); - if( indices.empty() ) + // primitives + for( uint32_t i = 0; i < srcNode->mesh->primitives_count; i++ ) { - continue; - } - + const cgltf_primitive& srcPrim = srcNode->mesh->primitives[ i ]; - RgMeshPrimitiveFlags dstFlags = 0; - - if( srcPrim.material ) - { - if( srcPrim.material->alpha_mode == cgltf_alpha_mode_mask ) + auto vertices = GatherVertices( srcPrim, *srcNode, gltfPath ); + if( vertices.empty() ) { - dstFlags |= RG_MESH_PRIMITIVE_ALPHA_TESTED; + continue; } - else if( srcPrim.material->alpha_mode == cgltf_alpha_mode_blend ) + + auto indices = GatherIndices( srcPrim, *srcNode, gltfPath ); + if( indices.empty() ) { - debug::Warning( - "{}: Ignoring primitive of ...->{}->{}: Found blend material, " - "so it requires to be uploaded each frame, and not once on load", - gltfPath, - NodeName( srcNode->parent ), - NodeName( srcNode ) ); continue; - dstFlags |= RG_MESH_PRIMITIVE_TRANSLUCENT; } - } - auto matinfo = UploadTextures( cmdForTextures, - frameIndex, - srcPrim.material, - textureManager, - gltfFolder, - gltfPath ); - - auto dstPrim = RgMeshPrimitiveInfo{ - .sType = RG_STRUCTURE_TYPE_MESH_PRIMITIVE_INFO, - .pNext = nullptr, - .flags = dstFlags, - .primitiveIndexInMesh = UINT32_MAX, - .pVertices = vertices.data(), - .vertexCount = uint32_t( vertices.size() ), - .pIndices = indices.empty() ? nullptr : indices.data(), - .indexCount = uint32_t( indices.size() ), - .pTextureName = matinfo.pTextureName.c_str(), - .textureFrame = 0, - .color = matinfo.color, - .emissive = matinfo.emissiveMult, - }; - - - auto extAttachedLight = std::optional< RgMeshPrimitiveAttachedLightEXT >{}; - auto extPbr = std::optional< RgMeshPrimitivePBREXT >{}; - - // use texture meta as fallback - { - textureMeta.Modify( dstPrim, extAttachedLight, extPbr, true ); - } + RgMeshPrimitiveFlags dstFlags = 0; - // gltf info has a higher priority, so overwrite - { - extPbr = RgMeshPrimitivePBREXT{ - .sType = RG_STRUCTURE_TYPE_MESH_PRIMITIVE_PBR_EXT, - .pNext = nullptr, - .metallicDefault = matinfo.metallicFactor, - .roughnessDefault = matinfo.roughnessFactor, - }; - - if( primitiveExtra.isGlass ) + if( srcPrim.material ) { - dstPrim.flags |= RG_MESH_PRIMITIVE_GLASS; + if( srcPrim.material->alpha_mode == cgltf_alpha_mode_mask ) + { + dstFlags |= RG_MESH_PRIMITIVE_ALPHA_TESTED; + } + else if( srcPrim.material->alpha_mode == cgltf_alpha_mode_blend ) + { + debug::Warning( + "Ignoring primitive of ...->{}->{}: Found blend material, " + "so it requires to be uploaded each frame, and not once on load. {}", + nodeName( srcNode->parent ), + nodeName( srcNode ), + gltfPath ); + continue; + dstFlags |= RG_MESH_PRIMITIVE_TRANSLUCENT; + } } - if( primitiveExtra.isMirror ) - { - dstPrim.flags |= RG_MESH_PRIMITIVE_MIRROR; - } - if( primitiveExtra.isWater ) - { - dstPrim.flags |= RG_MESH_PRIMITIVE_WATER; - } + auto matinfo = UploadTextures( cmdForTextures, + frameIndex, + srcPrim.material, + textureManager, + gltfFolder, + gltfPath ); + + auto dstPrim = RgMeshPrimitiveInfo{ + .sType = RG_STRUCTURE_TYPE_MESH_PRIMITIVE_INFO, + .pNext = nullptr, + .flags = dstFlags, + .primitiveIndexInMesh = UINT32_MAX, + .pVertices = vertices.data(), + .vertexCount = uint32_t( vertices.size() ), + .pIndices = indices.empty() ? nullptr : indices.data(), + .indexCount = uint32_t( indices.size() ), + .pTextureName = matinfo.pTextureName.c_str(), + .textureFrame = 0, + .color = matinfo.color, + .emissive = matinfo.emissiveMult, + }; - if( primitiveExtra.isSkyVisibility ) - { - dstPrim.flags |= RG_MESH_PRIMITIVE_SKY_VISIBILITY; - } - if( primitiveExtra.isAcid ) + auto extAttachedLight = std::optional< RgMeshPrimitiveAttachedLightEXT >{}; + auto extPbr = std::optional< RgMeshPrimitivePBREXT >{}; + + // use texture meta as fallback { - dstPrim.flags |= RG_MESH_PRIMITIVE_ACID; + textureMeta.Modify( dstPrim, extAttachedLight, extPbr, true ); } - if( primitiveExtra.isThinMedia ) + // gltf info has a higher priority, so overwrite { - dstPrim.flags |= RG_MESH_PRIMITIVE_THIN_MEDIA; - } - } + extPbr = RgMeshPrimitivePBREXT{ + .sType = RG_STRUCTURE_TYPE_MESH_PRIMITIVE_PBR_EXT, + .pNext = nullptr, + .metallicDefault = matinfo.metallicFactor, + .roughnessDefault = matinfo.roughnessFactor, + }; + + if( primitiveExtra.isGlass ) + { + dstPrim.flags |= RG_MESH_PRIMITIVE_GLASS; + } + if( primitiveExtra.isMirror ) + { + dstPrim.flags |= RG_MESH_PRIMITIVE_MIRROR; + } - result_dstModel.primitives.push_back( WholeModelFile::RawModelData::RawPrimitiveData{ - .vertices = std::move( vertices ), - .indices = std::move( indices ), - .flags = dstPrim.flags, - .textureName = Utils::SafeCstr( dstPrim.pTextureName ), - .color = dstPrim.color, - .emissive = dstPrim.emissive, - .attachedLight = extAttachedLight, - .pbr = extPbr, - .portal = {}, - } ); - } - } + if( primitiveExtra.isWater ) + { + dstPrim.flags |= RG_MESH_PRIMITIVE_WATER; + } - uint64_t counter = 0; + if( primitiveExtra.isSkyVisibility ) + { + dstPrim.flags |= RG_MESH_PRIMITIVE_SKY_VISIBILITY; + } - // lights - for( cgltf_node* srcNode : std::span( mainNode->children, mainNode->children_count ) ) - { - if( !srcNode || !srcNode->light ) - { - continue; - } + if( primitiveExtra.isAcid ) + { + dstPrim.flags |= RG_MESH_PRIMITIVE_ACID; + } - if( srcNode->children_count > 0 ) - { - debug::Warning( "{}: Found a child nodes of {}->{}. Ignoring them", - gltfPath, - mainNode->name, - srcNode->name ); + if( primitiveExtra.isThinMedia ) + { + dstPrim.flags |= RG_MESH_PRIMITIVE_THIN_MEDIA; + } + } + + + result_dstModel.primitives.push_back( + WholeModelFile::RawModelData::RawPrimitiveData{ + .vertices = std::move( vertices ), + .indices = std::move( indices ), + .flags = dstPrim.flags, + .textureName = Utils::SafeCstr( dstPrim.pTextureName ), + .color = dstPrim.color, + .emissive = dstPrim.emissive, + .attachedLight = extAttachedLight, + .pbr = extPbr, + .portal = {}, + } ); + } } - // TODO: change id - uint64_t uniqueId = UINT64_MAX - counter; - counter++; - if( auto l = ParseNodeAsLight( *srcNode, uniqueId, oneGameUnitInMeters ) ) + // local lights + for( const cgltf_node* child : std::span{ mainNode->children, mainNode->children_count } ) { - result.lights.push_back( *l ); + const auto childHash = hashCombine( srcNodeHash, nodeName( child ) ); + + if( auto l = ParseNodeAsLight( srcNode, childHash, oneGameUnitInMeters ) ) + { + result_dstModel.localLights.push_back( *l ); + } } } diff --git a/Source/Scene.cpp b/Source/Scene.cpp index 3c71166a..a29d70bd 100644 --- a/Source/Scene.cpp +++ b/Source/Scene.cpp @@ -30,6 +30,7 @@ namespace { +template< bool IsForScene = true > auto MakeGltfPath( const std::filesystem::path& base, std::string_view meshName ) -> std::filesystem::path { @@ -38,7 +39,14 @@ auto MakeGltfPath( const std::filesystem::path& base, std::string_view meshName std::ranges::replace( exportName, '\\', '_' ); std::ranges::replace( exportName, '/', '_' ); - return base / exportName / ( exportName + ".gltf" ); + if( IsForScene ) + { + return base / exportName / ( exportName + ".gltf" ); + } + else + { + return base / ( exportName + ".gltf" ); + } } auto SanitizePathToShow( const std::filesystem::path& p ) @@ -57,9 +65,7 @@ RTGL1::Scene::Scene( VkDevice _device, const ShaderManager& _shaderManager, bool _enableTexCoordLayer1, bool _enableTexCoordLayer2, - bool _enableTexCoordLayer3, - std::filesystem::path _replacementsFolder ) - : replacementsFolder{ std::move( _replacementsFolder ) } + bool _enableTexCoordLayer3 ) { geomInfoMgr = std::make_shared< GeomInfoManager >( _device, _allocator ); @@ -117,17 +123,16 @@ void RTGL1::Scene::SubmitForFrame( VkCommandBuffer cmd, disableRTGeometry ); } -RTGL1::UploadResult RTGL1::Scene::UploadPrimitive( VkCommandBuffer cmdForTextures, - uint32_t frameIndex, +RTGL1::UploadResult RTGL1::Scene::UploadPrimitive( uint32_t frameIndex, const RgMeshInfo& mesh, const RgMeshPrimitiveInfo& primitive, - TextureManager& textureManager, - const TextureMetaManager& textureMeta, + const TextureManager& textureManager, + LightManager& lightManager, bool isStatic ) { const auto uniqueID = PrimitiveUniqueID{ mesh, primitive }; - auto replacement = static_cast< const WholeModelFile* >( nullptr ); + auto replacement = static_cast< const WholeModelFile::RawModelData* >( nullptr ); if( !ignoreExternalGeometry ) { @@ -144,26 +149,6 @@ RTGL1::UploadResult RTGL1::Scene::UploadPrimitive( VkCommandBuffer cm if( mesh.flags & RG_MESH_INFO_EXPORT_AS_SEPARATE_FILE ) { replacement = find_p( replacements, mesh.pMeshName ); - - if( !replacement ) - { - auto tryParse = [ & ]( std::string_view meshName ) { - if( auto i = GltfImporter{ MakeGltfPath( replacementsFolder, meshName ), - RG_TRANSFORM_IDENTITY, - 1.0f } ) - { - return i.ParseFile( - cmdForTextures, frameIndex, textureManager, textureMeta ); - } - return WholeModelFile{}; - }; - - auto [ iter, isNew ] = - replacements.emplace( mesh.pMeshName, tryParse( mesh.pMeshName ) ); - assert( isNew ); - - replacement = &iter->second; - } } } } @@ -173,7 +158,7 @@ RTGL1::UploadResult RTGL1::Scene::UploadPrimitive( VkCommandBuffer cm return UploadResult::Fail; } - if( !replacement || replacement->models.empty() ) + if( !replacement ) { if( !asManager->AddMeshPrimitive( frameIndex, mesh, @@ -191,38 +176,39 @@ RTGL1::UploadResult RTGL1::Scene::UploadPrimitive( VkCommandBuffer cm { assert( !isStatic ); - // primitives that correspond to one mesh instance - // can be pushed multiple times, avoid that + // multiple primitives can correspond to one mesh instance, + // if a replacement for a mesh is present, upload it once if( !alreadyReplacedUniqueObjectIDs.contains( mesh.uniqueObjectID ) ) { - auto animFrame = replacement->models.size() == 1 - ? &replacement->models.values().at( 0 ).second - : find_p( replacement->models, "frame_0" ); + constexpr bool isReplacement = true; - if( animFrame ) + for( uint32_t i = 0; i < replacement->primitives.size(); i++ ) { - for( uint32_t i = 0; i < animFrame->primitives.size(); i++ ) - { - auto r = MakeMeshPrimitiveInfoAndProcess( - ( animFrame->primitives )[ i ], - i, // - [ & ]( const RgMeshPrimitiveInfo& replacementPrim ) { - if( !asManager->AddMeshPrimitive( - frameIndex, - mesh, - replacementPrim, - PrimitiveUniqueID{ mesh, replacementPrim }, - false, - replacement, - textureManager, - *geomInfoMgr ) ) - { - return UploadResult::Fail; - } - return UploadResult::ExportableDynamic; - } ); - assert( r != UploadResult::Fail ); - } + auto r = MakeMeshPrimitiveInfoAndProcess( + ( replacement->primitives )[ i ], + i, // + [ & ]( const RgMeshPrimitiveInfo& replacementPrim ) { + if( !asManager->AddMeshPrimitive( + frameIndex, + mesh, + replacementPrim, + PrimitiveUniqueID{ mesh, replacementPrim }, + false, + isReplacement, + textureManager, + *geomInfoMgr ) ) + { + return UploadResult::Fail; + } + return UploadResult::ExportableDynamic; + } ); + assert( r != UploadResult::Fail ); + } + + for( const auto& localLight : replacement->localLights ) + { + assert( localLight.base.uniqueID != 0 && localLight.base.isExportable ); + UploadLight( frameIndex, localLight, lightManager, false ); } alreadyReplacedUniqueObjectIDs.insert( mesh.uniqueObjectID ); @@ -260,15 +246,11 @@ RTGL1::UploadResult RTGL1::Scene::UploadLight( uint32_t frameIndex, return UploadResult::Fail; } - std::visit( - [ & ]( auto&& specific ) { - // adding static to light manager is done separately in SubmitStaticLights - if( !isStatic ) - { - lightManager.Add( frameIndex, light ); - } - }, - light.extension ); + // adding static to light manager is done separately in SubmitStaticLights + if( !isStatic ) + { + lightManager.Add( frameIndex, light ); + } return isStatic ? ( isExportable ? UploadResult::ExportableStatic : UploadResult::Static ) : ( isExportable ? UploadResult::ExportableDynamic : UploadResult::Dynamic ); @@ -384,7 +366,6 @@ void RTGL1::Scene::NewScene( VkCommandBuffer cmd, staticUniqueIDs.clear(); staticMeshNames.clear(); staticLights.clear(); - replacements.clear(); textureManager.FreeAllImportedMaterials( frameIndex ); @@ -407,11 +388,20 @@ void RTGL1::Scene::NewScene( VkCommandBuffer cmd, i, // [ & ]( const RgMeshPrimitiveInfo& prim ) { this->UploadPrimitive( - cmd, frameIndex, mesh, prim, textureManager, textureMeta, true ); + frameIndex, mesh, prim, textureManager, lightManager, true ); } ); + + if( !m.localLights.empty() ) + { + debug::Warning( "Lights under the scene mesh ({}) are ignored, " + "put them under the root node.", + name, + staticScene.FilePath() ); + } } } + // global lights for( const auto& l : sceneFile.lights ) { this->UploadLight( frameIndex, l, lightManager, true ); @@ -435,6 +425,63 @@ void RTGL1::Scene::NewScene( VkCommandBuffer cmd, debug::Info( "Static geometry was rebuilt" ); } +void RTGL1::Scene::RereadReplacements( VkCommandBuffer cmdForTextures, + uint32_t frameIndex, + const std::filesystem::path& replacementsFolder, + TextureManager& textureManager, + const TextureMetaManager& textureMeta ) +{ + namespace fs = std::filesystem; + + replacements.clear(); + + if( !exists( replacementsFolder ) ) + { + return; + } + + for( const fs::directory_entry& entry : fs::directory_iterator{ replacementsFolder } ) + { + if( !entry.is_regular_file() || MakeFileType( entry.path() ) != FileType::GLTF ) + { + continue; + } + + if( auto i = GltfImporter{ entry.path(), RG_TRANSFORM_IDENTITY, 1.0f } ) + { + auto wholeGltf = i.ParseFile( cmdForTextures, frameIndex, textureManager, textureMeta ); + + if( !wholeGltf.lights.empty() ) + { + debug::Warning( "Ignoring non-attached lights from \'{}\'", entry.path().string() ); + } + + for( const auto& [ meshName, mesh ] : wholeGltf.models ) + { + auto [ iter, isNew ] = replacements.emplace( meshName, mesh ); + + if( isNew ) + { + if( mesh.primitives.empty() && mesh.localLights.empty() ) + { + debug::Warning( "Replacement is empty, it doesn't have " + "any primitives or lights: \'{}\' - \'{}\'", + meshName, + entry.path().string() ); + } + } + else + { + debug::Warning( "Ignoring a replacement as it was already read " + "from another .gltf file. \'{}\' - \'{}\'", + meshName, + entry.path().string() ); + } + } + } + } +} + const std::shared_ptr< RTGL1::ASManager >& RTGL1::Scene::GetASManager() { return asManager; @@ -494,21 +541,31 @@ RTGL1::SceneImportExport::SceneImportExport( std::filesystem::path _scenesFolder const RgFloat3D& _worldUp, const RgFloat3D& _worldForward, const float& _worldScale ) - : scenesFolder( std::move( _scenesFolder ) ) - , replacementsFolder( std::move( _replacementsFolder ) ) - , worldUp( Utils::SafeNormalize( _worldUp, { 0, 1, 0 } ) ) - , worldForward( Utils::SafeNormalize( _worldForward, { 0, 0, 1 } ) ) - , worldScale( std::max( 0.0f, _worldScale ) ) + : scenesFolder{ std::move( _scenesFolder ) } + , replacementsFolder{ std::move( _replacementsFolder ) } + , reimportRequested{ false } // reread when new scene appears + , reimportReplacementsRequested{ true } // should reread initially + , worldUp{ Utils::SafeNormalize( _worldUp, { 0, 1, 0 } ) } + , worldForward{ Utils::SafeNormalize( _worldForward, { 0, 0, 1 } ) } + , worldScale{ std::max( 0.0f, _worldScale ) } { } +void RTGL1::SceneImportExport::RequestReimport() +{ + reimportRequested = true; +} + +void RTGL1::SceneImportExport::RequestReplacementsReimport() +{ + reimportReplacementsRequested = true; +} + void RTGL1::SceneImportExport::PrepareForFrame() { if( exportRequested ) { - exporters.emplace( - MakeGltfPath( scenesFolder, GetExportMapName() ).string(), - std::make_unique< GltfExporter >( MakeWorldTransform(), GetWorldScale() ) ); + sceneExporter = std::make_unique< GltfExporter >( MakeWorldTransform(), GetWorldScale() ); } exportRequested = false; } @@ -541,21 +598,34 @@ void RTGL1::SceneImportExport::CheckForNewScene( std::string_view mapName, } debug::Verbose( "New scene is ready" ); } + + if( reimportReplacementsRequested ) + { + reimportReplacementsRequested = false; + debug::Verbose( "Reading replacements..." ); + + scene.RereadReplacements( + cmd, frameIndex, replacementsFolder, textureManager, textureMeta ); + + debug::Verbose( "Replacements are ready" ); + } } void RTGL1::SceneImportExport::TryExport( const TextureManager& textureManager, const std::filesystem::path& ovrdFolder ) { - for( auto& [ path, e ] : exporters ) + if( sceneExporter ) { - e->ExportToFiles( path, textureManager, ovrdFolder ); + sceneExporter->ExportToFiles( + MakeGltfPath( scenesFolder, GetExportMapName() ), textureManager, ovrdFolder, true ); + sceneExporter.reset(); } - exporters.clear(); -} -void RTGL1::SceneImportExport::RequestReimport() -{ - reimportRequested = true; + for( auto& [ path, e ] : replacementExporters ) + { + e->ExportToFiles( path, textureManager, ovrdFolder, false ); + } + replacementExporters.clear(); } void RTGL1::SceneImportExport::OnFileChanged( FileType type, const std::filesystem::path& filepath ) @@ -569,37 +639,41 @@ void RTGL1::SceneImportExport::OnFileChanged( FileType type, const std::filesyst } else if( filepath.string().contains( REPLACEMENTS_FOLDER ) ) { - debug::Info( "Hot-reloading GLTF..." ); + debug::Info( "Hot-reloading GLTF replacements..." ); debug::Info( "Triggered by: {}", SanitizePathToShow( filepath ) ); - RequestReimport(); + RequestReplacementsReimport(); } } } RTGL1::GltfExporter* RTGL1::SceneImportExport::TryGetExporter( const char* customExportFileName ) { - if( exporters.empty() ) + if( !sceneExporter ) { return nullptr; } if( !Utils::IsCstrEmpty( customExportFileName ) ) { - if( auto e = find_p( exporters, customExportFileName ) ) + const auto replacementsPath = + MakeGltfPath< false >( replacementsFolder, customExportFileName ); + + auto found = replacementExporters.find( replacementsPath ); + if( found != replacementExporters.end() ) { - return e->get(); + return found->second.get(); } else { - auto [ iter, isNew ] = exporters.emplace( - MakeGltfPath( replacementsFolder, customExportFileName ), + auto [ iter, isNew ] = replacementExporters.emplace( + replacementsPath, std::make_unique< GltfExporter >( MakeWorldTransform(), GetWorldScale() ) ); return iter->second.get(); } } - return exporters.at( MakeGltfPath( scenesFolder, GetExportMapName() ).string() ).get(); + return sceneExporter.get(); } const RgFloat3D& RTGL1::SceneImportExport::GetWorldUp() const @@ -626,7 +700,7 @@ const RgFloat3D& RTGL1::SceneImportExport::GetWorldForward() const RgFloat3D RTGL1::SceneImportExport::GetWorldRight() const { - const auto& up = GetWorldUp(); + const auto& up = GetWorldUp(); const auto& forward = GetWorldForward(); RgFloat3D worldRight = Utils::Cross( up, forward ); diff --git a/Source/Scene.h b/Source/Scene.h index f1962039..1674b3fd 100644 --- a/Source/Scene.h +++ b/Source/Scene.h @@ -28,8 +28,6 @@ #include "TextureMeta.h" #include "UniqueID.h" -#include - namespace RTGL1 { @@ -53,8 +51,7 @@ class Scene const ShaderManager& shaderManager, bool enableTexCoordLayer1, bool enableTexCoordLayer2, - bool enableTexCoordLayer3, - std::filesystem::path replacementsFolder ); + bool enableTexCoordLayer3 ); ~Scene() = default; Scene( const Scene& other ) = delete; @@ -70,12 +67,11 @@ class Scene bool allowGeometryWithSkyFlag, bool disableRTGeometry ); - UploadResult UploadPrimitive( VkCommandBuffer cmdForTextures, - uint32_t frameIndex, + UploadResult UploadPrimitive( uint32_t frameIndex, const RgMeshInfo& mesh, const RgMeshPrimitiveInfo& primitive, - TextureManager& textureManager, - const TextureMetaManager& textureMeta, + const TextureManager& textureManager, + LightManager& lightManager, bool isStatic ); UploadResult UploadLight( uint32_t frameIndex, @@ -95,6 +91,12 @@ class Scene const TextureMetaManager& textureMeta, LightManager& lightManager ); + void RereadReplacements( VkCommandBuffer cmdForTextures, + uint32_t frameIndex, + const std::filesystem::path& replacementsFolder, + TextureManager& textureManager, + const TextureMetaManager& textureMeta ); + const std::shared_ptr< ASManager >& GetASManager(); const std::shared_ptr< VertexPreprocessing >& GetVertexPreprocessing(); @@ -125,8 +127,7 @@ class Scene rgl::string_set staticMeshNames; std::vector< LightCopy > staticLights; - std::filesystem::path replacementsFolder; - rgl::string_map< WholeModelFile > replacements; + rgl::string_map< WholeModelFile::RawModelData > replacements; StaticGeometryToken makingStatic{}; DynamicGeometryToken makingDynamic{}; @@ -161,6 +162,7 @@ class SceneImportExport : public IFileDependency void TryExport( const TextureManager& textureManager, const std::filesystem::path& ovrdFolder ); void RequestReimport(); + void RequestReplacementsReimport(); void OnFileChanged( FileType type, const std::filesystem::path& filepath ) override; void RequestExport(); @@ -180,10 +182,12 @@ class SceneImportExport : public IFileDependency std::filesystem::path scenesFolder; std::filesystem::path replacementsFolder; - bool reimportRequested{ false }; + bool reimportRequested; + bool reimportReplacementsRequested; bool exportRequested{ false }; - rgl::unordered_map< std::filesystem::path, std::unique_ptr< GltfExporter > > exporters{}; + std::unique_ptr< GltfExporter > sceneExporter{}; + rgl::unordered_map< std::filesystem::path, std::unique_ptr< GltfExporter > > replacementExporters{}; std::string currentMap{}; RgFloat3D worldUp; diff --git a/Source/VulkanDevice.cpp b/Source/VulkanDevice.cpp index 26bc7cec..d58ae5aa 100644 --- a/Source/VulkanDevice.cpp +++ b/Source/VulkanDevice.cpp @@ -1104,14 +1104,13 @@ void RTGL1::VulkanDevice::UploadMeshPrimitive( const RgMeshInfo* pMesh, } else { - UploadResult r = - scene->UploadPrimitive( currentFrameState.GetCmdBufferForMaterials( cmdManager ), - currentFrameState.GetFrameIndex(), - mesh, - prim, - *textureManager, - *textureMetaManager, - false ); + // upload a primitive, potentially loading replacements + UploadResult r = scene->UploadPrimitive( currentFrameState.GetFrameIndex(), + mesh, + prim, + *textureManager, + *lightManager, + false ); logDebugStat( Devmode::DebugPrimMode::RayTraced, &mesh, prim, r ); @@ -1129,6 +1128,7 @@ void RTGL1::VulkanDevice::UploadMeshPrimitive( const RgMeshInfo* pMesh, } + // TODO: remove legacy was to attach lights if( auto attachedLight = pnext::find< RgMeshPrimitiveAttachedLightEXT >( &prim ) ) { if( attachedLight->evenOnDynamic ) @@ -1484,7 +1484,7 @@ void RTGL1::VulkanDevice::UploadLight( const RgLightInfo* pInfo ) UploadResult r = scene->UploadLight( currentFrameState.GetFrameIndex(), light, *lightManager, false ); - if( auto e = sceneImportExport->TryGetExporter( {} ) ) + if( auto e = sceneImportExport->TryGetExporter( nullptr ) ) { if( r == UploadResult::ExportableDynamic || r == UploadResult::ExportableStatic ) { diff --git a/Source/VulkanDevice_Dev.cpp b/Source/VulkanDevice_Dev.cpp index 2f96ef66..7e3b77dd 100644 --- a/Source/VulkanDevice_Dev.cpp +++ b/Source/VulkanDevice_Dev.cpp @@ -723,12 +723,19 @@ void RTGL1::VulkanDevice::Dev_Draw() const ImGui::Separator(); ImGui::Dummy( ImVec2( 0, 16 ) ); { - if( ImGui::Button( "Reimport GLTF", { -1, 80 } ) ) + if( ImGui::Button( "Reimport replacements GLTF", { -1, 80 } ) ) + { + sceneImportExport->RequestReplacementsReimport(); + } + + ImGui::Dummy( ImVec2( 0, 16 ) ); + + if( ImGui::Button( "Reimport map GLTF", { -1, 80 } ) ) { sceneImportExport->RequestReimport(); } - ImGui::Text( "Import path: %s", + ImGui::Text( "Map import path: %s", sceneImportExport->dev_GetSceneImportGltfPath().c_str() ); ImGui::BeginDisabled( !dev.importName.enable ); { diff --git a/Source/VulkanDevice_Init.cpp b/Source/VulkanDevice_Init.cpp index 6254cbf3..45a9fee6 100644 --- a/Source/VulkanDevice_Init.cpp +++ b/Source/VulkanDevice_Init.cpp @@ -307,8 +307,7 @@ RTGL1::VulkanDevice::VulkanDevice( const RgInstanceCreateInfo* info ) *shaderManager, info->allowTexCoordLayer1, info->allowTexCoordLayer2, - info->allowTexCoordLayer3, - ovrdFolder / REPLACEMENTS_FOLDER); + info->allowTexCoordLayer3); sceneImportExport = std::make_shared< SceneImportExport >( ovrdFolder / SCENES_FOLDER,