16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-22 00:09:18 +02:00

EU7 scenery format and models streaming

This commit is contained in:
maj00r
2026-06-13 15:43:44 +02:00
parent f07c8fdcd2
commit 876a4c7c18
171 changed files with 27924 additions and 281 deletions

194
scene/eu7/eu7_apply.cpp Normal file
View File

@@ -0,0 +1,194 @@
/*
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_apply.h"
#include "scene/eu7/eu7_loader.h"
#include "scene/eu7/eu7_load_stats.h"
#include "scene/eu7/eu7_transform.h"
#include "simulation/simulation.h"
#include "simulation/simulationstateserializer.h"
#include "utilities/Logs.h"
namespace scene::eu7 {
namespace {
void
insert_mesh_shapes(
basic_region &region,
Eu7Module const &module,
scene::scratch_data &scratch ) {
for( auto const &source : module.scene.shapes ) {
shape_node shape { build_shape_node( source ) };
region.insert( shape, scratch, true );
}
}
void
insert_line_shapes(
basic_region &region,
Eu7Module const &module,
scene::scratch_data &scratch ) {
for( auto const &source : module.scene.lines ) {
lines_node lines { build_lines_node( source ) };
region.insert( lines, scratch );
}
}
void
transform_segment_path( Eu7SegmentPath &path, Eu7TransformContext const &prefix ) {
path.p_start = transform_point( path.p_start, prefix );
path.cp_out = transform_vector( path.cp_out, prefix );
path.cp_in = transform_vector( path.cp_in, prefix );
path.p_end = transform_point( path.p_end, prefix );
}
void
transform_shape_vertices( std::vector<Eu7WorldVertex> &vertices, Eu7TransformContext const &prefix ) {
for( auto &vertex : vertices ) {
vertex.position = transform_point( vertex.position, prefix );
}
}
} // namespace
bool
is_model_only_module( Eu7Module const &module ) {
if( module.has_terrain_chunk ) {
return false;
}
auto const &scene { module.scene };
return (
false == scene.models.empty() &&
scene.tracks.empty() &&
scene.traction.empty() &&
scene.power_sources.empty() &&
scene.shapes.empty() &&
scene.terrain_shapes.empty() &&
scene.lines.empty() &&
scene.memcells.empty() &&
scene.event_launchers.empty() &&
scene.dynamics.empty() &&
scene.sounds.empty() &&
scene.trainsets.empty() &&
scene.events.empty() &&
scene.first_init_count == 0 );
}
void
compose_models_with_prefix( std::vector<Eu7Model> &models, Eu7TransformContext const &prefix ) {
if( transform_is_empty( prefix ) ) {
return;
}
for( auto &model : models ) {
model.location = transform_point( model.location, prefix );
model.angles.x += prefix.rotation.x;
model.angles.y += prefix.rotation.y;
model.angles.z += prefix.rotation.z;
compose_node_transform( model.node.transform, prefix );
model.node.area.center = model.location;
}
}
void
compose_scene_with_include_prefix( Eu7Scene &scene, Eu7TransformContext const &prefix ) {
if( transform_is_empty( prefix ) ) {
return;
}
for( auto &track : scene.tracks ) {
for( auto &path : track.paths ) {
transform_segment_path( path, prefix );
}
compose_node_transform( track.node.transform, prefix );
}
for( auto &traction : scene.traction ) {
traction.wire_p1 = transform_point( traction.wire_p1, prefix );
traction.wire_p2 = transform_point( traction.wire_p2, prefix );
traction.wire_p3 = transform_point( traction.wire_p3, prefix );
traction.wire_p4 = transform_point( traction.wire_p4, prefix );
compose_node_transform( traction.node.transform, prefix );
}
for( auto &source : scene.power_sources ) {
source.position = transform_point( source.position, prefix );
compose_node_transform( source.node.transform, prefix );
}
for( auto &shape : scene.shapes ) {
shape.origin = transform_point( shape.origin, prefix );
transform_shape_vertices( shape.vertices, prefix );
compose_node_transform( shape.node.transform, prefix );
}
for( auto &shape : scene.terrain_shapes ) {
shape.origin = transform_point( shape.origin, prefix );
transform_shape_vertices( shape.vertices, prefix );
compose_node_transform( shape.node.transform, prefix );
}
for( auto &lines : scene.lines ) {
lines.origin = transform_point( lines.origin, prefix );
transform_shape_vertices( lines.vertices, prefix );
compose_node_transform( lines.node.transform, prefix );
}
compose_models_with_prefix( scene.models, prefix );
for( auto &cell : scene.memcells ) {
cell.node.area.center = transform_point( cell.node.area.center, prefix );
compose_node_transform( cell.node.transform, prefix );
}
for( auto &launcher : scene.event_launchers ) {
launcher.location = transform_point( launcher.location, prefix );
compose_node_transform( launcher.node.transform, prefix );
launcher.node.area.center = launcher.location;
}
for( auto &vehicle : scene.dynamics ) {
compose_node_transform( vehicle.node.transform, prefix );
}
for( auto &sound : scene.sounds ) {
sound.location = transform_point( sound.location, prefix );
compose_node_transform( sound.node.transform, prefix );
sound.node.area.center = sound.location;
}
}
void
apply_module(
Eu7Module const &module,
simulation::state_serializer &serializer,
scene::scratch_data &scratch ) {
if( simulation::Region != nullptr ) {
{
ScopedTimer const timer { load_stats().terr_ms };
insert_terrain_shapes( *simulation::Region, module );
}
{
ScopedTimer const timer { load_stats().mesh_ms };
insert_mesh_shapes( *simulation::Region, module, scratch );
}
{
ScopedTimer const timer { load_stats().line_ms };
insert_line_shapes( *simulation::Region, module, scratch );
}
}
serializer.apply_eu7_scene( module.scene, scratch );
}
} // namespace scene::eu7

37
scene/eu7/eu7_apply.h Normal file
View File

@@ -0,0 +1,37 @@
/*
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/.
*/
#pragma once
#include "scene/eu7/eu7_types.h"
namespace scene {
class scratch_data;
}
namespace simulation {
class state_serializer;
}
namespace scene::eu7 {
[[nodiscard]] bool
is_model_only_module( Eu7Module const &module );
void
compose_models_with_prefix( std::vector<Eu7Model> &models, Eu7TransformContext const &prefix );
// Wczytuje zawartosc Eu7Module do symulacji (bez ponownego INCL i bez petli include).
void
apply_module(
Eu7Module const &Module,
simulation::state_serializer &Serializer,
scene::scratch_data &Scratch );
} // namespace scene::eu7

337
scene/eu7/eu7_bake.cpp Normal file
View File

@@ -0,0 +1,337 @@
/*
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_bake.h"
#include "scene/eu7/eu7_loader.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
#include <thread>
#ifdef WITH_EU7_PARSER
#include "scene/eu7/eu7_bake_parser.h"
#endif
namespace scene::eu7 {
namespace {
[[nodiscard]] unsigned
bake_thread_count() {
if( Global.eu7_bake_threads > 0 ) {
return static_cast<unsigned>( Global.eu7_bake_threads );
}
auto const hardware { std::thread::hardware_concurrency() };
return hardware > 0 ? hardware : 1u;
}
#ifdef WITH_EU7_PARSER
[[nodiscard]] bool
run_scenario_tree_bake(
std::string const &text_root,
std::string const &eu7_path,
Eu7BakeOutcome &outcome ) {
WriteLog(
"EU7 bake: generowanie modulow z \"" + text_root + "\" (watek=" +
std::to_string( bake_thread_count() ) + ")" );
std::string error;
if( false == bake_parser::bake_scenario_tree( text_root, bake_thread_count(), error ) ) {
if( probe_file( eu7_path ) ) {
ErrorLog(
"EU7 bake nieudany, uzywam istniejacego modulu: " + error );
outcome.ok = true;
outcome.message = "fallback na stary .eu7 po bledzie bake";
return true;
}
outcome.message = error;
return false;
}
if( false == probe_file( eu7_path ) ) {
outcome.message = "bake zakonczony, ale brak pliku: " + eu7_path;
return false;
}
outcome.ok = true;
outcome.regenerated = true;
outcome.message = eu7_path;
WriteLog( "EU7 bake: zapisano " + eu7_path );
return true;
}
#endif
} // namespace
bool
scenario_needs_eu7_regen( std::string const &scenario_file ) {
#ifdef WITH_EU7_PARSER
if( false == Global.eu7_auto_bake ) {
return false;
}
auto const resolved { resolve_scenery_path( scenario_file ) };
if( probe_file( resolved ) ) {
return text_module_is_newer_than_binary( scenario_file );
}
if( false == is_text_module_extension( resolved ) || false == FileExists( resolved ) ) {
return false;
}
return false == should_use_binary_module( scenario_file );
#else
(void)scenario_file;
return false;
#endif
}
Eu7BakeOutcome
ensure_scenario_eu7( std::string const &scenario_file ) {
Eu7BakeOutcome outcome;
auto const resolved { resolve_scenery_path( scenario_file ) };
auto const eu7_path { resolve_scenery_path( binary_path( scenario_file ) ) };
if( probe_file( resolved ) ) {
#ifdef WITH_EU7_PARSER
if( Global.eu7_auto_bake && text_module_is_newer_than_binary( scenario_file ) ) {
auto const text_root { text_source_path( scenario_file ) };
if( false == text_root.empty() ) {
WriteLog(
"EU7: .eu7 nieaktualny wzgledem " + text_root + ", rebake..." );
run_scenario_tree_bake( text_root, eu7_path, outcome );
return outcome;
}
}
#endif
outcome.ok = true;
outcome.message = "uzywam .eu7: " + resolved;
WriteLog( "EU7: uzywam .eu7: " + resolved );
return outcome;
}
#ifdef WITH_EU7_PARSER
if( false == Global.eu7_auto_bake ) {
if( should_use_binary_module( scenario_file ) ) {
outcome.ok = true;
outcome.message = "eu7_auto_bake wylaczone, uzywam istniejacego .eu7";
return outcome;
}
outcome.ok = true;
outcome.message = "eu7_auto_bake wylaczone, ladowanie SCM";
return outcome;
}
if( false == is_text_module_extension( resolved ) ) {
outcome.ok = true;
outcome.message = "nie jest scenariuszem tekstowym";
return outcome;
}
if( false == FileExists( resolved ) ) {
outcome.message = "brak pliku scenariusza: " + resolved;
return outcome;
}
if( should_use_binary_module( scenario_file ) ) {
outcome.ok = true;
outcome.message = "uzywam istniejacego .eu7: " + eu7_path;
WriteLog( "EU7: uzywam .eu7: " + eu7_path );
return outcome;
}
if( probe_file( eu7_path ) ) {
WriteLog(
"EU7: .eu7 nieaktualny wzgledem " + resolved + ", rebake..." );
}
run_scenario_tree_bake( resolved, eu7_path, outcome );
return outcome;
#else
if( should_use_binary_module( scenario_file ) ) {
outcome.ok = true;
outcome.message = "uzywam istniejacego .eu7 (parser niedostepny w buildzie)";
return outcome;
}
outcome.ok = true;
outcome.message = "parser niedostepny, ladowanie SCM";
return outcome;
#endif
}
} // namespace scene::eu7

30
scene/eu7/eu7_bake.h Normal file
View File

@@ -0,0 +1,30 @@
/*
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/.
*/
#pragma once
#include <string>
namespace scene::eu7 {
struct Eu7BakeOutcome {
bool ok { false };
bool regenerated { false };
std::string message;
};
// Czy wejscie to tekst SCM/SCN bez gotowego .eu7 przy wlaczonym auto-bake.
[[nodiscard]] bool
scenario_needs_eu7_regen( std::string const &ScenarioFile );
// Przed ladowaniem mapy: uzyj istniejacego <stem>.eu7 lub wygeneruj cale drzewo include.
[[nodiscard]] Eu7BakeOutcome
ensure_scenario_eu7( std::string const &ScenarioFile );
} // namespace scene::eu7

View File

@@ -0,0 +1,170 @@
/*
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 <eu07/scene/bake/bake_tree.hpp>
#include <filesystem>
#include <mutex>
#include <string>
namespace scene::eu7::bake_parser {
namespace {
namespace fs = std::filesystem;
std::mutex g_progress_log_mutex;
void
log_bake_progress( eu07::scene::bake::BakeProgress const &progress ) {
std::lock_guard<std::mutex> lock { g_progress_log_mutex };
auto const path { progress.path.u8string() };
switch( progress.phase ) {
case eu07::scene::bake::BakeProgressPhase::Starting:
case eu07::scene::bake::BakeProgressPhase::Start:
break;
case eu07::scene::bake::BakeProgressPhase::PackModels:
case eu07::scene::bake::BakeProgressPhase::PackCompose:
case eu07::scene::bake::BakeProgressPhase::PackComposeDone:
case eu07::scene::bake::BakeProgressPhase::Bake:
case eu07::scene::bake::BakeProgressPhase::Done:
break;
}
(void)path;
}
[[nodiscard]] bool
is_text_scenery_path( fs::path const &path ) {
auto const ext { path.extension().string() };
return ext == ".scn" || ext == ".scm" || ext == ".inc" || ext == ".sbt";
}
} // namespace
bool
bake_scenario_tree(
std::string const &text_scenario_path,
unsigned const max_threads,
std::string &error_out ) {
error_out.clear();
fs::path const input { text_scenario_path };
if( false == fs::exists( input ) ) {
error_out = "brak pliku: " + text_scenario_path;
return false;
}
if( false == is_text_scenery_path( input ) ) {
error_out = "nie jest plikiem SCM/SCN/INC: " + text_scenario_path;
return false;
}
try {
eu07::scene::bake::BakeTreeOptions options;
options.maxThreads = max_threads;
options.onProgress = log_bake_progress;
eu07::scene::bake::BakeTreeStats stats;
(void)eu07::scene::bake::bakeModuleTree( input, &stats, options );
return true;
}
catch( std::exception const &ex ) {
error_out = ex.what();
return false;
}
catch( ... ) {
error_out = "nieznany wyjatek bake";
return false;
}
}
} // namespace scene::eu7::bake_parser

View File

@@ -0,0 +1,22 @@
/*
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/.
*/
#pragma once
#include <string>
namespace scene::eu7::bake_parser {
[[nodiscard]] bool
bake_scenario_tree(
std::string const &TextScenarioPath,
unsigned MaxThreads,
std::string &ErrorOut );
} // namespace scene::eu7::bake_parser

