mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 23:49:19 +02:00
588 lines
18 KiB
C++
588 lines
18 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "scene/eu7/eu7_loader.h"
|
|
|
|
#include "scene/eu7/eu7_apply.h"
|
|
#include "scene/eu7/eu7_emit.h"
|
|
#include "scene/eu7/eu7_load_stats.h"
|
|
#include "scene/eu7/eu7_pack_bench.h"
|
|
#include "scene/eu7/eu7_reader.h"
|
|
#include "scene/eu7/eu7_section_stream.h"
|
|
#include "scene/eu7/eu7_parameters.h"
|
|
#include "scene/eu7/eu7_transform.h"
|
|
#include "scene/scene.h"
|
|
#include "scene/scenenode.h"
|
|
#include "rendering/renderer.h"
|
|
#include "simulation/simulation.h"
|
|
#include "simulation/simulationstateserializer.h"
|
|
#include "scene/sn_utils.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "utilities/utilities.h"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
namespace scene::eu7 {
|
|
|
|
namespace {
|
|
|
|
std::unordered_map<std::string, Eu7Module> g_module_file_cache;
|
|
bool g_packed_root_active { false };
|
|
|
|
void
|
|
apply_material( shape_node::shapenode_data &data, std::string material_name ) {
|
|
replace_slashes( material_name );
|
|
data.material = GfxRenderer->Fetch_Material( material_name );
|
|
|
|
auto const texturehandle {
|
|
data.material != null_handle ?
|
|
GfxRenderer->Material( data.material )->GetTexture( 0 ) :
|
|
null_handle };
|
|
auto const &texture {
|
|
texturehandle ?
|
|
GfxRenderer->Texture( texturehandle ) :
|
|
*ITexture::null_texture() };
|
|
if( texturehandle != null_handle ) {
|
|
data.translucent = (
|
|
contains( texture.get_name(), '@' ) && texture.get_has_alpha() );
|
|
}
|
|
else {
|
|
data.translucent = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
finalize_shape_bounds( shape_node &shape, shape_node::shapenode_data &data ) {
|
|
if( data.vertices.empty() ) {
|
|
return;
|
|
}
|
|
data.area.center = glm::dvec3( 0.0 );
|
|
for( auto const &vertex : data.vertices ) {
|
|
data.area.center += vertex.position;
|
|
}
|
|
data.area.center /=
|
|
static_cast<double>( data.vertices.size() );
|
|
shape.invalidate_radius();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
shape_node
|
|
build_shape_node( Eu7Shape const &source ) {
|
|
shape_node shape;
|
|
auto &data { shape.m_data };
|
|
data.rangesquared_min = source.node.range_squared_min;
|
|
data.rangesquared_max = source.node.range_squared_max;
|
|
data.visible = source.node.visible;
|
|
data.origin = source.origin;
|
|
data.lighting.diffuse = glm::vec4(
|
|
source.lighting.diffuse.x, source.lighting.diffuse.y,
|
|
source.lighting.diffuse.z, source.lighting.diffuse.w );
|
|
data.lighting.ambient = glm::vec4(
|
|
source.lighting.ambient.x, source.lighting.ambient.y,
|
|
source.lighting.ambient.z, source.lighting.ambient.w );
|
|
data.lighting.specular = glm::vec4(
|
|
source.lighting.specular.x, source.lighting.specular.y,
|
|
source.lighting.specular.z, source.lighting.specular.w );
|
|
if( source.translucent ) {
|
|
data.translucent = true;
|
|
}
|
|
apply_material( data, source.material_path );
|
|
data.vertices.resize( source.vertices.size() );
|
|
for( std::size_t i { 0 }; i < source.vertices.size(); ++i ) {
|
|
auto const &src { source.vertices[ i ] };
|
|
world_vertex &dst { data.vertices[ i ] };
|
|
dst.position = src.position;
|
|
dst.normal = src.normal;
|
|
dst.texture = glm::vec2( static_cast<float>( src.u ), static_cast<float>( src.v ) );
|
|
}
|
|
finalize_shape_bounds( shape, data );
|
|
return shape;
|
|
}
|
|
|
|
lines_node
|
|
build_lines_node( Eu7Lines const &source ) {
|
|
lines_node node;
|
|
auto &data { node.m_data };
|
|
data.rangesquared_min = source.node.range_squared_min;
|
|
data.rangesquared_max = source.node.range_squared_max;
|
|
data.visible = source.node.visible;
|
|
data.line_width = source.line_width;
|
|
data.lighting.diffuse = glm::vec4(
|
|
source.lighting.diffuse.x, source.lighting.diffuse.y,
|
|
source.lighting.diffuse.z, source.lighting.diffuse.w );
|
|
data.lighting.ambient = glm::vec4(
|
|
source.lighting.ambient.x, source.lighting.ambient.y,
|
|
source.lighting.ambient.z, source.lighting.ambient.w );
|
|
data.lighting.specular = glm::vec4(
|
|
source.lighting.specular.x, source.lighting.specular.y,
|
|
source.lighting.specular.z, source.lighting.specular.w );
|
|
data.origin = source.origin;
|
|
data.vertices.resize( source.vertices.size() );
|
|
for( std::size_t i { 0 }; i < source.vertices.size(); ++i ) {
|
|
data.vertices[ i ].position = source.vertices[ i ].position;
|
|
}
|
|
node.m_name = source.node.name;
|
|
return node;
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr std::uint32_t kMagic { MAKE_ID4( 'E', 'U', '7', 'B' ) };
|
|
constexpr std::uint32_t kVersionV4 { 4 };
|
|
constexpr std::uint32_t kVersionV5 { 5 };
|
|
constexpr std::uint32_t kVersionV6 { 6 };
|
|
constexpr std::uint32_t kVersionV7 { 7 };
|
|
constexpr std::uint32_t kVersionV8 { 8 };
|
|
constexpr std::uint32_t kChunkTerr { MAKE_ID4( 'T', 'E', 'R', 'R' ) };
|
|
|
|
std::unordered_set<std::string> g_load_session;
|
|
|
|
[[nodiscard]] std::string
|
|
scenery_relative_file( std::string const &resolved ) {
|
|
if( resolved.starts_with( Global.asCurrentSceneryPath ) ) {
|
|
return resolved.substr( Global.asCurrentSceneryPath.size() );
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
[[nodiscard]] std::string
|
|
include_binary_path( Eu7Include const &include ) {
|
|
if( !include.binary_path.empty() ) {
|
|
return resolve_scenery_path( include.binary_path );
|
|
}
|
|
if( !include.source_path.empty() ) {
|
|
return binary_path( include.source_path );
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void
|
|
evict_non_pack_modules_from_cache( std::string const &pack_root_resolved ) {
|
|
for( auto it { g_module_file_cache.begin() }; it != g_module_file_cache.end(); ) {
|
|
if( it->first != pack_root_resolved ) {
|
|
it = g_module_file_cache.erase( it );
|
|
}
|
|
else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
load_module_recursive(
|
|
std::string const &path,
|
|
simulation::state_serializer &serializer,
|
|
std::unordered_set<std::string> &loaded,
|
|
Eu7TransformContext const &include_prefix = {},
|
|
std::vector<std::string> const &include_parameters = {} ) {
|
|
++load_stats().module_visits;
|
|
|
|
auto const resolved { resolve_scenery_path( path ) };
|
|
auto const load_key { module_load_key( resolved, include_parameters ) };
|
|
if( loaded.contains( load_key ) ) {
|
|
++load_stats().module_deduped;
|
|
return true;
|
|
}
|
|
if( false == probe_file( resolved ) ) {
|
|
return false;
|
|
}
|
|
|
|
Eu7Module const *template_module { nullptr };
|
|
Eu7Module owned_module;
|
|
try {
|
|
auto const cached { g_module_file_cache.find( resolved ) };
|
|
if( cached != g_module_file_cache.end() ) {
|
|
++load_stats().module_cache_hit;
|
|
template_module = &cached->second;
|
|
}
|
|
else {
|
|
ScopedTimer const read_timer { load_stats().read_ms };
|
|
owned_module = read_module( resolved );
|
|
++load_stats().module_read;
|
|
auto const emplaced { g_module_file_cache.emplace( resolved, std::move( owned_module ) ) };
|
|
template_module = &emplaced.first->second;
|
|
}
|
|
}
|
|
catch( std::exception const &ex ) {
|
|
ErrorLog( std::string{ "EU7: blad odczytu \"" + resolved + "\": " } + ex.what() );
|
|
return false;
|
|
}
|
|
|
|
if( g_packed_root_active && is_model_only_module( *template_module ) ) {
|
|
loaded.insert( load_key );
|
|
++load_stats().pack_skipped_includes;
|
|
return true;
|
|
}
|
|
|
|
loaded.insert( load_key );
|
|
++load_stats().module_applied;
|
|
|
|
if(
|
|
false == g_packed_root_active &&
|
|
include_parameters.empty() &&
|
|
transform_is_empty( include_prefix ) &&
|
|
template_module->has_pack_chunk ) {
|
|
g_packed_root_active = true;
|
|
}
|
|
|
|
std::vector<Eu7Include> fallback_includes;
|
|
for( auto const &include : template_module->includes ) {
|
|
auto const child { include_binary_path( include ) };
|
|
auto const ref {
|
|
include.source_path.empty() ? include.binary_path : include.source_path };
|
|
bool child_loaded { false };
|
|
if( !child.empty() && should_use_binary_module( ref ) ) {
|
|
child_loaded = load_module_recursive(
|
|
child,
|
|
serializer,
|
|
loaded,
|
|
include.site_transform,
|
|
include.parameters );
|
|
}
|
|
if( child_loaded ) {
|
|
continue;
|
|
}
|
|
if( !child.empty() && probe_file( child ) && false == should_use_binary_module( ref ) ) {
|
|
WriteLog(
|
|
"EU7: przestarzaly .eu7, fallback SCM: " + include_text_path( include ) );
|
|
}
|
|
else if( !child.empty() ) {
|
|
WriteLog(
|
|
"EU7 include niedostepny, fallback SCM: " + include_text_path( include ) );
|
|
}
|
|
fallback_includes.push_back( include );
|
|
}
|
|
|
|
scene::scratch_data scratch;
|
|
auto const current_relative { scenery_relative_file( resolved ) };
|
|
if( false == fallback_includes.empty() ) {
|
|
ScopedTimer const scm_timer { load_stats().scm_fallback_ms };
|
|
load_stats().scm_fallback += fallback_includes.size();
|
|
for( auto const &include : fallback_includes ) {
|
|
serializer.deserialize_include_file(
|
|
include_text_path( include ),
|
|
current_relative,
|
|
include.parameters,
|
|
scratch );
|
|
}
|
|
}
|
|
|
|
if(
|
|
fallback_includes.empty() &&
|
|
is_model_only_module( *template_module ) ) {
|
|
++load_stats().model_fast_path;
|
|
|
|
std::vector<Eu7Model> models { template_module->scene.models };
|
|
{
|
|
ScopedTimer const place_timer { load_stats().place_fast_ms };
|
|
if( false == include_parameters.empty() ) {
|
|
apply_include_parameters_to_models( models, include_parameters );
|
|
}
|
|
if( false == transform_is_empty( include_prefix ) ) {
|
|
compose_models_with_prefix( models, include_prefix );
|
|
}
|
|
apply_include_placement_to_models(
|
|
models, template_module->include_placement, include_parameters );
|
|
}
|
|
serializer.apply_eu7_models( models, scratch );
|
|
}
|
|
else {
|
|
++load_stats().module_full_path;
|
|
|
|
Eu7Module module { *template_module };
|
|
{
|
|
ScopedTimer const place_timer { load_stats().place_full_ms };
|
|
if( false == include_parameters.empty() ) {
|
|
apply_include_parameters_to_scene( module.scene, include_parameters );
|
|
}
|
|
|
|
if( false == transform_is_empty( include_prefix ) ) {
|
|
compose_scene_with_include_prefix( module.scene, include_prefix );
|
|
}
|
|
|
|
apply_include_placement_to_scene(
|
|
module.scene, module.include_placement, include_parameters );
|
|
}
|
|
|
|
apply_module( module, serializer, scratch );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
file_has_terr_chunk( std::string const &path ) {
|
|
std::ifstream input { path, std::ios::binary };
|
|
if( !input ) {
|
|
return false;
|
|
}
|
|
|
|
if( sn_utils::ld_uint32( input ) != kMagic ) {
|
|
return false;
|
|
}
|
|
const std::uint32_t version { sn_utils::ld_uint32( input ) };
|
|
if(
|
|
version != kVersionV4 && version != kVersionV5 && version != kVersionV6 &&
|
|
version != kVersionV7 && version != kVersionV8 ) {
|
|
return false;
|
|
}
|
|
|
|
while( input.peek() != EOF ) {
|
|
const std::uint32_t chunk_type { sn_utils::ld_uint32( input ) };
|
|
const std::uint32_t chunk_size { sn_utils::ld_uint32( input ) };
|
|
if( chunk_size < 8 ) {
|
|
return false;
|
|
}
|
|
if( chunk_type == kChunkTerr ) {
|
|
return true;
|
|
}
|
|
input.seekg( static_cast<std::streamoff>( chunk_size - 8 ), std::ios::cur );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string
|
|
resolve_scenery_path( std::string const &reference ) {
|
|
auto path { reference };
|
|
while( false == path.empty() && path[ 0 ] == '$' ) {
|
|
path.erase( 0, 1 );
|
|
}
|
|
replace_slashes( path );
|
|
if( path.starts_with( Global.asCurrentSceneryPath ) ) {
|
|
return path;
|
|
}
|
|
return Global.asCurrentSceneryPath + path;
|
|
}
|
|
|
|
std::string
|
|
binary_path( std::string const &reference ) {
|
|
auto path { resolve_scenery_path( reference ) };
|
|
if( path.ends_with( ".scm" ) || path.ends_with( ".sbt" ) || path.ends_with( ".inc" ) ||
|
|
path.ends_with( ".scn" ) ) {
|
|
path.replace( path.size() - 4, 4, ".eu7" );
|
|
}
|
|
else if( false == path.ends_with( ".eu7" ) ) {
|
|
erase_extension( path );
|
|
path += ".eu7";
|
|
}
|
|
return path;
|
|
}
|
|
|
|
std::string
|
|
resolve_parser_include_path(
|
|
std::string const &parser_path,
|
|
std::string const ¤t_file,
|
|
std::string const &include_reference ) {
|
|
auto reference { include_reference };
|
|
while( false == reference.empty() && reference[ 0 ] == '$' ) {
|
|
reference.erase( 0, 1 );
|
|
}
|
|
replace_slashes( reference );
|
|
if( reference.starts_with( Global.asCurrentSceneryPath ) ) {
|
|
return reference;
|
|
}
|
|
if( reference.find( '/' ) != std::string::npos ) {
|
|
return parser_path + reference;
|
|
}
|
|
if( false == current_file.empty() ) {
|
|
auto const slash { current_file.find_last_of( '/' ) };
|
|
if( slash != std::string::npos ) {
|
|
return parser_path + current_file.substr( 0, slash + 1 ) + reference;
|
|
}
|
|
}
|
|
return parser_path + reference;
|
|
}
|
|
|
|
std::string
|
|
include_eu7_path(
|
|
std::string const &parser_path,
|
|
std::string const ¤t_file,
|
|
std::string const &include_reference ) {
|
|
return binary_path( resolve_parser_include_path( parser_path, current_file, include_reference ) );
|
|
}
|
|
|
|
std::string
|
|
terrain_binary_path( std::string const &terrain_reference ) {
|
|
return binary_path( terrain_reference );
|
|
}
|
|
|
|
bool
|
|
probe_file( std::string const &path ) {
|
|
return FileExists( path ) && is_valid_eu7b_file( path );
|
|
}
|
|
|
|
bool
|
|
probe_baked_scenario( std::string const &scenario_file ) {
|
|
return probe_file( resolve_scenery_path( binary_path( scenario_file ) ) );
|
|
}
|
|
|
|
bool
|
|
is_text_module_extension( std::string const &path ) {
|
|
return path.ends_with( ".scn" ) || path.ends_with( ".scm" ) ||
|
|
path.ends_with( ".inc" ) || path.ends_with( ".sbt" );
|
|
}
|
|
|
|
std::string
|
|
text_source_path( std::string const &reference ) {
|
|
auto const resolved { resolve_scenery_path( reference ) };
|
|
if( is_text_module_extension( resolved ) ) {
|
|
return resolved;
|
|
}
|
|
if( resolved.ends_with( ".eu7" ) ) {
|
|
auto stem { resolved };
|
|
stem.erase( stem.size() - 4 );
|
|
for( auto const *ext : { ".scm", ".scn", ".inc", ".sbt" } ) {
|
|
auto const candidate { stem + ext };
|
|
if( FileExists( candidate ) ) {
|
|
return candidate;
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
bool
|
|
text_module_is_newer_than_binary( std::string const &reference ) {
|
|
auto const text { text_source_path( reference ) };
|
|
if( text.empty() || false == FileExists( text ) ) {
|
|
return false;
|
|
}
|
|
auto const eu7 { binary_path( reference ) };
|
|
if( false == FileExists( eu7 ) ) {
|
|
return true;
|
|
}
|
|
return last_modified( text ) > last_modified( eu7 );
|
|
}
|
|
|
|
bool
|
|
should_use_binary_module( std::string const &reference ) {
|
|
auto const eu7 { binary_path( reference ) };
|
|
return probe_file( eu7 ) && false == text_module_is_newer_than_binary( reference );
|
|
}
|
|
|
|
bool
|
|
probe_terrain_file( std::string const &path ) {
|
|
return FileExists( path ) && file_has_terr_chunk( path );
|
|
}
|
|
|
|
bool
|
|
is_scenario_terrain( std::string const &scenario_file ) {
|
|
auto stem { scenario_file };
|
|
while( false == stem.empty() && stem[ 0 ] == '$' ) {
|
|
stem.erase( 0, 1 );
|
|
}
|
|
erase_extension( stem );
|
|
return probe_terrain_file( resolve_scenery_path( stem + ".eu7" ) );
|
|
}
|
|
|
|
bool
|
|
try_load_scenario_terrain( basic_region ®ion, std::string const &scenario_file ) {
|
|
auto stem { scenario_file };
|
|
while( false == stem.empty() && stem[ 0 ] == '$' ) {
|
|
stem.erase( 0, 1 );
|
|
}
|
|
erase_extension( stem );
|
|
return load_terrain( region, resolve_scenery_path( stem + ".eu7" ) );
|
|
}
|
|
|
|
void
|
|
insert_terrain_shapes( basic_region ®ion, Eu7Module const &module ) {
|
|
scene::scratch_data scratch;
|
|
std::size_t shape_count { 0 };
|
|
std::size_t vertex_count { 0 };
|
|
for( auto const &source : module.scene.terrain_shapes ) {
|
|
shape_node shape { build_shape_node( source ) };
|
|
vertex_count += source.vertices.size();
|
|
region.insert( shape, scratch, false );
|
|
++shape_count;
|
|
}
|
|
if( shape_count > 0 ) {
|
|
WriteLog(
|
|
"EU7 terrain shapes: count=" + std::to_string( shape_count ) +
|
|
" vertices=" + std::to_string( vertex_count ) );
|
|
}
|
|
}
|
|
|
|
bool
|
|
load_terrain( basic_region ®ion, std::string const &path ) {
|
|
if( false == probe_terrain_file( path ) ) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
auto const module { read_module( path ) };
|
|
if( module.scene.terrain_shapes.empty() ) {
|
|
ErrorLog( "EU7: brak chunka TERR w \"" + path + "\"" );
|
|
return false;
|
|
}
|
|
insert_terrain_shapes( region, module );
|
|
WriteLog( "EU7 terrain loaded: " + path );
|
|
return true;
|
|
}
|
|
catch( std::exception const &ex ) {
|
|
ErrorLog( std::string{ "EU7: blad terenu \"" + path + "\": " } + ex.what() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
begin_load_session() {
|
|
g_load_session.clear();
|
|
g_packed_root_active = false;
|
|
reset_section_stream();
|
|
g_module_file_cache.clear();
|
|
reset_load_stats();
|
|
reset_pack_bench();
|
|
}
|
|
|
|
bool
|
|
is_module_loaded( std::string const &path ) {
|
|
return g_load_session.contains( resolve_scenery_path( path ) );
|
|
}
|
|
|
|
bool
|
|
pack_scenery_active() {
|
|
return g_packed_root_active;
|
|
}
|
|
|
|
bool
|
|
load_module( std::string const &path, simulation::state_serializer &serializer ) {
|
|
if( simulation::Region == nullptr ) {
|
|
ErrorLog( "EU7: Region nie jest zainicjalizowany" );
|
|
return false;
|
|
}
|
|
auto const resolved { resolve_scenery_path( path ) };
|
|
auto const ok { load_module_recursive( path, serializer, g_load_session ) };
|
|
if( ok ) {
|
|
if( auto const cached { g_module_file_cache.find( resolved ) };
|
|
cached != g_module_file_cache.end() && cached->second.has_pack_chunk ) {
|
|
init_section_stream( cached->second, resolved, serializer );
|
|
prime_section_stream( cached->second );
|
|
evict_non_pack_modules_from_cache( resolved );
|
|
}
|
|
else {
|
|
evict_non_pack_modules_from_cache( {} );
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
} // namespace scene::eu7
|