62
scene/eu7/eu7_chunks.h Normal file
View File

@@ -0,0 +1,62 @@
/*
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/.
*/
#pragma once
#include <cstdint>
namespace scene::eu7 {
namespace detail {
[[nodiscard]] constexpr std::uint32_t
make_id4( char const a, char const b, char const c, char const d ) noexcept {
return (
( static_cast<std::uint32_t>( static_cast<unsigned char>( d ) ) << 24u ) |
( static_cast<std::uint32_t>( static_cast<unsigned char>( c ) ) << 16u ) |
( static_cast<std::uint32_t>( static_cast<unsigned char>( b ) ) << 8u ) |
static_cast<std::uint32_t>( static_cast<unsigned char>( a ) ) );
}
} // namespace detail
constexpr std::uint32_t kEu7Magic { detail::make_id4( 'E', 'U', '7', 'B' ) };
constexpr std::uint32_t kEu7VersionV4 { 4 };
constexpr std::uint32_t kEu7VersionV5 { 5 };
constexpr std::uint32_t kEu7VersionV6 { 6 };
constexpr std::uint32_t kEu7VersionV7 { 7 };
constexpr std::uint32_t kEu7VersionV8 { 8 };
constexpr std::uint32_t kChunkStrs { detail::make_id4( 'S', 'T', 'R', 'S' ) };
constexpr std::uint32_t kChunkIncl { detail::make_id4( 'I', 'N', 'C', 'L' ) };
constexpr std::uint32_t kChunkTrak { detail::make_id4( 'T', 'R', 'A', 'K' ) };
constexpr std::uint32_t kChunkTrac { detail::make_id4( 'T', 'R', 'A', 'C' ) };
constexpr std::uint32_t kChunkPwrs { detail::make_id4( 'P', 'W', 'R', 'S' ) };
constexpr std::uint32_t kChunkTerr { detail::make_id4( 'T', 'E', 'R', 'R' ) };
constexpr std::uint32_t kChunkMesh { detail::make_id4( 'M', 'E', 'S', 'H' ) };
constexpr std::uint32_t kChunkLine { detail::make_id4( 'L', 'I', 'N', 'E' ) };
constexpr std::uint32_t kChunkModl { detail::make_id4( 'M', 'O', 'D', 'L' ) };
constexpr std::uint32_t kChunkMemc { detail::make_id4( 'M', 'E', 'M', 'C' ) };
constexpr std::uint32_t kChunkLaun { detail::make_id4( 'L', 'A', 'U', 'N' ) };
constexpr std::uint32_t kChunkDynm { detail::make_id4( 'D', 'Y', 'N', 'M' ) };
constexpr std::uint32_t kChunkSond { detail::make_id4( 'S', 'O', 'N', 'D' ) };
constexpr std::uint32_t kChunkTrset { detail::make_id4( 'T', 'R', 'S', 'E' ) };
constexpr std::uint32_t kChunkEvnt { detail::make_id4( 'E', 'V', 'N', 'T' ) };
constexpr std::uint32_t kChunkFint { detail::make_id4( 'F', 'I', 'N', 'T' ) };
constexpr std::uint32_t kChunkPlac { detail::make_id4( 'P', 'L', 'A', 'C' ) };
// Indeks sekcji 1 km -> offset w chunku PACK (EU7B v7+).
constexpr std::uint32_t kChunkPidx { detail::make_id4( 'P', 'I', 'D', 'X' ) };
// MODL per sekcja, world-space; payload = concat sekcji (offsety z PIDX).
constexpr std::uint32_t kChunkPack { detail::make_id4( 'P', 'A', 'C', 'K' ) };
// Wspolne definicje modeli (EU7B v8+).
constexpr std::uint32_t kChunkProt { detail::make_id4( 'P', 'R', 'O', 'T' ) };
constexpr std::uint8_t kPackSectionFormatV8 { 1 };
} // namespace scene::eu7

790
scene/eu7/eu7_emit.cpp Normal file
View File

@@ -0,0 +1,790 @@
/*
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_emit.h"
#include "scene/eu7/eu7_transform.h"
#include "utilities/utilities.h"
#include <cmath>
#include <format>
#include <limits>
#include <sstream>
namespace scene::eu7 {
namespace {
[[nodiscard]] std::string
format_double( double const value ) {
std::string text { std::format( "{:.4f}", value ) };
if( auto const dot { text.find( '.' ) }; dot != std::string::npos ) {
while( text.size() > dot + 1 && text.back() == '0' ) {
text.pop_back();
}
if( text.back() == '.' ) {
text.pop_back();
}
}
return text;
}
class TextEmitter {
public:
void
push( std::string value ) {
if( !first_on_line_ ) {
out_ << ' ';
}
first_on_line_ = false;
out_ << value;
}
void
push( std::string_view const value ) {
push( std::string( value ) );
}
void
push( char const *const value ) {
push( std::string_view( value ) );
}
void
push( double const value ) {
push( format_double( value ) );
}
void
push( float const value ) {
push( static_cast<double>( value ) );
}
void
push( int const value ) {
push( std::to_string( value ) );
}
void
push_vec3( glm::dvec3 const &v ) {
push( v.x );
push( v.y );
push( v.z );
}
void
newline() {
out_ << '\n';
first_on_line_ = true;
}
[[nodiscard]] std::string
str() const {
return out_.str();
}
private:
std::ostringstream out_;
bool first_on_line_ { true };
};
struct TransformEmitState {
std::vector<glm::dvec3> origin_stack;
std::vector<glm::dvec3> scale_stack;
glm::dvec3 rotation{};
std::size_t group_depth = 0;
};
[[nodiscard]] glm::dvec3
origin_push_delta( std::vector<glm::dvec3> const &stack, std::size_t const index ) {
auto const &cumulative { stack[ index ] };
if( index == 0 ) {
return cumulative;
}
auto const &parent { stack[ index - 1 ] };
return { cumulative.x - parent.x, cumulative.y - parent.y, cumulative.z - parent.z };
}
[[nodiscard]] glm::dvec3
scale_push_factor( std::vector<glm::dvec3> const &stack, std::size_t const index ) {
auto const &cumulative { stack[ index ] };
auto const parent { index == 0 ? glm::dvec3{ 1.0, 1.0, 1.0 } : stack[ index - 1 ] };
return {
parent.x != 0.0 ? cumulative.x / parent.x : cumulative.x,
parent.y != 0.0 ? cumulative.y / parent.y : cumulative.y,
parent.z != 0.0 ? cumulative.z / parent.z : cumulative.z };
}
void
sync_transform_stack( TextEmitter &w, TransformEmitState &state, Eu7TransformContext const &target ) {
while( state.group_depth > target.group_depth ) {
w.push( "endgroup" );
--state.group_depth;
}
while( state.scale_stack.size() > target.scale_stack.size() ) {
w.push( "endscale" );
state.scale_stack.pop_back();
}
while( state.origin_stack.size() > target.origin_stack.size() ) {
w.push( "endorigin" );
state.origin_stack.pop_back();
}
while( state.origin_stack.size() < target.origin_stack.size() ) {
auto const index { state.origin_stack.size() };
auto const delta { origin_push_delta( target.origin_stack, index ) };
w.push( "origin" );
w.push_vec3( delta );
state.origin_stack.push_back( target.origin_stack[ index ] );
}
while( state.scale_stack.size() < target.scale_stack.size() ) {
auto const index { state.scale_stack.size() };
auto const factor { scale_push_factor( target.scale_stack, index ) };
w.push( "scale" );
w.push_vec3( factor );
state.scale_stack.push_back( target.scale_stack[ index ] );
}
while( state.group_depth < target.group_depth ) {
w.push( "group" );
++state.group_depth;
}
if( state.rotation.x != target.rotation.x || state.rotation.y != target.rotation.y ||
state.rotation.z != target.rotation.z ) {
w.push( "rotate" );
w.push_vec3( target.rotation );
state.rotation = target.rotation;
}
}
void
emit_node_transform( TextEmitter &w, TransformEmitState &state, Eu7BasicNode const &node ) {
sync_transform_stack( w, state, node.transform );
}
[[nodiscard]] double
range_max_from_node( Eu7BasicNode const &node ) {
if( node.range_squared_max >= std::numeric_limits<double>::max() * 0.5 ) {
return -1.0;
}
return std::sqrt( node.range_squared_max );
}
[[nodiscard]] double
range_min_from_node( Eu7BasicNode const &node ) {
return std::sqrt( node.range_squared_min );
}
[[nodiscard]] std::string
node_display_name( Eu7BasicNode const &node ) {
return node.name.empty() ? "none" : node.name;
}
void
push_node_header( TextEmitter &w, Eu7BasicNode const &node ) {
w.push( "node" );
w.push( range_max_from_node( node ) );
w.push( range_min_from_node( node ) );
w.push( node_display_name( node ) );
}
[[nodiscard]] std::string_view
track_environment_name( Eu7TrackEnvironment const env ) {
switch( env ) {
case Eu7TrackEnvironment::Flat:
return "flat";
case Eu7TrackEnvironment::Mountains:
return "mountains";
case Eu7TrackEnvironment::Canyon:
return "canyon";
case Eu7TrackEnvironment::Tunnel:
return "tunnel";
case Eu7TrackEnvironment::Bridge:
return "bridge";
case Eu7TrackEnvironment::Bank:
return "bank";
default:
return "flat";
}
}
[[nodiscard]] std::string_view
track_kind_token( Eu7Track const &track ) {
if( track.category == Eu7TrackCategory::Road ) {
return "road";
}
if( track.category == Eu7TrackCategory::Water ) {
return "river";
}
switch( track.track_type ) {
case Eu7TrackType::Switch:
return "switch";
case Eu7TrackType::Cross:
return "cross";
case Eu7TrackType::Table:
return "turn";
case Eu7TrackType::Tributary:
return "tributary";
default:
return "normal";
}
}
void
push_track_core( TextEmitter &w, Eu7Track const &track ) {
w.push( track.length );
w.push( track.track_width );
w.push( track.friction );
w.push( track.sound_distance );
w.push( track.quality_flag );
w.push( track.damage_flag );
w.push( track_environment_name( track.environment ) );
if( track.visibility ) {
w.push( "vis" );
w.push( track.visibility->material1 );
w.push( track.visibility->tex_length );
w.push( track.visibility->material2 );
w.push( track.visibility->tex_height1 );
w.push( track.visibility->tex_width );
w.push( track.visibility->tex_slope );
}
else {
w.push( "unvis" );
}
}
void
push_track_segments( TextEmitter &w, Eu7Track const &track, Eu7TransformContext const &transform ) {
for( auto const &seg : track.paths ) {
w.push_vec3( subtract_origin_offset( seg.p_start, transform ) );
w.push( seg.roll_start );
w.push_vec3( seg.cp_out );
w.push_vec3( seg.cp_in );
w.push_vec3( subtract_origin_offset( seg.p_end, transform ) );
w.push( seg.roll_end );
w.push( seg.radius );
}
for( auto const &[key, value] : track.tail_keywords ) {
w.push( key );
if( key == "sleepermodel" ) {
std::size_t start { 0 };
while( start < value.size() ) {
auto const end { value.find( ' ', start ) };
if( end == std::string::npos ) {
w.push( value.substr( start ) );
break;
}
w.push( value.substr( start, end - start ) );
start = end + 1;
}
}
else {
w.push( value );
}
}
}
void
emit_track( TextEmitter &w, TransformEmitState &state, Eu7Track const &track ) {
emit_node_transform( w, state, track.node );
push_node_header( w, track.node );
w.push( "track" );
w.push( track_kind_token( track ) );
push_track_core( w, track );
push_track_segments( w, track, track.node.transform );
w.push( "endtrack" );
w.newline();
}
[[nodiscard]] std::string
traction_resistivity_token( Eu7Traction const &traction ) {
if( traction.resistivity_legacy != 0.0 ) {
return format_double( traction.resistivity_legacy );
}
return format_double( traction.resistivity_ohm_per_m / 0.001 );
}
[[nodiscard]] std::string
traction_material_token( Eu7Traction const &traction ) {
if( !traction.material_raw.empty() ) {
return traction.material_raw;
}
switch( traction.material ) {
case Eu7TractionWireMaterial::Aluminium:
return "al";
case Eu7TractionWireMaterial::None:
return "none";
default:
return "cu";
}
}
void
emit_traction( TextEmitter &w, TransformEmitState &state, Eu7Traction const &traction ) {
emit_node_transform( w, state, traction.node );
auto const &transform { traction.node.transform };
push_node_header( w, traction.node );
w.push( "traction" );
w.push( traction.power_supply_name );
w.push( traction.nominal_voltage );
w.push( traction.max_current );
w.push( traction_resistivity_token( traction ) );
w.push( traction_material_token( traction ) );
w.push( traction.wire_thickness );
w.push( traction.damage_flag );
w.push_vec3( subtract_origin_offset( traction.wire_p1, transform ) );
w.push_vec3( subtract_origin_offset( traction.wire_p2, transform ) );
w.push_vec3( subtract_origin_offset( traction.wire_p3, transform ) );
w.push_vec3( subtract_origin_offset( traction.wire_p4, transform ) );
w.push( traction.min_height );
w.push( traction.segment_length );
w.push( traction.wire_count );
w.push( traction.wire_offset );
w.push( traction.node.visible ? "vis" : "unvis" );
if( traction.parallel_name ) {
w.push( "parallel" );
w.push( *traction.parallel_name );
}
w.push( "endtraction" );
w.newline();
}
void
emit_power_source( TextEmitter &w, TransformEmitState &state, Eu7TractionPowerSource const &source ) {
emit_node_transform( w, state, source.node );
push_node_header( w, source.node );
w.push( "tractionpowersource" );
w.push_vec3( inverse_transform_point( source.position, source.node.transform ) );
w.push( source.nominal_voltage );
w.push( source.voltage_frequency );
w.push( source.internal_resistance_legacy );
w.push( source.max_output_current );
w.push( source.fast_fuse_timeout );
w.push( source.fast_fuse_repetition );
w.push( source.slow_fuse_timeout );
if( source.modifier == Eu7PowerSourceModifier::Recuperation ) {
w.push( "recuperation" );
}
else if( source.modifier == Eu7PowerSourceModifier::Section ) {
w.push( "section" );
}
w.push( "end" );
w.newline();
}
void
emit_shape( TextEmitter &w, TransformEmitState &state, Eu7Shape const &shape ) {
emit_node_transform( w, state, shape.node );
auto local_vertices { shape.vertices };
inverse_transform_shape_vertices( local_vertices, shape.node.transform );
push_node_header( w, shape.node );
auto const shape_type {
shape.node.node_type.empty() ? std::string_view{ "triangles" } :
std::string_view{ shape.node.node_type } };
w.push( shape_type );
w.push( shape.material_path );
auto const triangle_list { shape_type == "triangles" };
for( std::size_t i { 0 }; i < local_vertices.size(); ++i ) {
auto const &vert { local_vertices[ i ] };
w.push_vec3( vert.position );
w.push( static_cast<double>( vert.normal.x ) );
w.push( static_cast<double>( vert.normal.y ) );
w.push( static_cast<double>( vert.normal.z ) );
w.push( vert.u );
w.push( vert.v );
if( triangle_list && i % 3 != 2 ) {
w.push( "end" );
}
}
w.push( "endtri" );
w.newline();
}
void
emit_lines( TextEmitter &w, TransformEmitState &state, Eu7Lines const &lines ) {
emit_node_transform( w, state, lines.node );
auto local_vertices { lines.vertices };
inverse_transform_shape_vertices( local_vertices, lines.node.transform );
push_node_header( w, lines.node );
w.push( "lines" );
w.push( lines.lighting.diffuse.x * 255.0 );
w.push( lines.lighting.diffuse.y * 255.0 );
w.push( lines.lighting.diffuse.z * 255.0 );
w.push( lines.line_width );
for( std::size_t i { 0 }; i + 1 < local_vertices.size(); i += 2 ) {
w.push_vec3( local_vertices[ i ].position );
w.push_vec3( local_vertices[ i + 1 ].position );
}
w.push( "endline" );
w.newline();
}
void
emit_model( TextEmitter &w, TransformEmitState &state, Eu7Model const &model ) {
emit_node_transform( w, state, model.node );
auto const local_location { inverse_transform_point( model.location, model.node.transform ) };
auto const local_rotation_y { model.angles.y - model.node.transform.rotation.y };
if( model.is_terrain ) {
w.push( "node" );
w.push( -1.0 );
w.push( 0.0 );
w.push( node_display_name( model.node ) );
}
else {
push_node_header( w, model.node );
}
w.push( "model" );
w.push_vec3( local_location );
w.push( local_rotation_y );
w.push( model.model_file );
w.push( model.texture_file );
w.push( "endmodel" );
w.newline();
}
void
emit_memcell( TextEmitter &w, TransformEmitState &state, Eu7MemCell const &cell ) {
emit_node_transform( w, state, cell.node );
push_node_header( w, cell.node );
w.push( "memcell" );
w.push_vec3( inverse_transform_point( cell.node.area.center, cell.node.transform ) );
w.push( cell.text );
w.push( cell.value1 );
w.push( cell.value2 );
w.push( cell.track_name.value_or( "none" ) );
w.push( "endmemcell" );
w.newline();
}
[[nodiscard]] std::string
launcher_time_token( Eu7EventLauncher const &launcher ) {
if( launcher.launch_hour != 0 || launcher.launch_minute != 0 ) {
return std::format( "{}", launcher.launch_hour * 100 + launcher.launch_minute );
}
if( launcher.delta_time != 0.0 ) {
return format_double( -launcher.delta_time );
}
return "0";
}
void
emit_launcher( TextEmitter &w, TransformEmitState &state, Eu7EventLauncher const &launcher ) {
emit_node_transform( w, state, launcher.node );
push_node_header( w, launcher.node );
w.push( "eventlauncher" );
w.push_vec3( inverse_transform_point( launcher.location, launcher.node.transform ) );
w.push( std::sqrt( launcher.radius_squared ) );
w.push( launcher.activation_key_raw );
w.push( launcher_time_token( launcher ) );
w.push( launcher.event1_name );
if( !launcher.event2_name.empty() && launcher.event2_name != "none" ) {
w.push( launcher.event2_name );
}
if( launcher.condition ) {
w.push( "condition" );
w.push( launcher.condition->memcell_name );
w.push( launcher.condition->compare_text );
if( launcher.condition->check_mask & 2 ) {
w.push( launcher.condition->compare_value1 );
}
else {
w.push( "*" );
}
if( launcher.condition->check_mask & 4 ) {
w.push( launcher.condition->compare_value2 );
}
else {
w.push( "*" );
}
}
if( launcher.train_triggered ) {
w.push( "traintriggered" );
}
w.push( "endeventlauncher" );
w.newline();
}
void
emit_dynamic( TextEmitter &w, TransformEmitState &state, Eu7Dynamic const &vehicle, bool const in_trainset ) {
emit_node_transform( w, state, vehicle.node );
push_node_header( w, vehicle.node );
w.push( "dynamic" );
w.push( vehicle.data_folder );
w.push( vehicle.skin_file );
w.push( vehicle.mmd_file );
if( !in_trainset ) {
w.push( vehicle.track_name );
}
w.push( vehicle.offset );
w.push( vehicle.driver_type );
if( in_trainset ) {
w.push( vehicle.coupling_raw.empty() ? std::to_string( vehicle.coupling ) : vehicle.coupling_raw );
}
if( !in_trainset ) {
w.push( vehicle.velocity );
}
w.push( vehicle.load_count );
if( !vehicle.load_type.empty() ) {
w.push( vehicle.load_type );
}
if( vehicle.destination ) {
w.push( *vehicle.destination );
}
w.push( "enddynamic" );
w.newline();
}
void
emit_sound( TextEmitter &w, TransformEmitState &state, Eu7Sound const &sound ) {
emit_node_transform( w, state, sound.node );
push_node_header( w, sound.node );
w.push( "sound" );
w.push_vec3( inverse_transform_point( sound.location, sound.node.transform ) );
w.push( sound.wav_file );
w.push( "endsound" );
w.newline();
}
[[nodiscard]] std::string_view
event_type_name( Eu7EventType const type ) {
switch( type ) {
case Eu7EventType::AddValues:
return "addvalues";
case Eu7EventType::UpdateValues:
return "updatevalues";
case Eu7EventType::CopyValues:
return "copyvalues";
case Eu7EventType::GetValues:
return "getvalues";
case Eu7EventType::PutValues:
return "putvalues";
case Eu7EventType::Whois:
return "whois";
case Eu7EventType::LogValues:
return "logvalues";
case Eu7EventType::Multiple:
return "multiple";
case Eu7EventType::Switch:
return "switch";
case Eu7EventType::TrackVel:
return "trackvel";
case Eu7EventType::Sound:
return "sound";
case Eu7EventType::Texture:
return "texture";
case Eu7EventType::Animation:
return "animation";
case Eu7EventType::Lights:
return "lights";
case Eu7EventType::Voltage:
return "voltage";
case Eu7EventType::Visible:
return "visible";
case Eu7EventType::Friction:
return "friction";
case Eu7EventType::Message:
return "message";
default:
return "unknown";
}
}
[[nodiscard]] std::string
join_targets( std::vector<std::string> const &targets ) {
std::string joined;
for( std::size_t i { 0 }; i < targets.size(); ++i ) {
if( i > 0 ) {
joined += '|';
}
joined += targets[ i ];
}
return joined.empty() ? "none" : joined;
}
void
emit_event( TextEmitter &w, Eu7Event const &event ) {
w.push( "event" );
if( event.ignored ) {
w.push( std::string{ "none_" } + event.name );
}
else {
w.push( event.name );
}
w.push( event_type_name( event.type ) );
w.push( event.delay );
w.push( join_targets( event.targets ) );
for( auto const &[key, value] : event.payload ) {
if( !key.empty() ) {
w.push( key );
}
if( !value.empty() ) {
w.push( value );
}
}
if( event.delay_random != 0.0 ) {
w.push( "randomdelay" );
w.push( event.delay_random );
}
if( event.delay_departure != 0.0 ) {
w.push( "departuredelay" );
w.push( event.delay_departure );
}
if( event.passive ) {
w.push( "passive" );
}
w.push( "endevent" );
w.newline();
}
void
emit_trainset_block(
TextEmitter &w,
TransformEmitState &state,
Eu7Trainset const &trainset,
std::vector<Eu7Dynamic> const &dynamics ) {
w.push( "trainset" );
w.push( trainset.name );
w.push( trainset.track );
w.push( trainset.offset );
w.push( trainset.velocity );
if( false == trainset.assignment.empty() ) {
w.push( "assignment" );
for( auto const &[lang, assignment] : trainset.assignment ) {
w.push( lang );
w.push( '"' + assignment + '"' );
}
w.push( "endassignment" );
}
std::size_t vehicle_slot { 0 };
for( auto const index : trainset.vehicle_indices ) {
if( index < dynamics.size() ) {
auto vehicle { dynamics[ index ] };
if( vehicle_slot < trainset.couplings.size() ) {
vehicle.coupling_raw = std::to_string( trainset.couplings[ vehicle_slot ] );
}
emit_dynamic( w, state, vehicle, true );
++vehicle_slot;
}
}
w.push( "endtrainset" );
w.newline();
}
} // namespace
std::string
include_text_path( Eu7Include const &include ) {
if( !include.source_path.empty() ) {
return include.source_path;
}
auto path { include.binary_path };
replace_slashes( path );
if( path.ends_with( ".eu7" ) ) {
path.replace( path.size() - 4, 4, ".inc" );
}
return path;
}
std::string
emit_includes_text( std::vector<Eu7Include> const &includes ) {
if( includes.empty() ) {
return {};
}
TextEmitter w;
for( auto const &include : includes ) {
w.push( "include" );
w.push( include_text_path( include ) );
for( auto const &param : include.parameters ) {
w.push( param );
}
w.push( "end" );
w.newline();
}
return w.str();
}
std::string
emit_scene_objects_text( Eu7Module const &module ) {
TextEmitter w;
TransformEmitState transform_state;
auto const &scene { module.scene };
for( auto const &track : scene.tracks ) {
emit_track( w, transform_state, track );
}
for( auto const &traction : scene.traction ) {
emit_traction( w, transform_state, traction );
}
for( auto const &source : scene.power_sources ) {
emit_power_source( w, transform_state, source );
}
for( auto const &model : scene.models ) {
emit_model( w, transform_state, model );
}
for( auto const &cell : scene.memcells ) {
emit_memcell( w, transform_state, cell );
}
for( auto const &launcher : scene.event_launchers ) {
emit_launcher( w, transform_state, launcher );
}
std::vector<bool> emitted_in_trainset( scene.dynamics.size(), false );
for( auto const &trainset : scene.trainsets ) {
for( auto const index : trainset.vehicle_indices ) {
if( index < emitted_in_trainset.size() ) {
emitted_in_trainset[ index ] = true;
}
}
}
for( std::size_t i { 0 }; i < scene.dynamics.size(); ++i ) {
if( !emitted_in_trainset[ i ] ) {
emit_dynamic( w, transform_state, scene.dynamics[ i ], false );
}
}
for( auto const &sound : scene.sounds ) {
emit_sound( w, transform_state, sound );
}
for( auto const &event : scene.events ) {
emit_event( w, event );
}
for( std::uint32_t i { 0 }; i < scene.first_init_count; ++i ) {
w.push( "FirstInit" );
w.newline();
}
for( auto const &trainset : scene.trainsets ) {
emit_trainset_block( w, transform_state, trainset, scene.dynamics );
}
return w.str();
}
std::string
emit_module_text( Eu7Module const &module ) {
TextEmitter w;
TransformEmitState transform_state;
auto const &scene { module.scene };
for( auto const &shape : scene.shapes ) {
emit_shape( w, transform_state, shape );
}
for( auto const &lines : scene.lines ) {
emit_lines( w, transform_state, lines );
}
std::string text { w.str() };
text += emit_scene_objects_text( module );
return text;
}
} // namespace scene::eu7

34
scene/eu7/eu7_emit.h Normal file
View File

@@ -0,0 +1,34 @@
/*
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/.
*/
#pragma once
#include "scene/eu7/eu7_types.h"
#include <string>
namespace scene::eu7 {
// Sciezka tekstowa dla fallbacku include (source_path albo .eu7 -> .inc).
[[nodiscard]] std::string
include_text_path( Eu7Include const &Include );
// Emituje bloki "include ... end" dla parsera SCM.
[[nodiscard]] std::string
emit_includes_text( std::vector<Eu7Include> const &Includes );
// Emituje zawartosc modulu (bez INCL i bez terrain_shapes) do tekstu SCM.
[[nodiscard]] std::string
emit_module_text( Eu7Module const &Module );
// Torow, eventy, modele itd. — bez TERR/MESH/LINE (te ida bezposrednio z binarki).
[[nodiscard]] std::string
emit_scene_objects_text( Eu7Module const &Module );
} // namespace scene::eu7

View File

@@ -0,0 +1,148 @@
/*
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_load_stats.h"
#include "utilities/Logs.h"
#include <iomanip>
#include <sstream>
namespace scene::eu7 {
namespace {
Eu7LoadStats g_load_stats;
[[nodiscard]] std::string
format_seconds( double const ms ) {
std::ostringstream out;
out << std::fixed << std::setprecision( 1 ) << ( ms / 1000.0 ) << 's';
return out.str();
}
} // namespace
double
Eu7LoadStats::total_ms() const {
return (
read_ms +
scm_fallback_ms +
place_fast_ms +
place_full_ms +
terr_ms +
mesh_ms +
line_ms +
trak_ms +
trac_ms +
power_ms +
model_ms +
memcell_ms +
launcher_ms +
dynamic_ms +
sound_ms +
event_ms +
trainset_ms +
first_init_ms );
}
ScopedTimer::ScopedTimer( double &target )
: m_target { target }
, m_start { std::chrono::steady_clock::now() } {}
ScopedTimer::~ScopedTimer() {
auto const end { std::chrono::steady_clock::now() };
m_target += std::chrono::duration<double, std::milli>( end - m_start ).count();
}
Eu7LoadStats &
load_stats() {
return g_load_stats;
}
void
reset_load_stats() {
g_load_stats = {};
}
void
log_load_stats() {
auto const &s { g_load_stats };
auto const total { s.total_ms() };
WriteLog( "EU7 load stats (accounted " + format_seconds( total ) + "):" );
WriteLog(
" read/cache: " + format_seconds( s.read_ms ) +
" reads=" + std::to_string( s.module_read ) +
" cache_hit=" + std::to_string( s.module_cache_hit ) );
WriteLog(
" scm fallback: " + format_seconds( s.scm_fallback_ms ) +
" count=" + std::to_string( s.scm_fallback ) );
WriteLog(
" place fast (MODL-only): " + format_seconds( s.place_fast_ms ) +
" paths=" + std::to_string( s.model_fast_path ) );
WriteLog(
" place full: " + format_seconds( s.place_full_ms ) +
" paths=" + std::to_string( s.module_full_path ) );
WriteLog(
" terr: " + format_seconds( s.terr_ms ) );
WriteLog(
" mesh: " + format_seconds( s.mesh_ms ) );
WriteLog(
" line: " + format_seconds( s.line_ms ) );
WriteLog(
" trak: " + format_seconds( s.trak_ms ) +
" count=" + std::to_string( s.tracks ) );
WriteLog(
" trac: " + format_seconds( s.trac_ms ) +
" count=" + std::to_string( s.traction ) );
WriteLog(
" power: " + format_seconds( s.power_ms ) +
" count=" + std::to_string( s.power_sources ) );
WriteLog(
" model insert: " + format_seconds( s.model_ms ) +
" count=" + std::to_string( s.models ) );
WriteLog(
" memcell: " + format_seconds( s.memcell_ms ) +
" count=" + std::to_string( s.memcells ) );
WriteLog(
" launcher: " + format_seconds( s.launcher_ms ) +
" count=" + std::to_string( s.launchers ) );
WriteLog(
" dynamic: " + format_seconds( s.dynamic_ms ) +
" count=" + std::to_string( s.dynamics ) );
WriteLog(
" sound: " + format_seconds( s.sound_ms ) +
" count=" + std::to_string( s.sounds ) );
WriteLog(
" event: " + format_seconds( s.event_ms ) +
" count=" + std::to_string( s.events ) );
WriteLog(
" trainset: " + format_seconds( s.trainset_ms ) +
" count=" + std::to_string( s.trainsets ) );
WriteLog(
" first_init: " + format_seconds( s.first_init_ms ) );
if(
s.pack_sections_loaded > 0 || s.pack_skipped_includes > 0 ||
s.pack_skipped_models > 0 ) {
WriteLog(
" pack: sections=" + std::to_string( s.pack_sections_loaded ) +
" models=" + std::to_string( s.pack_models ) +
" skipped_incl=" + std::to_string( s.pack_skipped_includes ) +
" skipped_modl=" + std::to_string( s.pack_skipped_models ) );
}
WriteLog(
" visits=" + std::to_string( s.module_visits ) +
" applied=" + std::to_string( s.module_applied ) +
" deduped=" + std::to_string( s.module_deduped ) );
WriteLog(
" (roznica vs Scenario loading time = deserialize_continue, map geometry, itd.)" );
}
} // namespace scene::eu7

View File

@@ -0,0 +1,85 @@
/*
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/.
*/
#pragma once
#include <chrono>
#include <cstdint>
namespace scene::eu7 {
struct Eu7LoadStats {
double read_ms { 0.0 };
double scm_fallback_ms { 0.0 };
double place_fast_ms { 0.0 };
double place_full_ms { 0.0 };
double terr_ms { 0.0 };
double mesh_ms { 0.0 };
double line_ms { 0.0 };
double trak_ms { 0.0 };
double trac_ms { 0.0 };
double power_ms { 0.0 };
double model_ms { 0.0 };
double memcell_ms { 0.0 };
double launcher_ms { 0.0 };
double dynamic_ms { 0.0 };
double sound_ms { 0.0 };
double event_ms { 0.0 };
double trainset_ms { 0.0 };
double first_init_ms { 0.0 };
std::uint64_t module_visits { 0 };
std::uint64_t module_applied { 0 };
std::uint64_t module_deduped { 0 };
std::uint64_t module_read { 0 };
std::uint64_t module_cache_hit { 0 };
std::uint64_t model_fast_path { 0 };
std::uint64_t module_full_path { 0 };
std::uint64_t scm_fallback { 0 };
std::uint64_t models { 0 };
std::uint64_t tracks { 0 };
std::uint64_t traction { 0 };
std::uint64_t power_sources { 0 };
std::uint64_t memcells { 0 };
std::uint64_t launchers { 0 };
std::uint64_t dynamics { 0 };
std::uint64_t sounds { 0 };
std::uint64_t events { 0 };
std::uint64_t trainsets { 0 };
std::uint64_t pack_skipped_includes { 0 };
std::uint64_t pack_sections_loaded { 0 };
std::uint64_t pack_models { 0 };
std::uint64_t pack_skipped_models { 0 };
[[nodiscard]] double total_ms() const;
};
class ScopedTimer {
public:
explicit ScopedTimer( double &Target );
~ScopedTimer();
ScopedTimer( ScopedTimer const & ) = delete;
ScopedTimer &operator=( ScopedTimer const & ) = delete;
private:
double &m_target;
std::chrono::steady_clock::time_point m_start;
};
[[nodiscard]] Eu7LoadStats &
load_stats();
void
reset_load_stats();
void
log_load_stats();
} // namespace scene::eu7

587
scene/eu7/eu7_loader.cpp Normal file
View File

@@ -0,0 +1,587 @@
/*
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 &current_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 &current_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 &region, 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 &region, 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 &region, 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

123
scene/eu7/eu7_loader.h Normal file
View File

@@ -0,0 +1,123 @@
/*
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/.
*/
#pragma once
#include "scene/eu7/eu7_types.h"
#include <string>
namespace simulation {
class state_serializer;
}
namespace scene {
class basic_region;
class shape_node;
class lines_node;
namespace eu7 {
// Buduje shape_node z rekordu EU7B (friend shape_node).
[[nodiscard]] shape_node
build_shape_node( Eu7Shape const &Source );
// Buduje lines_node z rekordu EU7B (friend lines_node).
[[nodiscard]] lines_node
build_lines_node( Eu7Lines const &Source );
// Pełna ścieżka względem Global.asCurrentSceneryPath (jak basic_region::deserialize).
[[nodiscard]] std::string
resolve_scenery_path( std::string const &Reference );
// Zamienia .scm/.sbt/.inc/.scn na .eu7 przy tym samym stemie.
[[nodiscard]] std::string
binary_path( std::string const &Reference );
// Jak cParser: mPath + include, z katalogiem pliku biezacego dla sciezek wzglednych.
[[nodiscard]] std::string
resolve_parser_include_path(
std::string const &ParserPath,
std::string const &CurrentFile,
std::string const &IncludeReference );
// Sciezka .eu7 obok include (ta sama lokalizacja co SCM/INC).
[[nodiscard]] std::string
include_eu7_path(
std::string const &ParserPath,
std::string const &CurrentFile,
std::string const &IncludeReference );
// Alias dla terenu (kompatybilnosc wsteczna).
[[nodiscard]] std::string
terrain_binary_path( std::string const &TerrainReference );
// Czy plik istnieje i jest poprawnym EU7B (v4-v8).
[[nodiscard]] bool
probe_file( std::string const &Path );
// Czy obok scenariusza (.scn/.scm) jest zbakowany plik <stem>.eu7.
[[nodiscard]] bool
probe_baked_scenario( std::string const &ScenarioFile );
// Rozszerzenia modulow tekstowych (.scn/.scm/.inc/.sbt).
[[nodiscard]] bool
is_text_module_extension( std::string const &Path );
// Dla .eu7: istniejacy plik tekstowy obok; dla referencji tekstowej: sciezka rozwiazana.
[[nodiscard]] std::string
text_source_path( std::string const &Reference );
// Tekst istnieje i jest nowszy niz .eu7 (lub brak .eu7).
[[nodiscard]] bool
text_module_is_newer_than_binary( std::string const &Reference );
// Istniejacy, poprawny .eu7 nie starszy niz odpowiednik tekstowy.
[[nodiscard]] bool
should_use_binary_module( std::string const &Reference );
// Czy plik zawiera chunk TERR.
[[nodiscard]] bool
probe_terrain_file( std::string const &Path );
// Czy obok scenariusza jest <stem>.eu7 z terenem (analogia do .sbt).
[[nodiscard]] bool
is_scenario_terrain( std::string const &ScenarioFile );
// Ładuje <stem>.eu7 dla danego scenariusza. Zwraca false gdy brak pliku.
[[nodiscard]] bool
try_load_scenario_terrain( basic_region &Region, std::string const &ScenarioFile );
// Wczytuje chunk TERR z podanej ścieżki .eu7 do regionu (szybka ścieżka).
[[nodiscard]] bool
load_terrain( basic_region &Region, std::string const &Path );
// Wstawia kształty terenu z Eu7Module do regionu.
void
insert_terrain_shapes( basic_region &Region, Eu7Module const &Module );
// Czyści sesje deduplikacji przed ladowaniem scenariusza EU7.
void
begin_load_session();
// Czy modul juz zostal zaladowany w biezacej sesji (parser include nie laduje drugi raz).
[[nodiscard]] bool
is_module_loaded( std::string const &Path );
// Pełne ładowanie modułu EU7B (wszystkie chunki oprócz STRS).
[[nodiscard]] bool
load_module( std::string const &Path, simulation::state_serializer &Serializer );
// Root scenariusza ma chunk PACK — modele scenerii ida ze streamingu, nie z MODL w .inc.
[[nodiscard]] bool
pack_scenery_active();
} // namespace eu7
} // namespace scene

View File

@@ -0,0 +1,135 @@
/*
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_model_prefetch.h"
#include "model/AnimModel.h"
#include "model/MdlMngr.h"
#include "rendering/renderer.h"
#include "scene/eu7/eu7_pack_bench.h"
#include "utilities/utilities.h"
#include "vehicle/DynObj.h"
#include <mutex>
#include <string>
#include <unordered_set>
#include <vector>
namespace scene::eu7 {
namespace {
std::mutex g_pack_mesh_load_mutex;
std::unordered_set<std::string> g_session_warmed_textures;
[[nodiscard]] bool
pack_texture_usable( std::string texture_file ) {
if( texture_file.empty() || texture_file == "none" || texture_file.front() == '*' ) {
return false;
}
replace_slashes( texture_file );
if( texture_file == "none" || texture_file.ends_with( "/none" ) ) {
return false;
}
if( texture_file.find( "tr/none" ) != std::string::npos ) {
return false;
}
return true;
}
void
preload_pack_model_file( std::string const &model_file ) {
if( false == TModelsManager::IsModelCached( model_file ) ) {
return;
}
if( auto *const mesh { TModelsManager::GetModel( model_file, false, false ) } ) {
TAnimModel::warm_instanceable_cache( mesh );
}
}
[[nodiscard]] bool
warm_one_pack_texture( std::string texture_file, std::unordered_set<std::string> &seen ) {
if( false == pack_texture_usable( texture_file ) ) {
return false;
}
replace_slashes( texture_file );
if( false == seen.insert( texture_file ).second ) {
return false;
}
if( false == g_session_warmed_textures.insert( texture_file ).second ) {
return false;
}
auto const resolved { TextureTest( ToLower( texture_file ) ) };
if( resolved.empty() ) {
pack_bench_inc( &Eu7PackBench::main_texture_warm_miss );
log_pack_texture_fail( texture_file );
return false;
}
GfxRenderer->Fetch_Material( resolved );
for( int skinindex { 1 }; skinindex <= 4; ++skinindex ) {
auto const multi {
TextureTest( ToLower( texture_file + "," + std::to_string( skinindex ) ) ) };
if( multi.empty() ) {
break;
}
GfxRenderer->Fetch_Material( multi );
}
return true;
}
} // namespace
void
reset_pack_texture_warm_cache() {
g_session_warmed_textures.clear();
}
void
preload_pack_models( std::vector<Eu7Model> const &models ) {
std::unordered_set<std::string> seen;
seen.reserve( models.size() );
for( auto const &model : models ) {
auto model_file { model.model_file };
if( model_file.empty() || model_file == "notload" ) {
continue;
}
replace_slashes( model_file );
if( false == seen.insert( model_file ).second ) {
continue;
}
std::lock_guard<std::mutex> lock { g_pack_mesh_load_mutex };
preload_pack_model_file( model_file );
}
}
std::size_t
warm_pack_textures_main( Eu7Model const *const models, std::size_t const count ) {
if( models == nullptr || count == 0 || GfxRenderer == nullptr ) {
return 0;
}
std::unordered_set<std::string> seen;
seen.reserve( std::min( count, std::size_t { 64 } ) );
std::size_t warmed { 0 };
for( std::size_t i { 0 }; i < count; ++i ) {
if( warm_one_pack_texture( models[ i ].texture_file, seen ) ) {
++warmed;
}
}
return warmed;
}
} // namespace scene::eu7

View File

@@ -0,0 +1,33 @@
/*
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/.
*/
#pragma once
#include "scene/eu7/eu7_types.h"
#include <cstddef>
#include <string>
#include <unordered_set>
#include <vector>
namespace scene::eu7 {
// Tylko warm_instanceable_cache dla juz zcache'owanych meshy (bez cold GetModel na workerze).
void
preload_pack_models( std::vector<Eu7Model> const &Models );
// Main thread: Fetch_Material dla unikalnych texture_file z chunka przed apply.
// Zwraca liczbe unikalnych Fetch_Material w tym slice.
std::size_t
warm_pack_textures_main( Eu7Model const *models, std::size_t count );
void
reset_pack_texture_warm_cache();
} // namespace scene::eu7

View File

@@ -0,0 +1,405 @@
/*
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_pack_bench.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <unordered_set>
#include <vector>
namespace scene::eu7 {
namespace {
Eu7PackBench g_pack_bench;
Eu7PackBench g_pack_bench_stream;
Eu7PackBench g_pack_bench_bootstrap;
bool g_stream_phase { false };
std::unordered_set<std::string> g_logged_texture_fails;
struct BenchLine {
std::string label;
double ms { 0.0 };
std::string detail;
};
[[nodiscard]] std::string
format_seconds( double const ms ) {
std::ostringstream out;
out << std::fixed << std::setprecision( 1 ) << ( ms / 1000.0 ) << 's';
return out.str();
}
[[nodiscard]] std::string
format_ms_per( double const ms, std::uint64_t const count ) {
if( count == 0 ) {
return "n/a";
}
std::ostringstream out;
out << std::fixed << std::setprecision( 2 ) << ( ms / static_cast<double>( count ) ) << " ms/item";
return out.str();
}
void
note_peak(
std::uint64_t Eu7PackBench::*const field,
std::size_t const size ) {
auto &total_peak { g_pack_bench.*field };
total_peak = std::max( total_peak, size );
if( g_stream_phase ) {
auto &stream_peak { g_pack_bench_stream.*field };
stream_peak = std::max( stream_peak, size );
}
}
void
add_bench_ms( double Eu7PackBench::*const field, double const delta ) {
g_pack_bench.*field += delta;
if( g_stream_phase ) {
g_pack_bench_stream.*field += delta;
}
}
void
log_pack_bench_impl( Eu7PackBench const &bench, char const *const title ) {
auto const total { bench.accounted_ms() };
if( total <= 0.0 && bench.main_drain_calls == 0 && bench.worker_sections_done == 0 ) {
return;
}
auto const main_apply_sum {
bench.main_load_eu7_pack_ms + bench.main_load_eu7_full_ms + bench.main_region_insert_ms };
auto const chunk_sum {
bench.main_chunk_cold_ms + bench.main_chunk_warm_tex_ms + bench.main_chunk_pointer_apply_ms };
std::vector<BenchLine> lines {
{ "worker read_pack", bench.worker_read_pack_ms,
"sections=" + std::to_string( bench.worker_sections_done ) +
" finalized=" + std::to_string( bench.sections_finalized ) +
" models=" + std::to_string( bench.worker_models_decoded ) +
" fail=" + std::to_string( bench.worker_failures ) },
{ "main read_pack", bench.main_read_pack_ms,
"models=" + std::to_string( bench.worker_models_decoded ) },
{ "main GetModel (cold)", bench.main_cold_getmodel_ms,
"calls=" + std::to_string( bench.main_cold_getmodel_calls ) + " " +
format_ms_per( bench.main_cold_getmodel_ms, bench.main_cold_getmodel_calls ) },
{ "main LoadEu7Pack", bench.main_load_eu7_pack_ms,
"loads=" + std::to_string( bench.main_pack_fast_loads ) + " " +
format_ms_per( bench.main_load_eu7_pack_ms, bench.main_pack_fast_loads ) },
{ "main LoadEu7 (full)", bench.main_load_eu7_full_ms,
"loads=" + std::to_string( bench.main_pack_full_loads ) + " " +
format_ms_per( bench.main_load_eu7_full_ms, bench.main_pack_full_loads ) },
{ "main Region::insert", bench.main_region_insert_ms,
"inserts=" + std::to_string( bench.main_region_inserts ) + " " +
format_ms_per( bench.main_region_insert_ms, bench.main_region_inserts ) },
{ "main apply (load+region)", main_apply_sum,
"instances=" + std::to_string( bench.main_instances_applied ) + " " +
format_ms_per( main_apply_sum, bench.main_instances_applied ) },
{ "chunk cold preload", bench.main_chunk_cold_ms,
"loads=" + std::to_string( bench.main_chunk_cold_loads ) + " " +
format_ms_per( bench.main_chunk_cold_ms, bench.main_chunk_cold_loads ) },
{ "chunk warm textures", bench.main_chunk_warm_tex_ms,
"fetches=" + std::to_string( bench.main_chunk_tex_fetches ) + " " +
format_ms_per( bench.main_chunk_warm_tex_ms, bench.main_chunk_tex_fetches ) },
{ "chunk pointer apply", bench.main_chunk_pointer_apply_ms,
"chunks=" + std::to_string( bench.main_chunks ) + " inst=" +
std::to_string( bench.main_chunk_instances ) + " " +
format_ms_per( bench.main_chunk_pointer_apply_ms, bench.main_chunk_instances ) },
{ "chunk wall (phases)", chunk_sum,
"chunks=" + std::to_string( bench.main_chunks ) + " slow8=" +
std::to_string( bench.chunk_slow_8ms ) + " slow16=" +
std::to_string( bench.chunk_slow_16ms ) },
};
std::sort(
lines.begin(),
lines.end(),
[]( BenchLine const &lhs, BenchLine const &rhs ) {
return lhs.ms > rhs.ms;
} );
WriteLog( std::string{ title } + " (accounted " + format_seconds( total ) + "):" );
for( auto const &line : lines ) {
if( line.ms <= 0.0 ) {
continue;
}
auto const pct {
total > 0.0 ?
( 100.0 * line.ms / total ) :
0.0 };
std::ostringstream row;
row << std::fixed << std::setprecision( 1 );
row << " " << line.label << ": " << format_seconds( line.ms );
if( total > 0.0 ) {
row << " (" << pct << "%)";
}
if( false == line.detail.empty() ) {
row << " " << line.detail;
}
WriteLog( row.str() );
}
WriteLog(
" drain: calls=" + std::to_string( bench.main_drain_calls ) +
" wait_cold_frames=" + std::to_string( bench.main_drain_wait_cold_mesh ) +
" wait_worker_frames=" + std::to_string( bench.main_drain_wait_worker ) +
" idle_frames=" + std::to_string( bench.main_drain_idle ) );
WriteLog(
" stream: reenqueue=" + std::to_string( bench.stream_reenqueue ) +
" lookahead=" + std::to_string( bench.stream_lookahead_enqueue ) +
" tex_fail=" + std::to_string( bench.main_texture_assign_fail ) +
" warm_miss=" + std::to_string( bench.main_texture_warm_miss ) );
WriteLog(
" peaks: ready_q=" + std::to_string( bench.peak_ready_queue ) +
" in_flight=" + std::to_string( bench.peak_in_flight ) +
" cold_q=" + std::to_string( bench.peak_pending_cold_meshes ) );
if( bench.main_chunks > 0 ) {
WriteLog(
" chunk peaks: wall_ms=" + std::to_string( static_cast<int>( bench.peak_chunk_ms ) ) +
" inst=" + std::to_string( bench.peak_chunk_instances ) +
" cold=" + std::to_string( bench.peak_chunk_cold ) +
" last_ms=" + std::to_string( static_cast<int>( bench.last_chunk_ms ) ) +
" last_inst=" + std::to_string( bench.last_chunk_instances ) +
" budget_stops=" + std::to_string( bench.drain_budget_stops ) );
}
if( false == lines.empty() && lines.front().ms > 0.0 ) {
WriteLog( " >> bottleneck: " + lines.front().label );
}
}
} // namespace
double
Eu7PackBench::accounted_ms() const {
return (
worker_read_pack_ms +
main_read_pack_ms +
main_cold_getmodel_ms +
main_load_eu7_pack_ms +
main_load_eu7_full_ms +
main_region_insert_ms );
}
double
Eu7PackBench::chunk_accounted_ms() const {
return (
main_chunk_cold_ms +
main_chunk_warm_tex_ms +
main_chunk_pointer_apply_ms );
}
PackBenchTimer::PackBenchTimer( double Eu7PackBench::*const field )
: m_field { field }
, m_start { std::chrono::steady_clock::now() } {}
PackBenchTimer::~PackBenchTimer() {
auto const end { std::chrono::steady_clock::now() };
auto const delta {
std::chrono::duration<double, std::milli>( end - m_start ).count() };
g_pack_bench.*m_field += delta;
if( g_stream_phase ) {
g_pack_bench_stream.*m_field += delta;
}
}
Eu7PackBench &
pack_bench() {
return g_pack_bench;
}
Eu7PackBench &
pack_bench_stream() {
return g_pack_bench_stream;
}
Eu7PackBench const &
pack_bench_bootstrap() {
return g_pack_bench_bootstrap;
}
bool
pack_bench_stream_phase_active() {
return g_stream_phase;
}
void
reset_pack_bench() {
g_pack_bench = {};
g_pack_bench_stream = {};
g_pack_bench_bootstrap = {};
g_stream_phase = false;
g_logged_texture_fails.clear();
}
void
pack_bench_begin_stream_phase() {
g_pack_bench_bootstrap = g_pack_bench;
g_pack_bench_stream = {};
g_stream_phase = true;
}
void
pack_bench_note_ready_queue( std::size_t const size ) {
note_peak( &Eu7PackBench::peak_ready_queue, size );
}
void
pack_bench_note_in_flight( std::size_t const size ) {
note_peak( &Eu7PackBench::peak_in_flight, size );
}
void
pack_bench_note_pending_cold_meshes( std::size_t const size ) {
note_peak( &Eu7PackBench::peak_pending_cold_meshes, size );
}
void
pack_bench_inc( std::uint64_t Eu7PackBench::*const field, std::uint64_t const amount ) {
g_pack_bench.*field += amount;
if( g_stream_phase ) {
g_pack_bench_stream.*field += amount;
}
}
void
pack_bench_note_chunk(
double const wall_ms,
std::size_t const instances,
std::size_t const cold_meshes,
double const cold_ms,
double const warm_tex_ms,
double const pointer_apply_ms ) {
add_bench_ms( &Eu7PackBench::main_chunk_cold_ms, cold_ms );
add_bench_ms( &Eu7PackBench::main_chunk_warm_tex_ms, warm_tex_ms );
add_bench_ms( &Eu7PackBench::main_chunk_pointer_apply_ms, pointer_apply_ms );
pack_bench_inc( &Eu7PackBench::main_chunks );
pack_bench_inc( &Eu7PackBench::main_chunk_instances, instances );
pack_bench_inc( &Eu7PackBench::main_chunk_cold_loads, cold_meshes );
g_pack_bench.last_chunk_ms = wall_ms;
g_pack_bench.last_chunk_instances = instances;
g_pack_bench.last_chunk_cold = cold_meshes;
if( wall_ms > g_pack_bench.peak_chunk_ms ) {
g_pack_bench.peak_chunk_ms = wall_ms;
g_pack_bench.peak_chunk_instances = instances;
g_pack_bench.peak_chunk_cold = cold_meshes;
}
if( wall_ms >= 8.0 ) {
pack_bench_inc( &Eu7PackBench::chunk_slow_8ms );
}
if( wall_ms >= 16.0 ) {
pack_bench_inc( &Eu7PackBench::chunk_slow_16ms );
}
if( false == g_stream_phase ) {
return;
}
auto &stream { g_pack_bench_stream };
stream.last_chunk_ms = wall_ms;
stream.last_chunk_instances = instances;
stream.last_chunk_cold = cold_meshes;
if( wall_ms > stream.peak_chunk_ms ) {
stream.peak_chunk_ms = wall_ms;
stream.peak_chunk_instances = instances;
stream.peak_chunk_cold = cold_meshes;
}
}
void
log_pack_texture_fail( std::string const &texture_path ) {
if( texture_path.empty() ) {
return;
}
auto key { texture_path };
replace_slashes( key );
key = ToLower( key );
if( false == g_logged_texture_fails.insert( key ).second ) {
return;
}
WriteLog( "EU7 PACK tex_fail: \"" + texture_path + "\"" );
}
void
log_pack_bench() {
if( g_pack_bench_bootstrap.worker_sections_done > 0
|| g_pack_bench_bootstrap.accounted_ms() > 0.0 ) {
log_pack_bench_impl( g_pack_bench_bootstrap, "EU7 PACK bench [bootstrap]" );
return;
}
log_pack_bench_impl( g_pack_bench, "EU7 PACK bench" );
}
void
log_pack_stream_bench() {
log_pack_bench_impl( g_pack_bench_stream, "EU7 PACK bench [stream]" );
}
void
flush_pack_stream_bench() {
if( false == g_stream_phase ) {
return;
}
if(
g_pack_bench_stream.sections_finalized == 0 &&
g_pack_bench_stream.main_drain_calls == 0 &&
g_pack_bench_stream.accounted_ms() <= 0.0 ) {
return;
}
log_pack_stream_bench();
}
void
log_pack_stream_status(
glm::dvec3 const &camera_position,
double const camera_speed_mps,
float const inner_ring_progress,
float const outer_ring_progress,
std::size_t const ready_queue,
std::size_t const in_flight,
std::size_t const pending_offset,
std::size_t const pending_total,
double const drain_budget_ms,
std::size_t const chunk_limit ) {
auto const &bench { g_pack_bench_stream };
WriteLog(
"EU7 PACK [live]: cam=" + std::to_string( camera_position.x ) + "," +
std::to_string( camera_position.z ) + " m/s=" + std::to_string( camera_speed_mps ) +
" ring4=" + std::to_string( static_cast<int>( inner_ring_progress * 100.f ) ) + "%" +
" ring11=" + std::to_string( static_cast<int>( outer_ring_progress * 100.f ) ) + "%" +
" ready=" + std::to_string( ready_queue ) +
" in_flight=" + std::to_string( in_flight ) +
" pending=" + std::to_string( pending_offset ) + "/" + std::to_string( pending_total ) +
" slice=" + std::to_string( chunk_limit ) +
" budget_ms=" + std::to_string( static_cast<int>( drain_budget_ms ) ) +
" applied=" + std::to_string( bench.main_instances_applied ) +
" tex_fail=" + std::to_string( bench.main_texture_assign_fail ) +
" warm_miss=" + std::to_string( bench.main_texture_warm_miss ) +
" chunks=" + std::to_string( bench.main_chunks ) +
" last_chunk_ms=" + std::to_string( static_cast<int>( bench.last_chunk_ms ) ) +
" peak_chunk_ms=" + std::to_string( static_cast<int>( bench.peak_chunk_ms ) ) +
" slow8=" + std::to_string( bench.chunk_slow_8ms ) +
" cold_ms=" + std::to_string( static_cast<int>( bench.main_chunk_cold_ms ) ) +
" warm_ms=" + std::to_string( static_cast<int>( bench.main_chunk_warm_tex_ms ) ) +
" apply_ms=" + std::to_string( static_cast<int>( bench.main_chunk_pointer_apply_ms ) ) +
" reenq=" + std::to_string( bench.stream_reenqueue ) +
" ahead=" + std::to_string( bench.stream_lookahead_enqueue ) );
}
} // namespace scene::eu7

159
scene/eu7/eu7_pack_bench.h Normal file
View File

@@ -0,0 +1,159 @@
/*
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/.
*/
#pragma once
#include <chrono>
#include <cstdint>
#include <glm/glm.hpp>
namespace scene::eu7 {
// Liczniki i czasy PACK — osobno od Eu7LoadStats (deserialize / SCM).
struct Eu7PackBench {
// --- worker (async read PACK) ---
double worker_read_pack_ms { 0.0 };
std::uint64_t worker_sections_done { 0 };
std::uint64_t sections_finalized { 0 };
std::uint64_t worker_models_decoded { 0 };
std::uint64_t worker_failures { 0 };
// --- main thread: apply ---
double main_read_pack_ms { 0.0 };
double main_cold_getmodel_ms { 0.0 };
double main_load_eu7_pack_ms { 0.0 };
double main_load_eu7_full_ms { 0.0 };
double main_region_insert_ms { 0.0 };
std::uint64_t main_drain_calls { 0 };
std::uint64_t main_cold_getmodel_calls { 0 };
std::uint64_t main_instances_applied { 0 };
std::uint64_t main_pack_fast_loads { 0 };
std::uint64_t main_pack_full_loads { 0 };
std::uint64_t main_region_inserts { 0 };
std::uint64_t main_drain_wait_cold_mesh { 0 };
std::uint64_t main_drain_wait_worker { 0 };
std::uint64_t main_drain_idle { 0 };
std::uint64_t peak_ready_queue { 0 };
std::uint64_t peak_in_flight { 0 };
std::uint64_t peak_pending_cold_meshes { 0 };
std::uint64_t main_texture_assign_fail { 0 };
std::uint64_t main_texture_warm_miss { 0 };
std::uint64_t stream_reenqueue { 0 };
std::uint64_t stream_lookahead_enqueue { 0 };
// --- main: pointer chunk apply (diag zwiechy) ---
double main_chunk_cold_ms { 0.0 };
double main_chunk_warm_tex_ms { 0.0 };
double main_chunk_pointer_apply_ms { 0.0 };
std::uint64_t main_chunks { 0 };
std::uint64_t main_chunk_instances { 0 };
std::uint64_t main_chunk_cold_loads { 0 };
std::uint64_t main_chunk_tex_fetches { 0 };
std::uint64_t chunk_slow_8ms { 0 };
std::uint64_t chunk_slow_16ms { 0 };
std::uint64_t drain_budget_stops { 0 };
double peak_chunk_ms { 0.0 };
std::uint64_t peak_chunk_instances { 0 };
std::uint64_t peak_chunk_cold { 0 };
double last_chunk_ms { 0.0 };
std::uint64_t last_chunk_instances { 0 };
std::uint64_t last_chunk_cold { 0 };
[[nodiscard]] double accounted_ms() const;
[[nodiscard]] double chunk_accounted_ms() const;
};
class PackBenchTimer {
public:
explicit PackBenchTimer( double Eu7PackBench::*Field );
~PackBenchTimer();
PackBenchTimer( PackBenchTimer const & ) = delete;
PackBenchTimer &operator=( PackBenchTimer const & ) = delete;
private:
double Eu7PackBench::*const m_field;
std::chrono::steady_clock::time_point m_start;
};
[[nodiscard]] Eu7PackBench &
pack_bench();
// Po dismiss overlay — liczniki runtime streamingu (latanie kamera / jazda).
[[nodiscard]] Eu7PackBench &
pack_bench_stream();
// Snapshot z fazy bootstrap (ekran ladowania).
[[nodiscard]] Eu7PackBench const &
pack_bench_bootstrap();
[[nodiscard]] bool
pack_bench_stream_phase_active();
void
reset_pack_bench();
// Wywolac raz przy dismiss overlay — oddziela bootstrap od streamu.
void
pack_bench_begin_stream_phase();
void
pack_bench_note_ready_queue( std::size_t Size );
void
pack_bench_note_in_flight( std::size_t Size );
void
pack_bench_note_pending_cold_meshes( std::size_t Size );
void
pack_bench_inc( std::uint64_t Eu7PackBench::*Field, std::uint64_t Amount = 1 );
void
pack_bench_note_chunk(
double WallMs,
std::size_t Instances,
std::size_t ColdMeshes,
double ColdMs,
double WarmTexMs,
double PointerApplyMs );
void
log_pack_texture_fail( std::string const &TexturePath );
void
log_pack_bench();
void
log_pack_stream_bench();
// Na wyjsciu z jazdy / reset sceny — jednorazowy dump bez czekania na timer.
void
flush_pack_stream_bench();
// Co kilka sekund / przy szybkiej kamerze — stan kolejek i ringu.
void
log_pack_stream_status(
glm::dvec3 const &CameraPosition,
double CameraSpeedMps,
float InnerRingProgress,
float OuterRingProgress,
std::size_t ReadyQueue,
std::size_t InFlight,
std::size_t PendingOffset,
std::size_t PendingTotal,
double DrainBudgetMs,
std::size_t ChunkLimit );
} // namespace scene::eu7

View File

@@ -0,0 +1,282 @@
/*
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_parameters.h"
#include "scene/eu7/eu7_apply.h"
#include "scene/eu7/eu7_transform.h"
#include <cctype>
#include <cstdlib>
#include <stdexcept>
#include <unordered_map>
namespace scene::eu7 {
namespace {
void
bind_include_parameter_impl( std::string &text, std::vector<std::string> const &parameters, bool const to_lower ) {
if( parameters.empty() ) {
return;
}
std::size_t pos { 0 };
while( ( pos = text.find( "(p", pos ) ) != std::string::npos ) {
auto const close { text.find( ')', pos ) };
if( close == std::string::npos ) {
break;
}
auto const index_text { text.substr( pos + 2, close - ( pos + 2 ) ) };
text.erase( pos, ( close - pos ) + 1 );
auto const index { static_cast<std::size_t>( std::atoi( index_text.c_str() ) ) };
auto const replacement { (
index >= 1 && ( index - 1 ) < parameters.size() ) ?
parameters[ index - 1 ] :
std::string{ "none" } };
text.insert( pos, replacement );
if( to_lower ) {
for( std::size_t i { pos }; i < pos + replacement.size(); ++i ) {
text[ i ] = static_cast<char>( tolower( static_cast<unsigned char>( text[ i ] ) ) );
}
}
pos += replacement.size();
}
}
void
bind_name( std::string &text, std::vector<std::string> const &parameters ) {
bind_include_parameter_impl( text, parameters, true );
}
void
bind_path( std::string &text, std::vector<std::string> const &parameters ) {
bind_include_parameter_impl( text, parameters, false );
}
void
bind_optional_name( std::optional<std::string> &text, std::vector<std::string> const &parameters ) {
if( text ) {
bind_name( *text, parameters );
}
}
void
bind_node_strings( Eu7BasicNode &node, std::vector<std::string> const &parameters ) {
bind_name( node.name, parameters );
bind_name( node.node_type, parameters );
}
} // namespace
void
bind_include_parameter( std::string &text, std::vector<std::string> const &parameters, bool const to_lower ) {
bind_include_parameter_impl( text, parameters, to_lower );
}
void
apply_include_parameters_to_models( std::vector<Eu7Model> &models, std::vector<std::string> const &parameters ) {
if( parameters.empty() ) {
return;
}
for( auto &model : models ) {
bind_node_strings( model.node, parameters );
bind_path( model.model_file, parameters );
bind_path( model.texture_file, parameters );
}
}
void
apply_include_parameters_to_scene( Eu7Scene &scene, std::vector<std::string> const &parameters ) {
if( parameters.empty() ) {
return;
}
for( auto &track : scene.tracks ) {
bind_node_strings( track.node, parameters );
if( track.visibility ) {
bind_path( track.visibility->material1, parameters );
bind_path( track.visibility->material2, parameters );
}
for( auto &[key, value] : track.tail_keywords ) {
bind_name( key, parameters );
bind_name( value, parameters );
}
}
for( auto &traction : scene.traction ) {
bind_node_strings( traction.node, parameters );
bind_name( traction.power_supply_name, parameters );
bind_name( traction.material_raw, parameters );
bind_optional_name( traction.parallel_name, parameters );
}
for( auto &source : scene.power_sources ) {
bind_node_strings( source.node, parameters );
}
for( auto &shape : scene.shapes ) {
bind_node_strings( shape.node, parameters );
bind_path( shape.material_path, parameters );
}
for( auto &shape : scene.terrain_shapes ) {
bind_node_strings( shape.node, parameters );
bind_path( shape.material_path, parameters );
}
for( auto &lines : scene.lines ) {
bind_node_strings( lines.node, parameters );
}
apply_include_parameters_to_models( scene.models, parameters );
for( auto &cell : scene.memcells ) {
bind_node_strings( cell.node, parameters );
bind_name( cell.text, parameters );
bind_optional_name( cell.track_name, parameters );
}
for( auto &launcher : scene.event_launchers ) {
bind_node_strings( launcher.node, parameters );
bind_name( launcher.activation_key_raw, parameters );
bind_name( launcher.event1_name, parameters );
bind_name( launcher.event2_name, parameters );
if( launcher.condition ) {
bind_name( launcher.condition->memcell_name, parameters );
bind_name( launcher.condition->compare_text, parameters );
}
}
for( auto &vehicle : scene.dynamics ) {
bind_node_strings( vehicle.node, parameters );
bind_path( vehicle.data_folder, parameters );
bind_path( vehicle.skin_file, parameters );
bind_path( vehicle.mmd_file, parameters );
bind_name( vehicle.track_name, parameters );
bind_name( vehicle.driver_type, parameters );
bind_name( vehicle.coupling_raw, parameters );
bind_path( vehicle.coupling_params, parameters );
bind_name( vehicle.load_type, parameters );
bind_optional_name( vehicle.destination, parameters );
}
for( auto &sound : scene.sounds ) {
bind_node_strings( sound.node, parameters );
bind_path( sound.wav_file, parameters );
}
for( auto &event : scene.events ) {
bind_name( event.name, parameters );
for( auto &target : event.targets ) {
bind_name( target, parameters );
}
for( auto &[key, value] : event.payload ) {
bind_name( key, parameters );
bind_name( value, parameters );
}
}
for( auto &trainset : scene.trainsets ) {
bind_name( trainset.name, parameters );
bind_name( trainset.track, parameters );
std::unordered_map<std::string, std::string> rebound;
rebound.reserve( trainset.assignment.size() );
for( auto const &[key, value] : trainset.assignment ) {
auto bound_key { key };
auto bound_value { value };
bind_name( bound_key, parameters );
bind_name( bound_value, parameters );
rebound.emplace( std::move( bound_key ), std::move( bound_value ) );
}
trainset.assignment = std::move( rebound );
}
}
[[nodiscard]] double
resolve_placement_param(
std::uint8_t const param_index,
std::vector<std::string> const &parameters ) {
if( param_index == 0 ) {
return 0.0;
}
auto const index { static_cast<std::size_t>( param_index ) };
if( index < 1 || index > parameters.size() ) {
throw std::runtime_error( "EU7: brak parametru p" + std::to_string( param_index ) );
}
return std::stod( parameters[ index - 1 ] );
}
Eu7TransformContext
placement_transform_from_include_parameters(
Eu7IncludePlacement const &binding,
std::vector<std::string> const &parameters ) {
Eu7TransformContext placement;
if( binding.empty() ) {
return placement;
}
try {
auto const origin_x { resolve_placement_param( binding.origin_x_param, parameters ) };
auto const origin_y { resolve_placement_param( binding.origin_y_param, parameters ) };
auto const origin_z { resolve_placement_param( binding.origin_z_param, parameters ) };
placement.origin_stack.push_back( { origin_x, origin_y, origin_z } );
placement.rotation.y = resolve_placement_param( binding.rotation_y_param, parameters );
}
catch( std::exception const & ) {
placement = {};
}
return placement;
}
void
apply_include_placement_to_scene(
Eu7Scene &scene,
Eu7IncludePlacement const &binding,
std::vector<std::string> const &parameters ) {
auto const placement {
placement_transform_from_include_parameters( binding, parameters ) };
if( false == transform_is_empty( placement ) ) {
compose_scene_with_include_prefix( scene, placement );
}
}
void
apply_include_placement_to_models(
std::vector<Eu7Model> &models,
Eu7IncludePlacement const &binding,
std::vector<std::string> const &parameters ) {
auto const placement {
placement_transform_from_include_parameters( binding, parameters ) };
if( false == transform_is_empty( placement ) ) {
compose_models_with_prefix( models, placement );
}
}
std::string
module_load_key(
std::string const &resolved_path,
std::vector<std::string> const &parameters ) {
std::string key { resolved_path };
for( auto const &parameter : parameters ) {
key.push_back( '\0' );
key += parameter;
}
return key;
}
} // namespace scene::eu7

View File

@@ -0,0 +1,51 @@
/*
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/.
*/
#pragma once
#include "scene/eu7/eu7_types.h"
#include <string>
#include <vector>
namespace scene::eu7 {
void
bind_include_parameter( std::string &text, std::vector<std::string> const &parameters, bool to_lower = false );
void
apply_include_parameters_to_scene( Eu7Scene &scene, std::vector<std::string> const &parameters );
void
apply_include_parameters_to_models( std::vector<Eu7Model> &models, std::vector<std::string> const &parameters );
// Placement z chunku PLAC (indeksy pN z origin/rotate w .inc) + wartosci z INCL.
[[nodiscard]] Eu7TransformContext
placement_transform_from_include_parameters(
Eu7IncludePlacement const &binding,
std::vector<std::string> const &parameters );
void
apply_include_placement_to_scene(
Eu7Scene &scene,
Eu7IncludePlacement const &binding,
std::vector<std::string> const &parameters );
void
apply_include_placement_to_models(
std::vector<Eu7Model> &models,
Eu7IncludePlacement const &binding,
std::vector<std::string> const &parameters );
[[nodiscard]] std::string
module_load_key(
std::string const &resolved_path,
std::vector<std::string> const &parameters );
} // namespace scene::eu7

1149
scene/eu7/eu7_reader.cpp Normal file

File diff suppressed because it is too large Load Diff

61
scene/eu7/eu7_reader.h Normal file
View File

@@ -0,0 +1,61 @@
/*
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/.
*/
#pragma once
#include "scene/eu7/eu7_types.h"
#include <istream>
#include <optional>
#include <string>
#include <vector>
namespace scene::eu7 {
[[nodiscard]] Eu7Module
read_module( std::string const &Path );
[[nodiscard]] bool
is_valid_eu7b_file( std::string const &Path );
// Szuka wpisu PIDX dla sekcji 1 km (row/column jak basic_region::section).
[[nodiscard]] std::optional<Eu7PackIndexEntry>
find_pack_entry( Eu7Module const &Module, int Row, int Column );
// Odczyt MODL z chunku PACK dla jednej sekcji (world-space, po bake).
[[nodiscard]] std::vector<Eu7Model>
read_pack_section( Eu7Module const &Module, int Row, int Column );
// Seek do pack_offset z PIDX i parsuj naglowek sekcji (v7/v8).
void
seek_pack_section(
Eu7Module const &Module,
std::istream &Input,
Eu7PackIndexEntry const &Entry,
Eu7PackSectionCursor &Cursor );
// Odczyt do max_count modeli z biezacej pozycji strumienia (po seek_pack_section).
[[nodiscard]] std::vector<Eu7Model>
read_pack_models_chunk(
Eu7Module const &Module,
std::istream &Input,
Eu7PackSectionCursor &Cursor,
std::size_t MaxCount );
// Wznowienie odczytu sub-chunka (offset wzgledem pack_offset sekcji z PIDX).
void
resume_pack_section(
Eu7Module const &Module,
std::istream &Input,
Eu7PackIndexEntry const &Entry,
std::uint64_t ResumeByteOffset,
Eu7PackSectionCursor ResumeCursor,
Eu7PackSectionCursor &Cursor );
} // namespace scene::eu7

47
scene/eu7/eu7_section.cpp Normal file
View File

@@ -0,0 +1,47 @@
/*
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_section.h"
#include <algorithm>
#include <cmath>
namespace scene::eu7 {
std::pair<int, int>
section_row_column( glm::dvec3 const &world_location ) {
auto const column {
static_cast<int>( std::floor(
world_location.x / static_cast<double>( kSectionSizeM ) +
kRegionSideSectionCount / 2 ) ) };
auto const row {
static_cast<int>( std::floor(
world_location.z / static_cast<double>( kSectionSizeM ) +
kRegionSideSectionCount / 2 ) ) };
return {
std::clamp( row, 0, kRegionSideSectionCount - 1 ),
std::clamp( column, 0, kRegionSideSectionCount - 1 ) };
}
std::size_t
section_index( glm::dvec3 const &world_location ) {
auto const [row, column] { section_row_column( world_location ) };
return section_index( row, column );
}
std::size_t
section_index( int const row, int const column ) {
return (
static_cast<std::size_t>( row ) * static_cast<std::size_t>( kRegionSideSectionCount ) +
static_cast<std::size_t>( column ) );
}
} // namespace scene::eu7

33
scene/eu7/eu7_section.h Normal file
View File

@@ -0,0 +1,33 @@
/*
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/.
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <utility>
#include <glm/glm.hpp>
namespace scene::eu7 {
// Musi byc zgodne z scene::EU07_SECTIONSIZE / EU07_REGIONSIDESECTIONCOUNT.
constexpr int kSectionSizeM { 1000 };
constexpr int kRegionSideSectionCount { 500 };
[[nodiscard]] std::pair<int, int>
section_row_column( glm::dvec3 const &world_location );
[[nodiscard]] std::size_t
section_index( glm::dvec3 const &world_location );
[[nodiscard]] std::size_t
section_index( int const row, int const column );
} // namespace scene::eu7

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/*
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/.
*/
#pragma once
#include "scene/eu7/eu7_types.h"
#include <glm/glm.hpp>
namespace simulation {
class state_serializer;
}
namespace scene::eu7 {
constexpr int kSectionStreamBootstrapRadiusKm { 3 };
constexpr int kSectionStreamGameplayRadiusKm { 4 };
void
init_section_stream(
Eu7Module const &RootModule,
std::string const &ResolvedPath,
simulation::state_serializer &Serializer );
void
prime_section_stream( Eu7Module const &RootModule );
[[nodiscard]] glm::dvec3
resolve_stream_position();
// Opcjonalnie blokuje do zaladowania pierścienia bootstrap wokol pozycji (debug/narzedzia).
void
bootstrap_section_stream( glm::dvec3 const &WorldPosition );
void
update_section_stream( glm::dvec3 const &WorldPosition );
// Worker dekoduje sekcje; watek mapy wstawia po wskazniku do bufora (bez re-read pliku).
void
drain_section_stream(
std::size_t MaxColdMeshes = 0,
std::size_t MaxInstances = 0 );
[[nodiscard]] bool
section_stream_active();
[[nodiscard]] bool
section_stream_needs_bootstrap();
// Po wejsciu w symulacje: kolejkuje bootstrap bez blokowania petli renderu.
void
kick_section_stream_bootstrap();
// Blokujacy bootstrap (preload / narzedzia).
void
try_bootstrap_section_stream();
// Po zaladowaniu scenariusza: bootstrap + drain PACK przed wejsciem w jazde.
void
preload_section_stream( double MaxDrainMs = 300.0 );
// Wszystkie sekcje PACK w pierścieniu RadiusKm wokol pozycji wczytane i zaaplikowane.
[[nodiscard]] bool
section_stream_ready_around(
glm::dvec3 const &WorldPosition,
int RadiusKm = kSectionStreamBootstrapRadiusKm );
// ready_around stabilnie przez kilka klatek — bez migania overlay/sceny.
[[nodiscard]] bool
section_stream_presentable_around(
glm::dvec3 const &WorldPosition,
int RadiusKm = kSectionStreamBootstrapRadiusKm );
// Renderer/UI: trzymaj czarny ekran + overlay, nie rysuj swiata 3D.
// Jednorazowo przy starcie — po dismiss nie wraca przy streamingu w locie.
[[nodiscard]] bool
loading_screen_blocks_world(
glm::dvec3 const &WorldPosition,
int RadiusKm = kSectionStreamBootstrapRadiusKm );
[[nodiscard]] bool
loading_screen_dismissed();
void
dismiss_loading_screen();
[[nodiscard]] glm::dvec3
stream_loading_position();
// 0..1 — ile sekcji PACK z pierścienia juz siedzi w Region.
[[nodiscard]] float
section_stream_ring_progress(
glm::dvec3 const &WorldPosition,
int RadiusKm = kSectionStreamBootstrapRadiusKm );
void
reset_section_stream();
} // namespace scene::eu7

258
scene/eu7/eu7_transform.h Normal file
View File

@@ -0,0 +1,258 @@
/*
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/.
*/
#pragma once
#include "scene/eu7/eu7_types.h"
#include <cmath>
#include <vector>
namespace scene::eu7 {
namespace detail {
inline constexpr double kPi { 3.14159265358979323846 };
struct ScratchLocation {
std::vector<glm::dvec3> offset_stack;
std::vector<glm::dvec3> scale_stack;
glm::dvec3 rotation{};
};
[[nodiscard]] inline ScratchLocation
scratch_from_transform( Eu7TransformContext const &transform ) {
ScratchLocation scratch;
scratch.offset_stack = transform.origin_stack;
scratch.scale_stack = transform.scale_stack;
scratch.rotation = transform.rotation;
return scratch;
}
} // namespace detail
[[nodiscard]] inline glm::dvec3
inverse_transform_point( glm::dvec3 location, Eu7TransformContext const &transform ) {
auto const scratch { detail::scratch_from_transform( transform ) };
if( !scratch.offset_stack.empty() ) {
auto const &off { scratch.offset_stack.back() };
location.x -= off.x;
location.y -= off.y;
location.z -= off.z;
}
if( !scratch.scale_stack.empty() ) {
auto const &sc { scratch.scale_stack.back() };
if( sc.x != 0.0 ) {
location.x /= sc.x;
}
if( sc.y != 0.0 ) {
location.y /= sc.y;
}
if( sc.z != 0.0 ) {
location.z /= sc.z;
}
}
if( scratch.rotation.y != 0.0 ) {
auto const rad { -scratch.rotation.y * ( detail::kPi / 180.0 ) };
auto const c { std::cos( rad ) };
auto const s { std::sin( rad ) };
auto const x { location.x * c + location.z * s };
auto const z { -location.x * s + location.z * c };
location.x = x;
location.z = z;
}
return location;
}
inline void
inverse_transform_shape_vertices(
std::vector<Eu7WorldVertex> &vertices,
Eu7TransformContext const &transform ) {
auto const scratch { detail::scratch_from_transform( transform ) };
if( !scratch.offset_stack.empty() ) {
auto const &off { scratch.offset_stack.back() };
for( auto &vtx : vertices ) {
vtx.position.x -= off.x;
vtx.position.y -= off.y;
vtx.position.z -= off.z;
}
}
if( scratch.rotation.x != 0.0 || scratch.rotation.y != 0.0 || scratch.rotation.z != 0.0 ) {
auto const rx { -scratch.rotation.x * ( detail::kPi / 180.0 ) };
auto const ry { -scratch.rotation.y * ( detail::kPi / 180.0 ) };
auto const rz { -scratch.rotation.z * ( detail::kPi / 180.0 ) };
auto rotate_y = [&]( glm::dvec3 &v ) {
auto const c { std::cos( ry ) };
auto const s { std::sin( ry ) };
auto const x { v.x * c + v.z * s };
auto const z { -v.x * s + v.z * c };
v.x = x;
v.z = z;
};
auto rotate_x = [&]( glm::dvec3 &v ) {
auto const c { std::cos( rx ) };
auto const s { std::sin( rx ) };
auto const y { v.y * c - v.z * s };
auto const z { v.y * s + v.z * c };
v.y = y;
v.z = z;
};
auto rotate_z = [&]( glm::dvec3 &v ) {
auto const c { std::cos( rz ) };
auto const s { std::sin( rz ) };
auto const x { v.x * c - v.y * s };
auto const y { v.x * s + v.y * c };
v.x = x;
v.y = y;
};
for( auto &vtx : vertices ) {
rotate_y( vtx.position );
rotate_x( vtx.position );
rotate_z( vtx.position );
glm::dvec3 n { vtx.normal.x, vtx.normal.y, vtx.normal.z };
rotate_y( n );
rotate_x( n );
rotate_z( n );
vtx.normal = glm::vec3( static_cast<float>( n.x ), static_cast<float>( n.y ), static_cast<float>( n.z ) );
}
}
}
[[nodiscard]] inline bool
transform_is_empty( Eu7TransformContext const &transform ) {
return (
transform.origin_stack.empty() &&
transform.scale_stack.empty() &&
transform.rotation.x == 0.0 &&
transform.rotation.y == 0.0 &&
transform.rotation.z == 0.0 &&
transform.group_depth == 0 );
}
[[nodiscard]] inline glm::dvec3
transform_point( glm::dvec3 location, Eu7TransformContext const &transform ) {
if( transform.rotation.y != 0.0 ) {
auto const rad { transform.rotation.y * ( detail::kPi / 180.0 ) };
auto const c { std::cos( rad ) };
auto const s { std::sin( rad ) };
auto const x { location.x * c + location.z * s };
auto const z { -location.x * s + location.z * c };
location.x = x;
location.z = z;
}
if( !transform.scale_stack.empty() ) {
auto const &sc { transform.scale_stack.back() };
if( sc.x != 0.0 ) {
location.x *= sc.x;
}
if( sc.y != 0.0 ) {
location.y *= sc.y;
}
if( sc.z != 0.0 ) {
location.z *= sc.z;
}
}
if( !transform.origin_stack.empty() ) {
auto const &off { transform.origin_stack.back() };
location.x += off.x;
location.y += off.y;
location.z += off.z;
}
return location;
}
// Wzgledne wektory kontrolne toru (cp_out/cp_in) — rotacja i skala, bez translacji origin.
[[nodiscard]] inline glm::dvec3
transform_vector( glm::dvec3 vector, Eu7TransformContext const &transform ) {
if( transform.rotation.y != 0.0 ) {
auto const rad { transform.rotation.y * ( detail::kPi / 180.0 ) };
auto const c { std::cos( rad ) };
auto const s { std::sin( rad ) };
auto const x { vector.x * c + vector.z * s };
auto const z { -vector.x * s + vector.z * c };
vector.x = x;
vector.z = z;
}
if( !transform.scale_stack.empty() ) {
auto const &sc { transform.scale_stack.back() };
if( sc.x != 0.0 ) {
vector.x *= sc.x;
}
if( sc.y != 0.0 ) {
vector.y *= sc.y;
}
if( sc.z != 0.0 ) {
vector.z *= sc.z;
}
}
return vector;
}
inline void
compose_node_transform( Eu7TransformContext &node, Eu7TransformContext const &prefix ) {
if( transform_is_empty( prefix ) ) {
return;
}
auto const parent_origin {
prefix.origin_stack.empty() ?
glm::dvec3{} :
prefix.origin_stack.back() };
for( auto &offset : node.origin_stack ) {
offset.x += parent_origin.x;
offset.y += parent_origin.y;
offset.z += parent_origin.z;
}
auto const parent_scale {
prefix.scale_stack.empty() ?
glm::dvec3{ 1.0, 1.0, 1.0 } :
prefix.scale_stack.back() };
for( auto &scale : node.scale_stack ) {
scale.x *= parent_scale.x;
scale.y *= parent_scale.y;
scale.z *= parent_scale.z;
}
node.rotation.x += prefix.rotation.x;
node.rotation.y += prefix.rotation.y;
node.rotation.z += prefix.rotation.z;
node.group_depth += prefix.group_depth;
}
void
compose_scene_with_include_prefix( Eu7Scene &scene, Eu7TransformContext const &prefix );
[[nodiscard]] inline glm::dvec3
subtract_origin_offset( glm::dvec3 const &world, Eu7TransformContext const &transform ) {
glm::dvec3 local { world };
if( !transform.origin_stack.empty() ) {
auto const &off { transform.origin_stack.back() };
local.x -= off.x;
local.y -= off.y;
local.z -= off.z;
}
return local;
}
} // namespace scene::eu7

393
scene/eu7/eu7_types.h Normal file
View File

@@ -0,0 +1,393 @@
/*
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/.
*/
#pragma once
#include <cstdint>
#include <limits>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <glm/glm.hpp>
namespace scene::eu7 {
struct Eu7Vec4 {
float x = 0.f;
float y = 0.f;
float z = 0.f;
float w = 1.f;
};
struct Eu7WorldVertex {
glm::dvec3 position{};
glm::vec3 normal{};
double u = 0.0;
double v = 0.0;
};
struct Eu7LightingData {
Eu7Vec4 diffuse{ 0.8f, 0.8f, 0.8f, 1.f };
Eu7Vec4 ambient{ 0.2f, 0.2f, 0.2f, 1.f };
Eu7Vec4 specular{ 0.f, 0.f, 0.f, 1.f };
};
struct Eu7BoundingArea {
glm::dvec3 center{};
float radius = -1.f;
};
struct Eu7TransformContext {
std::vector<glm::dvec3> origin_stack;
std::vector<glm::dvec3> scale_stack;
glm::dvec3 rotation{};
std::size_t group_depth = 0;
};
struct Eu7BasicNode {
Eu7BoundingArea area;
double range_squared_min = 0.0;
double range_squared_max = std::numeric_limits<double>::max();
bool visible = true;
std::string name;
std::string node_type;
std::size_t group_handle = 0;
bool group_valid = false;
Eu7TransformContext transform;
};
struct Eu7SegmentPath {
glm::dvec3 p_start{};
double roll_start = 0.0;
glm::dvec3 cp_out{};
glm::dvec3 cp_in{};
glm::dvec3 p_end{};
double roll_end = 0.0;
double radius = 0.0;
};
enum class Eu7TrackType : std::uint8_t {
Unknown,
Normal,
Switch,
Table,
Cross,
Tributary,
};
enum class Eu7TrackCategory : std::uint8_t {
Rail = 1,
Road = 2,
Water = 4,
};
enum class Eu7TrackEnvironment : std::int8_t {
Unknown = -1,
Flat = 0,
Mountains,
Canyon,
Tunnel,
Bridge,
Bank,
};
struct Eu7TrackVisibility {
std::string material1;
float tex_length = 4.f;
std::string material2;
float tex_height1 = 0.f;
float tex_width = 0.f;
float tex_slope = 0.f;
};
struct Eu7Track {
Eu7BasicNode node;
Eu7TrackType track_type = Eu7TrackType::Unknown;
Eu7TrackCategory category = Eu7TrackCategory::Rail;
float length = 0.f;
float track_width = 0.f;
float friction = 0.f;
float sound_distance = 0.f;
int quality_flag = 0;
int damage_flag = 0;
Eu7TrackEnvironment environment = Eu7TrackEnvironment::Unknown;
std::optional<Eu7TrackVisibility> visibility;
std::vector<Eu7SegmentPath> paths;
std::vector<std::pair<std::string, std::string>> tail_keywords;
};
enum class Eu7TractionWireMaterial : std::uint8_t {
None = 0,
Copper = 1,
Aluminium = 2,
};
struct Eu7Traction {
Eu7BasicNode node;
std::string power_supply_name;
float nominal_voltage = 0.f;
float max_current = 0.f;
float resistivity_ohm_per_m = 0.f;
double resistivity_legacy = 0.0;
std::string material_raw = "cu";
Eu7TractionWireMaterial material = Eu7TractionWireMaterial::Copper;
float wire_thickness = 0.f;
int damage_flag = 0;
glm::dvec3 wire_p1{};
glm::dvec3 wire_p2{};
glm::dvec3 wire_p3{};
glm::dvec3 wire_p4{};
double min_height = 0.0;
double segment_length = 0.0;
int wire_count = 0;
float wire_offset = 0.f;
std::optional<std::string> parallel_name;
};
enum class Eu7PowerSourceModifier : std::uint8_t {
None,
Recuperation,
Section,
};
struct Eu7TractionPowerSource {
Eu7BasicNode node;
glm::dvec3 position{};
float nominal_voltage = 0.f;
float voltage_frequency = 0.f;
double internal_resistance_legacy = 0.2;
float internal_resistance = 0.2f;
float max_output_current = 0.f;
float fast_fuse_timeout = 0.f;
float fast_fuse_repetition = 0.f;
float slow_fuse_timeout = 0.f;
Eu7PowerSourceModifier modifier = Eu7PowerSourceModifier::None;
};
struct Eu7Shape {
Eu7BasicNode node;
bool translucent = false;
std::string material_path;
Eu7LightingData lighting;
glm::dvec3 origin{};
std::vector<Eu7WorldVertex> vertices;
};
struct Eu7Lines {
Eu7BasicNode node;
Eu7LightingData lighting;
float line_width = 1.f;
glm::dvec3 origin{};
std::vector<Eu7WorldVertex> vertices;
};
struct Eu7Model {
Eu7BasicNode node;
glm::dvec3 location{};
glm::dvec3 angles{};
glm::dvec3 scale{ 1.0, 1.0, 1.0 };
std::string model_file;
std::string texture_file;
std::vector<float> light_states;
std::vector<std::uint32_t> light_colors;
bool transition = true;
bool is_terrain = false;
};
// Wspolna definicja modelu (EU7B v8 PROT) — bez transformacji instancji.
struct Eu7ModelPrototype {
Eu7BasicNode node;
std::string model_file;
std::string texture_file;
std::vector<float> light_states;
std::vector<std::uint32_t> light_colors;
bool transition = true;
bool is_terrain = false;
};
struct Eu7MemCell {
Eu7BasicNode node;
std::string text;
double value1 = 0.0;
double value2 = 0.0;
std::optional<std::string> track_name;
};
struct Eu7EventLauncherCondition {
std::string memcell_name;
std::string compare_text;
double compare_value1 = 0.0;
double compare_value2 = 0.0;
int check_mask = 0;
};
struct Eu7EventLauncher {
Eu7BasicNode node;
glm::dvec3 location{};
double radius_squared = 0.0;
std::string activation_key_raw;
int activation_key = 0;
double delta_time = -1.0;
std::string event1_name;
std::string event2_name;
std::optional<Eu7EventLauncherCondition> condition;
bool train_triggered = false;
int launch_hour = -1;
int launch_minute = -1;
};
struct Eu7Dynamic {
Eu7BasicNode node;
std::string data_folder;
std::string skin_file;
std::string mmd_file;
std::string track_name;
double offset = -1.0;
std::string driver_type;
int coupling = 3;
std::string coupling_raw = "3";
std::string coupling_params;
float velocity = 0.f;
int load_count = 0;
std::string load_type;
std::optional<std::string> destination;
std::optional<std::size_t> trainset_index;
};
struct Eu7Sound {
Eu7BasicNode node;
glm::dvec3 location{};
std::string wav_file;
};
enum class Eu7EventType : std::uint8_t {
AddValues,
UpdateValues,
CopyValues,
GetValues,
PutValues,
Whois,
LogValues,
Multiple,
Switch,
TrackVel,
Sound,
Texture,
Animation,
Lights,
Voltage,
Visible,
Friction,
Message,
Unknown,
};
struct Eu7Event {
std::string name;
Eu7EventType type = Eu7EventType::Unknown;
double delay = 0.0;
std::vector<std::string> targets;
double delay_random = 0.0;
double delay_departure = 0.0;
bool ignored = false;
bool passive = false;
std::vector<std::pair<std::string, std::string>> payload;
};
struct Eu7Trainset {
std::string name;
std::string track;
float offset = 0.f;
float velocity = 0.f;
std::unordered_map<std::string, std::string> assignment;
std::vector<std::size_t> vehicle_indices;
std::vector<int> couplings;
std::size_t driver_index = static_cast<std::size_t>( -1 );
};
struct Eu7IncludePlacement {
std::uint8_t origin_x_param = 0;
std::uint8_t origin_y_param = 0;
std::uint8_t origin_z_param = 0;
std::uint8_t rotation_y_param = 0;
[[nodiscard]] bool
empty() const noexcept {
return origin_x_param == 0 && origin_y_param == 0 && origin_z_param == 0 &&
rotation_y_param == 0;
}
};
struct Eu7Include {
std::uint32_t source_line = 0;
std::string source_path;
std::string binary_path;
std::vector<std::string> parameters;
Eu7TransformContext site_transform;
};
struct Eu7Scene {
std::vector<Eu7Track> tracks;
std::vector<Eu7Traction> traction;
std::vector<Eu7TractionPowerSource> power_sources;
std::vector<Eu7Shape> shapes;
std::vector<Eu7Shape> terrain_shapes;
std::vector<Eu7Lines> lines;
std::vector<Eu7Model> models;
std::vector<Eu7MemCell> memcells;
std::vector<Eu7EventLauncher> event_launchers;
std::vector<Eu7Dynamic> dynamics;
std::vector<Eu7Sound> sounds;
std::vector<Eu7Trainset> trainsets;
std::vector<Eu7Event> events;
std::uint32_t first_init_count = 0;
};
// Wpis indeksu PACK: sekcja 1 km x 1 km -> offset w payload chunku PACK.
struct Eu7PackIndexEntry {
std::uint16_t row = 0;
std::uint16_t column = 0;
std::uint32_t model_count = 0;
std::uint64_t pack_offset = 0;
};
// Stan inkrementalnego odczytu sekcji PACK z istream (po seek_pack_section).
struct Eu7PackSectionCursor {
std::uint32_t solo_remaining { 0 };
std::uint32_t inst_remaining { 0 };
std::uint32_t models_read { 0 };
std::uint32_t model_total { 0 };
std::uint8_t section_format { 0 };
bool header_parsed { false };
};
struct Eu7PackCatalog {
std::vector<Eu7PackIndexEntry> entries;
std::unordered_map<std::uint32_t, std::size_t> index_by_section;
std::uint64_t pack_payload_size = 0;
};
struct Eu7Module {
std::vector<Eu7Include> includes;
Eu7Scene scene;
Eu7IncludePlacement include_placement;
Eu7PackCatalog pack_catalog;
std::vector<Eu7ModelPrototype> model_prototypes;
std::vector<std::string> strings;
std::string source_path;
std::uint64_t pack_payload_offset = 0;
std::uint32_t version = 0;
bool has_terrain_chunk = false;
bool has_pack_chunk = false;
};
} // namespace scene::eu7

View File

@@ -359,6 +359,21 @@ basic_cell::insert( TAnimModel *Instance ) {
m_active = true;
if( Instance->m_eu7_pack && Instance->m_instanceable ) {
m_instancesopaque.emplace_back( Instance );
if( Instance->Model() != nullptr ) {
instance_bucket_key key;
key.pModel = Instance->Model();
auto const *mat = Instance->Material();
if( mat != nullptr ) {
for( int i = 0; i < 5; ++i ) { key.skins[ i ] = mat->replacable_skins[ i ]; }
}
m_instancebuckets_opaque[ key ].emplace_back( Instance );
}
enclose_area( Instance );
return;
}
auto const flags = Instance->Flags();
auto alpha =
( Instance->Material() != nullptr ?

View File

@@ -74,11 +74,22 @@ struct node_data {
std::string type;
};
class shape_node;
class lines_node;
namespace eu7 {
struct Eu7Shape;
struct Eu7Lines;
shape_node build_shape_node( Eu7Shape const &Source );
lines_node build_lines_node( Eu7Lines const &Source );
} // namespace eu7
// holds unique piece of geometry, covered with single material
class shape_node
{
friend class basic_region; // region might want to modify node content when it's being inserted
friend shape_node eu7::build_shape_node( eu7::Eu7Shape const & );
public:
// types
@@ -176,6 +187,7 @@ shape_node::data() const {
class lines_node
{
friend class basic_region; // region might want to modify node content when it's being inserted
friend lines_node eu7::build_lines_node( eu7::Eu7Lines const & );
public:
// types